tests/run-tests.py
changeset 43076 2372284d9457
parent 43073 5c9c71cde1c9
child 43283 96eb9ef777a8
--- a/tests/run-tests.py	Sat Oct 05 10:29:34 2019 -0400
+++ b/tests/run-tests.py	Sun Oct 06 09:45:02 2019 -0400
@@ -75,9 +75,11 @@
 
 try:
     import shlex
+
     shellquote = shlex.quote
 except (ImportError, AttributeError):
     import pipes
+
     shellquote = pipes.quote
 
 processlock = threading.Lock()
@@ -85,13 +87,14 @@
 pygmentspresent = False
 # ANSI color is unsupported prior to Windows 10
 if os.name != 'nt':
-    try: # is pygments installed
+    try:  # is pygments installed
         import pygments
         import pygments.lexers as lexers
         import pygments.lexer as lexer
         import pygments.formatters as formatters
         import pygments.token as token
         import pygments.style as style
+
         pygmentspresent = True
         difflexer = lexers.DiffLexer()
         terminal256formatter = formatters.Terminal256Formatter()
@@ -99,6 +102,7 @@
         pass
 
 if pygmentspresent:
+
     class TestRunnerStyle(style.Style):
         default_style = ""
         skipped = token.string_to_tokentype("Token.Generic.Skipped")
@@ -106,10 +110,10 @@
         skippedname = token.string_to_tokentype("Token.Generic.SName")
         failedname = token.string_to_tokentype("Token.Generic.FName")
         styles = {
-            skipped:         '#e5e5e5',
-            skippedname:     '#00ffff',
-            failed:          '#7f0000',
-            failedname:      '#ff0000',
+            skipped: '#e5e5e5',
+            skippedname: '#00ffff',
+            failed: '#7f0000',
+            failedname: '#ff0000',
         }
 
     class TestRunnerLexer(lexer.RegexLexer):
@@ -127,7 +131,7 @@
             'failed': [
                 (testpattern, token.Generic.FName),
                 (r'(:| ).*', token.Generic.Failed),
-            ]
+            ],
         }
 
     runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
@@ -137,7 +141,8 @@
 
 if sys.version_info > (3, 5, 0):
     PYTHON3 = True
-    xrange = range # we use xrange in one place, and we'd rather not use range
+    xrange = range  # we use xrange in one place, and we'd rather not use range
+
     def _bytespath(p):
         if p is None:
             return p
@@ -158,20 +163,27 @@
                 self.__len__ = strenv.__len__
                 self.clear = strenv.clear
                 self._strenv = strenv
+
             def __getitem__(self, k):
                 v = self._strenv.__getitem__(_strpath(k))
                 return _bytespath(v)
+
             def __setitem__(self, k, v):
                 self._strenv.__setitem__(_strpath(k), _strpath(v))
+
             def __delitem__(self, k):
                 self._strenv.__delitem__(_strpath(k))
+
             def __contains__(self, k):
                 return self._strenv.__contains__(_strpath(k))
+
             def __iter__(self):
                 return iter([_bytespath(k) for k in iter(self._strenv)])
+
             def get(self, k, default=None):
                 v = self._strenv.get(_strpath(k), _strpath(default))
                 return _bytespath(v)
+
             def pop(self, k, default=None):
                 v = self._strenv.pop(_strpath(k), _strpath(default))
                 return _bytespath(v)
@@ -183,9 +195,11 @@
         getcwdb = lambda: _bytespath(os.getcwd())
 
 elif sys.version_info >= (3, 0, 0):
-    print('%s is only supported on Python 3.5+ and 2.7, not %s' %
-          (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])))
-    sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
+    print(
+        '%s is only supported on Python 3.5+ and 2.7, not %s'
+        % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))
+    )
+    sys.exit(70)  # EX_SOFTWARE from `man 3 sysexit`
 else:
     PYTHON3 = False
 
@@ -228,9 +242,11 @@
     else:
         return False
 
+
 # useipv6 will be set by parseargs
 useipv6 = None
 
+
 def checkportisavailable(port):
     """return true if a port seems free to bind on localhost"""
     if useipv6:
@@ -243,19 +259,31 @@
         s.close()
         return True
     except socket.error as exc:
-        if exc.errno not in (errno.EADDRINUSE, errno.EADDRNOTAVAIL,
-                             errno.EPROTONOSUPPORT):
+        if exc.errno not in (
+            errno.EADDRINUSE,
+            errno.EADDRNOTAVAIL,
+            errno.EPROTONOSUPPORT,
+        ):
             raise
     return False
 
+
 closefds = os.name == 'posix'
+
+
 def Popen4(cmd, wd, timeout, env=None):
     processlock.acquire()
-    p = subprocess.Popen(_strpath(cmd), shell=True, bufsize=-1,
-                         cwd=_strpath(wd), env=env,
-                         close_fds=closefds,
-                         stdin=subprocess.PIPE, stdout=subprocess.PIPE,
-                         stderr=subprocess.STDOUT)
+    p = subprocess.Popen(
+        _strpath(cmd),
+        shell=True,
+        bufsize=-1,
+        cwd=_strpath(wd),
+        env=env,
+        close_fds=closefds,
+        stdin=subprocess.PIPE,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.STDOUT,
+    )
     processlock.release()
 
     p.fromchild = p.stdout
@@ -264,17 +292,20 @@
 
     p.timeout = False
     if timeout:
+
         def t():
             start = time.time()
             while time.time() - start < timeout and p.returncode is None:
-                time.sleep(.1)
+                time.sleep(0.1)
             p.timeout = True
             if p.returncode is None:
                 terminate(p)
+
         threading.Thread(target=t).start()
 
     return p
 
+
 if sys.executable:
     sysexecutable = sys.executable
 elif os.environ.get('PYTHONEXECUTABLE'):
@@ -297,9 +328,11 @@
     'shell': ('HGTEST_SHELL', 'sh'),
 }
 
+
 def canonpath(path):
     return os.path.realpath(os.path.expanduser(path))
 
+
 def parselistfiles(files, listtype, warn=True):
     entries = dict()
     for filename in files:
@@ -321,6 +354,7 @@
         f.close()
     return entries
 
