# HG changeset patch
# User Dan Villiom Podlaski Christiansen <danchr@gmail.com>
# Date 1602419671 -7200
#      Sun Oct 11 14:34:31 2020 +0200
# Node ID 658c0979a77e6873e0653b3aa4712e4b8db16635
# Parent  71dab394b0187077c84c6790b999fbef8e259d7e
synchronise run-tests.py with latest version from stable mercurial

Copied from changeset 5a19d7c9129b in the stable branch, latest as of
2020-10-11.

https://www.mercurial-scm.org/repo/hg/rev/5a19d7c9129b

diff --git a/tests/hghave.py b/tests/hghave.py
--- a/tests/hghave.py
+++ b/tests/hghave.py
@@ -29,12 +29,12 @@
 
 if sys.version_info[0] >= 3:
 
-    def _bytespath(p):
+    def _sys2bytes(p):
         if p is None:
             return p
         return p.encode('utf-8')
 
-    def _strpath(p):
+    def _bytes2sys(p):
         if p is None:
             return p
         return p.decode('utf-8')
@@ -42,10 +42,10 @@
 
 else:
 
-    def _bytespath(p):
+    def _sys2bytes(p):
         return p
 
-    _strpath = _bytespath
+    _bytes2sys = _sys2bytes
 
 
 def check(name, desc):
@@ -307,13 +307,23 @@
         return False
 
 
-def gethgversion():
+def _gethgversion():
     m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)')
     if not m:
         return (0, 0)
     return (int(m.group(1)), int(m.group(2)))
 
 
+_hgversion = None
+
+
+def gethgversion():
+    global _hgversion
+    if _hgversion is None:
+        _hgversion = _gethgversion()
+    return _hgversion
+
+
 @checkvers(
     "hg", "Mercurial >= %s", list([(1.0 * x) / 10 for x in range(9, 99)])
 )
@@ -322,6 +332,17 @@
     return gethgversion() >= (int(major), int(minor))
 
 
+@check("rust", "Using the Rust extensions")
+def has_rust():
+    """Check is the mercurial currently running is using some rust code"""
+    cmd = 'hg debuginstall --quiet 2>&1'
+    match = br'checking module policy \(([^)]+)\)'
+    policy = matchoutput(cmd, match)
+    if not policy:
+        return False
+    return b'rust' in policy.group(1)
+
+
 @check("hg08", "Mercurial >= 0.8")
 def has_hg08():
     if checks["hg09"][0]():
@@ -360,6 +381,17 @@
     return (int(m.group(1)), int(m.group(2)))
 
 
+@check("pygit2", "pygit2 Python library")
+def has_git():
+    try:
+        import pygit2
+
+        pygit2.Oid  # silence unused import
+        return True
+    except ImportError:
+        return False
+
+
 # https://github.com/git-lfs/lfs-test-server
 @check("lfs-test-server", "git-lfs test server")
 def has_lfsserver():
@@ -451,7 +483,7 @@
     os.close(fh)
     name = tempfile.mktemp(dir='.', prefix=tempprefix)
     try:
-        util.oslink(_bytespath(fn), _bytespath(name))
+        util.oslink(_sys2bytes(fn), _sys2bytes(name))
         os.unlink(name)
         return True
     except OSError:
@@ -542,11 +574,14 @@
 
 @check("pyflakes", "Pyflakes python linter")
 def has_pyflakes():
-    return matchoutput(
-        "sh -c \"echo 'import re' 2>&1 | pyflakes\"",
-        br"<stdin>:1: 're' imported but unused",
-        True,
-    )
+    try:
+        import pyflakes
+
+        pyflakes.__version__
+    except ImportError:
+        return False
+    else:
+        return True
 
 
 @check("pylint", "Pylint python linter")
@@ -556,7 +591,7 @@
 
 @check("clang-format", "clang-format C code formatter")
 def has_clang_format():
-    m = matchoutput('clang-format --version', br'clang-format version (\d)')
+    m = matchoutput('clang-format --version', br'clang-format version (\d+)')
     # style changed somewhere between 4.x and 6.x
     return m and int(m.group(1)) >= 6
 
@@ -610,35 +645,11 @@
         return False
 
 
