Mercurial > hg
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) |