+
 def parsettestcases(path):
     """read a .t test file, return a set of test case names
 
@@ -337,131 +371,262 @@
             raise
     return cases
 
+
 def getparser():
     """Obtain the OptionParser used by the CLI."""
     parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
 
     selection = parser.add_argument_group('Test Selection')
-    selection.add_argument('--allow-slow-tests', action='store_true',
-        help='allow extremely slow tests')
-    selection.add_argument("--blacklist", action="append",
-        help="skip tests listed in the specified blacklist file")
-    selection.add_argument("--changed",
-        help="run tests that are changed in parent rev or working directory")
-    selection.add_argument("-k", "--keywords",
-        help="run tests matching keywords")
-    selection.add_argument("-r", "--retest", action="store_true",
-        help = "retest failed tests")
-    selection.add_argument("--test-list", action="append",
-        help="read tests to run from the specified file")
-    selection.add_argument("--whitelist", action="append",
-        help="always run tests listed in the specified whitelist file")
-    selection.add_argument('tests', metavar='TESTS', nargs='*',
-                        help='Tests to run')
+    selection.add_argument(
+        '--allow-slow-tests',
+        action='store_true',
+        help='allow extremely slow tests',
+    )
+    selection.add_argument(
+        "--blacklist",
+        action="append",
+        help="skip tests listed in the specified blacklist file",
+    )
+    selection.add_argument(
+        "--changed",
+        help="run tests that are changed in parent rev or working directory",
+    )
+    selection.add_argument(
+        "-k", "--keywords", help="run tests matching keywords"
+    )
+    selection.add_argument(
+        "-r", "--retest", action="store_true", help="retest failed tests"
+    )
+    selection.add_argument(
+        "--test-list",
+        action="append",
+        help="read tests to run from the specified file",
+    )
+    selection.add_argument(
+        "--whitelist",
+        action="append",
+        help="always run tests listed in the specified whitelist file",
+    )
+    selection.add_argument(
+        'tests', metavar='TESTS', nargs='*', help='Tests to run'
+    )
 
     harness = parser.add_argument_group('Test Harness Behavior')
-    harness.add_argument('--bisect-repo',
-                        metavar='bisect_repo',
-                        help=("Path of a repo to bisect. Use together with "
-                              "--known-good-rev"))
-    harness.add_argument("-d", "--debug", action="store_true",
+    harness.add_argument(
+        '--bisect-repo',
+        metavar='bisect_repo',
+        help=(
+            "Path of a repo to bisect. Use together with " "--known-good-rev"
+        ),
+    )
+    harness.add_argument(
+        "-d",
+        "--debug",
+        action="store_true",
         help="debug mode: write output of test scripts to console"
-             " rather than capturing and diffing it (disables timeout)")
-    harness.add_argument("-f", "--first", action="store_true",
-        help="exit on the first test failure")
-    harness.add_argument("-i", "--interactive", action="store_true",
-        help="prompt to accept changed output")
-    harness.add_argument("-j", "--jobs", type=int,
+        " rather than capturing and diffing it (disables timeout)",
+    )
+    harness.add_argument(
+        "-f",
+        "--first",
+        action="store_true",
+        help="exit on the first test failure",
+    )
+    harness.add_argument(
+        "-i",
+        "--interactive",
+        action="store_true",
+        help="prompt to accept changed output",
+    )
+    harness.add_argument(
+        "-j",
+        "--jobs",
+        type=int,
         help="number of jobs to run in parallel"
-             " (default: $%s or %d)" % defaults['jobs'])
-    harness.add_argument("--keep-tmpdir", action="store_true",
-        help="keep temporary directory after running tests")
-    harness.add_argument('--known-good-rev',
-                        metavar="known_good_rev",
-                        help=("Automatically bisect any failures using this "
-                              "revision as a known-good revision."))
-    harness.add_argument("--list-tests", action="store_true",
-        help="list tests instead of running them")
-    harness.add_argument("--loop", action="store_true",
-        help="loop tests repeatedly")
-    harness.add_argument('--random', action="store_true",
-        help='run tests in random order')
-    harness.add_argument('--order-by-runtime', action="store_true",
-        help='run slowest tests first, according to .testtimes')
-    harness.add_argument("-p", "--port", type=int,
+        " (default: $%s or %d)" % defaults['jobs'],
+    )
+    harness.add_argument(
+        "--keep-tmpdir",
+        action="store_true",
+        help="keep temporary directory after running tests",
+    )
+    harness.add_argument(
+        '--known-good-rev',
+        metavar="known_good_rev",
+        help=(
+            "Automatically bisect any failures using this "
+            "revision as a known-good revision."
+        ),
+    )
+    harness.add_argument(
+        "--list-tests",
+        action="store_true",
+        help="list tests instead of running them",
+    )
+    harness.add_argument(
+        "--loop", action="store_true", help="loop tests repeatedly"
+    )
+    harness.add_argument(
+        '--random', action="store_true", help='run tests in random order'
+    )
+    harness.add_argument(
+        '--order-by-runtime',
+        action="store_true",
+        help='run slowest tests first, according to .testtimes',
+    )
+    harness.add_argument(
+        "-p",
+        "--port",
+        type=int,
         help="port on which servers should listen"
-             " (default: $%s or %d)" % defaults['port'])
-    harness.add_argument('--profile-runner', action='store_true',
-                        help='run statprof on run-tests')
-    harness.add_argument("-R", "--restart", action="store_true",
-        help="restart at last error")
-    harness.add_argument("--runs-per-test", type=int, dest="runs_per_test",
-        help="run each test N times (default=1)", default=1)
-    harness.add_argument("--shell",
-        help="shell to use (default: $%s or %s)" % defaults['shell'])
-    harness.add_argument('--showchannels', action='store_true',
-                        help='show scheduling channels')
-    harness.add_argument("--slowtimeout", type=int,
+        " (default: $%s or %d)" % defaults['port'],
+    )
+    harness.add_argument(
+        '--profile-runner',
+        action='store_true',
+        help='run statprof on run-tests',
+    )
+    harness.add_argument(
+        "-R", "--restart", action="store_true", help="restart at last error"
+    )
+    harness.add_argument(
+        "--runs-per-test",
+        type=int,
+        dest="runs_per_test",
+        help="run each test N times (default=1)",
+        default=1,
+    )
+    harness.add_argument(
+        "--shell", help="shell to use (default: $%s or %s)" % defaults['shell']
+    )
+    harness.add_argument(
+        '--showchannels', action='store_true', help='show scheduling channels'
+    )
+    harness.add_argument(
+        "--slowtimeout",
+        type=int,
         help="kill errant slow tests after SLOWTIMEOUT seconds"
-             " (default: $%s or %d)" % defaults['slowtimeout'])
-    harness.add_argument("-t", "--timeout", type=int,
+        " (default: $%s or %d)" % defaults['slowtimeout'],
+    )
+    harness.add_argument(
+        "-t",
+        "--timeout",
+        type=int,
         help="kill errant tests after TIMEOUT seconds"
-             " (default: $%s or %d)" % defaults['timeout'])
-    harness.add_argument("--tmpdir",
+        " (default: $%s or %d)" % defaults['timeout'],
+    )
+    harness.add_argument(
+        "--tmpdir",
         help="run tests in the given temporary directory"
-             " (implies --keep-tmpdir)")
-    harness.add_argument("-v", "--verbose", action="store_true",
-        help="output verbose messages")
+        " (implies --keep-tmpdir)",
+    )
+    harness.add_argument(
+        "-v", "--verbose", action="store_true", help="output verbose messages"
+    )
 
     hgconf = parser.add_argument_group('Mercurial Configuration')
-    hgconf.add_argument("--chg", action="store_true",
-        help="install and use chg wrapper in place of hg")
-    hgconf.add_argument("--compiler",
-        help="compiler to build with")
-    hgconf.add_argument('--extra-config-opt', action="append", default=[],
-        help='set the given config opt in the test hgrc')
-    hgconf.add_argument("-l", "--local", action="store_true",
+    hgconf.add_argument(
+        "--chg",
+        action="store_true",
+        help="install and use chg wrapper in place of hg",
+    )
+    hgconf.add_argument("--compiler", help="compiler to build with")
+    hgconf.add_argument(
+        '--extra-config-opt',
+        action="append",
+        default=[],
+        help='set the given config opt in the test hgrc',
+    )
+    hgconf.add_argument(
+        "-l",
+        "--local",
+        action="store_true",
         help="shortcut for --with-hg=<testdir>/../hg, "
-             "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set")
-    hgconf.add_argument("--ipv6", action="store_true",
-        help="prefer IPv6 to IPv4 for network related tests")
-    hgconf.add_argument("--pure", action="store_true",
-        help="use pure Python code instead of C extensions")
-    hgconf.add_argument("-3", "--py3-warnings", action="store_true",
-        help="enable Py3k warnings on Python 2.7+")
-    hgconf.add_argument("--with-chg", metavar="CHG",
-        help="use specified chg wrapper in place of hg")
-    hgconf.add_argument("--with-hg",
+        "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set",
+    )
+    hgconf.add_argument(
+        "--ipv6",
+        action="store_true",
+        help="prefer IPv6 to IPv4 for network related tests",
+    )
+    hgconf.add_argument(
+        "--pure",
+        action="store_true",
+        help="use pure Python code instead of C extensions",
+    )
+    hgconf.add_argument(
+        "-3",
+        "--py3-warnings",
+        action="store_true",
+        help="enable Py3k warnings on Python 2.7+",
+    )
+    hgconf.add_argument(
+        "--with-chg",
+        metavar="CHG",
+        help="use specified chg wrapper in place of hg",
+    )
+    hgconf.add_argument(
+        "--with-hg",
         metavar="HG",
         help="test using specified hg script rather than a "
-             "temporary installation")
+        "temporary installation",
+    )
 
     reporting = parser.add_argument_group('Results Reporting')
-    reporting.add_argument("-C", "--annotate", action="store_true",
-        help="output files annotated with coverage")
-    reporting.add_argument("--color", choices=["always", "auto", "never"],
+    reporting.add_argument(
+        "-C",
+        "--annotate",
+        action="store_true",
+        help="output files annotated with coverage",
+    )
+    reporting.add_argument(
+        "--color",
+        choices=["always", "auto", "never"],
         default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
-        help="colorisation: always|auto|never (default: auto)")
-    reporting.add_argument("-c", "--cover", action="store_true",
-        help="print a test coverage report")
-    reporting.add_argument('--exceptions', action='store_true',
-        help='log all exceptions and generate an exception report')
-    reporting.add_argument("-H", "--htmlcov", action="store_true",
-        help="create an HTML report of the coverage of the files")
-    reporting.add_argument("--json", action="store_true",
-        help="store test result data in 'report.json' file")
-    reporting.add_argument("--outputdir",
-        help="directory to write error logs to (default=test directory)")
-    reporting.add_argument("-n", "--nodiff", action="store_true",
-        help="skip showing test changes")
-    reporting.add_argument("-S", "--noskips", action="store_true",
-        help="don't report skip tests verbosely")
-    reporting.add_argument("--time", action="store_true",
-        help="time how long each test takes")
-    reporting.add_argument("--view",
-        help="external diff viewer")
-    reporting.add_argument("--xunit",
-        help="record xunit results at specified path")
+        help="colorisation: always|auto|never (default: auto)",
+    )
+    reporting.add_argument(
+        "-c",
+        "--cover",
+        action="store_true",
+        help="print a test coverage report",
+    )
+    reporting.add_argument(
+        '--exceptions',
+        action='store_true',
+        help='log all exceptions and generate an exception report',
+    )
+    reporting.add_argument(
+        "-H",
+        "--htmlcov",
+        action="store_true",
+        help="create an HTML report of the coverage of the files",
+    )
+    reporting.add_argument(
+        "--json",
+        action="store_true",
+        help="store test result data in 'report.json' file",
+    )
+    reporting.add_argument(
+        "--outputdir",
+        help="directory to write error logs to (default=test directory)",
+    )
+    reporting.add_argument(
+        "-n", "--nodiff", action="store_true", help="skip showing test changes"
+    )
+    reporting.add_argument(
+        "-S",
+        "--noskips",
+        action="store_true",
+        help="don't report skip tests verbosely",
+    )
+    reporting.add_argument(
+        "--time", action="store_true", help="time how long each test takes"
+    )
+    reporting.add_argument("--view", help="external diff viewer")
+    reporting.add_argument(
+        "--xunit", help="record xunit results at specified path"
+    )
 
     for option, (envvar, default) in defaults.items():
         defaults[option] = type(default)(os.environ.get(envvar, default))
@@ -469,6 +634,7 @@
 
     return parser
 
+
 def parseargs(args, parser):
     """Parse arguments with our OptionParser and validate results."""
     options = parser.parse_args(args)
@@ -488,14 +654,18 @@
         for relpath, attr in pathandattrs:
             binpath = os.path.join(reporootdir, relpath)
             if os.name != 'nt' and not os.access(binpath, os.X_OK):
-                parser.error('--local specified, but %r not found or '
-                             'not executable' % binpath)
+                parser.error(
+                    '--local specified, but %r not found or '
+                    'not executable' % binpath
+                )
             setattr(options, attr, _strpath(binpath))
 
     if options.with_hg:
         options.with_hg = canonpath(_bytespath(options.with_hg))
-        if not (os.path.isfile(options.with_hg) and
-                os.access(options.with_hg, os.X_OK)):
+        if not (
+            os.path.isfile(options.with_hg)
+            and os.access(options.with_hg, os.X_OK)
+        ):
             parser.error('--with-hg must specify an executable hg script')
         if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
             sys.stderr.write('warning: --with-hg should specify an hg script\n')
@@ -506,17 +676,23 @@
     if options.with_chg:
         options.chg = False  # no installation to temporary location
         options.with_chg = canonpath(_bytespath(options.with_chg))
-        if not (os.path.isfile(options.with_chg) and
-                os.access(options.with_chg, os.X_OK)):
+        if not (
+            os.path.isfile(options.with_chg)
+            and os.access(options.with_chg, os.X_OK)
+        ):
             parser.error('--with-chg must specify a chg executable')
     if options.chg and options.with_hg:
         # chg shares installation location with hg
-        parser.error('--chg does not work when --with-hg is specified '
-                     '(use --with-chg instead)')
+        parser.error(
+            '--chg does not work when --with-hg is specified '
+            '(use --with-chg instead)'
+        )
 
     if options.color == 'always' and not pygmentspresent:
-        sys.stderr.write('warning: --color=always ignored because '
-                         'pygments is not installed\n')
+        sys.stderr.write(
+            'warning: --color=always ignored because '
+            'pygments is not installed\n'
+        )
 
     if options.bisect_repo and not options.known_good_rev:
         parser.error("--bisect-repo cannot be used without --known-good-rev")
@@ -526,13 +702,15 @@
         useipv6 = checksocketfamily('AF_INET6')
     else:
         # only use IPv6 if IPv4 is unavailable and IPv6 is available
-        useipv6 = ((not checksocketfamily('AF_INET'))
-                   and checksocketfamily('AF_INET6'))
+        useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily(
+            'AF_INET6'
+        )
 
     options.anycoverage = options.cover or options.annotate or options.htmlcov
     if options.anycoverage:
         try:
             import coverage
+
             covver = version.StrictVersion(coverage.__version__).version
             if covver < (3, 3):
                 parser.error('coverage options require coverage 3.3 or later')
@@ -541,12 +719,14 @@
 
     if options.anycoverage and options.local:
         # this needs some path mangling somewhere, I guess
-        parser.error("sorry, coverage options do not work when --local "
-                     "is specified")
+        parser.error(
+            "sorry, coverage options do not work when --local " "is specified"
+        )
 
     if options.anycoverage and options.with_hg:
-        parser.error("sorry, coverage options do not work when --with-hg "
-                     "is specified")
+        parser.error(
+            "sorry, coverage options do not work when --with-hg " "is specified"
+        )
 
     global verbose
     if options.verbose:
@@ -561,17 +741,16 @@
         parser.error("-i/--interactive and -d/--debug are incompatible")
     if options.debug:
         if options.timeout != defaults['timeout']:
-            sys.stderr.write(
-                'warning: --timeout option ignored with --debug\n')
+            sys.stderr.write('warning: --timeout option ignored with --debug\n')
         if options.slowtimeout != defaults['slowtimeout']:
             sys.stderr.write(
-                'warning: --slowtimeout option ignored with --debug\n')
+                'warning: --slowtimeout option ignored with --debug\n'
+            )
         options.timeout = 0
         options.slowtimeout = 0
     if options.py3_warnings:
         if PYTHON3:
-            parser.error(
-                '--py3-warnings can only be used on Python 2.7')
+            parser.error('--py3-warnings can only be used on Python 2.7')
 
     if options.blacklist:
         options.blacklist = parselistfiles(options.blacklist, 'blacklist')
@@ -585,6 +764,7 @@
 
     return options
 
+
 def rename(src, dst):
     """Like os.rename(), trade atomicity and opened files friendliness
     for existing destination support.