-@check("sslcontext", "python >= 2.7.9 ssl")
-def has_sslcontext():
-    try:
-        import ssl
-
-        ssl.SSLContext
-        return True
-    except (ImportError, AttributeError):
-        return False
-
-
-@check("defaultcacerts", "can verify SSL certs by system's CA certs store")
-def has_defaultcacerts():
-    from mercurial import sslutil, ui as uimod
-
-    ui = uimod.ui.load()
-    return sslutil._defaultcacerts(ui) or sslutil._canloaddefaultcerts
-
-
 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
 def has_defaultcacertsloaded():
     import ssl
     from mercurial import sslutil, ui as uimod
 
-    if not has_defaultcacerts():
-        return False
-    if not has_sslcontext():
-        return False
-
     ui = uimod.ui.load()
     cafile = sslutil._defaultcacerts(ui)
     ctx = ssl.create_default_context()
@@ -672,6 +683,17 @@
     return True
 
 
+@check("setprocname", "whether osutil.setprocname is available or not")
+def has_setprocname():
+    try:
+        from mercurial.utils import procutil
+
+        procutil.setprocname
+        return True
+    except AttributeError:
+        return False
+
+
 @check("test-repo", "running tests from repository")
 def has_test_repo():
     t = os.environ["TESTDIR"]
@@ -685,7 +707,7 @@
 
         curses.COLOR_BLUE
         return matchoutput('test -x "`which tic`"', br'')
-    except ImportError:
+    except (ImportError, AttributeError):
         return False
 
 
@@ -864,8 +886,11 @@
         return False
 
 
-@check("virtualenv", "Python virtualenv support")
-def has_virtualenv():
+@check("py2virtualenv", "Python2 virtualenv support")
+def has_py2virtualenv():
+    if sys.version_info[0] != 2:
+        return False
+
     try:
         import virtualenv
 
@@ -1022,7 +1047,7 @@
     version_regex = b'black, version ([0-9a-b.]+)'
     version = matchoutput(blackcmd, version_regex)
     sv = distutils.version.StrictVersion
-    return version and sv(_strpath(version.group(1))) >= sv('19.10b0')
+    return version and sv(_bytes2sys(version.group(1))) >= sv('19.10b0')
 
 
 @check('pytype', 'the pytype type checker')
@@ -1030,7 +1055,7 @@
     pytypecmd = 'pytype --version'
     version = matchoutput(pytypecmd, b'[0-9a-b.]+')
     sv = distutils.version.StrictVersion
-    return version and sv(_strpath(version.group(0))) >= sv('2019.10.17')
+    return version and sv(_bytes2sys(version.group(0))) >= sv('2019.10.17')
 
 
 @check("rustfmt", "rustfmt tool")
@@ -1039,3 +1064,14 @@
     return matchoutput(
         '`rustup which --toolchain nightly rustfmt` --version', b'rustfmt'
     )
+
+
+@check("lzma", "python lzma module")
+def has_lzma():
+    try:
+        import _lzma
+
+        _lzma.FORMAT_XZ
+        return True
+    except ImportError:
+        return False
diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -53,6 +53,7 @@
 import json
 import multiprocessing
 import os
+import platform
 import random
 import re
 import shutil
@@ -143,12 +144,12 @@
     PYTHON3 = True
     xrange = range  # we use xrange in one place, and we'd rather not use range
 
-    def _bytespath(p):
+    def _sys2bytes(p):
         if p is None:
             return p
         return p.encode('utf-8')
 
-    def _strpath(p):
+    def _bytes2sys(p):
         if p is None:
             return p
         return p.decode('utf-8')
@@ -165,34 +166,34 @@
                 self._strenv = strenv
 
             def __getitem__(self, k):
-                v = self._strenv.__getitem__(_strpath(k))
-                return _bytespath(v)
+                v = self._strenv.__getitem__(_bytes2sys(k))
+                return _sys2bytes(v)
 
             def __setitem__(self, k, v):
-                self._strenv.__setitem__(_strpath(k), _strpath(v))
+                self._strenv.__setitem__(_bytes2sys(k), _bytes2sys(v))
 
             def __delitem__(self, k):
-                self._strenv.__delitem__(_strpath(k))
+                self._strenv.__delitem__(_bytes2sys(k))
 
             def __contains__(self, k):
-                return self._strenv.__contains__(_strpath(k))
+                return self._strenv.__contains__(_bytes2sys(k))
 
             def __iter__(self):
