Testsuite: add/fix cleanup for certain tests
[ghc.git] / testsuite / driver / testlib.py
index fec6939..dbae8d7 100644 (file)
@@ -17,13 +17,7 @@ import copy
 import glob
 from math import ceil, trunc
 import collections
-
-have_subprocess = False
-try:
-    import subprocess
-    have_subprocess = True
-except:
-    print("Warning: subprocess not found, will fall back to spawnv")
+import subprocess
 
 from testglobals import *
 from testutil import *
@@ -103,26 +97,24 @@ def _reqlib( name, opts, lib ):
     if lib in have_lib:
         got_it = have_lib[lib]
     else:
-        if have_subprocess:
-            # By preference we use subprocess, as the alternative uses
-            # /dev/null which mingw doesn't have.
-            cmd = strip_quotes(config.ghc_pkg)
-            p = subprocess.Popen([cmd, '--no-user-package-db', 'describe', lib],
-                                 stdout=subprocess.PIPE,
-                                 stderr=subprocess.PIPE)
-            # read from stdout and stderr to avoid blocking due to
-            # buffers filling
-            p.communicate()
-            r = p.wait()
-        else:
-            r = os.system(config.ghc_pkg + ' --no-user-package-db describe '
-                                         + lib + ' > /dev/null 2> /dev/null')
+        cmd = strip_quotes(config.ghc_pkg)
+        p = subprocess.Popen([cmd, '--no-user-package-db', 'describe', lib],
+                             stdout=subprocess.PIPE,
+                             stderr=subprocess.PIPE)
+        # read from stdout and stderr to avoid blocking due to
+        # buffers filling
+        p.communicate()
+        r = p.wait()
         got_it = r == 0
         have_lib[lib] = got_it
 
     if not got_it:
         opts.expect = 'missing-lib'
 
+def req_haddock( name, opts ):
+    if not config.haddock:
+        opts.expect = 'missing-lib'
+
 def req_profiling( name, opts ):
     if not config.have_profiling:
         opts.expect = 'fail'
@@ -272,6 +264,7 @@ def _extra_hc_opts( name, opts, v ):
 # -----
 
 def extra_clean( files ):
+    assert not isinstance(files, str), files
     return lambda name, opts, v=files: _extra_clean(name, opts, v);
 
 def _extra_clean( name, opts, v ):
@@ -759,6 +752,11 @@ def test_common_work (name, opts, func, args):
 
 def clean(strs):
     for str in strs:
+        if (str.endswith('.package.conf') or
+            str.startswith('package.conf.') and not str.endswith('/*')):
+            # Package confs are directories now.
+            str += '/*'
+
         for name in glob.glob(in_testdir(str)):
             clean_full_path(name)
 