@@ -592,6 +772,7 @@
     shutil.copy(src, dst)
     os.remove(src)
 
+
 def makecleanable(path):
     """Try to fix directory permission recursively so that the entire tree
     can be deleted"""
@@ -603,11 +784,14 @@
             except OSError:
                 pass
 
+
 _unified_diff = difflib.unified_diff
 if PYTHON3:
     import functools
+
     _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
 
+
 def getdiff(expected, output, ref, err):
     servefail = False
     lines = []
@@ -618,12 +802,16 @@
                 line = line[:-2] + b'\n'
         lines.append(line)
         if not servefail and line.startswith(
-                             b'+  abort: child process failed to start'):
+            b'+  abort: child process failed to start'
+        ):
             servefail = True
 
     return servefail, lines
 
+
 verbose = False
+
+
 def vlog(*msg):
     """Log only when in verbose mode."""
     if verbose is False:
@@ -631,6 +819,7 @@
 
     return log(*msg)
 
+
 # Bytes that break XML even in a CDATA block: control characters 0-31
 # sans \t, \n and \r
 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
@@ -641,6 +830,7 @@
 #   output..output (feature !)\n
 optline = re.compile(br'(.*) \((.+?) !\)\n$')
 
+
 def cdatasafe(data):
     """Make a string safe to include in a CDATA block.
 
@@ -651,6 +841,7 @@
     """
     return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
 
+
 def log(*msg):
     """Log something to stdout.
 
@@ -664,12 +855,15 @@
         print()
         sys.stdout.flush()
 
+
 def highlightdiff(line, color):
     if not color:
         return line
     assert pygmentspresent
-    return pygments.highlight(line.decode('latin1'), difflexer,
-                              terminal256formatter).encode('latin1')
+    return pygments.highlight(
+        line.decode('latin1'), difflexer, terminal256formatter
+    ).encode('latin1')
+
 
 def highlightmsg(msg, color):
     if not color:
@@ -677,6 +871,7 @@
     assert pygmentspresent
     return pygments.highlight(msg, runnerlexer, runnerformatter)
 
+
 def terminate(proc):
     """Terminate subprocess"""
     vlog('# Terminating process %d' % proc.pid)
@@ -685,10 +880,12 @@
     except OSError:
         pass
 
+
 def killdaemons(pidfile):
     import killdaemons as killmod
-    return killmod.killdaemons(pidfile, tryhard=False, remove=True,
-                               logfn=vlog)
+
+    return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog)
+
 
 class Test(unittest.TestCase):
     """Encapsulates a single, runnable test.
@@ -701,14 +898,24 @@
     # Status code reserved for skipped tests (used by hghave).
     SKIPPED_STATUS = 80
 