-                return iter([_bytespath(k) for k in iter(self._strenv)])
+                return iter([_sys2bytes(k) for k in iter(self._strenv)])
 
             def get(self, k, default=None):
-                v = self._strenv.get(_strpath(k), _strpath(default))
-                return _bytespath(v)
+                v = self._strenv.get(_bytes2sys(k), _bytes2sys(default))
+                return _sys2bytes(v)
 
             def pop(self, k, default=None):
-                v = self._strenv.pop(_strpath(k), _strpath(default))
-                return _bytespath(v)
+                v = self._strenv.pop(_bytes2sys(k), _bytes2sys(default))
+                return _sys2bytes(v)
 
         osenvironb = environbytes(os.environ)
 
     getcwdb = getattr(os, 'getcwdb')
     if not getcwdb or os.name == 'nt':
-        getcwdb = lambda: _bytespath(os.getcwd())
+        getcwdb = lambda: _sys2bytes(os.getcwd())
 
 elif sys.version_info >= (3, 0, 0):
     print(
@@ -207,10 +208,10 @@
     # bytestrings by default, so we don't have to do any extra
     # fiddling there. We define the wrapper functions anyway just to
     # help keep code consistent between platforms.
-    def _bytespath(p):
+    def _sys2bytes(p):
         return p
 
-    _strpath = _bytespath
+    _bytes2sys = _sys2bytes
     osenvironb = os.environ
     getcwdb = os.getcwd
 
@@ -274,10 +275,10 @@
 def Popen4(cmd, wd, timeout, env=None):
     processlock.acquire()
     p = subprocess.Popen(
-        _strpath(cmd),
+        _bytes2sys(cmd),
         shell=True,
         bufsize=-1,
-        cwd=_strpath(wd),
+        cwd=_bytes2sys(wd),
         env=env,
         close_fds=closefds,
         stdin=subprocess.PIPE,
@@ -315,12 +316,12 @@
 else:
     raise AssertionError('Could not find Python interpreter')
 
-PYTHON = _bytespath(sysexecutable.replace('\\', '/'))
+PYTHON = _sys2bytes(sysexecutable.replace('\\', '/'))
 IMPL_PATH = b'PYTHONPATH'
 if 'java' in sys.platform:
     IMPL_PATH = b'JYTHONPATH'
 
-defaults = {
+default_defaults = {
     'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
     'timeout': ('HGTEST_TIMEOUT', 180),
     'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
@@ -328,6 +329,8 @@
     'shell': ('HGTEST_SHELL', 'sh'),
 }
 
+defaults = default_defaults.copy()
+
 
 def canonpath(path):
     return os.path.realpath(os.path.expanduser(path))
@@ -530,6 +533,9 @@
         action="store_true",
         help="install and use chg wrapper in place of hg",
     )
+    hgconf.add_argument(
+        "--chg-debug", action="store_true", help="show chg debug logs",
+    )
     hgconf.add_argument("--compiler", help="compiler to build with")
     hgconf.add_argument(
         '--extra-config-opt',
@@ -555,10 +561,14 @@
         help="use pure Python code instead of C extensions",
     )
     hgconf.add_argument(
-        "-3",
-        "--py3-warnings",
+        "--rust",
         action="store_true",
-        help="enable Py3k warnings on Python 2.7+",
+        help="use Rust code alongside C extensions",
+    )
+    hgconf.add_argument(
+        "--no-rust",
+        action="store_true",
+        help="do not use Rust code even if compiled",
     )
     hgconf.add_argument(
         "--with-chg",
@@ -643,10 +653,19 @@
     if 'java' in sys.platform or '__pypy__' in sys.modules:
         options.pure = True
 
+    if platform.python_implementation() != 'CPython' and options.rust:
+        parser.error('Rust extensions are only available with CPython')
+
+    if options.pure and options.rust:
+        parser.error('--rust cannot be used with --pure')
+
+    if options.rust and options.no_rust:
+        parser.error('--rust cannot be used with --no-rust')
+
     if options.local:
         if options.with_hg or options.with_chg:
             parser.error('--local cannot be used with --with-hg or --with-chg')
-        testdir = os.path.dirname(_bytespath(canonpath(sys.argv[0])))
+        testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
         reporootdir = os.path.dirname(testdir)
         pathandattrs = [(b'hg', 'with_hg')]
         if options.chg:
@@ -658,10 +677,10 @@
                     '--local specified, but %r not found or '
                     'not executable' % binpath
                 )
-            setattr(options, attr, _strpath(binpath))
+            setattr(options, attr, _bytes2sys(binpath))
 
     if options.with_hg:
-        options.with_hg = canonpath(_bytespath(options.with_hg))
+        options.with_hg = canonpath(_sys2bytes(options.with_hg))
         if not (
             os.path.isfile(options.with_hg)
             and os.access(options.with_hg, os.X_OK)
@@ -675,7 +694,7 @@
         parser.error('chg does not work on %s' % os.name)
     if options.with_chg:
         options.chg = False  # no installation to temporary location
-        options.with_chg = canonpath(_bytespath(options.with_chg))
+        options.with_chg = canonpath(_sys2bytes(options.with_chg))
         if not (
             os.path.isfile(options.with_chg)
             and os.access(options.with_chg, os.X_OK)
@@ -748,9 +767,6 @@
             )
         options.timeout = 0
         options.slowtimeout = 0
-    if options.py3_warnings:
-        if PYTHON3:
-            parser.error('--py3-warnings can only be used on Python 2.7')
 
     if options.blacklist:
         options.blacklist = parselistfiles(options.blacklist, 'blacklist')
@@ -909,11 +925,11 @@
         timeout=None,
         startport=None,
         extraconfigopts=None,
-        py3warnings=False,
         shell=None,
         hgcommand=None,
         slowtimeout=None,
         usechg=False,
+        chgdebug=False,
         useipv6=False,
     ):
         """Create a test from parameters.
@@ -942,8 +958,6 @@
         must have the form "key=value" (something understood by hgrc). Values
         of the form "foo.key=value" will result in "[foo] key=value".
 
-        py3warnings enables Py3k warnings.
-
         shell is the shell to execute tests in.
         """
         if timeout is None:
@@ -954,7 +968,7 @@
             slowtimeout = defaults['slowtimeout']
         self.path = path
         self.bname = os.path.basename(path)
-        self.name = _strpath(self.bname)
+        self.name = _bytes2sys(self.bname)
         self._testdir = os.path.dirname(path)
         self._outputdir = outputdir
         self._tmpname = os.path.basename(path)
@@ -968,10 +982,10 @@
         self._slowtimeout = slowtimeout
         self._startport = startport
         self._extraconfigopts = extraconfigopts or []
-        self._py3warnings = py3warnings
-        self._shell = _bytespath(shell)
+        self._shell = _sys2bytes(shell)
         self._hgcommand = hgcommand or b'hg'
         self._usechg = usechg
+        self._chgdebug = chgdebug
         self._useipv6 = useipv6
 
         self._aborted = False
@@ -1178,10 +1192,7 @@
         if self._keeptmpdir:
             log(
                 '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s'
-                % (
-                    self._testtmp.decode('utf-8'),
-                    self._threadtmp.decode('utf-8'),
-                )
+                % (_bytes2sys(self._testtmp), _bytes2sys(self._threadtmp),)
             )
         else:
             try:
@@ -1281,7 +1292,7 @@
         environment."""
         # Put the restoreenv script inside self._threadtmp
         scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
-        testenv['HGTEST_RESTOREENV'] = _strpath(scriptpath)
+        testenv['HGTEST_RESTOREENV'] = _bytes2sys(scriptpath)
 
         # Only restore environment variable names that the shell allows
         # us to export.
@@ -1315,15 +1326,18 @@
         env = os.environ.copy()
         env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
         env['HGEMITWARNINGS'] = '1'
-        env['TESTTMP'] = _strpath(self._testtmp)
+        env['TESTTMP'] = _bytes2sys(self._testtmp)
         env['TESTNAME'] = self.name
-        env['HOME'] = _strpath(self._testtmp)
+        env['HOME'] = _bytes2sys(self._testtmp)
+        formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1])
+        env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout
+        env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout)
         # This number should match portneeded in _getport
         for port in xrange(3):
             # 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(
+        env["HGRCPATH"] = _bytes2sys(os.path.join(self._threadtmp, b'.hgrc'))
+        env["DAEMON_PIDS"] = _bytes2sys(
             os.path.join(self._threadtmp, b'daemon.pids')
         )
         env["HGEDITOR"] = (
@@ -1344,7 +1358,7 @@
 
         extraextensions = []
         for opt in self._extraconfigopts:
-            section, key = opt.encode('utf-8').split(b'.', 1)
+            section, key = _sys2bytes(opt).split(b'.', 1)
             if section != 'extensions':
                 continue
             name = key.split(b'=', 1)[0]
@@ -1355,7 +1369,7 @@
 
         # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
         # IP addresses.
-        env['LOCALIP'] = _strpath(self._localip())
+        env['LOCALIP'] = _bytes2sys(self._localip())
 
         # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
         # but this is needed for testing python instances like dummyssh,
@@ -1413,6 +1427,8 @@
 
         if self._usechg:
             env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
+        if self._chgdebug:
+            env['CHGDEBUG'] = 'true'
 
         return env
 
@@ -1441,11 +1457,11 @@
             )
             hgrc.write(b'[web]\n')
             hgrc.write(b'address = localhost\n')
-            hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii'))
+            hgrc.write(b'ipv6 = %r\n' % self._useipv6)
             hgrc.write(b'server-header = testing stub value\n')
 
             for opt in self._extraconfigopts:
-                section, key = opt.encode('utf-8').split(b'.', 1)
+                section, key = _sys2bytes(opt).split(b'.', 1)
                 assert b'=' in key, (
                     'extra config opt %s must ' 'have an = for assignment' % opt
                 )
@@ -1464,7 +1480,10 @@
         """
         if self._debug:
             proc = subprocess.Popen(
-                _strpath(cmd), shell=True, cwd=_strpath(self._testtmp), env=env
+                _bytes2sys(cmd),
+                shell=True,
+                cwd=_bytes2sys(self._testtmp),
+                env=env,
             )
             ret = proc.wait()
             return (ret, None)
@@ -1515,9 +1534,8 @@
         return os.path.join(self._testdir, b'%s.out' % self.bname)
 
     def _run(self, env):
-        py3switch = self._py3warnings and b' -3' or b''
         # Quote the python(3) executable for Windows
-        cmd = b'"%s"%s "%s"' % (PYTHON, py3switch, self.path)
+        cmd = b'"%s" "%s"' % (PYTHON, self.path)
         vlog("# Running", cmd.decode("utf-8"))
         normalizenewlines = os.name == 'nt'
         result = self._runcommand(cmd, env, normalizenewlines=normalizenewlines)
@@ -1564,7 +1582,7 @@
     NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
 
     ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
-    ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256))
+    ESCAPEMAP = {bchr(i): br'\x%02x' % i for i in range(256)}
     ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
 
     def __init__(self, path, *args, **kwds):
