comparison tests/run-tests.py @ 47573:75b623801f6a

run-tests: use a global WINDOWS constant instead of multiple tests This should make the code clearer. This required the adjustement of a hack in the code testing this code. Differential Revision: https://phab.mercurial-scm.org/D11041
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Fri, 09 Jul 2021 15:39:43 +0200
parents e9c5c368be17
children 2b2f5cf979c3
comparison
equal deleted inserted replaced
47572:da497189a93a 47573:75b623801f6a
68 import time 68 import time
69 import unittest 69 import unittest
70 import uuid 70 import uuid
71 import xml.dom.minidom as minidom 71 import xml.dom.minidom as minidom
72 72
73 WINDOWS = os.name == r'nt'
74
73 try: 75 try:
74 import Queue as queue 76 import Queue as queue
75 except ImportError: 77 except ImportError:
76 import queue 78 import queue
77 79
81 shellquote = shlex.quote 83 shellquote = shlex.quote
82 except (ImportError, AttributeError): 84 except (ImportError, AttributeError):
83 import pipes 85 import pipes
84 86
85 shellquote = pipes.quote 87 shellquote = pipes.quote
88
86 89
87 processlock = threading.Lock() 90 processlock = threading.Lock()
88 91
89 pygmentspresent = False 92 pygmentspresent = False
90 try: # is pygments installed 93 try: # is pygments installed
93 import pygments.lexer as lexer 96 import pygments.lexer as lexer
94 import pygments.formatters as formatters 97 import pygments.formatters as formatters
95 import pygments.token as token 98 import pygments.token as token
96 import pygments.style as style 99 import pygments.style as style
97 100
98 if os.name == 'nt': 101 if WINDOWS:
99 hgpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 102 hgpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
100 sys.path.append(hgpath) 103 sys.path.append(hgpath)
101 try: 104 try:
102 from mercurial import win32 # pytype: disable=import-error 105 from mercurial import win32 # pytype: disable=import-error
103 106
149 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle) 152 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
150 runnerlexer = TestRunnerLexer() 153 runnerlexer = TestRunnerLexer()
151 154
152 origenviron = os.environ.copy() 155 origenviron = os.environ.copy()
153 156
157
154 if sys.version_info > (3, 5, 0): 158 if sys.version_info > (3, 5, 0):
155 PYTHON3 = True 159 PYTHON3 = True
156 xrange = range # we use xrange in one place, and we'd rather not use range 160 xrange = range # we use xrange in one place, and we'd rather not use range
157 161
158 def _sys2bytes(p): 162 def _sys2bytes(p):
201 return _sys2bytes(v) 205 return _sys2bytes(v)
202 206
203 osenvironb = environbytes(os.environ) 207 osenvironb = environbytes(os.environ)
204 208
205 getcwdb = getattr(os, 'getcwdb') 209 getcwdb = getattr(os, 'getcwdb')
206 if not getcwdb or os.name == 'nt': 210 if not getcwdb or WINDOWS:
207 getcwdb = lambda: _sys2bytes(os.getcwd()) 211 getcwdb = lambda: _sys2bytes(os.getcwd())
208 212
209 elif sys.version_info >= (3, 0, 0): 213 elif sys.version_info >= (3, 0, 0):
210 print( 214 print(
211 '%s is only supported on Python 3.5+ and 2.7, not %s' 215 '%s is only supported on Python 3.5+ and 2.7, not %s'
268 try: 272 try:
269 with contextlib.closing(socket.socket(family, socket.SOCK_STREAM)) as s: 273 with contextlib.closing(socket.socket(family, socket.SOCK_STREAM)) as s:
270 s.bind(('localhost', port)) 274 s.bind(('localhost', port))
271 return True 275 return True
272 except socket.error as exc: 276 except socket.error as exc:
273 if os.name == 'nt' and exc.errno == errno.WSAEACCES: 277 if WINDOWS and exc.errno == errno.WSAEACCES:
274 return False 278 return False
275 elif PYTHON3: 279 elif PYTHON3:
276 # TODO: make a proper exception handler after dropping py2. This 280 # TODO: make a proper exception handler after dropping py2. This
277 # works because socket.error is an alias for OSError on py3, 281 # works because socket.error is an alias for OSError on py3,
278 # which is also the baseclass of PermissionError. 282 # which is also the baseclass of PermissionError.
707 pathandattrs.append((b'contrib/chg/chg', 'with_chg')) 711 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
708 if options.rhg: 712 if options.rhg:
709 pathandattrs.append((b'rust/target/release/rhg', 'with_rhg')) 713 pathandattrs.append((b'rust/target/release/rhg', 'with_rhg'))
710 for relpath, attr in pathandattrs: 714 for relpath, attr in pathandattrs:
711 binpath = os.path.join(reporootdir, relpath) 715 binpath = os.path.join(reporootdir, relpath)
712 if os.name != 'nt' and not os.access(binpath, os.X_OK): 716 if not (WINDOWS or os.access(binpath, os.X_OK)):
713 parser.error( 717 parser.error(
714 '--local specified, but %r not found or ' 718 '--local specified, but %r not found or '
715 'not executable' % binpath 719 'not executable' % binpath
716 ) 720 )
717 setattr(options, attr, _bytes2sys(binpath)) 721 setattr(options, attr, _bytes2sys(binpath))
725 parser.error('--with-hg must specify an executable hg script') 729 parser.error('--with-hg must specify an executable hg script')
726 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']: 730 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
727 sys.stderr.write('warning: --with-hg should specify an hg script\n') 731 sys.stderr.write('warning: --with-hg should specify an hg script\n')
728 sys.stderr.flush() 732 sys.stderr.flush()
729 733
730 if (options.chg or options.with_chg) and os.name == 'nt': 734 if (options.chg or options.with_chg) and WINDOWS:
731 parser.error('chg does not work on %s' % os.name) 735 parser.error('chg does not work on %s' % os.name)
732 if (options.rhg or options.with_rhg) and os.name == 'nt': 736 if (options.rhg or options.with_rhg) and WINDOWS:
733 parser.error('rhg does not work on %s' % os.name) 737 parser.error('rhg does not work on %s' % os.name)
734 if options.with_chg: 738 if options.with_chg:
735 options.chg = False # no installation to temporary location 739 options.chg = False # no installation to temporary location
736 options.with_chg = canonpath(_sys2bytes(options.with_chg)) 740 options.with_chg = canonpath(_sys2bytes(options.with_chg))
737 if not ( 741 if not (
1306 self._portmap(2), 1310 self._portmap(2),
1307 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'), 1311 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
1308 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'), 1312 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
1309 ] 1313 ]
1310 r.append((self._escapepath(self._testtmp), b'$TESTTMP')) 1314 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
1311 if os.name == 'nt': 1315 if WINDOWS:
1312 # JSON output escapes backslashes in Windows paths, so also catch a 1316 # JSON output escapes backslashes in Windows paths, so also catch a
1313 # double-escape. 1317 # double-escape.
1314 replaced = self._testtmp.replace(b'\\', br'\\') 1318 replaced = self._testtmp.replace(b'\\', br'\\')
1315 r.append((self._escapepath(replaced), b'$STR_REPR_TESTTMP')) 1319 r.append((self._escapepath(replaced), b'$STR_REPR_TESTTMP'))
1316 1320
1329 raise ValueError(msg) 1333 raise ValueError(msg)
1330 r.append(value) 1334 r.append(value)
1331 return r 1335 return r
1332 1336
1333 def _escapepath(self, p): 1337 def _escapepath(self, p):
1334 if os.name == 'nt': 1338 if WINDOWS:
1335 return b''.join( 1339 return b''.join(
1336 c.isalpha() 1340 c.isalpha()
1337 and b'[%s%s]' % (c.lower(), c.upper()) 1341 and b'[%s%s]' % (c.lower(), c.upper())
1338 or c in b'/\\' 1342 or c in b'/\\'
1339 and br'[/\\]' 1343 and br'[/\\]'
1393 env['TESTTMP'] = _bytes2sys(self._testtmp) 1397 env['TESTTMP'] = _bytes2sys(self._testtmp)
1394 uid_file = os.path.join(_bytes2sys(self._testtmp), 'UID') 1398 uid_file = os.path.join(_bytes2sys(self._testtmp), 'UID')
1395 env['HGTEST_UUIDFILE'] = uid_file 1399 env['HGTEST_UUIDFILE'] = uid_file
1396 env['TESTNAME'] = self.name 1400 env['TESTNAME'] = self.name
1397 env['HOME'] = _bytes2sys(self._testtmp) 1401 env['HOME'] = _bytes2sys(self._testtmp)
1398 if os.name == 'nt': 1402 if WINDOWS:
1399 env['REALUSERPROFILE'] = env['USERPROFILE'] 1403 env['REALUSERPROFILE'] = env['USERPROFILE']
1400 # py3.8+ ignores HOME: https://bugs.python.org/issue36264 1404 # py3.8+ ignores HOME: https://bugs.python.org/issue36264
1401 env['USERPROFILE'] = env['HOME'] 1405 env['USERPROFILE'] = env['HOME']
1402 formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1]) 1406 formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1])
1403 env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout 1407 env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout
1442 env['LOCALIP'] = _bytes2sys(self._localip()) 1446 env['LOCALIP'] = _bytes2sys(self._localip())
1443 1447
1444 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c, 1448 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
1445 # but this is needed for testing python instances like dummyssh, 1449 # but this is needed for testing python instances like dummyssh,
1446 # dummysmtpd.py, and dumbhttp.py. 1450 # dummysmtpd.py, and dumbhttp.py.
1447 if PYTHON3 and os.name == 'nt': 1451 if PYTHON3 and WINDOWS:
1448 env['PYTHONLEGACYWINDOWSSTDIO'] = '1' 1452 env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1449 1453
1450 # Modified HOME in test environment can confuse Rust tools. So set 1454 # Modified HOME in test environment can confuse Rust tools. So set
1451 # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is 1455 # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is
1452 # present and these variables aren't already defined. 1456 # present and these variables aren't already defined.
1607 1611
1608 def _run(self, env): 1612 def _run(self, env):
1609 # Quote the python(3) executable for Windows 1613 # Quote the python(3) executable for Windows
1610 cmd = b'"%s" "%s"' % (PYTHON, self.path) 1614 cmd = b'"%s" "%s"' % (PYTHON, self.path)
1611 vlog("# Running", cmd.decode("utf-8")) 1615 vlog("# Running", cmd.decode("utf-8"))
1612 normalizenewlines = os.name == 'nt' 1616 result = self._runcommand(cmd, env, normalizenewlines=WINDOWS)
1613 result = self._runcommand(cmd, env, normalizenewlines=normalizenewlines)
1614 if self._aborted: 1617 if self._aborted:
1615 raise KeyboardInterrupt() 1618 raise KeyboardInterrupt()
1616 1619
1617 return result 1620 return result
1618 1621
2081 # supported right now, but this should be easy to extend. 2084 # supported right now, but this should be easy to extend.
2082 flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2] 2085 flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2]
2083 flags = flags or b'' 2086 flags = flags or b''
2084 el = flags + b'(?:' + el + b')' 2087 el = flags + b'(?:' + el + b')'
2085 # use \Z to ensure that the regex matches to the end of the string 2088 # use \Z to ensure that the regex matches to the end of the string
2086 if os.name == 'nt': 2089 if WINDOWS:
2087 return re.match(el + br'\r?\n\Z', l) 2090 return re.match(el + br'\r?\n\Z', l)
2088 return re.match(el + br'\n\Z', l) 2091 return re.match(el + br'\n\Z', l)
2089 except re.error: 2092 except re.error:
2090 # el is an invalid regex 2093 # el is an invalid regex
2091 return False 2094 return False
2142 if PYTHON3: 2145 if PYTHON3:
2143 el = el[:-7].decode('unicode_escape') + '\n' 2146 el = el[:-7].decode('unicode_escape') + '\n'
2144 el = el.encode('latin-1') 2147 el = el.encode('latin-1')
2145 else: 2148 else:
2146 el = el[:-7].decode('string-escape') + '\n' 2149 el = el[:-7].decode('string-escape') + '\n'
2147 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l: 2150 if el == l or WINDOWS and el[:-1] + b'\r\n' == l:
2148 return True, True 2151 return True, True
2149 if el.endswith(b" (re)\n"): 2152 if el.endswith(b" (re)\n"):
2150 return (TTest.rematch(el[:-6], l) or retry), False 2153 return (TTest.rematch(el[:-6], l) or retry), False
2151 if el.endswith(b" (glob)\n"): 2154 if el.endswith(b" (glob)\n"):
2152 # ignore '(glob)' added to l by 'replacements' 2155 # ignore '(glob)' added to l by 'replacements'
2153 if l.endswith(b" (glob)\n"): 2156 if l.endswith(b" (glob)\n"):
2154 l = l[:-8] + b"\n" 2157 l = l[:-8] + b"\n"
2155 return (TTest.globmatch(el[:-8], l) or retry), False 2158 return (TTest.globmatch(el[:-8], l) or retry), False
2156 if os.altsep: 2159 if os.altsep:
2157 _l = l.replace(b'\\', b'/') 2160 _l = l.replace(b'\\', b'/')
2158 if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l: 2161 if el == _l or WINDOWS and el[:-1] + b'\r\n' == _l:
2159 return True, True 2162 return True, True
2160 return retry, True 2163 return retry, True
2161 2164
2162 @staticmethod 2165 @staticmethod
2163 def parsehghaveoutput(lines): 2166 def parsehghaveoutput(lines):
2220 if options.color == 'auto': 2223 if options.color == 'auto':
2221 isatty = self.stream.isatty() 2224 isatty = self.stream.isatty()
2222 # For some reason, redirecting stdout on Windows disables the ANSI 2225 # For some reason, redirecting stdout on Windows disables the ANSI
2223 # color processing of stderr, which is what is used to print the 2226 # color processing of stderr, which is what is used to print the
2224 # output. Therefore, both must be tty on Windows to enable color. 2227 # output. Therefore, both must be tty on Windows to enable color.
2225 if os.name == 'nt': 2228 if WINDOWS:
2226 isatty = isatty and sys.stdout.isatty() 2229 isatty = isatty and sys.stdout.isatty()
2227 self.color = pygmentspresent and isatty 2230 self.color = pygmentspresent and isatty
2228 elif options.color == 'never': 2231 elif options.color == 'never':
2229 self.color = False 2232 self.color = False
2230 else: # 'always', for testing purposes 2233 else: # 'always', for testing purposes
3097 return 1 3100 return 1
3098 3101
3099 os.makedirs(tmpdir) 3102 os.makedirs(tmpdir)
3100 else: 3103 else:
3101 d = None 3104 d = None
3102 if os.name == 'nt': 3105 if WINDOWS:
3103 # without this, we get the default temp dir location, but 3106 # without this, we get the default temp dir location, but
3104 # in all lowercase, which causes troubles with paths (issue3490) 3107 # in all lowercase, which causes troubles with paths (issue3490)
3105 d = osenvironb.get(b'TMP', None) 3108 d = osenvironb.get(b'TMP', None)
3106 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d) 3109 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
3107 3110
3146 3149
3147 # Force the use of hg.exe instead of relying on MSYS to recognize hg is 3150 # Force the use of hg.exe instead of relying on MSYS to recognize hg is
3148 # a python script and feed it to python.exe. Legacy stdio is force 3151 # a python script and feed it to python.exe. Legacy stdio is force
3149 # enabled by hg.exe, and this is a more realistic way to launch hg 3152 # enabled by hg.exe, and this is a more realistic way to launch hg
3150 # anyway. 3153 # anyway.
3151 if os.name == 'nt' and not self._hgcommand.endswith(b'.exe'): 3154 if WINDOWS and not self._hgcommand.endswith(b'.exe'):
3152 self._hgcommand += b'.exe' 3155 self._hgcommand += b'.exe'
3153 3156
3154 # set CHGHG, then replace "hg" command by "chg" 3157 # set CHGHG, then replace "hg" command by "chg"
3155 chgbindir = self._bindir 3158 chgbindir = self._bindir
3156 if self.options.chg or self.options.with_chg: 3159 if self.options.chg or self.options.with_chg:
3541 else: 3544 else:
3542 pyexe_names = [b'python', b'python3'] 3545 pyexe_names = [b'python', b'python3']
3543 3546
3544 # os.symlink() is a thing with py3 on Windows, but it requires 3547 # os.symlink() is a thing with py3 on Windows, but it requires
3545 # Administrator rights. 3548 # Administrator rights.
3546 if getattr(os, 'symlink', None) and os.name != 'nt': 3549 if getattr(os, 'symlink', None) and not WINDOWS:
3547 msg = "# Making python executable in test path a symlink to '%s'" 3550 msg = "# Making python executable in test path a symlink to '%s'"
3548 msg %= sysexecutable 3551 msg %= sysexecutable
3549 vlog(msg) 3552 vlog(msg)
3550 for pyexename in pyexe_names: 3553 for pyexename in pyexe_names:
3551 mypython = os.path.join(self._tmpbindir, pyexename) 3554 mypython = os.path.join(self._tmpbindir, pyexename)
3641 exe = _sys2bytes(exe) 3644 exe = _sys2bytes(exe)
3642 hgroot = os.path.dirname(os.path.dirname(script)) 3645 hgroot = os.path.dirname(os.path.dirname(script))
3643 self._hgroot = hgroot 3646 self._hgroot = hgroot
3644 os.chdir(hgroot) 3647 os.chdir(hgroot)
3645 nohome = b'--home=""' 3648 nohome = b'--home=""'
3646 if os.name == 'nt': 3649 if WINDOWS:
3647 # The --home="" trick works only on OS where os.sep == '/' 3650 # The --home="" trick works only on OS where os.sep == '/'
3648 # because of a distutils convert_path() fast-path. Avoid it at 3651 # because of a distutils convert_path() fast-path. Avoid it at
3649 # least on Windows for now, deal with .pydistutils.cfg bugs 3652 # least on Windows for now, deal with .pydistutils.cfg bugs
3650 # when they happen. 3653 # when they happen.
3651 nohome = b'' 3654 nohome = b''
3860 """Search PATH for a executable program""" 3863 """Search PATH for a executable program"""
3861 dpb = _sys2bytes(os.defpath) 3864 dpb = _sys2bytes(os.defpath)
3862 sepb = _sys2bytes(os.pathsep) 3865 sepb = _sys2bytes(os.pathsep)
3863 for p in osenvironb.get(b'PATH', dpb).split(sepb): 3866 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3864 name = os.path.join(p, program) 3867 name = os.path.join(p, program)
3865 if os.name == 'nt' or os.access(name, os.X_OK): 3868 if WINDOWS or os.access(name, os.X_OK):
3866 return _bytes2sys(name) 3869 return _bytes2sys(name)
3867 return None 3870 return None
3868 3871
3869 def _checktools(self): 3872 def _checktools(self):
3870 """Ensure tools required to run tests are present.""" 3873 """Ensure tools required to run tests are present."""
3871 for p in self.REQUIREDTOOLS: 3874 for p in self.REQUIREDTOOLS:
3872 if os.name == 'nt' and not p.endswith(b'.exe'): 3875 if WINDOWS and not p.endswith(b'.exe'):
3873 p += b'.exe' 3876 p += b'.exe'
3874 found = self._findprogram(p) 3877 found = self._findprogram(p)
3875 p = p.decode("utf-8") 3878 p = p.decode("utf-8")
3876 if found: 3879 if found:
3877 vlog("# Found prerequisite", p, "at", found) 3880 vlog("# Found prerequisite", p, "at", found)