-    def __init__(self, path, outputdir, tmpdir, keeptmpdir=False,
-                 debug=False,
-                 first=False,
-                 timeout=None,
-                 startport=None, extraconfigopts=None,
-                 py3warnings=False, shell=None, hgcommand=None,
-                 slowtimeout=None, usechg=False,
-                 useipv6=False):
+    def __init__(
+        self,
+        path,
+        outputdir,
+        tmpdir,
+        keeptmpdir=False,
+        debug=False,
+        first=False,
+        timeout=None,
+        startport=None,
+        extraconfigopts=None,
+        py3warnings=False,
+        shell=None,
+        hgcommand=None,
+        slowtimeout=None,
+        usechg=False,
+        useipv6=False,
+    ):
         """Create a test from parameters.
 
         path is the full path to the file defining the test.
@@ -783,7 +990,7 @@
         # If we're not in --debug mode and reference output file exists,
         # check test output against it.
         if self._debug:
-            return None # to match "out is None"
+            return None  # to match "out is None"
         elif os.path.exists(self.refpath):
             with open(self.refpath, 'rb') as f:
                 return f.read().splitlines(True)
@@ -830,8 +1037,9 @@
                     raise
 
         if self._usechg:
-            self._chgsockdir = os.path.join(self._threadtmp,
-                                            b'%s.chgsock' % name)
+            self._chgsockdir = os.path.join(
+                self._threadtmp, b'%s.chgsock' % name
+            )
             os.mkdir(self._chgsockdir)
 
     def run(self, result):
@@ -914,7 +1122,7 @@
         self._skipped = False
 
         if ret == self.SKIPPED_STATUS:
-            if out is None: # Debug mode, nothing to parse.
+            if out is None:  # Debug mode, nothing to parse.
                 missing = ['unknown']
                 failed = None
             else:
@@ -934,8 +1142,11 @@
             self.fail('no result code from test')
         elif out != self._refout:
             # Diff generation may rely on written .err file.
-            if ((ret != 0 or out != self._refout) and not self._skipped
-                and not self._debug):
+            if (
+                (ret != 0 or out != self._refout)
+                and not self._skipped
+                and not self._debug
+            ):
                 with open(self.errpath, 'wb') as f:
                     for line in out:
                         f.write(line)
@@ -965,9 +1176,13 @@
         self._daemonpids = []
 
         if self._keeptmpdir:
-            log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' %
-                (self._testtmp.decode('utf-8'),
-                 self._threadtmp.decode('utf-8')))
+            log(
+                '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s'
+                % (
+                    self._testtmp.decode('utf-8'),
+                    self._threadtmp.decode('utf-8'),
+                )
+            )
         else:
             try:
                 shutil.rmtree(self._testtmp)
@@ -983,8 +1198,12 @@
             # files are deleted
             shutil.rmtree(self._chgsockdir, True)
 
-        if ((self._ret != 0 or self._out != self._refout) and not self._skipped
-            and not self._debug and self._out):
+        if (
+            (self._ret != 0 or self._out != self._refout)
+            and not self._skipped
+            and not self._debug
+            and self._out
+        ):
             with open(self.errpath, 'wb') as f:
                 for line in self._out:
                     f.write(line)
@@ -1017,7 +1236,7 @@
             self._portmap(2),
             (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
             (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
-            ]
+        ]
         r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
 
         replacementfile = os.path.join(self._testdir, b'common-pattern.py')
@@ -1038,10 +1257,15 @@
 
     def _escapepath(self, p):
         if os.name == 'nt':
-            return (
-                (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
-                    c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
-                    for c in [p[i:i + 1] for i in range(len(p))]))
+            return b''.join(
+                c.isalpha()
+                and b'[%s%s]' % (c.lower(), c.upper())
+                or c in b'/\\'
+                and br'[/\\]'
+                or c.isdigit()
+                and c
+                or b'\\' + c
+                for c in [p[i : i + 1] for i in range(len(p))]
             )
         else:
             return re.escape(p)
@@ -1083,9 +1307,11 @@
 
     def _getenv(self):
         """Obtain environment variables to use during test execution."""
+
         def defineport(i):
             offset = '' if i == 0 else '%s' % i
             env["HGPORT%s" % offset] = '%s' % (self._startport + i)
+
         env = os.environ.copy()
         env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
         env['HGEMITWARNINGS'] = '1'
@@ -1097,11 +1323,13 @@
             # This list should be parallel to _portmap in _getreplacements
             defineport(port)
         env["HGRCPATH"] = _strpath(os.path.join(self._threadtmp, b'.hgrc'))
-        env["DAEMON_PIDS"] = _strpath(os.path.join(self._threadtmp,
-                                                   b'daemon.pids'))
-        env["HGEDITOR"] = ('"' + sysexecutable + '"'
-                           + ' -c "import sys; sys.exit(0)"')
-        env["HGUSER"]   = "test"
+        env["DAEMON_PIDS"] = _strpath(
+            os.path.join(self._threadtmp, b'daemon.pids')
+        )
+        env["HGEDITOR"] = (
+            '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
+        )
+        env["HGUSER"] = "test"
         env["HGENCODING"] = "ascii"
         env["HGENCODINGMODE"] = "strict"
         env["HGHOSTNAME"] = "test-hostname"
@@ -1111,7 +1339,8 @@
             # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
             # non-test one in as a default, otherwise set to devnull
             env['HGTESTCATAPULTSERVERPIPE'] = env.get(
-                'HGCATAPULTSERVERPIPE', os.devnull)
+                'HGCATAPULTSERVERPIPE', os.devnull
+            )
 
         extraextensions = []
         for opt in self._extraconfigopts:
@@ -1187,11 +1416,15 @@
             hgrc.write(b'all-warnings = true\n')
             hgrc.write(b'default-date = 0 0\n')
             hgrc.write(b'[largefiles]\n')
-            hgrc.write(b'usercache = %s\n' %
-                       (os.path.join(self._testtmp, b'.cache/largefiles')))
+            hgrc.write(
+                b'usercache = %s\n'
+                % (os.path.join(self._testtmp, b'.cache/largefiles'))
+            )
             hgrc.write(b'[lfs]\n')
-            hgrc.write(b'usercache = %s\n' %
-                       (os.path.join(self._testtmp, b'.cache/lfs')))
+            hgrc.write(
+                b'usercache = %s\n'
+                % (os.path.join(self._testtmp, b'.cache/lfs'))
+            )
             hgrc.write(b'[web]\n')
             hgrc.write(b'address = localhost\n')
             hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii'))
@@ -1199,8 +1432,9 @@
 
             for opt in self._extraconfigopts:
                 section, key = opt.encode('utf-8').split(b'.', 1)
-                assert b'=' in key, ('extra config opt %s must '
-                                     'have an = for assignment' % opt)
+                assert b'=' in key, (
+                    'extra config opt %s must ' 'have an = for assignment' % opt
+                )
                 hgrc.write(b'[%s]\n%s\n' % (section, key))
 
     def fail(self, msg):
@@ -1215,13 +1449,14 @@
         Return a tuple (exitcode, output). output is None in debug mode.
         """
         if self._debug:
-            proc = subprocess.Popen(_strpath(cmd), shell=True,
-                                    cwd=_strpath(self._testtmp),
-                                    env=env)
+            proc = subprocess.Popen(
+                _strpath(cmd), shell=True, cwd=_strpath(self._testtmp), env=env
+            )
             ret = proc.wait()
             return (ret, None)
 
         proc = Popen4(cmd, self._testtmp, self._timeout, env)
+
         def cleanup():
             terminate(proc)
             ret = proc.wait()
@@ -1257,6 +1492,7 @@
 
         return ret, output.splitlines(True)
 
+
 class PythonTest(Test):
     """A Python-based test."""
 
@@ -1270,13 +1506,13 @@
         cmd = b'"%s"%s "%s"' % (PYTHON, py3switch, self.path)
         vlog("# Running", cmd)
         normalizenewlines = os.name == 'nt'
-        result = self._runcommand(cmd, env,
-                                  normalizenewlines=normalizenewlines)
+        result = self._runcommand(cmd, env, normalizenewlines=normalizenewlines)
         if self._aborted:
             raise KeyboardInterrupt()
 
         return result
 
+
 # Some glob patterns apply only in some circumstances, so the script
 # might want to remove (glob) annotations that otherwise should be
 # retained.
@@ -1301,9 +1537,11 @@
 
 MARK_OPTIONAL = b" (?)\n"
 
+
 def isoptional(line):
     return line.endswith(MARK_OPTIONAL)
 
+
 class TTest(Test):
     """A "t test" is a test backed by a .t file."""
 
@@ -1376,9 +1614,12 @@
         # TODO do something smarter when all other uses of hghave are gone.
         runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
         tdir = runtestdir.replace(b'\\', b'/')
-        proc = Popen4(b'%s -c "%s/hghave %s"' %
-                      (self._shell, tdir, allreqs),
-                      self._testtmp, 0, self._getenv())
+        proc = Popen4(
+            b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
+            self._testtmp,
+            0,
+            self._getenv(),
+        )
         stdout, stderr = proc.communicate()
         ret = proc.wait()
         if wifexited(ret):
@@ -1419,25 +1660,30 @@
         # up script results with our source. These markers include input
         # line number and the last return code.
         salt = b"SALT%d" % time.time()
+
         def addsalt(line, inpython):
             if inpython:
                 script.append(b'%s %d 0\n' % (salt, line))
             else:
                 script.append(b'echo %s %d $?\n' % (salt, line))
+
         activetrace = []
         session = str(uuid.uuid4())
         if PYTHON3:
             session = session.encode('ascii')