@@ -1575,9 +1593,9 @@
         super(TTest, self).__init__(path, *args, **kwds)
         if case:
             casepath = b'#'.join(case)
-            self.name = '%s#%s' % (self.name, _strpath(casepath))
+            self.name = '%s#%s' % (self.name, _bytes2sys(casepath))
             self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
-            self._tmpname += b'-%s' % casepath
+            self._tmpname += b'-%s' % casepath.replace(b'#', b'-')
         self._have = {}
 
     @property
@@ -1626,7 +1644,7 @@
             return self._have.get(allreqs)
 
         # TODO do something smarter when all other uses of hghave are gone.
-        runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
+        runtestdir = osenvironb[b'RUNTESTDIR']
         tdir = runtestdir.replace(b'\\', b'/')
         proc = Popen4(
             b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
@@ -1770,7 +1788,7 @@
 
         if self._case:
             casestr = b'#'.join(self._case)
-            if isinstance(self._case, str):
+            if isinstance(casestr, str):
                 quoted = shellquote(casestr)
             else:
                 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
@@ -2051,7 +2069,7 @@
         if el.endswith(b" (esc)\n"):
             if PYTHON3:
                 el = el[:-7].decode('unicode_escape') + '\n'
-                el = el.encode('utf-8')
+                el = el.encode('latin-1')
             else:
                 el = el[:-7].decode('string-escape') + '\n'
         if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
@@ -2081,12 +2099,10 @@
         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(_bytes2sys(line[len(TTest.SKIPPED_PREFIX) :]))
             elif line.startswith(TTest.FAILED_PREFIX):
                 line = line.splitlines()[0]
-                failed.append(line[len(TTest.FAILED_PREFIX) :].decode('utf-8'))
+                failed.append(_bytes2sys(line[len(TTest.FAILED_PREFIX) :]))
 
         return missing, failed
 
@@ -2213,7 +2229,7 @@
                 v = self._options.view
                 subprocess.call(
                     r'"%s" "%s" "%s"'
-                    % (v, _strpath(test.refpath), _strpath(test.errpath)),
+                    % (v, _bytes2sys(test.refpath), _bytes2sys(test.errpath)),
                     shell=True,
                 )
             else:
@@ -2244,7 +2260,7 @@
                         'changes)'
                     )
                 else:
-                    self.stream.write('Accept this change? [n] ')
+                    self.stream.write('Accept this change? [y/N] ')
                     self.stream.flush()
                     answer = sys.stdin.readline().strip()
                     if answer.lower() in ('y', 'yes'):
@@ -2535,7 +2551,7 @@
 def savetimes(outputdir, result):
     saved = dict(loadtimes(outputdir))
     maxruns = 5
-    skipped = set([str(t[0]) for t in result.skipped])
+    skipped = {str(t[0]) for t in result.skipped}
     for tdata in result.times:
         test, real = tdata[0], tdata[3]
         if test not in skipped:
@@ -2702,7 +2718,7 @@
             opts = ''
             withhg = self._runner.options.with_hg
             if withhg:
-                opts += ' --with-hg=%s ' % shellquote(_strpath(withhg))
+                opts += ' --with-hg=%s ' % shellquote(_bytes2sys(withhg))
             rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
             data = pread(bisectcmd + ['--command', rtc])
             m = re.search(
@@ -2748,7 +2764,7 @@
     @staticmethod
     def _writexunit(result, outf):
         # See http://llg.cubic.org/docs/junit/ for a reference.
-        timesd = dict((t[0], t[3]) for t in result.times)
+        timesd = {t[0]: t[3] for t in result.times}
         doc = minidom.Document()
         s = doc.createElement('testsuite')
         s.setAttribute('errors', "0")  # TODO
@@ -2944,7 +2960,7 @@
         try:
             parser = parser or getparser()
             options = parseargs(args, parser)
-            tests = [_bytespath(a) for a in options.tests]
+            tests = [_sys2bytes(a) for a in options.tests]
             if options.test_list is not None:
                 for listfile in options.test_list:
                     with open(listfile, 'rb') as f:
@@ -2976,7 +2992,7 @@
                 testdir = os.path.join(testdir, pathname)
         self._testdir = osenvironb[b'TESTDIR'] = testdir
         if self.options.outputdir:
-            self._outputdir = canonpath(_bytespath(self.options.outputdir))
+            self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
         else:
             self._outputdir = getcwdb()
             if testdescs and pathname:
@@ -2991,9 +3007,15 @@
             # we do the randomness ourself to know what seed is used
             os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
 
+        # Rayon (Rust crate for multi-threading) will use all logical CPU cores
+        # by default, causing thrashing on high-cpu-count systems.
+        # Setting its limit to 3 during tests should still let us uncover
+        # multi-threading bugs while keeping the thrashing reasonable.
+        os.environ.setdefault("RAYON_NUM_THREADS", "3")
+
         if self.options.tmpdir:
             self.options.keep_tmpdir = True
-            tmpdir = _bytespath(self.options.tmpdir)
+            tmpdir = _sys2bytes(self.options.tmpdir)
             if os.path.exists(tmpdir):
                 # Meaning of tmpdir has changed since 1.3: we used to create
                 # HGTMP inside tmpdir; now HGTMP is tmpdir.  So fail if
@@ -3022,7 +3044,7 @@
             os.makedirs(self._tmpbindir)
 
             normbin = os.path.normpath(os.path.abspath(whg))
-            normbin = normbin.replace(os.sep.encode('ascii'), b'/')
+            normbin = normbin.replace(_sys2bytes(os.sep), b'/')
 
             # Other Python scripts in the test harness need to
             # `import mercurial`. If `hg` is a Python script, we assume
@@ -3071,11 +3093,11 @@
         osenvironb[b"BINDIR"] = self._bindir
         osenvironb[b"PYTHON"] = PYTHON
 
-        fileb = _bytespath(__file__)
+        fileb = _sys2bytes(__file__)
         runtestdir = os.path.abspath(os.path.dirname(fileb))
         osenvironb[b'RUNTESTDIR'] = runtestdir
         if PYTHON3:
-            sepb = _bytespath(os.pathsep)
+            sepb = _sys2bytes(os.pathsep)
         else:
             sepb = os.pathsep
         path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
@@ -3109,6 +3131,13 @@
         if self.options.pure:
             os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
             os.environ["HGMODULEPOLICY"] = "py"
+        if self.options.rust:
+            os.environ["HGMODULEPOLICY"] = "rust+c"
+        if self.options.no_rust:
+            current_policy = os.environ.get("HGMODULEPOLICY", "")
+            if current_policy.startswith("rust+"):
+                os.environ["HGMODULEPOLICY"] = current_policy[len("rust+") :]
+            os.environ.pop("HGWITHRUSTEXT", None)
 
         if self.options.allow_slow_tests:
             os.environ["HGTEST_SLOW"] = "slow"
@@ -3135,14 +3164,14 @@
                 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
             )
 
