[Git][ghc/ghc][wip/explicit-perf-baseline] Deleted 6 commits: testsuite: Refactor runtests.py

Ben Gamari gitlab at gitlab.haskell.org
Mon Jul 27 19:46:57 UTC 2020



Ben Gamari pushed to branch wip/explicit-perf-baseline at Glasgow Haskell Compiler / GHC


WARNING: The push did not contain any new commits, but force pushed to delete the commits and changes below.


Deleted commits:
2680da51 by Ben Gamari at 2020-07-05T14:23:37-04:00
testsuite: Refactor runtests.py

Trying to bring some order to the chaos of this module. Starting by
moving most code to `main` and breaking out local function bindings.

- - - - -
8617a7ff by Ben Gamari at 2020-07-05T17:25:53-04:00
testsuite: Refactor compiler configuration

- - - - -
869bfae0 by Ben Gamari at 2020-07-05T19:29:41-04:00
Further refactoring

- - - - -
e3e762b6 by Ben Gamari at 2020-07-05T21:23:26-04:00
Fix it

- - - - -
4b559f04 by Ben Gamari at 2020-07-05T22:16:44-04:00
Things

- - - - -
2b239b32 by Ben Gamari at 2020-07-05T22:24:08-04:00
hi

- - - - -


8 changed files:

- hadrian/src/Settings/Builders/RunTest.hs
- − testsuite/config/ghc
- testsuite/driver/runtests.py
- testsuite/driver/testglobals.py
- testsuite/driver/testlib.py
- + testsuite/driver/testsuite_config.py
- testsuite/mk/test.mk
- testsuite/tests/concurrent/prog002/all.T


Changes:

=====================================
hadrian/src/Settings/Builders/RunTest.hs
=====================================
@@ -103,6 +103,7 @@ runTestBuilderArgs = builder RunTest ? do
     -- TODO: set CABAL_MINIMAL_BUILD/CABAL_PLUGIN_BUILD
     mconcat [ arg $ "testsuite/driver/runtests.py"
             , pure [ "--rootdir=" ++ testdir | testdir <- rootdirs ]
+            , arg "--extra-hc-flag", arg ghcFlags
             , arg "-e", arg $ "windows=" ++ show windowsHost
             , arg "-e", arg $ "darwin=" ++ show osxHost
             , arg "-e", arg $ "config.local=False"
@@ -111,23 +112,20 @@ runTestBuilderArgs = builder RunTest ? do
             , arg "-e", arg $ "config.accept_platform=" ++ show acceptPlatform
             , arg "-e", arg $ "config.accept_os=" ++ show acceptOS
             , arg "-e", arg $ "config.exeext=" ++ quote (if null exe then "" else "."<>exe)
-            , arg "-e", arg $ "config.compiler_debugged=" ++
-              show debugged
+            , arg "-e", arg $ "config.compiler_debugged=" ++ show debugged
             , arg "-e", arg $ asBool "ghc_with_native_codegen=" withNativeCodeGen
 
             , arg "-e", arg $ "config.have_interp=" ++ show withInterpreter
             , arg "-e", arg $ "config.unregisterised=" ++ show unregisterised
 
-            , arg "-e", arg $ "ghc_compiler_always_flags=" ++ quote ghcFlags
-            , arg "-e", arg $ asBool "ghc_with_dynamic_rts="  (hasRtsWay "dyn")
-            , arg "-e", arg $ asBool "ghc_with_threaded_rts=" (hasRtsWay "thr")
+            , arg "-e", arg $ asBool "config.ghc_with_dynamic_rts="  (hasRtsWay "dyn")
+            , arg "-e", arg $ asBool "config.ghc_with_threaded_rts=" (hasRtsWay "thr")
             , arg "-e", arg $ asBool "config.have_vanilla="   (hasLibWay vanilla)
             , arg "-e", arg $ asBool "config.have_dynamic="   (hasLibWay dynamic)
             , arg "-e", arg $ asBool "config.have_profiling=" (hasLibWay profiling)
             , arg "-e", arg $ asBool "config.have_fast_bignum=" (bignumBackend /= "native" && not bignumCheck)
-            , arg "-e", arg $ asBool "ghc_with_smp=" withSMP
-            , arg "-e", arg $ asBool "ghc_with_llvm=" withLlvm
-
+            , arg "-e", arg $ asBool "config.ghc_with_smp=" withSMP
+            , arg "-e", arg $ asBool "config.ghc_with_llvm=" withLlvm
 
             , arg "-e", arg $ "config.ghc_dynamic_by_default=" ++ show hasDynamicByDefault
             , arg "-e", arg $ "config.ghc_dynamic=" ++ show hasDynamic


=====================================
testsuite/config/ghc deleted
=====================================
@@ -1,266 +0,0 @@
-# vim: set filetype=python:
-
-import re
-
-# Testsuite configuration setup for GHC
-#
-# This file is Python source
-#
-config.compiler_always_flags = ghc_compiler_always_flags.split()
-
-# By default, the 'normal' and 'hpc' ways are enabled. In addition, certain
-# ways are enabled automatically if this GHC supports them. Ways that fall in
-# this group are 'optasm', 'optllvm', 'profasm', 'threaded1', 'threaded2',
-# 'profthreaded', 'ghci', and whichever of 'static/dyn' is not this GHC's
-# default mode. Other ways should be set explicitly from .T files.
-config.compile_ways       = ['normal', 'hpc']
-config.run_ways           = ['normal', 'hpc']
-
-# ways that are not enabled by default, but can always be invoked explicitly
-config.other_ways         = ['prof', 'normal_h',
-                             'prof_hc_hb','prof_hb',
-                             'prof_hd','prof_hy','prof_hr',
-                             'sanity',
-                             'threaded1_ls', 'threaded2_hT', 'debug_numa',
-                             'llvm', 'debugllvm',
-                             'profllvm', 'profoptllvm', 'profthreadedllvm',
-                             'debug',
-                             'ghci-ext', 'ghci-ext-prof',
-                             'ext-interp',
-                             'nonmoving',
-                             'nonmoving_thr',
-                             'nonmoving_thr_ghc',
-                             'compacting_gc',
-                             ]
-
-if ghc_with_native_codegen:
-    config.compile_ways.append('optasm')
-    config.run_ways.append('optasm')
-
-if config.have_profiling:
-    config.compile_ways.append('profasm')
-    config.run_ways.append('profasm')
-
-if config.have_interp:
-    config.run_ways.append('ghci')
-
-if ghc_with_threaded_rts:
-    config.run_ways.append('threaded1')
-    if ghc_with_smp:
-        config.have_smp = True
-        config.run_ways.append('threaded2')
-        if config.speed == 0:
-            config.run_ways.append('nonmoving_thr')
-
-if ghc_with_dynamic_rts:
-    config.have_shared_libs = True
-
-if config.ghc_dynamic_by_default and config.have_vanilla == 1:
-    config.run_ways.append('static')
-else:
-    if ghc_with_dynamic_rts:
-        config.run_ways.append('dyn')
-
-if (config.have_profiling and ghc_with_threaded_rts):
-    config.run_ways.append('profthreaded')
-
-if (ghc_with_llvm and not config.unregisterised):
-    config.compile_ways.append('optllvm')
-    config.run_ways.append('optllvm')
-
-config.way_flags = {
-    'normal'       : [],
-    'normal_h'     : [],
-    'g1'           : [],
-    'nursery_chunks' : [],
-    'debug_numa'   : ['-threaded', '-debug'],
-    'optasm'       : ['-O', '-fasm'],
-    'llvm'         : ['-fllvm'],
-    'optllvm'      : ['-O', '-fllvm'],
-    'debugllvm'    : ['-fllvm', '-keep-llvm-files'],
-    'prof'         : ['-prof', '-static', '-fprof-auto', '-fasm'],
-    'prof_no_auto' : ['-prof', '-static', '-fasm'],
-    'profasm'      : ['-O', '-prof', '-static', '-fprof-auto'],
-    'profthreaded' : ['-O', '-prof', '-static', '-fprof-auto', '-threaded'],
-    'ghci'         : ['--interactive', '-v0', '-ignore-dot-ghci', '-fno-ghci-history', '+RTS', '-I0.1', '-RTS'] + (['-fghci-leak-check'] if not config.compiler_debugged else []),
-    'sanity'       : ['-debug'],
-    'threaded1'    : ['-threaded', '-debug'],
-    'threaded1_ls' : ['-threaded', '-debug'],
-    'threaded2'    : ['-O', '-threaded', '-eventlog'],
-    'threaded2_hT' : ['-O', '-threaded'],
-    'hpc'          : ['-O', '-fhpc'],
-    'prof_hc_hb'   : ['-O', '-prof', '-static', '-fprof-auto'],
-    'prof_hb'      : ['-O', '-prof', '-static', '-fprof-auto'],
-    'prof_hd'      : ['-O', '-prof', '-static', '-fprof-auto'],
-    'prof_hy'      : ['-O', '-prof', '-static', '-fprof-auto'],
-    'prof_hr'      : ['-O', '-prof', '-static', '-fprof-auto'],
-    'dyn'          : ['-O', '-dynamic'],
-    'static'       : ['-O', '-static'],
-    'debug'        : ['-O', '-g', '-dannot-lint'],
-    # llvm variants...
-    'profllvm'         : ['-prof', '-static', '-fprof-auto', '-fllvm'],
-    'profoptllvm'      : ['-O', '-prof', '-static', '-fprof-auto', '-fllvm'],
-    'profthreadedllvm' : ['-O', '-prof', '-static', '-fprof-auto', '-threaded', '-fllvm'],
-    'ghci-ext'         : ['--interactive', '-v0', '-ignore-dot-ghci', '-fno-ghci-history', '-fexternal-interpreter', '+RTS', '-I0.1', '-RTS'],
-    'ghci-ext-prof'    : ['--interactive', '-v0', '-ignore-dot-ghci', '-fno-ghci-history', '-fexternal-interpreter', '-prof', '+RTS', '-I0.1', '-RTS'],
-    'ext-interp'   : ['-fexternal-interpreter'],
-    'nonmoving'    : [],
-    'nonmoving_thr': ['-threaded'],
-    'nonmoving_thr_ghc': ['+RTS', '-xn', '-N2', '-RTS', '-threaded'],
-    'compacting_gc': [],
-   }
-
-config.way_rts_flags = {
-    'normal'       : [],
-    'normal_h'     : ['-h'], # works without -prof
-    'g1'           : ['-G1'],
-    'nursery_chunks' : ['-n32k'],
-    'debug_numa'   : ['-N2', '--debug-numa=2'],
-    'optasm'       : [],
-    'llvm'         : [],
-    'optllvm'      : [],
-    'debugllvm'    : [],
-    'prof'         : ['-p'],
-    'prof_no_auto' : ['-p'],
-    'profasm'      : ['-hc', '-p'], # test heap profiling too
-    'profthreaded' : ['-p'],
-    'ghci'         : [],
-    'sanity'       : ['-DS'],
-    'threaded1'    : [],
-    'threaded1_ls' : ['-ls'],
-    'threaded2'    : ['-N2', '-ls'],
-    'threaded2_hT' : ['-N2', '-hT'],
-    'hpc'          : [],
-    'prof_hc_hb'   : ['-hc', '-hbvoid'],
-    'prof_hb'      : ['-hb'],
-    'prof_hd'      : ['-hd'],
-    'prof_hy'      : ['-hy'],
-    'prof_hr'      : ['-hr'],
-    'dyn'          : [],
-    'static'       : [],
-    'debug'        : [],
-    # llvm variants...
-    'profllvm'         : ['-p'],
-    'profoptllvm'      : ['-hc', '-p'],
-    'profthreadedllvm' : ['-p'],
-    'ghci-ext'         : [],
-    'ghci-ext-prof'    : [],
-    'ext-interp'       : [],
-    'nonmoving'        : ['-xn'],
-    'nonmoving_thr'    : ['-xn', '-N2'],
-    'nonmoving_thr_ghc': ['-xn', '-N2'],
-    'compacting_gc': ['-c'],
-   }
-
-# Useful classes of ways that can be used with only_ways(), omit_ways() and
-# expect_broken_for().
-
-prof_ways     = [x[0] for x in config.way_flags.items()
-                      if '-prof' in x[1]]
-
-threaded_ways = [x[0] for x in config.way_flags.items()
-                      if '-threaded' in x[1] or 'ghci' == x[0]]
-
-# Ways which run with multiple capabilities
-concurrent_ways = [name for name, flags in config.way_flags.items()
-                        if '-threaded' in flags or 'ghci' == name
-                        if '-N2' in config.way_rts_flags.get(name, [])]
-
-opt_ways      = [x[0] for x in config.way_flags.items()
-                      if '-O' in x[1]]
-
-llvm_ways     = [x[0] for x in config.way_flags.items()
-                      if '-fflvm' in x[1]]
-
-
-def get_compiler_info():
-    s = getStdout([config.compiler, '--info'])
-    s = re.sub('[\r\n]', '', s)
-    compilerInfoDict = dict(eval(s))
-    s = getStdout([config.compiler, '+RTS', '--info'])
-    s = re.sub('[\r\n]', '', s)
-    rtsInfoDict = dict(eval(s))
-
-    config.have_ncg = compilerInfoDict.get("Have native code generator", "NO") == "YES"
-
-    # Whether GHC itself was built using the LLVM backend. We need to know this
-    # since some tests in ext-interp fail when stage2 ghc is built using
-    # LLVM. See #16087.
-    #
-    # The condition here is a bit approximate: we assume that if stage2 doesn't
-    # have the NCG and isn't unregisterised then it must be using the LLVM
-    # backend by default.
-    config.ghc_built_by_llvm = not config.have_ncg and not config.unregisterised
-
-    config.have_RTS_linker = compilerInfoDict.get("target has RTS linker", "NO") == "YES"
-    # external interpreter needs RTS linker support
-    # If the field is not present (GHC 8.0 and earlier), assume we don't
-    # have -fexternal-interpreter (though GHC 8.0 actually does)
-    # so we can still run most tests.
-    config.have_ext_interp = compilerInfoDict.get("target has RTS linker", "NO") == "YES"
-
-    # See Note [Replacing backward slashes in config.libdir].
-    config.libdir = compilerInfoDict['LibDir'].replace('\\', '/')
-
-    if re.match(".*_p(_.*|$)", rtsInfoDict["RTS way"]):
-        config.compiler_profiled = True
-    else:
-        config.compiler_profiled = False
-
-    try:
-        config.package_conf_cache_file = compilerInfoDict["Global Package DB"] + '/package.cache'
-    except:
-        config.package_conf_cache_file = ''
-
-    # See Note [WayFlags]
-    if config.ghc_dynamic:
-        config.ghc_th_way_flags = "-dynamic"
-        config.ghci_way_flags   = "-dynamic"
-        config.plugin_way_flags = "-dynamic"
-        config.ghc_th_way       = "dyn"
-        config.ghc_plugin_way   = "dyn"
-    elif config.compiler_profiled:
-        config.ghc_th_way_flags = "-prof"
-        config.ghci_way_flags   = "-prof"
-        config.plugin_way_flags = "-prof"
-        config.ghc_th_way       = "prof"
-        config.ghc_plugin_way   = "prof"
-    else:
-        config.ghc_th_way_flags = "-static"
-        config.ghci_way_flags   = "-static"
-        config.plugin_way_flags = "-static"
-        config.ghc_th_way       = "normal"
-        config.ghc_plugin_way   = "normal"
-
-# Note [Replacing backward slashes in config.libdir]
-# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-#
-# We *do* need to replace backslashes in config.libdir, for the following
-# reason:
-#
-# * Tests use config.libdir as follows:
-#
-#     extra_run_opts('"' + config.libdir + '"')
-#
-#   The double quotes are there because config.libdir might contain
-#   spaces.
-#
-# * This string is then written /as is/ to <testname>.genscript in
-#   testlib.interpreter_run:
-#
-#     script.write(':set args ' + opts.extra_run_opts + '\n')
-#
-# * But GHCi expects the arguments to ':set args' to be proper Haskell
-#   strings (when they are quoted), with backslashes escaped. Since
-#   config.libdir contains single backslash characters, tests such as T5313
-#   will fail for WAY=ghci with "Pattern match failure in do expression".
-#
-# Arguably the above code for writing `:set args` should be smarter. This
-# is tricky to get right though, because in GHCI `:set args foo\bar` (no
-# double quotes) works perfectly fine, and is interpreted as the Haskell
-# string "foo\\bar". Therefore, simply escaping all backward slashes in
-# opts.extra_run_opts before concatenating it with ':set args' is not right
-# either.
-#
-# Replacing backslashes to forward slashes in config.libdir works around the
-# problem.


=====================================
testsuite/driver/runtests.py
=====================================
@@ -5,6 +5,7 @@
 #
 
 import argparse
+from copy import copy
 import signal
 import sys
 import os
@@ -14,6 +15,7 @@ import tempfile
 import time
 import re
 import traceback
+import datetime
 
 # We don't actually need subprocess in runtests.py, but:
 # * We do need it in testlibs.py
@@ -26,212 +28,27 @@ import subprocess
 from testutil import getStdout, Watcher, str_warn, str_info
 from testglobals import getConfig, ghc_env, getTestRun, TestConfig, \
                         TestOptions, brokens, PerfMetric
-from my_typing import TestName
+from my_typing import TestName, List
 from perf_notes import MetricChange, GitRef, inside_git_repo, is_worktree_dirty, format_perf_stat
+import perf_notes as Perf
+import testsuite_config
 from junit import junit
 import term_color
 from term_color import Color, colored
 import cpu_features
 
-# Readline sometimes spews out ANSI escapes for some values of TERM,
-# which result in test failures. Thus set TERM to a nice, simple, safe
-# value.
-os.environ['TERM'] = 'vt100'
-ghc_env['TERM'] = 'vt100'
-
-global config
-config = getConfig() # get it from testglobals
-
 def signal_handler(signal, frame):
     stopNow()
 
-def get_compiler_info() -> TestConfig:
-    """ Overriddden by configuration file. """
-    raise NotImplementedError
-
-# -----------------------------------------------------------------------------
-# cmd-line options
-
-parser = argparse.ArgumentParser(description="GHC's testsuite driver")
-perf_group = parser.add_mutually_exclusive_group()
-
-parser.add_argument("-e", action='append', help="A string to execute from the command line.")
-parser.add_argument("--config-file", action="append", help="config file")
-parser.add_argument("--config", action='append', help="config field")
-parser.add_argument("--rootdir", action='append', help="root of tree containing tests (default: .)")
-parser.add_argument("--metrics-file", help="file in which to save (append) the performance test metrics. If omitted, git notes will be used.")
-parser.add_argument("--summary-file", help="file in which to save the (human-readable) summary")
-parser.add_argument("--no-print-summary", action="store_true", help="should we print the summary?")
-parser.add_argument("--only", action="append", help="just this test (can be give multiple --only= flags)")
-parser.add_argument("--way", action="append", help="just this way")
-parser.add_argument("--skipway", action="append", help="skip this way")
-parser.add_argument("--threads", type=int, help="threads to run simultaneously")
-parser.add_argument("--verbose", type=int, choices=[0,1,2,3,4,5], help="verbose (Values 0 through 5 accepted)")
-parser.add_argument("--junit", type=argparse.FileType('wb'), help="output testsuite summary in JUnit format")
-parser.add_argument("--broken-test", action="append", default=[], help="a test name to mark as broken for this run")
-parser.add_argument("--test-env", default='local', help="Override default chosen test-env.")
-parser.add_argument("--perf-baseline", type=GitRef, metavar='COMMIT', help="Baseline commit for performance comparsons.")
-perf_group.add_argument("--skip-perf-tests", action="store_true", help="skip performance tests")
-perf_group.add_argument("--only-perf-tests", action="store_true", help="Only do performance tests")
-
-args = parser.parse_args()
-
-# Initialize variables that are set by the build system with -e
-windows = False
-darwin = False
-
-if args.e:
-    for e in args.e:
-        exec(e)
-
-if args.config_file:
-    for arg in args.config_file:
-        exec(open(arg).read())
-
-if args.config:
-    for arg in args.config:
-        field, value = arg.split('=', 1)
-        setattr(config, field, value)
-
-all_ways = config.run_ways+config.compile_ways+config.other_ways
-
-if args.rootdir:
-    config.rootdirs = args.rootdir
-
-config.metrics_file = args.metrics_file
-hasMetricsFile = config.metrics_file is not None
-config.summary_file = args.summary_file
-config.no_print_summary = args.no_print_summary
-config.baseline_commit = args.perf_baseline
-
-if args.only:
-    config.only = args.only
-    config.run_only_some_tests = True
-
-if args.way:
-    for way in args.way:
-        if way not in all_ways:
-            print('WARNING: Unknown WAY %s in --way' % way)
-        else:
-            config.cmdline_ways += [way]
-            if way in config.other_ways:
-                config.run_ways += [way]
-                config.compile_ways += [way]
-
-if args.skipway:
-    for way in args.skipway:
-        if way not in all_ways:
-            print('WARNING: Unknown WAY %s in --skipway' % way)
-
-    config.other_ways = [w for w in config.other_ways if w not in args.skipway]
-    config.run_ways = [w for w in config.run_ways if w not in args.skipway]
-    config.compile_ways = [w for w in config.compile_ways if w not in args.skipway]
-
-config.broken_tests |= {TestName(t) for t in args.broken_test}
-
-if args.threads:
-    config.threads = args.threads
-    config.use_threads = True
-
-if args.verbose is not None:
-    config.verbose = args.verbose
-
-# Note force skip perf tests: skip if this is not a git repo (estimated with inside_git_repo)
-# and no metrics file is given. In this case there is no way to read the previous commit's
-# perf test results, nor a way to store new perf test results.
-forceSkipPerfTests = not hasMetricsFile and not inside_git_repo()
-config.skip_perf_tests = args.skip_perf_tests or forceSkipPerfTests
-config.only_perf_tests = args.only_perf_tests
-
-if args.test_env:
-    config.test_env = args.test_env
-
-config.cygwin = False
-config.msys = False
-
-if windows:
-    h = os.popen('uname -s', 'r')
-    v = h.read()
-    h.close()
-    if v.startswith("CYGWIN"):
-        config.cygwin = True
-    elif v.startswith("MINGW") or v.startswith("MSYS"):
-# msys gives "MINGW32"
-# msys2 gives "MINGW_NT-6.2" or "MSYS_NT-6.3"
-        config.msys = True
-    else:
-        raise Exception("Can't detect Windows terminal type")
-
-# Try to use UTF8
-if windows:
-    import ctypes
-    # Windows and mingw* Python provide windll, msys2 python provides cdll.
-    if hasattr(ctypes, 'WinDLL'):
-        mydll = ctypes.WinDLL    # type: ignore
-    else:
-        mydll = ctypes.CDLL
-
-    # This actually leaves the terminal in codepage 65001 (UTF8) even
-    # after python terminates. We ought really remember the old codepage
-    # and set it back.
-    kernel32 = mydll('kernel32.dll')
-    if kernel32.SetConsoleCP(65001) == 0:
-        raise Exception("Failure calling SetConsoleCP(65001)")
-    if kernel32.SetConsoleOutputCP(65001) == 0:
-        raise Exception("Failure calling SetConsoleOutputCP(65001)")
-
-    # register the interrupt handler
-    signal.signal(signal.SIGINT, signal_handler)
-else:
-    # Try and find a utf8 locale to use
-    # First see if we already have a UTF8 locale
-    h = os.popen('locale | grep LC_CTYPE | grep -i utf', 'r')
-    v = h.read()
-    h.close()
-    if v == '':
-        # We don't, so now see if 'locale -a' works
-        h = os.popen('locale -a | grep -F .', 'r')
-        v = h.read()
-        h.close()
-        if v != '':
-            # If it does then use the first utf8 locale that is available
-            h = os.popen('locale -a | grep -i "utf8\|utf-8" 2>/dev/null', 'r')
-            v = h.readline().strip()
-            h.close()
-            if v != '':
-                os.environ['LC_ALL'] = v
-                ghc_env['LC_ALL'] = v
-                print("setting LC_ALL to", v)
-            else:
-                print('WARNING: No UTF8 locale found.')
-                print('You may get some spurious test failures.')
-
-# https://stackoverflow.com/a/22254892/1308058
-def supports_colors():
-    """
-    Returns True if the running system's terminal supports color, and False
-    otherwise.
-    """
-    plat = sys.platform
-    supported_platform = plat != 'Pocket PC' and (plat != 'win32' or
-                                                  'ANSICON' in os.environ)
-    # isatty is not always implemented, #6223.
-    is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
-    if not supported_platform or not is_a_tty:
-        return False
-    return True
-
-config.supports_colors = supports_colors()
-term_color.enable_color = config.supports_colors
-
-# This has to come after arg parsing as the args can change the compiler
-get_compiler_info()
-
-# Can't import this earlier as we need to know if threading will be
-# enabled or not
-from testlib import *
+# Globals
+config = getConfig() # get it from testglobals
+windows = False # type: bool
+cygwin = False # type: bool
+darwin = False # type: bool
+tempdir = '' # type: str
 
-def format_path(path):
+def format_path(path) -> str:
+    global config, windows
     if windows:
         if os.pathsep == ':':
             # If using msys2 python instead of mingw we have to change the drive
@@ -246,101 +63,102 @@ def format_path(path):
             path = re.sub('\\\\', '/', path)
     return path
 
-# On Windows we need to set $PATH to include the paths to all the DLLs
-# in order for the dynamic library tests to work.
-if windows or darwin:
-    pkginfo = getStdout([config.ghc_pkg, 'dump'])
-    topdir = config.libdir
-    if windows:
-        mingw = os.path.abspath(os.path.join(topdir, '../mingw/bin'))
-        mingw = format_path(mingw)
-        ghc_env['PATH'] = os.pathsep.join([ghc_env.get("PATH", ""), mingw])
-    for line in pkginfo.split('\n'):
-        if line.startswith('library-dirs:'):
-            path = line.rstrip()
-            path = re.sub('^library-dirs: ', '', path)
-            # Use string.replace instead of re.sub, because re.sub
-            # interprets backslashes in the replacement string as
-            # escape sequences.
-            path = path.replace('$topdir', topdir)
-            if path.startswith('"'):
-                path = re.sub('^"(.*)"$', '\\1', path)
-                path = re.sub('\\\\(.)', '\\1', path)
-            if windows:
-                path = format_path(path)
-                ghc_env['PATH'] = os.pathsep.join([path, ghc_env.get("PATH", "")])
-            else:
-                # darwin
-                ghc_env['DYLD_LIBRARY_PATH'] = os.pathsep.join([path, ghc_env.get("DYLD_LIBRARY_PATH", "")])
-
-testopts_local.x = TestOptions()
-
-# if timeout == -1 then we try to calculate a sensible value
-if config.timeout == -1:
-    config.timeout = int(read_no_crs(config.top + '/timeout/calibrate.out'))
-
-print('Timeout is ' + str(config.timeout))
-print('Known ways: ' + ', '.join(config.other_ways))
-print('Run ways: ' + ', '.join(config.run_ways))
-print('Compile ways: ' + ', '.join(config.compile_ways))
-
-# Try get allowed performance changes from the git commit.
-try:
-    config.allowed_perf_changes = Perf.get_allowed_perf_changes()
-except subprocess.CalledProcessError:
-    print('Failed to get allowed metric changes from the HEAD git commit message.')
-
-print('Allowing performance changes in: ' + ', '.join(config.allowed_perf_changes.keys()))
-
-# -----------------------------------------------------------------------------
-# The main dude
-
-if config.rootdirs == []:
-    config.rootdirs = ['.']
-
-t_files = list(findTFiles(config.rootdirs))
-
-print('Found', len(t_files), '.T files...')
-
-t = getTestRun() # type: TestRun
-
-# Avoid cmd.exe built-in 'date' command on Windows
-t.start_time = datetime.datetime.now()
-
-print('Beginning test run at', t.start_time.strftime("%c %Z"))
-
-# For reference
-try:
-    print('Detected CPU features: ', cpu_features.get_cpu_features())
-except Exception as e:
-    print('Failed to detect CPU features: ', e)
-
-sys.stdout.flush()
-# we output text, which cannot be unbuffered
-sys.stdout = os.fdopen(sys.__stdout__.fileno(), "w")
-
-if config.local:
-    tempdir = ''
-else:
-    # See note [Running tests in /tmp]
-    tempdir = tempfile.mkdtemp('', 'ghctest-')
-
-    # opts.testdir should be quoted when used, to make sure the testsuite
-    # keeps working when it contains backward slashes, for example from
-    # using os.path.join. Windows native and mingw* python
-    # (/mingw64/bin/python) set `os.path.sep = '\\'`, while msys2 python
-    # (/bin/python, /usr/bin/python or /usr/local/bin/python) sets
-    # `os.path.sep = '/'`.
-    # To catch usage of unquoted opts.testdir early, insert some spaces into
-    # tempdir.
-    tempdir = os.path.join(tempdir, 'test   spaces')
+# https://stackoverflow.com/a/22254892/1308058
+def supports_colors():
+    """
+    Returns True if the running system's terminal supports color, and False
+    otherwise.
+    """
+    plat = sys.platform
+    supported_platform = plat != 'Pocket PC' and (plat != 'win32' or
+                                                'ANSICON' in os.environ)
+    # isatty is not always implemented, #6223.
+    is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
+    if not supported_platform or not is_a_tty:
+        return False
+    return True
 
-def cleanup_and_exit(exitcode):
-    if config.cleanup and tempdir:
-        shutil.rmtree(tempdir, ignore_errors=True)
-    exit(exitcode)
+def setup_locale() -> None:
+    """
+    Try to use UTF-8
+    """
+    global windows
 
-def tabulate_metrics(metrics: List[PerfMetric]) -> None:
+    if windows:
+        import ctypes
+        # Windows and mingw* Python provide windll, msys2 python provides cdll.
+        if hasattr(ctypes, 'WinDLL'):
+            mydll = ctypes.WinDLL    # type: ignore
+        else:
+            mydll = ctypes.CDLL
+
+        # This actually leaves the terminal in codepage 65001 (UTF8) even
+        # after python terminates. We ought really remember the old codepage
+        # and set it back.
+        kernel32 = mydll('kernel32.dll')
+        if kernel32.SetConsoleCP(65001) == 0:
+            raise Exception("Failure calling SetConsoleCP(65001)")
+        if kernel32.SetConsoleOutputCP(65001) == 0:
+            raise Exception("Failure calling SetConsoleOutputCP(65001)")
+
+        # register the interrupt handler
+        signal.signal(signal.SIGINT, signal_handler)
+    else:
+        # Try and find a utf8 locale to use
+        # First see if we already have a UTF8 locale
+        h = os.popen('locale | grep LC_CTYPE | grep -i utf', 'r')
+        v = h.read()
+        h.close()
+        if v == '':
+            # We don't, so now see if 'locale -a' works
+            h = os.popen('locale -a | grep -F .', 'r')
+            v = h.read()
+            h.close()
+            if v != '':
+                # If it does then use the first utf8 locale that is available
+                h = os.popen('locale -a | grep -i "utf8\|utf-8" 2>/dev/null', 'r')
+                v = h.readline().strip()
+                h.close()
+                if v != '':
+                    os.environ['LC_ALL'] = v
+                    ghc_env['LC_ALL'] = v
+                    print("setting LC_ALL to", v)
+                else:
+                    print('WARNING: No UTF8 locale found.')
+                    print('You may get some spurious test failures.')
+
+def setup_path() -> None:
+    """
+    On Windows we need to set $PATH to include the paths to all the DLLs
+    in order for the dynamic library tests to work.
+    """
+    global windows, darwin
+    if windows or darwin:
+        pkginfo = getStdout([config.ghc_pkg, 'dump'])
+        topdir = config.libdir
+        if windows:
+            mingw = os.path.abspath(os.path.join(topdir, '../mingw/bin'))
+            mingw = format_path(mingw)
+            ghc_env['PATH'] = os.pathsep.join([ghc_env.get("PATH", ""), mingw])
+        for line in pkginfo.split('\n'):
+            if line.startswith('library-dirs:'):
+                path = line.rstrip()
+                path = re.sub('^library-dirs: ', '', path)
+                # Use string.replace instead of re.sub, because re.sub
+                # interprets backslashes in the replacement string as
+                # escape sequences.
+                path = path.replace('$topdir', topdir)
+                if path.startswith('"'):
+                    path = re.sub('^"(.*)"$', '\\1', path)
+                    path = re.sub('\\\\(.)', '\\1', path)
+                if windows:
+                    path = format_path(path)
+                    ghc_env['PATH'] = os.pathsep.join([path, ghc_env.get("PATH", "")])
+                else:
+                    # darwin
+                    ghc_env['DYLD_LIBRARY_PATH'] = os.pathsep.join([path, ghc_env.get("DYLD_LIBRARY_PATH", "")])
+
+def tabulate_perf_metrics(metrics: List[PerfMetric]) -> None:
     for metric in sorted(metrics, key=lambda m: (m.stat.test, m.stat.way, m.stat.metric)):
         print("{test:24}  {metric:40}  {value:15.3f}".format(
             test = "{}({})".format(metric.stat.test, metric.stat.way),
@@ -360,81 +178,21 @@ def tabulate_metrics(metrics: List[PerfMetric]) -> None:
                 rel = rel
             ))
 
-# First collect all the tests to be run
-t_files_ok = True
-for file in t_files:
-    if_verbose(2, '====> Scanning %s' % file)
-    newTestDir(tempdir, os.path.dirname(file))
-    try:
-        with io.open(file, encoding='utf8') as f:
-            src = f.read()
-
-        exec(src)
-    except Exception as e:
-        traceback.print_exc()
-        framework_fail(None, None, 'exception: %s' % e)
-        t_files_ok = False
-
-for name in config.only:
-    if t_files_ok:
-        # See Note [Mutating config.only]
-        framework_fail(name, None, 'test not found')
-    else:
-        # Let user fix .T file errors before reporting on unfound tests.
-        # The reason the test can not be found is likely because of those
-        # .T file errors.
-        pass
-
-if config.list_broken:
-    print('')
-    print('Broken tests:')
-    print('\n  '.join('#{ticket}({a}/{b})'.format(ticket=ticket, a=a, b=b)
-                      for ticket, a, b in brokens))
-    print('')
-
-    if t.framework_failures:
-        print('WARNING:', len(t.framework_failures), 'framework failures!')
-        print('')
-else:
-    # completion watcher
-    watcher = Watcher(len(parallelTests))
-
-    # Now run all the tests
-    try:
-        for oneTest in parallelTests:
-            if stopping():
-                break
-            oneTest(watcher)
-
-        # wait for parallel tests to finish
-        if not stopping():
-            watcher.wait()
-
-        # Run the following tests purely sequential
-        config.use_threads = False
-        for oneTest in aloneTests:
-            if stopping():
-                break
-            oneTest(watcher)
-    except KeyboardInterrupt:
-        pass
-
-    # flush everything before we continue
-    sys.stdout.flush()
-
+def print_perf_summary(skip_perf_tests: bool, force_skip_perf_tests: bool) -> None:
     # Dump metrics data.
+    t = getTestRun()
     print("\nPerformance Metrics (test environment: {}):\n".format(config.test_env))
     if config.baseline_commit:
         print('Performance baseline: %s\n' % config.baseline_commit)
     if any(t.metrics):
-        tabulate_metrics(t.metrics)
+        tabulate_perf_metrics(t.metrics)
     else:
         print("\nNone collected.")
     print("")
 
-    # Warn if had to force skip perf tests (see Note force skip perf tests).
+    # Warn if had to force skip perf tests (see Note [force skip perf tests]).
     spacing = "       "
-    if forceSkipPerfTests and not args.skip_perf_tests:
+    if force_skip_perf_tests and not skip_perf_tests:
         print()
         print(str_warn('Skipping All Performance Tests') + ' `git` exited with non-zero exit code.')
         print(spacing + 'Git is required because performance test results are compared with ancestor git commits\' results (stored with git notes).')
@@ -455,11 +213,11 @@ else:
                 ' the missing metrics. Alternatively, a baseline may be' + \
                 ' recovered from ci results once fetched:\n\n' + \
                 spacing + 'git fetch ' + \
-                  'https://gitlab.haskell.org/ghc/ghc-performance-notes.git' + \
-                  ' refs/notes/perf:refs/notes/' + Perf.CiNamespace
+                'https://gitlab.haskell.org/ghc/ghc-performance-notes.git' + \
+                ' refs/notes/perf:refs/notes/' + Perf.CiNamespace
         else:
             reason = "this is not a git repo so the previous git commit's" + \
-                     " metrics cannot be loaded from git notes:"
+                    " metrics cannot be loaded from git notes:"
             fix = ""
         print()
         print(str_warn('Missing Baseline Metrics') + \
@@ -478,40 +236,317 @@ else:
         print(Perf.allow_changes_string([(m.change, m.stat) for m in t.metrics]))
         print('-' * 25)
 
-    summary(t, sys.stdout, config.no_print_summary, config.supports_colors)
+def main() -> None:
+    global config, windows, darwin, tempdir
+
+    # Readline sometimes spews out ANSI escapes for some values of TERM,
+    # which result in test failures. Thus set TERM to a nice, simple, safe
+    # value.
+    os.environ['TERM'] = 'vt100'
+    ghc_env['TERM'] = 'vt100'
+
+    # -----------------------------------------------------------------------------
+    # cmd-line options
+
+    parser = argparse.ArgumentParser(description="GHC's testsuite driver")
+    perf_group = parser.add_mutually_exclusive_group()
+
+    parser.add_argument("-e", action='append', help="A string to execute from the command line.")
+    parser.add_argument("--config-file", action="append", help="config file")
+    parser.add_argument("--config", action='append', help="config field")
+    parser.add_argument("--rootdir", action='append', help="root of tree containing tests (default: .)")
+    parser.add_argument("--metrics-file", help="file in which to save (append) the performance test metrics. If omitted, git notes will be used.")
+    parser.add_argument("--summary-file", help="file in which to save the (human-readable) summary")
+    parser.add_argument("--no-print-summary", action="store_true", help="should we print the summary?")
+    parser.add_argument("--only", action="append", help="just this test (can be give multiple --only= flags)")
+    parser.add_argument("--way", action="append", help="just this way")
+    parser.add_argument("--skipway", action="append", help="skip this way")
+    parser.add_argument("--threads", type=int, help="threads to run simultaneously")
+    parser.add_argument("--verbose", type=int, choices=[0,1,2,3,4,5], help="verbose (Values 0 through 5 accepted)")
+    parser.add_argument("--junit", type=argparse.FileType('wb'), help="output testsuite summary in JUnit format")
+    parser.add_argument("--broken-test", action="append", default=[], help="a test name to mark as broken for this run")
+    parser.add_argument('--extra-hc-flag', action="append", default=[], help="extra flags to pass to the Haskell compiler")
+    parser.add_argument("--test-env", default='local', help="Override default chosen test-env.")
+    parser.add_argument("--perf-baseline", type=GitRef, metavar='COMMIT', help="Baseline commit for performance comparsons.")
+    perf_group.add_argument("--skip-perf-tests", action="store_true", help="skip performance tests")
+    perf_group.add_argument("--only-perf-tests", action="store_true", help="Only do performance tests")
+
+    args = parser.parse_args()
+
+    # Initialize variables that are set by the build system with -e
+    windows = False
+    darwin = False
+
+    if args.e:
+        for e in args.e:
+            exec(e)
+
+    ts_config = testsuite_config.GHCTestsuiteConfig
+    ts_config_globals = ts_config.init_config(config)
+
+    if args.config:
+        for arg in args.config:
+            field, value = arg.split('=', 1)
+            setattr(config, field, value)
+
+    all_ways = config.run_ways+config.compile_ways+config.other_ways
+
+    if args.rootdir:
+        config.rootdirs = args.rootdir
+
+    config.compiler_always_flags = [
+        flag
+        for flags in args.extra_hc_flag
+        for flag in flags.split()
+    ]
+
+    config.metrics_file = args.metrics_file
+    hasMetricsFile = config.metrics_file is not None
+    config.summary_file = args.summary_file
+    config.no_print_summary = args.no_print_summary
+    config.baseline_commit = args.perf_baseline
+
+    if args.only:
+        config.only = args.only
+        config.run_only_some_tests = True
+
+    if args.way:
+        for way in args.way:
+            if way not in all_ways:
+                print('WARNING: Unknown WAY %s in --way' % way)
+            else:
+                config.cmdline_ways += [way]
+                if way in config.other_ways:
+                    config.run_ways += [way]
+                    config.compile_ways += [way]
 
-    # Write perf stats if any exist or if a metrics file is specified.
-    stats = [stat for (_, stat, __) in t.metrics] # type: List[PerfStat]
-    if hasMetricsFile:
-        print('Appending ' + str(len(stats)) + ' stats to file: ' + config.metrics_file)
-        with open(config.metrics_file, 'a') as f:
-            f.write("\n" + Perf.format_perf_stat(stats))
-    elif inside_git_repo() and any(stats):
-        if is_worktree_dirty():
-            print()
-            print(str_warn('Performance Metrics NOT Saved') + \
-                ' working tree is dirty. Commit changes or use ' + \
-                '--metrics-file to save metrics to a file.')
+    if args.skipway:
+        for way in args.skipway:
+            if way not in all_ways:
+                print('WARNING: Unknown WAY %s in --skipway' % way)
+
+        config.other_ways = [w for w in config.other_ways if w not in args.skipway]
+        config.run_ways = [w for w in config.run_ways if w not in args.skipway]
+        config.compile_ways = [w for w in config.compile_ways if w not in args.skipway]
+
+    config.broken_tests |= {TestName(t) for t in args.broken_test}
+
+    if args.threads:
+        config.threads = args.threads
+        config.use_threads = True
+
+    if args.verbose is not None:
+        config.verbose = args.verbose
+
+    # Note [force skip perf tests]
+    # skip if this is not a git repo (estimated with inside_git_repo)
+    # and no metrics file is given. In this case there is no way to read the previous commit's
+    # perf test results, nor a way to store new perf test results.
+    force_skip_perf_tests = not hasMetricsFile and not inside_git_repo()
+    config.skip_perf_tests = args.skip_perf_tests or force_skip_perf_tests
+    config.only_perf_tests = args.only_perf_tests
+
+    if args.test_env:
+        config.test_env = args.test_env
+
+    config.cygwin = False
+    config.msys = False
+
+    if windows:
+        h = os.popen('uname -s', 'r')
+        v = h.read()
+        h.close()
+        if v.startswith("CYGWIN"):
+            config.cygwin = True
+        elif v.startswith("MINGW") or v.startswith("MSYS"):
+    # msys gives "MINGW32"
+    # msys2 gives "MINGW_NT-6.2" or "MSYS_NT-6.3"
+            config.msys = True
         else:
-            Perf.append_perf_stat(stats)
+            raise Exception("Can't detect Windows terminal type")
+
+    setup_locale()
+
+    config.supports_colors = supports_colors()
+    term_color.enable_color = config.supports_colors
+
+    # This has to come after arg parsing as the args can change the compiler
+    ts_config.get_compiler_info(config)
 
-    # Write summary
-    if config.summary_file:
-        with open(config.summary_file, 'w') as f:
-            summary(t, f)
+    # Can't import this earlier as we need to know if threading will be
+    # enabled or not
+    import testlib
 
-    if args.junit:
-        junit(t).write(args.junit)
+    setup_path()
 
-if len(t.unexpected_failures) > 0 or \
-   len(t.unexpected_stat_failures) > 0 or \
-   len(t.unexpected_passes) > 0 or \
-   len(t.framework_failures) > 0:
-    exitcode = 1
-else:
-    exitcode = 0
+    testlib.testopts_local.x = TestOptions()
 
-cleanup_and_exit(exitcode)
+    # if timeout == -1 then we try to calculate a sensible value
+    if config.timeout == -1:
+        config.timeout = int(testlib.read_no_crs(config.top + '/timeout/calibrate.out'))
+
+    print('Timeout is ' + str(config.timeout))
+    print('Known ways: ' + ', '.join(config.other_ways))
+    print('Run ways: ' + ', '.join(config.run_ways))
+    print('Compile ways: ' + ', '.join(config.compile_ways))
+
+    # Try get allowed performance changes from the git commit.
+    try:
+        config.allowed_perf_changes = Perf.get_allowed_perf_changes()
+    except subprocess.CalledProcessError:
+        print('Failed to get allowed metric changes from the HEAD git commit message.')
+
+    print('Allowing performance changes in: ' + ', '.join(config.allowed_perf_changes.keys()))
+
+    # -----------------------------------------------------------------------------
+    # The main dude
+
+    if config.rootdirs == []:
+        config.rootdirs = ['.']
+
+    t_files = list(testlib.findTFiles(config.rootdirs))
+
+    print('Found', len(t_files), '.T files...')
+
+    t = getTestRun() # type: testlib.TestRun
+
+    # Avoid cmd.exe built-in 'date' command on Windows
+    t.start_time = datetime.datetime.now()
+
+    print('Beginning test run at', t.start_time.strftime("%c %Z"))
+
+    # For reference
+    try:
+        print('Detected CPU features: ', cpu_features.get_cpu_features())
+    except Exception as e:
+        print('Failed to detect CPU features: ', e)
+
+    sys.stdout.flush()
+    # we output text, which cannot be unbuffered
+    sys.stdout = os.fdopen(sys.__stdout__.fileno(), "w")
+
+    if config.local:
+        tempdir = ''
+    else:
+        # See note [Running tests in /tmp]
+        tempdir = tempfile.mkdtemp('', 'ghctest-')
+
+        # opts.testdir should be quoted when used, to make sure the testsuite
+        # keeps working when it contains backward slashes, for example from
+        # using os.path.join. Windows native and mingw* python
+        # (/mingw64/bin/python) set `os.path.sep = '\\'`, while msys2 python
+        # (/bin/python, /usr/bin/python or /usr/local/bin/python) sets
+        # `os.path.sep = '/'`.
+        # To catch usage of unquoted opts.testdir early, insert some spaces into
+        # tempdir.
+        tempdir = os.path.join(tempdir, 'test   spaces')
+
+    def cleanup_and_exit(exitcode: int):
+        if config.cleanup and tempdir:
+            shutil.rmtree(tempdir, ignore_errors=True)
+        exit(exitcode)
+
+    # First collect all the tests to be run
+    t_files_ok = True
+    test_globals = copy(testlib.__dict__)
+    test_globals.update(ts_config_globals)
+    for file in t_files:
+        testlib.if_verbose(2, '====> Scanning %s' % file)
+        testlib.newTestDir(tempdir, os.path.dirname(file))
+        try:
+            with io.open(file, encoding='utf8') as f:
+                src = f.read()
+
+            exec(src, test_globals)
+        except Exception as e:
+            traceback.print_exc()
+            testlib.framework_fail(None, None, 'exception: %s' % e)
+            t_files_ok = False
+
+    for name in config.only:
+        if t_files_ok:
+            # See Note [Mutating config.only]
+            testlib.framework_fail(name, None, 'test not found')
+        else:
+            # Let user fix .T file errors before reporting on unfound tests.
+            # The reason the test can not be found is likely because of those
+            # .T file errors.
+            pass
+
+    if config.list_broken:
+        print('')
+        print('Broken tests:')
+        print('\n  '.join('#{ticket}({a}/{b})'.format(ticket=ticket, a=a, b=b)
+                        for ticket, a, b in brokens))
+        print('')
+
+        if t.framework_failures:
+            print('WARNING:', len(t.framework_failures), 'framework failures!')
+            print('')
+    else:
+        # completion watcher
+        watcher = Watcher(len(testlib.parallelTests))
+
+        # Now run all the tests
+        try:
+            for oneTest in testlib.parallelTests:
+                if testlib.stopping():
+                    break
+                oneTest(watcher)
+
+            # wait for parallel tests to finish
+            if not testlib.stopping():
+                watcher.wait()
+
+            # Run the following tests purely sequential
+            config.use_threads = False
+            for oneTest in testlib.aloneTests:
+                if testlib.stopping():
+                    break
+                oneTest(watcher)
+        except KeyboardInterrupt:
+            pass
+
+        # flush everything before we continue
+        sys.stdout.flush()
+
+        print_perf_summary(
+            force_skip_perf_tests=force_skip_perf_tests,
+            skip_perf_tests=args.skip_perf_tests)
+        testlib.summary(t, sys.stdout, config.no_print_summary, config.supports_colors)
+
+        # Write perf stats if any exist or if a metrics file is specified.
+        stats = [stat for (_, stat, __) in t.metrics] # type: List[Perf.PerfStat]
+        if hasMetricsFile:
+            print('Appending ' + str(len(stats)) + ' stats to file: ' + config.metrics_file)
+            with open(config.metrics_file, 'a') as f:
+                f.write("\n" + Perf.format_perf_stat(stats))
+        elif inside_git_repo() and any(stats):
+            if is_worktree_dirty():
+                print()
+                print(str_warn('Performance Metrics NOT Saved') + \
+                    ' working tree is dirty. Commit changes or use ' + \
+                    '--metrics-file to save metrics to a file.')
+            else:
+                Perf.append_perf_stat(stats)
+
+        # Write summary
+        if config.summary_file:
+            with open(config.summary_file, 'w') as f:
+                testlib.summary(t, f)
+
+        if args.junit:
+            junit(t).write(args.junit)
+
+    if len(t.unexpected_failures) > 0 or \
+    len(t.unexpected_stat_failures) > 0 or \
+    len(t.unexpected_passes) > 0 or \
+    len(t.framework_failures) > 0:
+        exitcode = 1
+    else:
+        exitcode = 0
+
+    cleanup_and_exit(exitcode)
 
 # Note [Running tests in /tmp]
 #
@@ -544,3 +579,6 @@ cleanup_and_exit(exitcode)
 #
 # [1]
 # https://downloads.haskell.org/~ghc/8.0.1/docs/html/users_guide/separate_compilation.html#output-files
+
+if __name__ == '__main__':
+    main()
\ No newline at end of file


=====================================
testsuite/driver/testglobals.py
=====================================
@@ -189,6 +189,13 @@ class TestConfig:
         # I have no idea what this does
         self.package_conf_cache_file = None # type: Optional[Path]
 
+        self.ghc_compiler_always_flags = [] # type: List[str]
+        self.ghc_with_native_codegen = False
+        self.ghc_dynamic_by_default = False
+        self.ghc_with_dynamic_rts = False
+        self.ghc_with_threaded_rts = False
+        self.ghc_with_smp = False
+        self.ghc_with_llvm = False
 
 global config
 config = TestConfig()


=====================================
testsuite/driver/testlib.py
=====================================
@@ -502,6 +502,9 @@ def doing_ghci() -> bool:
 def ghc_dynamic() -> bool:
     return config.ghc_dynamic
 
+def ghc_with_threaded_rts() -> bool:
+    return config.ghc_with_threaded_rts
+
 def fast() -> bool:
     return config.speed == 2
 
@@ -1942,7 +1945,8 @@ def check_prof_ok(name: TestName, way: WayName) -> bool:
 def compare_outputs(way: WayName,
                     kind: str,
                     normaliser: OutputNormalizer,
-                    expected_file, actual_file, diff_file=None,
+                    expected_file: str, actual_file: str,
+                    diff_file: Optional[str]=None,
                     whitespace_normaliser: OutputNormalizer=lambda x:x) -> bool:
 
     expected_path = in_srcdir(expected_file)
@@ -1997,18 +2001,19 @@ def compare_outputs(way: WayName,
             if_verbose(1, 'Test is expected to fail. Not accepting new output.')
             return False
         elif config.accept and actual_raw:
+            suffix = ''
             if config.accept_platform:
                 if_verbose(1, 'Accepting new output for platform "'
                               + config.platform + '".')
-                expected_path += '-' + config.platform
+                suffix += '-' + config.platform
             elif config.accept_os:
                 if_verbose(1, 'Accepting new output for os "'
                               + config.os + '".')
-                expected_path += '-' + config.os
+                suffix += '-' + config.os
             else:
                 if_verbose(1, 'Accepting new output.')
 
-            write_file(expected_path, actual_raw)
+            write_file(expected_path.with_suffix(expected_path.suffix + suffix), actual_raw)
             return True
         elif config.accept:
             if_verbose(1, 'No output. Deleting "{0}".'.format(expected_path))


=====================================
testsuite/driver/testsuite_config.py
=====================================
@@ -0,0 +1,287 @@
+# vim: set filetype=python:
+
+import re
+from testglobals import TestConfig, WayName
+from testutil import getStdout
+
+class TestsuiteConfig:
+    @staticmethod
+    def init_config(config: TestConfig) -> None:
+        raise NotImplemented
+
+    @staticmethod
+    def get_compiler_info(config: TestConfig) -> None:
+        raise NotImplemented
+
+class GHCTestsuiteConfig(TestsuiteConfig):
+    """
+    Testsuite configuration setup for GHC
+    """
+
+    @staticmethod
+    def init_config(config: TestConfig):
+        WayNames = lambda xs: [ WayName(x) for x in xs ]
+
+        # By default, the 'normal' and 'hpc' ways are enabled. In addition, certain
+        # ways are enabled automatically if this GHC supports them. Ways that fall in
+        # this group are 'optasm', 'optllvm', 'profasm', 'threaded1', 'threaded2',
+        # 'profthreaded', 'ghci', and whichever of 'static/dyn' is not this GHC's
+        # default mode. Other ways should be set explicitly from .T files.
+        config.compile_ways       = WayNames(['normal', 'hpc'])
+        config.run_ways           = WayNames(['normal', 'hpc'])
+
+        # ways that are not enabled by default, but can always be invoked explicitly
+        config.other_ways         = WayNames([
+                                    'prof', 'normal_h',
+                                    'prof_hc_hb','prof_hb',
+                                    'prof_hd','prof_hy','prof_hr',
+                                    'sanity',
+                                    'threaded1_ls', 'threaded2_hT', 'debug_numa',
+                                    'llvm', 'debugllvm',
+                                    'profllvm', 'profoptllvm', 'profthreadedllvm',
+                                    'debug',
+                                    'ghci-ext', 'ghci-ext-prof',
+                                    'ext-interp',
+                                    'nonmoving',
+                                    'nonmoving_thr',
+                                    'nonmoving_thr_ghc',
+                                    'compacting_gc',
+                                    ])
+
+        if config.ghc_with_native_codegen:
+            config.compile_ways.append(WayName('optasm'))
+            config.run_ways.append(WayName('optasm'))
+
+        if config.have_profiling:
+            config.compile_ways.append(WayName('profasm'))
+            config.run_ways.append(WayName('profasm'))
+
+        if config.have_interp:
+            config.run_ways.append(WayName('ghci'))
+
+        if config.ghc_with_threaded_rts:
+            config.run_ways.append(WayName('threaded1'))
+            if config.ghc_with_smp:
+                config.have_smp = True
+                config.run_ways.append(WayName('threaded2'))
+                if config.speed == 0:
+                    config.run_ways.append(WayName('nonmoving_thr'))
+
+        if config.ghc_with_dynamic_rts:
+            config.have_shared_libs = True
+
+        if config.ghc_dynamic_by_default and config.have_vanilla == 1:
+            config.run_ways.append(WayName('static'))
+        else:
+            if config.ghc_with_dynamic_rts:
+                config.run_ways.append(WayName('dyn'))
+
+        if (config.have_profiling and config.ghc_with_threaded_rts):
+            config.run_ways.append(WayName('profthreaded'))
+
+        if (config.ghc_with_llvm and not config.unregisterised):
+            config.compile_ways.append(WayName('optllvm'))
+            config.run_ways.append(WayName('optllvm'))
+
+        WayFlags = lambda d: { WayName(k): v for k, v in d.items() }
+        config.way_flags = WayFlags({
+            'normal'       : [],
+            'normal_h'     : [],
+            'g1'           : [],
+            'nursery_chunks' : [],
+            'debug_numa'   : ['-threaded', '-debug'],
+            'optasm'       : ['-O', '-fasm'],
+            'llvm'         : ['-fllvm'],
+            'optllvm'      : ['-O', '-fllvm'],
+            'debugllvm'    : ['-fllvm', '-keep-llvm-files'],
+            'prof'         : ['-prof', '-static', '-fprof-auto', '-fasm'],
+            'prof_no_auto' : ['-prof', '-static', '-fasm'],
+            'profasm'      : ['-O', '-prof', '-static', '-fprof-auto'],
+            'profthreaded' : ['-O', '-prof', '-static', '-fprof-auto', '-threaded'],
+            'ghci'         : ['--interactive', '-v0', '-ignore-dot-ghci', '-fno-ghci-history', '+RTS', '-I0.1', '-RTS'] + (['-fghci-leak-check'] if not config.compiler_debugged else []),
+            'sanity'       : ['-debug'],
+            'threaded1'    : ['-threaded', '-debug'],
+            'threaded1_ls' : ['-threaded', '-debug'],
+            'threaded2'    : ['-O', '-threaded', '-eventlog'],
+            'threaded2_hT' : ['-O', '-threaded'],
+            'hpc'          : ['-O', '-fhpc'],
+            'prof_hc_hb'   : ['-O', '-prof', '-static', '-fprof-auto'],
+            'prof_hb'      : ['-O', '-prof', '-static', '-fprof-auto'],
+            'prof_hd'      : ['-O', '-prof', '-static', '-fprof-auto'],
+            'prof_hy'      : ['-O', '-prof', '-static', '-fprof-auto'],
+            'prof_hr'      : ['-O', '-prof', '-static', '-fprof-auto'],
+            'dyn'          : ['-O', '-dynamic'],
+            'static'       : ['-O', '-static'],
+            'debug'        : ['-O', '-g', '-dannot-lint'],
+            # llvm variants...
+            'profllvm'         : ['-prof', '-static', '-fprof-auto', '-fllvm'],
+            'profoptllvm'      : ['-O', '-prof', '-static', '-fprof-auto', '-fllvm'],
+            'profthreadedllvm' : ['-O', '-prof', '-static', '-fprof-auto', '-threaded', '-fllvm'],
+            'ghci-ext'         : ['--interactive', '-v0', '-ignore-dot-ghci', '-fno-ghci-history', '-fexternal-interpreter', '+RTS', '-I0.1', '-RTS'],
+            'ghci-ext-prof'    : ['--interactive', '-v0', '-ignore-dot-ghci', '-fno-ghci-history', '-fexternal-interpreter', '-prof', '+RTS', '-I0.1', '-RTS'],
+            'ext-interp'   : ['-fexternal-interpreter'],
+            'nonmoving'    : [],
+            'nonmoving_thr': ['-threaded'],
+            'nonmoving_thr_ghc': ['+RTS', '-xn', '-N2', '-RTS', '-threaded'],
+            'compacting_gc': [],
+        })
+
+        config.way_rts_flags = WayFlags({
+            'normal'       : [],
+            'normal_h'     : ['-h'], # works without -prof
+            'g1'           : ['-G1'],
+            'nursery_chunks' : ['-n32k'],
+            'debug_numa'   : ['-N2', '--debug-numa=2'],
+            'optasm'       : [],
+            'llvm'         : [],
+            'optllvm'      : [],
+            'debugllvm'    : [],
+            'prof'         : ['-p'],
+            'prof_no_auto' : ['-p'],
+            'profasm'      : ['-hc', '-p'], # test heap profiling too
+            'profthreaded' : ['-p'],
+            'ghci'         : [],
+            'sanity'       : ['-DS'],
+            'threaded1'    : [],
+            'threaded1_ls' : ['-ls'],
+            'threaded2'    : ['-N2', '-ls'],
+            'threaded2_hT' : ['-N2', '-hT'],
+            'hpc'          : [],
+            'prof_hc_hb'   : ['-hc', '-hbvoid'],
+            'prof_hb'      : ['-hb'],
+            'prof_hd'      : ['-hd'],
+            'prof_hy'      : ['-hy'],
+            'prof_hr'      : ['-hr'],
+            'dyn'          : [],
+            'static'       : [],
+            'debug'        : [],
+            # llvm variants...
+            'profllvm'         : ['-p'],
+            'profoptllvm'      : ['-hc', '-p'],
+            'profthreadedllvm' : ['-p'],
+            'ghci-ext'         : [],
+            'ghci-ext-prof'    : [],
+            'ext-interp'       : [],
+            'nonmoving'        : ['-xn'],
+            'nonmoving_thr'    : ['-xn', '-N2'],
+            'nonmoving_thr_ghc': ['-xn', '-N2'],
+            'compacting_gc': ['-c'],
+        })
+
+        # Useful classes of ways that can be used with only_ways(), omit_ways() and
+        # expect_broken_for().
+        # 
+        # These are injected into the global scope which the tests are
+        # evaluated within.
+        globals = {
+            'prof_ways'       : [x[0] for x in config.way_flags.items()
+                                      if '-prof' in x[1]],
+
+            'threaded_ways'   : [x[0] for x in config.way_flags.items()
+                                      if '-threaded' in x[1] or 'ghci' == x[0]],
+
+            # Ways which run with multiple capabilities
+            'concurrent_ways' : [name for name, flags in config.way_flags.items()
+                                      if '-threaded' in flags or 'ghci' == name
+                                      if '-N2' in config.way_rts_flags.get(name, [])],
+
+            'opt_ways'        : [x[0] for x in config.way_flags.items()
+                                      if '-O' in x[1]],
+
+            'llvm_ways'       : [x[0] for x in config.way_flags.items()
+                                      if '-fflvm' in x[1]],
+        }
+        return globals
+
+    @staticmethod
+    def get_compiler_info(config):
+        s = getStdout([config.compiler, '--info'])
+        s = re.sub('[\r\n]', '', s)
+        compilerInfoDict = dict(eval(s))
+        s = getStdout([config.compiler, '+RTS', '--info'])
+        s = re.sub('[\r\n]', '', s)
+        rtsInfoDict = dict(eval(s))
+
+        config.have_ncg = compilerInfoDict.get("Have native code generator", "NO") == "YES"
+
+        # Whether GHC itself was built using the LLVM backend. We need to know this
+        # since some tests in ext-interp fail when stage2 ghc is built using
+        # LLVM. See #16087.
+        #
+        # The condition here is a bit approximate: we assume that if stage2 doesn't
+        # have the NCG and isn't unregisterised then it must be using the LLVM
+        # backend by default.
+        config.ghc_built_by_llvm = not config.have_ncg and not config.unregisterised
+
+        config.have_RTS_linker = compilerInfoDict.get("target has RTS linker", "NO") == "YES"
+        # external interpreter needs RTS linker support
+        # If the field is not present (GHC 8.0 and earlier), assume we don't
+        # have -fexternal-interpreter (though GHC 8.0 actually does)
+        # so we can still run most tests.
+        config.have_ext_interp = compilerInfoDict.get("target has RTS linker", "NO") == "YES"
+
+        # See Note [Replacing backward slashes in config.libdir].
+        config.libdir = compilerInfoDict['LibDir'].replace('\\', '/')
+
+        if re.match(".*_p(_.*|$)", rtsInfoDict["RTS way"]):
+            config.compiler_profiled = True
+        else:
+            config.compiler_profiled = False
+
+        try:
+            config.package_conf_cache_file = compilerInfoDict["Global Package DB"] + '/package.cache'
+        except:
+            config.package_conf_cache_file = ''
+
+        # See Note [WayFlags]
+        if config.ghc_dynamic:
+            config.ghc_th_way_flags = "-dynamic"
+            config.ghci_way_flags   = "-dynamic"
+            config.plugin_way_flags = "-dynamic"
+            config.ghc_th_way       = "dyn"
+            config.ghc_plugin_way   = "dyn"
+        elif config.compiler_profiled:
+            config.ghc_th_way_flags = "-prof"
+            config.ghci_way_flags   = "-prof"
+            config.plugin_way_flags = "-prof"
+            config.ghc_th_way       = "prof"
+            config.ghc_plugin_way   = "prof"
+        else:
+            config.ghc_th_way_flags = "-static"
+            config.ghci_way_flags   = "-static"
+            config.plugin_way_flags = "-static"
+            config.ghc_th_way       = "normal"
+            config.ghc_plugin_way   = "normal"
+
+# Note [Replacing backward slashes in config.libdir]
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+# We *do* need to replace backslashes in config.libdir, for the following
+# reason:
+#
+# * Tests use config.libdir as follows:
+#
+#     extra_run_opts('"' + config.libdir + '"')
+#
+#   The double quotes are there because config.libdir might contain
+#   spaces.
+#
+# * This string is then written /as is/ to <testname>.genscript in
+#   testlib.interpreter_run:
+#
+#     script.write(':set args ' + opts.extra_run_opts + '\n')
+#
+# * But GHCi expects the arguments to ':set args' to be proper Haskell
+#   strings (when they are quoted), with backslashes escaped. Since
+#   config.libdir contains single backslash characters, tests such as T5313
+#   will fail for WAY=ghci with "Pattern match failure in do expression".
+#
+# Arguably the above code for writing `:set args` should be smarter. This
+# is tricky to get right though, because in GHCI `:set args foo\bar` (no
+# double quotes) works perfectly fine, and is interpreted as the Haskell
+# string "foo\\bar". Therefore, simply escaping all backward slashes in
+# opts.extra_run_opts before concatenating it with ':set args' is not right
+# either.
+#
+# Replacing backslashes to forward slashes in config.libdir works around the
+# problem.


=====================================
testsuite/mk/test.mk
=====================================
@@ -79,7 +79,7 @@ else
 dllext = .so
 endif
 
-RUNTEST_OPTS += -e "ghc_compiler_always_flags='$(TEST_HC_OPTS)'"
+RUNTEST_OPTS += --extra-hc-flag "$(TEST_HC_OPTS)"
 
 ifeq "$(GhcDebugged)" "YES"
 RUNTEST_OPTS += -e "config.compiler_debugged=True"
@@ -123,15 +123,15 @@ RUNTEST_OPTS += -e config.have_profiling=False
 endif
 
 ifeq "$(filter thr, $(GhcRTSWays))" "thr"
-RUNTEST_OPTS += -e ghc_with_threaded_rts=True
+RUNTEST_OPTS += -e config.ghc_with_threaded_rts=True
 else
-RUNTEST_OPTS += -e ghc_with_threaded_rts=False
+RUNTEST_OPTS += -e config.ghc_with_threaded_rts=False
 endif
 
 ifeq "$(filter dyn, $(GhcRTSWays))" "dyn"
-RUNTEST_OPTS += -e ghc_with_dynamic_rts=True
+RUNTEST_OPTS += -e config.ghc_with_dynamic_rts=True
 else
-RUNTEST_OPTS += -e ghc_with_dynamic_rts=False
+RUNTEST_OPTS += -e config.ghc_with_dynamic_rts=False
 endif
 
 ifeq "$(GhcWithInterpreter)" "NO"
@@ -183,21 +183,21 @@ CABAL_PLUGIN_BUILD = --enable-library-vanilla --disable-shared
 endif
 
 ifeq "$(GhcWithSMP)" "YES"
-RUNTEST_OPTS += -e ghc_with_smp=True
+RUNTEST_OPTS += -e config.ghc_with_smp=True
 else
-RUNTEST_OPTS += -e ghc_with_smp=False
+RUNTEST_OPTS += -e config.ghc_with_smp=False
 endif
 
 # Does the LLVM backend work?
 ifeq "$(LLC)" ""
-RUNTEST_OPTS += -e ghc_with_llvm=False
+RUNTEST_OPTS += -e config.ghc_with_llvm=False
 else ifeq "$(TargetARCH_CPP)" "powerpc"
-RUNTEST_OPTS += -e ghc_with_llvm=False
+RUNTEST_OPTS += -e config.ghc_with_llvm=False
 else ifneq "$(LLC)" "llc"
 # If we have a real detected value for LLVM, then it really ought to work
-RUNTEST_OPTS += -e ghc_with_llvm=True
+RUNTEST_OPTS += -e config.ghc_with_llvm=True
 else
-RUNTEST_OPTS += -e ghc_with_llvm=False
+RUNTEST_OPTS += -e config.ghc_with_llvm=False
 endif
 
 ifeq "$(WINDOWS)" "YES"


=====================================
testsuite/tests/concurrent/prog002/all.T
=====================================
@@ -1,7 +1,7 @@
 # Test for bug #713, results in crashes in GHC prior to 20060315 with +RTS -N2
 
 # Add 'threaded2_hT' so that we have at least one test for bug #5127
-if ghc_with_threaded_rts and ghc_with_smp:
+if ghc_with_threaded_rts():
    ways = ['threaded2_hT']
 else:
    ways = []



View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/9636134e798e5964526dd0c5a82d025f8bf2518d...2b239b32cf53fccfb8240eab48ea5566daf74be6

-- 
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/9636134e798e5964526dd0c5a82d025f8bf2518d...2b239b32cf53fccfb8240eab48ea5566daf74be6
You're receiving this email because of your account on gitlab.haskell.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/ghc-commits/attachments/20200727/93c42e42/attachment-0001.html>


More information about the ghc-commits mailing list