-        hgcatapult = (os.getenv('HGTESTCATAPULTSERVERPIPE') or
-                      os.getenv('HGCATAPULTSERVERPIPE'))
+        hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
+            'HGCATAPULTSERVERPIPE'
+        )
+
         def toggletrace(cmd=None):
             if not hgcatapult or hgcatapult == os.devnull:
                 return
 
             if activetrace:
                 script.append(
-                    b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n' % (
-                        session, activetrace[0]))
+                    b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
+                    % (session, activetrace[0])
+                )
             if cmd is None:
                 return
 
@@ -1447,8 +1693,9 @@
                 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
             quoted = quoted.replace(b'\\', b'\\\\')
             script.append(
-                b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n' % (
-                    session, quoted))
+                b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
+                % (session, quoted)
+            )
             activetrace[0:] = [quoted]
 
         script = []
@@ -1550,21 +1797,21 @@
                 after.setdefault(pos, []).append(l)
             elif skipping:
                 after.setdefault(pos, []).append(l)
-            elif l.startswith(b'  >>> '): # python inlines
+            elif l.startswith(b'  >>> '):  # python inlines
                 after.setdefault(pos, []).append(l)
                 prepos = pos
                 pos = n
                 if not inpython:
                     # We've just entered a Python block. Add the header.
                     inpython = True
-                    addsalt(prepos, False) # Make sure we report the exit code.
+                    addsalt(prepos, False)  # Make sure we report the exit code.
                     script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
                 addsalt(n, True)
                 script.append(l[2:])
-            elif l.startswith(b'  ... '): # python inlines
+            elif l.startswith(b'  ... '):  # python inlines
                 after.setdefault(prepos, []).append(l)
                 script.append(l[2:])
-            elif l.startswith(b'  $ '): # commands
+            elif l.startswith(b'  $ '):  # commands
                 if inpython:
                     script.append(b'EOF\n')
                     inpython = False
@@ -1578,10 +1825,10 @@
                 if len(cmd) == 2 and cmd[0] == b'cd':
                     l = b'  $ cd %s || exit 1\n' % cmd[1]
                 script.append(rawcmd)
-            elif l.startswith(b'  > '): # continuations
+            elif l.startswith(b'  > '):  # continuations
                 after.setdefault(prepos, []).append(l)
                 script.append(l[4:])
-            elif l.startswith(b'  '): # results
+            elif l.startswith(b'  '):  # results
                 # Queue up a list of expected results.
                 expected.setdefault(pos, []).append(l[2:])
             else:
@@ -1603,7 +1850,7 @@
 
     def _processoutput(self, exitcode, output, salt, after, expected):
         # Merge the script output back into a unified test.
-        warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
+        warnonly = WARN_UNDEFINED  # 1: not yet; 2: yes; 3: for sure not
         if exitcode != 0:
             warnonly = WARN_NO
 
@@ -1614,19 +1861,16 @@
             if salt in out_rawline:
                 out_line, cmd_line = out_rawline.split(salt, 1)
 
-            pos, postout, warnonly = self._process_out_line(out_line,
-                                                            pos,
-                                                            postout,
-                                                            expected,
-                                                            warnonly)
-            pos, postout = self._process_cmd_line(cmd_line, pos, postout,
-                                                  after)
+            pos, postout, warnonly = self._process_out_line(
+                out_line, pos, postout, expected, warnonly
+            )
+            pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
 
         if pos in after:
             postout += after.pop(pos)
 
         if warnonly == WARN_YES:
-            exitcode = False # Set exitcode to warned.
+            exitcode = False  # Set exitcode to warned.
 
         return exitcode, postout
 
@@ -1648,7 +1892,7 @@
                 if isinstance(r, str):
                     if r == '-glob':
                         out_line = ''.join(el.rsplit(' (glob)', 1))
-                        r = '' # Warn only this line.
+                        r = ''  # Warn only this line.
                     elif r == "retry":
                         postout.append(b'  ' + el)
                     else:
@@ -1663,8 +1907,7 @@
                     else:
                         m = optline.match(el)
                         if m:
-                            conditions = [
-                                c for c in m.group(2).split(b' ')]
+                            conditions = [c for c in m.group(2).split(b' ')]
 
                             if not self._iftest(conditions):
                                 optional.append(i)
@@ -1685,10 +1928,11 @@
                 postout.append(b'  ' + el)
             else:
                 if self.NEEDESCAPE(out_line):
-                    out_line = TTest._stringescape(b'%s (esc)\n' %
-                                               out_line.rstrip(b'\n'))
-                postout.append(b'  ' + out_line) # Let diff deal with it.
-                if r != '': # If line failed.
+                    out_line = TTest._stringescape(
+                        b'%s (esc)\n' % out_line.rstrip(b'\n')
+                    )
+                postout.append(b'  ' + out_line)  # Let diff deal with it.
+                if r != '':  # If line failed.
                     warnonly = WARN_NO
                 elif warnonly == WARN_UNDEFINED:
                     warnonly = WARN_YES
@@ -1752,10 +1996,10 @@
         i, n = 0, len(el)
         res = b''
         while i < n:
-            c = el[i:i + 1]
+            c = el[i : i + 1]
             i += 1
-            if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
-                res += el[i - 1:i + 1]
+            if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
+                res += el[i - 1 : i + 1]
                 i += 1
             elif c == b'*':
                 res += b'.*'
@@ -1768,12 +2012,12 @@
         return TTest.rematch(res, l)
 
     def linematch(self, el, l):
-        if el == l: # perfect match (fast)
+        if el == l:  # perfect match (fast)
             return True, True
         retry = False
         if isoptional(el):
             retry = "retry"
-            el = el[:-len(MARK_OPTIONAL)] + b"\n"
+            el = el[: -len(MARK_OPTIONAL)] + b"\n"
         else:
             m = optline.match(el)
             if m:
@@ -1817,10 +2061,12 @@
         for line in lines:
             if line.startswith(TTest.SKIPPED_PREFIX):
                 line = line.splitlines()[0]
-                missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8'))
+                missing.append(
+                    line[len(TTest.SKIPPED_PREFIX) :].decode('utf-8')
+                )
             elif line.startswith(TTest.FAILED_PREFIX):
                 line = line.splitlines()[0]
-                failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8'))
+                failed.append(line[len(TTest.FAILED_PREFIX) :].decode('utf-8'))
 
         return missing, failed
 
@@ -1832,12 +2078,15 @@
     def _stringescape(s):
         return TTest.ESCAPESUB(TTest._escapef, s)
 
+
 iolock = threading.RLock()
 firstlock = threading.RLock()
 firsterror = False
 
+
 class TestResult(unittest._TextTestResult):
     """Holds results when executing via unittest."""
+
     # Don't worry too much about accessing the non-public _TextTestResult.
     # It is relatively common in Python testing tools.
     def __init__(self, options, *args, **kwargs):
@@ -1864,7 +2113,7 @@
             self.color = pygmentspresent and self.stream.isatty()
         elif options.color == 'never':
             self.color = False
-        else: # 'always', for testing purposes
+        else:  # 'always', for testing purposes
             self.color = pygmentspresent
 
     def onStart(self, test):
@@ -1942,12 +2191,15 @@
                 pass
             elif self._options.view:
                 v = self._options.view
-                subprocess.call(r'"%s" "%s" "%s"' %
-                                (v, _strpath(test.refpath),
-                                 _strpath(test.errpath)), shell=True)
+                subprocess.call(
+                    r'"%s" "%s" "%s"'
+                    % (v, _strpath(test.refpath), _strpath(test.errpath)),
+                    shell=True,
+                )
             else:
-                servefail, lines = getdiff(expected, got,
-                                           test.refpath, test.errpath)
+                servefail, lines = getdiff(
+                    expected, got, test.refpath, test.errpath
+                )
                 self.stream.write('\n')
                 for line in lines:
                     line = highlightdiff(line, self.color)
@@ -1961,14 +2213,16 @@
 
                 if servefail:
                     raise test.failureException(
-                        'server failed to start (HGPORT=%s)' % test._startport)
+                        'server failed to start (HGPORT=%s)' % test._startport
+                    )
 
             # handle interactive prompt without releasing iolock
             if self._options.interactive:
                 if test.readrefout() != expected:
                     self.stream.write(
                         'Reference output has changed (run again to prompt '
-                        'changes)')
+                        'changes)'
+                    )
                 else:
                     self.stream.write('Accept this change? [n] ')
                     self.stream.flush()
@@ -1992,7 +2246,7 @@
         # This module has one limitation. It can only work for Linux user
         # and not for Windows.
         test.started = os.times()
-        if self._firststarttime is None: # thread racy but irrelevant
+        if self._firststarttime is None:  # thread racy but irrelevant
             self._firststarttime = test.started[4]
 
     def stopTest(self, test, interrupted=False):
@@ -2003,18 +2257,24 @@
         starttime = test.started
         endtime = test.stopped
         origin = self._firststarttime