-        vlog("# Using TESTDIR", _strpath(self._testdir))
-        vlog("# Using RUNTESTDIR", _strpath(osenvironb[b'RUNTESTDIR']))
-        vlog("# Using HGTMP", _strpath(self._hgtmp))
+        vlog("# Using TESTDIR", _bytes2sys(self._testdir))
+        vlog("# Using RUNTESTDIR", _bytes2sys(osenvironb[b'RUNTESTDIR']))
+        vlog("# Using HGTMP", _bytes2sys(self._hgtmp))
         vlog("# Using PATH", os.environ["PATH"])
         vlog(
-            "# Using", _strpath(IMPL_PATH), _strpath(osenvironb[IMPL_PATH]),
+            "# Using", _bytes2sys(IMPL_PATH), _bytes2sys(osenvironb[IMPL_PATH]),
         )
-        vlog("# Writing to directory", _strpath(self._outputdir))
+        vlog("# Writing to directory", _bytes2sys(self._outputdir))
 
         try:
             return self._runtests(testdescs) or 0
@@ -3160,7 +3189,7 @@
             if self.options.changed:
                 proc = Popen4(
                     b'hg st --rev "%s" -man0 .'
-                    % _bytespath(self.options.changed),
+                    % _sys2bytes(self.options.changed),
                     None,
                     0,
                 )