@@ -1041,7 +1039,7 @@ def do_compile( name, way, should_fail, top_mod, extra_mods, extra_hc_opts, over
     (platform_specific, expected_stderr_file) = platform_wordsize_qualify(namebase, 'stderr')
     actual_stderr_file = qualify(name, 'comp.stderr')
 
-    if not compare_outputs('stderr',
+    if not compare_outputs(way, 'stderr',
                            join_normalisers(getTestOpts().extra_errmsg_normaliser,
                                             normalise_errmsg,
                                             normalise_whitespace),
@@ -1071,7 +1069,8 @@ def compile_cmp_asm( name, way, extra_hc_opts ):
     (platform_specific, expected_asm_file) = platform_wordsize_qualify(namebase, 'asm')
     actual_asm_file = qualify(name, 's')
 
-    if not compare_outputs('asm', join_normalisers(normalise_errmsg, normalise_asm), \
+    if not compare_outputs(way, 'asm',
+                           join_normalisers(normalise_errmsg, normalise_asm),
                            expected_asm_file, actual_asm_file):
         return failBecause('asm mismatch')
 
@@ -1127,7 +1126,10 @@ def checkStats(name, way, stats_file, range_fields):
 
     result = passed()
     if len(range_fields) > 0:
-        f = open(in_testdir(stats_file))
+        try:
+            f = open(in_testdir(stats_file))
+        except IOError as e:
+            return failBecause(str(e))
         contents = f.read()
         f.close()
 
@@ -1306,11 +1308,11 @@ def simple_run( name, way, prog, args ):
         stdin_comes_from = ' <' + use_stdin
 
     if opts.combined_output:
-        redirection        = ' > {} 2>&1'.format(run_stdout)
-        redirection_append = ' >> {} 2>&1'.format(run_stdout)
+        redirection        = ' > {0} 2>&1'.format(run_stdout)
+        redirection_append = ' >> {0} 2>&1'.format(run_stdout)
     else:
-        redirection        = ' > {} 2> {}'.format(run_stdout, run_stderr)
-        redirection_append = ' >> {} 2>> {}'.format(run_stdout, run_stderr)
+        redirection        = ' > {0} 2> {1}'.format(run_stdout, run_stderr)
+        redirection_append = ' >> {0} 2>> {1}'.format(run_stdout, run_stderr)
 
     cmd = prog + ' ' + args + ' '  \
         + my_rts_flags + ' '       \
@@ -1339,8 +1341,8 @@ def simple_run( name, way, prog, args ):
     check_prof = my_rts_flags.find("-p") != -1
 
     if not opts.ignore_output:
-        bad_stderr = not opts.combined_output and not check_stderr_ok(name)
-        bad_stdout = not check_stdout_ok(name)
+        bad_stderr = not opts.combined_output and not check_stderr_ok(name, way)
+        bad_stdout = not check_stdout_ok(name, way)
         if bad_stderr:
             return failBecause('bad stderr')
         if bad_stdout:
@@ -1348,7 +1350,7 @@ def simple_run( name, way, prog, args ):
         # exit_code > 127 probably indicates a crash, so don't try to run hp2ps.
         if check_hp and (exit_code <= 127 or exit_code == 251) and not check_hp_ok(name):
             return failBecause('bad heap profile')
-        if check_prof and not check_prof_ok(name):
+        if check_prof and not check_prof_ok(name, way):
             return failBecause('bad profile')
 
     return checkStats(name, way, stats_file, opts.stats_range_fields)
@@ -1418,11 +1420,11 @@ def interpreter_run( name, way, extra_hc_opts, compile_only, top_mod ):
                      config.way_flags(name)[way])
 
     if getTestOpts().combined_output:
-        redirection        = ' > {} 2>&1'.format(outname)
-        redirection_append = ' >> {} 2>&1'.format(outname)
+        redirection        = ' > {0} 2>&1'.format(outname)
+        redirection_append = ' >> {0} 2>&1'.format(outname)
     else:
-        redirection        = ' > {} 2> {}'.format(outname, errname)
-        redirection_append = ' >> {} 2>> {}'.format(outname, errname)
+        redirection        = ' > {0} 2> {1}'.format(outname, errname)
+        redirection_append = ' >> {0} 2>> {1}'.format(outname, errname)
 
     cmd = ('{{compiler}} {srcname} {flags} {extra_hc_opts} '
            '< {scriptname} {redirection}'
@@ -1455,8 +1457,8 @@ def interpreter_run( name, way, extra_hc_opts, compile_only, top_mod ):
 
     # ToDo: if the sub-shell was killed by ^C, then exit
 
-    if getTestOpts().ignore_output or (check_stderr_ok(name) and
-                                       check_stdout_ok(name)):
+    if getTestOpts().ignore_output or (check_stderr_ok(name, way) and
+                                       check_stdout_ok(name, way)):
         return passed()
     else:
         return failBecause('bad stdout or stderr')
@@ -1501,7 +1503,7 @@ def get_compiler_flags(override_flags, noforce):
 
     return flags
 
-def check_stdout_ok( name ):
+def check_stdout_ok(name, way):
    if getTestOpts().with_namebase == None:
        namebase = name
    else:
@@ -1522,15 +1524,14 @@ def check_stdout_ok( name ):
    if check_stdout:
       return check_stdout(actual_stdout_file, extra_norm)
 
-   return compare_outputs('stdout', \
-                          extra_norm, \
+   return compare_outputs(way, 'stdout', extra_norm,
                           expected_stdout_file, actual_stdout_file)
 
 def dump_stdout( name ):
    print('Stdout:')
    print(read_no_crs(qualify(name, 'run.stdout')))
 
-def check_stderr_ok( name ):
+def check_stderr_ok(name, way):
    if getTestOpts().with_namebase == None:
        namebase = name
    else:
@@ -1545,7 +1546,7 @@ def check_stderr_ok( name ):
       else:
          return normalise_errmsg(str)
 
-   return compare_outputs('stderr', \
+   return compare_outputs(way, 'stderr',
                           join_normalisers(norm, getTestOpts().extra_errmsg_normaliser), \
                           expected_stderr_file, actual_stderr_file)
 
@@ -1595,7 +1596,7 @@ def check_hp_ok(name):
         print("hp2ps error when processing heap profile for " + name)
         return(False)
 
-def check_prof_ok(name):
+def check_prof_ok(name, way):
 
     prof_file = qualify(name,'prof')
 
@@ -1619,14 +1620,14 @@ def check_prof_ok(name):
     if not os.path.exists(expected_prof_file):
         return True
     else:
-        return compare_outputs('prof', \
+        return compare_outputs(way, 'prof',
                                join_normalisers(normalise_whitespace,normalise_prof), \
                                expected_prof_file, prof_file)
 
 # Compare expected output to actual output, and optionally accept the
 # new output. Returns true if output matched or was accepted, false
 # otherwise.
-def compare_outputs( kind, normaliser, expected_file, actual_file ):
+def compare_outputs(way, kind, normaliser, expected_file, actual_file):
     if os.path.exists(expected_file):
         expected_raw = read_no_crs(expected_file)
         # print "norm:", normaliser(expected_raw)
@@ -1671,7 +1672,11 @@ def compare_outputs( kind, normaliser, expected_file, actual_file ):
                 r = os.system( 'diff -u ' + expected_file_for_diff + \
                                       ' ' + actual_file )
 
-        if config.accept:
+        if config.accept and (getTestOpts().expect == 'fail' or
+                              way in getTestOpts().expect_fail_for):
+            if_verbose(1, 'Test is expected to fail. Not accepting new output.')
+            return 0
+        elif config.accept:
             if_verbose(1, 'Accepting new output.')
             write_file(expected_file, actual_raw)
             return 1
@@ -1685,6 +1690,11 @@ def normalise_whitespace( str ):
     return str
 
 def normalise_errmsg( str ):
+    # remove " error:" and lower-case " Warning:" to make patch for
+    # trac issue #10021 smaller
+    str = modify_lines(str, lambda l: re.sub(' error:', '', l))
+    str = modify_lines(str, lambda l: re.sub(' Warning:', ' warning:', l))
+
     # If somefile ends in ".exe" or ".exe:", zap ".exe" (for Windows)
     #    the colon is there because it appears in error messages; this
     #    hacky solution is used in place of more sophisticated filename
@@ -1740,6 +1750,10 @@ def normalise_exe_( str ):
     return str
 
 def normalise_output( str ):
+    # remove " error:" and lower-case " Warning:" to make patch for
+    # trac issue #10021 smaller
+    str = modify_lines(str, lambda l: re.sub(' error:', '', l))
+    str = modify_lines(str, lambda l: re.sub(' Warning:', ' warning:', l))
     # Remove a .exe extension (for Windows)
     # This can occur in error messages generated by the program.
     str = re.sub('([^\\s])\\.exe', '\\1', str)
@@ -1783,13 +1797,8 @@ def rawSystem(cmd_and_args):
     # with the Windows (non-cygwin) python. An argument "a b c"
     # turns into three arguments ["a", "b", "c"].
 
-    # However, subprocess is new in python 2.4, so fall back to
-    # using spawnv if we don't have it
     cmd = cmd_and_args[0]
-    if have_subprocess:
-        return subprocess.call([strip_quotes(cmd)] + cmd_and_args[1:])
-    else:
-        return os.spawnv(os.P_WAIT, cmd, cmd_and_args)
+    return subprocess.call([strip_quotes(cmd)] + cmd_and_args[1:])
 
 # When running under native msys Python, any invocations of non-msys binaries,
 # including timeout.exe, will have their arguments munged according to some
@@ -1828,6 +1837,9 @@ def rawSystemWithTimeout(cmd_and_args):
 # Then, when using the native Python, os.system will invoke the cmd shell
 
 def runCmd( cmd ):
+    # Format cmd using config. Example: cmd='{hpc} report A.tix'
+    cmd = cmd.format(**config.__dict__)
+
     if_verbose( 3, cmd )
     r = 0
     if config.os == 'mingw32':
@@ -2057,7 +2069,8 @@ if config.have_profiling:
   if config.gs != '':
     resultGood = runCmdExitCode(genGSCmd(config.confdir + '/good.ps'));
     if resultGood == 0:
-        resultBad = runCmdExitCode(genGSCmd(config.confdir + '/bad.ps'));
+        resultBad = runCmdExitCode(genGSCmd(config.confdir + '/bad.ps') +
+                                   ' >/dev/null 2>&1')
         if resultBad != 0:
             print("GhostScript available for hp2ps tests")
             gs_working = 1;
@@ -2179,10 +2192,15 @@ def findTFiles_(path):
 # -----------------------------------------------------------------------------
 # Output a test summary to the specified file object
 
-def summary(t, file):
+def summary(t, file, short=False):
 
     file.write('\n')
     printUnexpectedTests(file, [t.unexpected_passes, t.unexpected_failures, t.unexpected_stat_failures])
+
+    if short:
+        # Only print the list of unexpected tests above.
+        return
+
     file.write('OVERALL SUMMARY for test run started at '
                + time.strftime("%c %Z", t.start_time) + '\n'
                + str(datetime.timedelta(seconds=
@@ -2269,21 +2287,5 @@ def printFailingTestInfosSummary(file, testInfos):
                           ' (' + ','.join(testInfos[directory][test][reason]) + ')\n')
     file.write('\n')
 
-def getStdout(cmd):
-    if have_subprocess:
-        p = subprocess.Popen(strip_quotes(cmd),
-                             stdout=subprocess.PIPE,
-                             stderr=subprocess.PIPE)
-        (stdout, stderr) = p.communicate()
-        r = p.wait()
-        if r != 0:
-            raise Exception("Command failed: " + str(cmd))
-        if stderr != '':
-            raise Exception("stderr from command: " + str(cmd))
-        return stdout
-    else:
-        raise Exception("Need subprocess to get stdout, but don't have it")
-
-def strip_quotes(s):
-    # Don't wrap commands to subprocess.call/Popen in quotes.
-    return s.strip('\'"')
+def modify_lines(s, f):
+    return '\n'.join([f(l) for l in s.splitlines()])