-        self.times.append((test.name,
-                           endtime[2] - starttime[2], # user space CPU time
-                           endtime[3] - starttime[3], # sys  space CPU time
-                           endtime[4] - starttime[4], # real time
-                           starttime[4] - origin, # start date in run context
-                           endtime[4] - origin, # end date in run context
-                           ))
+        self.times.append(
+            (
+                test.name,
+                endtime[2] - starttime[2],  # user space CPU time
+                endtime[3] - starttime[3],  # sys  space CPU time
+                endtime[4] - starttime[4],  # real time
+                starttime[4] - origin,  # start date in run context
+                endtime[4] - origin,  # end date in run context
+            )
+        )
 
         if interrupted:
             with iolock:
-                self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
-                    test.name, self.times[-1][3]))
+                self.stream.writeln(
+                    'INTERRUPTED: %s (after %d seconds)'
+                    % (test.name, self.times[-1][3])
+                )
+
 
 def getTestResult():
     """
@@ -2026,13 +2286,25 @@
     else:
         return TestResult
 
+
 class TestSuite(unittest.TestSuite):
     """Custom unittest TestSuite that knows how to execute Mercurial tests."""
 
-    def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
-                 retest=False, keywords=None, loop=False, runs_per_test=1,
-                 loadtest=None, showchannels=False,
-                 *args, **kwargs):
+    def __init__(
+        self,
+        testdir,
+        jobs=1,
+        whitelist=None,
+        blacklist=None,
+        retest=False,
+        keywords=None,
+        loop=False,
+        runs_per_test=1,
+        loadtest=None,
+        showchannels=False,
+        *args,
+        **kwargs
+    ):
         """Create a new instance that can run tests with a configuration.
 
         testdir specifies the directory where tests are executed from. This
@@ -2079,11 +2351,13 @@
         tests = []
         num_tests = [0]
         for test in self._tests:
+
             def get():
                 num_tests[0] += 1
                 if getattr(test, 'should_reload', False):
                     return self._loadtest(test, num_tests[0])
                 return test
+
             if not os.path.exists(test.path):
                 result.addSkip(test, "Doesn't exist")
                 continue
@@ -2131,7 +2405,7 @@
                 done.put(None)
             except KeyboardInterrupt:
                 pass
-            except: # re-raises
+            except:  # re-raises
                 done.put(('!', test, 'run-test raised an error, see traceback'))
                 raise
             finally:
@@ -2156,7 +2430,7 @@
                     sys.stdout.flush()
                 for x in xrange(10):
                     if channels:
-                        time.sleep(.1)
+                        time.sleep(0.1)
                 count += 1
 
         stoppedearly = False
@@ -2181,15 +2455,15 @@
                     if self._loop:
                         if getattr(test, 'should_reload', False):
                             num_tests[0] += 1
-                            tests.append(
-                                self._loadtest(test, num_tests[0]))
+                            tests.append(self._loadtest(test, num_tests[0]))
                         else:
                             tests.append(test)
                     if self._jobs == 1:
                         job(test, result)
                     else:
-                        t = threading.Thread(target=job, name=test.name,
-                                             args=(test, result))
+                        t = threading.Thread(
+                            target=job, name=test.name, args=(test, result)
+                        )
                         t.start()
                     running += 1
 
@@ -2212,24 +2486,28 @@
 
         return result
 
+
 # Save the most recent 5 wall-clock runtimes of each test to a
 # human-readable text file named .testtimes. Tests are sorted
 # alphabetically, while times for each test are listed from oldest to
 # newest.
 
+
 def loadtimes(outputdir):
     times = []
     try:
         with open(os.path.join(outputdir, b'.testtimes')) as fp:
             for line in fp:
                 m = re.match('(.*?) ([0-9. ]+)', line)
-                times.append((m.group(1),
-                              [float(t) for t in m.group(2).split()]))
+                times.append(
+                    (m.group(1), [float(t) for t in m.group(2).split()])
+                )
     except IOError as err:
         if err.errno != errno.ENOENT:
             raise
     return times
 
+
 def savetimes(outputdir, result):
     saved = dict(loadtimes(outputdir))
     maxruns = 5
@@ -2241,8 +2519,9 @@
             ts.append(real)
             ts[:] = ts[-maxruns:]
 
-    fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes',
-                                   dir=outputdir, text=True)
+    fd, tmpname = tempfile.mkstemp(
+        prefix=b'.testtimes', dir=outputdir, text=True
+    )
     with os.fdopen(fd, 'w') as fp:
         for name, ts in sorted(saved.items()):
             fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
@@ -2256,6 +2535,7 @@
     except OSError:
         pass
 
+
 class TextTestRunner(unittest.TextTestRunner):
     """Custom unittest test runner that uses appropriate settings."""
 
@@ -2264,8 +2544,9 @@
 
         self._runner = runner
 
-        self._result = getTestResult()(self._runner.options, self.stream,
-                                       self.descriptions, self.verbosity)
+        self._result = getTestResult()(
+            self._runner.options, self.stream, self.descriptions, self.verbosity
+        )
 
     def listtests(self, test):
         test = sorted(test, key=lambda t: t.name)
@@ -2299,17 +2580,20 @@
             self.stream.writeln('')
 
             if not self._runner.options.noskips:
-                for test, msg in sorted(self._result.skipped,
-                                        key=lambda s: s[0].name):
+                for test, msg in sorted(
+                    self._result.skipped, key=lambda s: s[0].name
+                ):
                     formatted = 'Skipped %s: %s\n' % (test.name, msg)
                     msg = highlightmsg(formatted, self._result.color)
                     self.stream.write(msg)
-            for test, msg in sorted(self._result.failures,
-                                    key=lambda f: f[0].name):
+            for test, msg in sorted(
+                self._result.failures, key=lambda f: f[0].name
+            ):
                 formatted = 'Failed %s: %s\n' % (test.name, msg)
                 self.stream.write(highlightmsg(formatted, self._result.color))
-            for test, msg in sorted(self._result.errors,
-                                    key=lambda e: e[0].name):
+            for test, msg in sorted(
+                self._result.errors, key=lambda e: e[0].name
+            ):
                 self.stream.writeln('Errored %s: %s' % (test.name, msg))
 
             if self._runner.options.xunit:
@@ -2329,31 +2613,41 @@
                 self._bisecttests(t for t, m in self._result.failures)
             self.stream.writeln(
                 '# Ran %d tests, %d skipped, %d failed.'
-                % (self._result.testsRun, skipped + ignored, failed))
+                % (self._result.testsRun, skipped + ignored, failed)
+            )
             if failed:
-                self.stream.writeln('python hash seed: %s' %
-                    os.environ['PYTHONHASHSEED'])
+                self.stream.writeln(
+                    'python hash seed: %s' % os.environ['PYTHONHASHSEED']
+                )
             if self._runner.options.time:
                 self.printtimes(self._result.times)
 
             if self._runner.options.exceptions:
                 exceptions = aggregateexceptions(
-                    os.path.join(self._runner._outputdir, b'exceptions'))
+                    os.path.join(self._runner._outputdir, b'exceptions')
+                )
 
                 self.stream.writeln('Exceptions Report:')
-                self.stream.writeln('%d total from %d frames' %
-                                    (exceptions['total'],
-                                     len(exceptions['exceptioncounts'])))
+                self.stream.writeln(
+                    '%d total from %d frames'
+                    % (exceptions['total'], len(exceptions['exceptioncounts']))
+                )
                 combined = exceptions['combined']
                 for key in sorted(combined, key=combined.get, reverse=True):
                     frame, line, exc = key
                     totalcount, testcount, leastcount, leasttest = combined[key]
 
-                    self.stream.writeln('%d (%d tests)\t%s: %s (%s - %d total)'
-                                        % (totalcount,
-                                           testcount,
-                                           frame, exc,
-                                           leasttest, leastcount))
+                    self.stream.writeln(
+                        '%d (%d tests)\t%s: %s (%s - %d total)'
+                        % (
+                            totalcount,
+                            testcount,
+                            frame,
+                            exc,
+                            leasttest,
+                            leastcount,
+                        )
+                    )
 
             self.stream.flush()
 
@@ -2364,14 +2658,17 @@
         bisectrepo = self._runner.options.bisect_repo
         if bisectrepo:
             bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
+
         def pread(args):
             env = os.environ.copy()
             env['HGPLAIN'] = '1'
-            p = subprocess.Popen(args, stderr=subprocess.STDOUT,
-                                 stdout=subprocess.PIPE, env=env)
+            p = subprocess.Popen(
+                args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
+            )
             data = p.stdout.read()
             p.wait()
             return data
+
         for test in tests:
             pread(bisectcmd + ['--reset']),
             pread(bisectcmd + ['--bad', '.'])
@@ -2382,32 +2679,43 @@
             withhg = self._runner.options.with_hg
             if withhg:
                 opts += ' --with-hg=%s ' % shellquote(_strpath(withhg))