@@ -3354,7 +3383,7 @@
         tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
 
         # extra keyword parameters. 'case' is used by .t tests
-        kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc)
+        kwds = {k: testdesc[k] for k in ['case'] if k in testdesc}
 
         t = testcls(
             refpath,
@@ -3366,10 +3395,10 @@
             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),
+            chgdebug=self.options.chg_debug,
             useipv6=useipv6,
             **kwds
         )
@@ -3381,7 +3410,7 @@
         if self.options.keep_tmpdir:
             return
 
-        vlog("# Cleaning up HGTMP", _strpath(self._hgtmp))
+        vlog("# Cleaning up HGTMP", _bytes2sys(self._hgtmp))
         shutil.rmtree(self._hgtmp, True)
         for f in self._createdfiles:
             try:
@@ -3440,18 +3469,21 @@
         compiler = ''
         if self.options.compiler:
             compiler = '--compiler ' + self.options.compiler
+        setup_opts = b""
         if self.options.pure:
-            pure = b"--pure"
-        else:
-            pure = b""
+            setup_opts = b"--pure"
+        elif self.options.rust:
+            setup_opts = b"--rust"
+        elif self.options.no_rust:
+            setup_opts = b"--no-rust"
 
         # Run installer in hg root
         script = os.path.realpath(sys.argv[0])
         exe = sysexecutable
         if PYTHON3:
-            compiler = _bytespath(compiler)
-            script = _bytespath(script)
-            exe = _bytespath(exe)
+            compiler = _sys2bytes(compiler)
+            script = _sys2bytes(script)
+            exe = _sys2bytes(exe)
         hgroot = os.path.dirname(os.path.dirname(script))
         self._hgroot = hgroot
         os.chdir(hgroot)