-            rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts,
-                                   test)
+            rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
             data = pread(bisectcmd + ['--command', rtc])
             m = re.search(
-                (br'\nThe first (?P<goodbad>bad|good) revision '
-                 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
-                 br'summary: +(?P<summary>[^\n]+)\n'),
-                data, (re.MULTILINE | re.DOTALL))
+                (
+                    br'\nThe first (?P<goodbad>bad|good) revision '
+                    br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
+                    br'summary: +(?P<summary>[^\n]+)\n'
+                ),
+                data,
+                (re.MULTILINE | re.DOTALL),
+            )
             if m is None:
                 self.stream.writeln(
-                    'Failed to identify failure point for %s' % test)
+                    'Failed to identify failure point for %s' % test
+                )
                 continue
             dat = m.groupdict()
             verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
             self.stream.writeln(
-                '%s %s by %s (%s)' % (
-                    test, verb, dat['node'].decode('ascii'),
-                    dat['summary'].decode('utf8', 'ignore')))
+                '%s %s by %s (%s)'
+                % (
+                    test,
+                    verb,
+                    dat['node'].decode('ascii'),
+                    dat['summary'].decode('utf8', 'ignore'),
+                )
+            )
 
     def printtimes(self, times):
         # iolock held by run
         self.stream.writeln('# Producing time report')
         times.sort(key=lambda t: (t[3]))
         cols = '%7.3f %7.3f %7.3f %7.3f %7.3f   %s'
-        self.stream.writeln('%-7s %-7s %-7s %-7s %-7s   %s' %
-                            ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
+        self.stream.writeln(
+            '%-7s %-7s %-7s %-7s %-7s   %s'
+            % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
+        )
         for tdata in times:
             test = tdata[0]
             cuser, csys, real, start, end = tdata[1:6]
@@ -2419,11 +2727,12 @@
         timesd = dict((t[0], t[3]) for t in result.times)
         doc = minidom.Document()
         s = doc.createElement('testsuite')
-        s.setAttribute('errors', "0") # TODO
+        s.setAttribute('errors', "0")  # TODO
         s.setAttribute('failures', str(len(result.failures)))
         s.setAttribute('name', 'run-tests')
-        s.setAttribute('skipped', str(len(result.skipped) +
-                                      len(result.ignored)))
+        s.setAttribute(
+            'skipped', str(len(result.skipped) + len(result.ignored))
+        )
         s.setAttribute('tests', str(result.testsRun))
         doc.appendChild(s)
         for tc in result.successes:
@@ -2474,10 +2783,11 @@
             timesd[test] = tdata[1:]
 
         outcome = {}
-        groups = [('success', ((tc, None)
-                   for tc in result.successes)),
-                  ('failure', result.failures),
-                  ('skip', result.skipped)]
+        groups = [
+            ('success', ((tc, None) for tc in result.successes)),
+            ('failure', result.failures),
+            ('skip', result.skipped),
+        ]
         for res, testcases in groups:
             for tc, __ in testcases:
                 if tc.name in timesd:
@@ -2486,23 +2796,26 @@
                         diff = diff.decode('unicode_escape')
                     except UnicodeDecodeError as e:
                         diff = '%r decoding diff, sorry' % e
-                    tres = {'result': res,
-                            'time': ('%0.3f' % timesd[tc.name][2]),
-                            'cuser': ('%0.3f' % timesd[tc.name][0]),
-                            'csys': ('%0.3f' % timesd[tc.name][1]),
-                            'start': ('%0.3f' % timesd[tc.name][3]),
-                            'end': ('%0.3f' % timesd[tc.name][4]),
-                            'diff': diff,
-                            }
+                    tres = {
+                        'result': res,
+                        'time': ('%0.3f' % timesd[tc.name][2]),
+                        'cuser': ('%0.3f' % timesd[tc.name][0]),
+                        'csys': ('%0.3f' % timesd[tc.name][1]),
+                        'start': ('%0.3f' % timesd[tc.name][3]),
+                        'end': ('%0.3f' % timesd[tc.name][4]),
+                        'diff': diff,
+                    }
                 else:
                     # blacklisted test
                     tres = {'result': res}
 
                 outcome[tc.name] = tres
-        jsonout = json.dumps(outcome, sort_keys=True, indent=4,
-                             separators=(',', ': '))
+        jsonout = json.dumps(
+            outcome, sort_keys=True, indent=4, separators=(',', ': ')
+        )
         outf.writelines(("testreport =", jsonout))
 
+
 def sorttests(testdescs, previoustimes, shuffle=False):
     """Do an in-place sort of tests."""
     if shuffle:
@@ -2510,29 +2823,32 @@
         return
 
     if previoustimes:
+
         def sortkey(f):
             f = f['path']
             if f in previoustimes:
                 # Use most recent time as estimate
-                return -previoustimes[f][-1]
+                return -(previoustimes[f][-1])
             else:
                 # Default to a rather arbitrary value of 1 second for new tests
                 return -1.0
+
     else:
         # keywords for slow tests
-        slow = {b'svn': 10,
-                b'cvs': 10,
-                b'hghave': 10,
-                b'largefiles-update': 10,
-                b'run-tests': 10,
-                b'corruption': 10,
-                b'race': 10,
-                b'i18n': 10,
-                b'check': 100,
-                b'gendoc': 100,
-                b'contrib-perf': 200,
-                b'merge-combination': 100,
-                }
+        slow = {
+            b'svn': 10,
+            b'cvs': 10,
+            b'hghave': 10,
+            b'largefiles-update': 10,
+            b'run-tests': 10,
+            b'corruption': 10,
+            b'race': 10,
+            b'i18n': 10,
+            b'check': 100,
+            b'gendoc': 100,
+            b'contrib-perf': 200,
+            b'merge-combination': 100,
+        }
         perf = {}
 
         def sortkey(f):
@@ -2558,6 +2874,7 @@
 
     testdescs.sort(key=sortkey)
 
+
 class TestRunner(object):
     """Holds context for executing tests.
 
@@ -2614,6 +2931,7 @@
             testdescs = self.findtests(tests)
             if options.profile_runner:
                 import statprof
+
                 statprof.start()
             result = self._run(testdescs)
             if options.profile_runner:
@@ -2668,8 +2986,7 @@
                 d = osenvironb.get(b'TMP', None)
             tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
 
-        self._hgtmp = osenvironb[b'HGTMP'] = (
-            os.path.realpath(tmpdir))
+        self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir)
 
         if self.options.with_hg:
             self._installdir = None
@@ -2791,7 +3108,8 @@
             osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
             logexceptions = os.path.join(self._testdir, b'logexceptions.py')
             self.options.extra_config_opt.append(
-                'extensions.logexceptions=%s' % logexceptions.decode('utf-8'))
+                'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
+            )
 
         vlog("# Using TESTDIR", self._testdir)
         vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
@@ -2803,7 +3121,7 @@
         try:
             return self._runtests(testdescs) or 0
         finally:
-            time.sleep(.1)
+            time.sleep(0.1)
             self._cleanup()
 
     def findtests(self, args):
@@ -2814,8 +3132,12 @@
         """
         if not args:
             if self.options.changed:
-                proc = Popen4(b'hg st --rev "%s" -man0 .' %
-                              _bytespath(self.options.changed), None, 0)
+                proc = Popen4(
+                    b'hg st --rev "%s" -man0 .'
+                    % _bytespath(self.options.changed),
+                    None,
+                    0,
+                )
                 stdout, stderr = proc.communicate()
                 args = stdout.strip(b'\0').split(b'\0')
             else:
@@ -2832,13 +3154,16 @@
         args = expanded_args
 
         testcasepattern = re.compile(
-            br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-\.#]+))')
+            br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-\.#]+))'
+        )
         tests = []
         for t in args:
             case = []
 
-            if not (os.path.basename(t).startswith(b'test-')
-                    and (t.endswith(b'.py') or t.endswith(b'.t'))):
+            if not (
+                os.path.basename(t).startswith(b'test-')
+                and (t.endswith(b'.py') or t.endswith(b'.t'))
+            ):
 
                 m = testcasepattern.match(os.path.basename(t))
                 if m is not None:
@@ -2854,12 +3179,14 @@
                 casedimensions = parsettestcases(t)
                 if casedimensions:
                     cases = []
+
                     def addcases(case, casedimensions):
                         if not casedimensions:
                             cases.append(case)
                         else:
                             for c in casedimensions[0]:
                                 addcases(case + [c], casedimensions[1:])
+
                     addcases([], casedimensions)
                     if case and case in cases:
                         cases = [case]
@@ -2913,16 +3240,19 @@
             if kws is not None and PYTHON3:
                 kws = kws.encode('utf-8')
 
-            suite = TestSuite(self._testdir,
-                              jobs=jobs,
-                              whitelist=self.options.whitelisted,
-                              blacklist=self.options.blacklist,
-                              retest=self.options.retest,
-                              keywords=kws,
-                              loop=self.options.loop,
-                              runs_per_test=self.options.runs_per_test,
-                              showchannels=self.options.showchannels,
-                              tests=tests, loadtest=_reloadtest)
+            suite = TestSuite(
+                self._testdir,
+                jobs=jobs,
+                whitelist=self.options.whitelisted,
+                blacklist=self.options.blacklist,
+                retest=self.options.retest,
+                keywords=kws,
+                loop=self.options.loop,
+                runs_per_test=self.options.runs_per_test,
+                showchannels=self.options.showchannels,
+                tests=tests,
+                loadtest=_reloadtest,
+            )
             verbosity = 1
             if self.options.list_tests:
                 verbosity = 0
@@ -2942,8 +3272,10 @@
                     assert self._installdir
                     self._installchg()
 
-                log('running %d tests using %d parallel processes' % (
-                    num_tests, jobs))
+                log(
+                    'running %d tests using %d parallel processes'
+                    % (num_tests, jobs)
+                )
 
                 result = runner.run(suite)
 
@@ -2962,7 +3294,7 @@
             return 1
 
     def _getport(self, count):
-        port = self._ports.get(count) # do we have a cached entry?
+        port = self._ports.get(count)  # do we have a cached entry?
         if port is None:
             portneeded = 3
             # above 100 tries we just give up and let test reports failure
@@ -3000,18 +3332,23 @@
         # extra keyword parameters. 'case' is used by .t tests
         kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc)
 
-        t = testcls(refpath, self._outputdir, tmpdir,
-                    keeptmpdir=self.options.keep_tmpdir,
-                    debug=self.options.debug,
-                    first=self.options.first,
-                    timeout=self.options.timeout,
-                    startport=self._getport(count),
-                    extraconfigopts=self.options.extra_config_opt,
-                    py3warnings=self.options.py3_warnings,
-                    shell=self.options.shell,
-                    hgcommand=self._hgcommand,
-                    usechg=bool(self.options.with_chg or self.options.chg),
-                    useipv6=useipv6, **kwds)
+        t = testcls(
+            refpath,
+            self._outputdir,
+            tmpdir,
+            keeptmpdir=self.options.keep_tmpdir,
+            debug=self.options.debug,
+            first=self.options.first,
+            timeout=self.options.timeout,
+            startport=self._getport(count),
+            extraconfigopts=self.options.extra_config_opt,
+            py3warnings=self.options.py3_warnings,
+            shell=self.options.shell,
+            hgcommand=self._hgcommand,
+            usechg=bool(self.options.with_chg or self.options.chg),
+            useipv6=useipv6,
+            **kwds
+        )
         t.should_reload = True
         return t
 
@@ -3036,8 +3373,10 @@
         # os.symlink() is a thing with py3 on Windows, but it requires
         # Administrator rights.
         if getattr(os, 'symlink', None) and os.name != 'nt':
-            vlog("# Making python executable in test path a symlink to '%s'" %
-                 sysexecutable)
+            vlog(
+                "# Making python executable in test path a symlink to '%s'"
+                % sysexecutable
+            )
             mypython = os.path.join(self._tmpbindir, pyexename)
             try:
                 if os.readlink(mypython) == sysexecutable:
@@ -3056,8 +3395,10 @@
                         raise
         else:
             exedir, exename = os.path.split(sysexecutable)
-            vlog("# Modifying search path to find %s as %s in '%s'" %
-                 (exename, pyexename, exedir))
+            vlog(
+                "# Modifying search path to find %s as %s in '%s'"
+                % (exename, pyexename, exedir)
+            )
             path = os.environ['PATH'].split(os.pathsep)
             while exedir in path:
                 path.remove(exedir)
@@ -3097,17 +3438,24 @@
             # least on Windows for now, deal with .pydistutils.cfg bugs
             # when they happen.
             nohome = b''
-        cmd = (b'"%(exe)s" setup.py %(pure)s clean --all'
-               b' build %(compiler)s --build-base="%(base)s"'
-               b' install --force --prefix="%(prefix)s"'
-               b' --install-lib="%(libdir)s"'
-               b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
-               % {b'exe': exe, b'pure': pure,
-                  b'compiler': compiler,
-                  b'base': os.path.join(self._hgtmp, b"build"),
-                  b'prefix': self._installdir, b'libdir': self._pythondir,
-                  b'bindir': self._bindir,
-                  b'nohome': nohome, b'logfile': installerrs})
+        cmd = (
+            b'"%(exe)s" setup.py %(pure)s clean --all'
+            b' build %(compiler)s --build-base="%(base)s"'
+            b' install --force --prefix="%(prefix)s"'
+            b' --install-lib="%(libdir)s"'
+            b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
+            % {
+                b'exe': exe,
+                b'pure': pure,
+                b'compiler': compiler,
+                b'base': os.path.join(self._hgtmp, b"build"),
+                b'prefix': self._installdir,
+                b'libdir': self._pythondir,
+                b'bindir': self._bindir,
+                b'nohome': nohome,
+                b'logfile': installerrs,
+            }
+        )
 
         # setuptools requires install directories to exist.
         def makedirs(p):
@@ -3116,6 +3464,7 @@
             except OSError as e:
                 if e.errno != errno.EEXIST:
                     raise
+
         makedirs(self._pythondir)
         makedirs(self._bindir)
 
@@ -3155,8 +3504,10 @@
             with open(hgbat, 'rb') as f:
                 data = f.read()
             if br'"%~dp0..\python" "%~dp0hg" %*' in data:
-                data = data.replace(br'"%~dp0..\python" "%~dp0hg" %*',
-                                    b'"%~dp0python" "%~dp0hg" %*')
+                data = data.replace(
+                    br'"%~dp0..\python" "%~dp0hg" %*',
+                    b'"%~dp0python" "%~dp0hg" %*',
+                )
                 with open(hgbat, 'wb') as f:
                     f.write(data)
             else:
@@ -3182,17 +3533,20 @@
     def _checkhglib(self, verb):
         """Ensure that the 'mercurial' package imported by python is
         the one we expect it to be.  If not, print a warning to stderr."""
-        if ((self._bindir == self._pythondir) and
-            (self._bindir != self._tmpbindir)):
+        if (self._bindir == self._pythondir) and (
+            self._bindir != self._tmpbindir
+        ):
             # The pythondir has been inferred from --with-hg flag.
             # We cannot expect anything sensible here.
             return
         expecthg = os.path.join(self._pythondir, b'mercurial')
         actualhg = self._gethgpath()
         if os.path.abspath(actualhg) != os.path.abspath(expecthg):
-            sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
-                             '         (expected %s)\n'
-                             % (verb, actualhg, expecthg))
+            sys.stderr.write(
+                'warning: %s with unexpected mercurial lib: %s\n'
+                '         (expected %s)\n' % (verb, actualhg, expecthg)
+            )
+
     def _gethgpath(self):
         """Return the path to the mercurial package that is actually found by
         the current Python interpreter."""
@@ -3216,14 +3570,20 @@
         vlog('# Performing temporary installation of CHG')
         assert os.path.dirname(self._bindir) == self._installdir
         assert self._hgroot, 'must be called after _installhg()'
-        cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
-               % {b'make': b'make',  # TODO: switch by option or environment?
-                  b'prefix': self._installdir})
+        cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
+            b'make': b'make',  # TODO: switch by option or environment?
+            b'prefix': self._installdir,
+        }
         cwd = os.path.join(self._hgroot, b'contrib', b'chg')
         vlog("# Running", cmd)
-        proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
-                                stdin=subprocess.PIPE, stdout=subprocess.PIPE,
-                                stderr=subprocess.STDOUT)
+        proc = subprocess.Popen(
+            cmd,
+            shell=True,
+            cwd=cwd,
+            stdin=subprocess.PIPE,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.STDOUT,
+        )
         out, _err = proc.communicate()
         if proc.returncode != 0:
             if PYTHON3:
@@ -3235,6 +3595,7 @@
     def _outputcoverage(self):
         """Produce code coverage output."""
         import coverage
+
         coverage = coverage.coverage
 
         vlog('# Producing coverage report')
@@ -3280,8 +3641,11 @@
             if found:
                 vlog("# Found prerequisite", p, "at", found)
             else:
-                print("WARNING: Did not find prerequisite tool: %s " %
-                      p.decode("utf-8"))
+                print(
+                    "WARNING: Did not find prerequisite tool: %s "
+                    % p.decode("utf-8")
+                )
+
 
 def aggregateexceptions(path):
     exceptioncounts = collections.Counter()
@@ -3322,10 +3686,12 @@
     # impacted tests.
     combined = {}
     for key in exceptioncounts:
-        combined[key] = (exceptioncounts[key],
-                         len(testsbyfailure[key]),
-                         leastfailing[key][0],
-                         leastfailing[key][1])
+        combined[key] = (
+            exceptioncounts[key],
+            len(testsbyfailure[key]),
+            leastfailing[key][0],
+            leastfailing[key][1],
+        )
 
     return {
         'exceptioncounts': exceptioncounts,
@@ -3336,11 +3702,13 @@
         'bytest': failuresbytest,
     }
 
+
 if __name__ == '__main__':
     runner = TestRunner()
 
     try:
         import msvcrt
+
         msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
         msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
         msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)