@@ -3463,14 +3495,14 @@
             # when they happen.
             nohome = b''
         cmd = (
-            b'"%(exe)s" setup.py %(pure)s clean --all'
+            b'"%(exe)s" setup.py %(setup_opts)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'setup_opts': setup_opts,
                 b'compiler': compiler,
                 b'base': os.path.join(self._hgtmp, b"build"),
                 b'prefix': self._installdir,
@@ -3493,7 +3525,7 @@
         makedirs(self._bindir)
 
         vlog("# Running", cmd.decode("utf-8"))
-        if subprocess.call(_strpath(cmd), shell=True) == 0:
+        if subprocess.call(_bytes2sys(cmd), shell=True) == 0:
             if not self.options.verbose:
                 try:
                     os.remove(installerrs)
@@ -3512,15 +3544,6 @@
 
         self._usecorrectpython()
 
-        if self.options.py3_warnings and not self.options.anycoverage:
-            vlog("# Updating hg command to enable Py3k Warnings switch")
-            with open(os.path.join(self._bindir, 'hg'), 'rb') as f:
-                lines = [line.rstrip() for line in f]
-                lines[0] += ' -3'
-            with open(os.path.join(self._bindir, 'hg'), 'wb') as f:
-                for line in lines:
-                    f.write(line + '\n')
-
         hgbat = os.path.join(self._bindir, b'hg.bat')
         if os.path.isfile(hgbat):
             # hg.bat expects to be put in bin/scripts while run-tests.py
@@ -3582,7 +3605,7 @@
         cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
         cmd = cmd % PYTHON
         if PYTHON3:
-            cmd = _strpath(cmd)
+            cmd = _bytes2sys(cmd)
 
         p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
         out, err = p.communicate()
@@ -3628,37 +3651,37 @@
         # chdir is the easiest way to get short, relative paths in the
         # output.
         os.chdir(self._hgroot)
-        covdir = os.path.join(_strpath(self._installdir), '..', 'coverage')
+        covdir = os.path.join(_bytes2sys(self._installdir), '..', 'coverage')
         cov = coverage(data_file=os.path.join(covdir, 'cov'))
 
         # Map install directory paths back to source directory.
-        cov.config.paths['srcdir'] = ['.', _strpath(self._pythondir)]
+        cov.config.paths['srcdir'] = ['.', _bytes2sys(self._pythondir)]
 
         cov.combine()
 
         omit = [
-            _strpath(os.path.join(x, b'*'))
+            _bytes2sys(os.path.join(x, b'*'))
             for x in [self._bindir, self._testdir]
         ]
         cov.report(ignore_errors=True, omit=omit)
 
         if self.options.htmlcov:
-            htmldir = os.path.join(_strpath(self._outputdir), 'htmlcov')
+            htmldir = os.path.join(_bytes2sys(self._outputdir), 'htmlcov')
             cov.html_report(directory=htmldir, omit=omit)
         if self.options.annotate:
-            adir = os.path.join(_strpath(self._outputdir), 'annotated')
+            adir = os.path.join(_bytes2sys(self._outputdir), 'annotated')
             if not os.path.isdir(adir):
                 os.mkdir(adir)
             cov.annotate(directory=adir, omit=omit)
 
     def _findprogram(self, program):
         """Search PATH for a executable program"""
-        dpb = _bytespath(os.defpath)
-        sepb = _bytespath(os.pathsep)
+        dpb = _sys2bytes(os.defpath)
+        sepb = _sys2bytes(os.pathsep)
         for p in osenvironb.get(b'PATH', dpb).split(sepb):
             name = os.path.join(p, program)
             if os.name == 'nt' or os.access(name, os.X_OK):
-                return name
+                return _bytes2sys(name)
         return None
 
     def _checktools(self):
@@ -3669,7 +3692,7 @@
             found = self._findprogram(p)
             p = p.decode("utf-8")
             if found:
-                vlog("# Found prerequisite", p, "at", _strpath(found))
+                vlog("# Found prerequisite", p, "at", found)
             else:
                 print("WARNING: Did not find prerequisite tool: %s " % p)
 
diff --git a/tests/test-check-pyflakes.t b/tests/test-check-pyflakes.t
--- a/tests/test-check-pyflakes.t
+++ b/tests/test-check-pyflakes.t
@@ -8,8 +8,8 @@
   $ cat > test.py <<EOF
   > print(undefinedname)
   > EOF
-  $ pyflakes test.py 2>/dev/null
-  test.py:1: undefined name 'undefinedname'
+  $ $PYTHON -m pyflakes test.py 2>/dev/null
+  test.py:1:* undefined name 'undefinedname' (glob)
   [1]
   $ cd "`dirname "$TESTDIR"`"