This should make the code clearer.
This required the adjustement of a hack in the code testing this code.
| hg-reviewers |
This should make the code clearer.
This required the adjustement of a hack in the code testing this code.
| Automatic diff as part of commit; lint not applicable. |
| Automatic diff as part of commit; unit tests not applicable. |
| Path | Packages | |||
|---|---|---|---|---|
| M | tests/run-tests.py (47 lines) | |||
| M | tests/test-run-tests.py (3 lines) |
| Status | Author | Revision | |
|---|---|---|---|
| Closed | Alphare | ||
| Closed | marmoute | ||
| Abandoned | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | D11067 windows: use abspath in url | |
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | Alphare | ||
| Closed | Alphare | ||
| Closed | Alphare | ||
| Closed | marmoute | ||
| Closed | marmoute | ||
| Closed | Alphare | ||
| Closed | Alphare |
| import sysconfig | import sysconfig | ||||
| import tempfile | import tempfile | ||||
| import threading | import threading | ||||
| import time | import time | ||||
| import unittest | import unittest | ||||
| import uuid | import uuid | ||||
| import xml.dom.minidom as minidom | import xml.dom.minidom as minidom | ||||
| WINDOWS = os.name == r'nt' | |||||
| try: | try: | ||||
| import Queue as queue | import Queue as queue | ||||
| except ImportError: | except ImportError: | ||||
| import queue | import queue | ||||
| try: | try: | ||||
| import shlex | import shlex | ||||
| shellquote = shlex.quote | shellquote = shlex.quote | ||||
| except (ImportError, AttributeError): | except (ImportError, AttributeError): | ||||
| import pipes | import pipes | ||||
| shellquote = pipes.quote | shellquote = pipes.quote | ||||
| processlock = threading.Lock() | processlock = threading.Lock() | ||||
| pygmentspresent = False | pygmentspresent = False | ||||
| try: # is pygments installed | try: # is pygments installed | ||||
| import pygments | import pygments | ||||
| import pygments.lexers as lexers | import pygments.lexers as lexers | ||||
| import pygments.lexer as lexer | import pygments.lexer as lexer | ||||
| import pygments.formatters as formatters | import pygments.formatters as formatters | ||||
| import pygments.token as token | import pygments.token as token | ||||
| import pygments.style as style | import pygments.style as style | ||||
| if os.name == 'nt': | if WINDOWS: | ||||
| hgpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | hgpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | ||||
| sys.path.append(hgpath) | sys.path.append(hgpath) | ||||
| try: | try: | ||||
| from mercurial import win32 # pytype: disable=import-error | from mercurial import win32 # pytype: disable=import-error | ||||
| # Don't check the result code because it fails on heptapod, but | # Don't check the result code because it fails on heptapod, but | ||||
| # something is able to convert to color anyway. | # something is able to convert to color anyway. | ||||
| win32.enablevtmode() | win32.enablevtmode() | ||||
| ], | ], | ||||
| } | } | ||||
| runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle) | runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle) | ||||
| runnerlexer = TestRunnerLexer() | runnerlexer = TestRunnerLexer() | ||||
| origenviron = os.environ.copy() | origenviron = os.environ.copy() | ||||
| if sys.version_info > (3, 5, 0): | if sys.version_info > (3, 5, 0): | ||||
| PYTHON3 = True | 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 _sys2bytes(p): | def _sys2bytes(p): | ||||
| if p is None: | if p is None: | ||||
| return p | return p | ||||
| return p.encode('utf-8') | return p.encode('utf-8') | ||||
| def pop(self, k, default=None): | def pop(self, k, default=None): | ||||
| v = self._strenv.pop(_bytes2sys(k), _bytes2sys(default)) | v = self._strenv.pop(_bytes2sys(k), _bytes2sys(default)) | ||||
| return _sys2bytes(v) | return _sys2bytes(v) | ||||
| osenvironb = environbytes(os.environ) | osenvironb = environbytes(os.environ) | ||||
| getcwdb = getattr(os, 'getcwdb') | getcwdb = getattr(os, 'getcwdb') | ||||
| if not getcwdb or os.name == 'nt': | if not getcwdb or WINDOWS: | ||||
| getcwdb = lambda: _sys2bytes(os.getcwd()) | getcwdb = lambda: _sys2bytes(os.getcwd()) | ||||
| elif sys.version_info >= (3, 0, 0): | elif sys.version_info >= (3, 0, 0): | ||||
| print( | print( | ||||
| '%s is only supported on Python 3.5+ and 2.7, not %s' | '%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.argv[0], '.'.join(str(v) for v in sys.version_info[:3])) | ||||
| ) | ) | ||||
| sys.exit(70) # EX_SOFTWARE from `man 3 sysexit` | sys.exit(70) # EX_SOFTWARE from `man 3 sysexit` | ||||
| family = socket.AF_INET6 | family = socket.AF_INET6 | ||||
| else: | else: | ||||
| family = socket.AF_INET | family = socket.AF_INET | ||||
| try: | try: | ||||
| with contextlib.closing(socket.socket(family, socket.SOCK_STREAM)) as s: | with contextlib.closing(socket.socket(family, socket.SOCK_STREAM)) as s: | ||||
| s.bind(('localhost', port)) | s.bind(('localhost', port)) | ||||
| return True | return True | ||||
| except socket.error as exc: | except socket.error as exc: | ||||
| if os.name == 'nt' and exc.errno == errno.WSAEACCES: | if WINDOWS and exc.errno == errno.WSAEACCES: | ||||
| return False | return False | ||||
| elif PYTHON3: | elif PYTHON3: | ||||
| # TODO: make a proper exception handler after dropping py2. This | # TODO: make a proper exception handler after dropping py2. This | ||||
| # works because socket.error is an alias for OSError on py3, | # works because socket.error is an alias for OSError on py3, | ||||
| # which is also the baseclass of PermissionError. | # which is also the baseclass of PermissionError. | ||||
| if isinstance(exc, PermissionError): | if isinstance(exc, PermissionError): | ||||
| return False | return False | ||||
| if exc.errno not in ( | if exc.errno not in ( | ||||
| reporootdir = os.path.dirname(testdir) | reporootdir = os.path.dirname(testdir) | ||||
| pathandattrs = [(b'hg', 'with_hg')] | pathandattrs = [(b'hg', 'with_hg')] | ||||
| if options.chg: | if options.chg: | ||||
| pathandattrs.append((b'contrib/chg/chg', 'with_chg')) | pathandattrs.append((b'contrib/chg/chg', 'with_chg')) | ||||
| if options.rhg: | if options.rhg: | ||||
| pathandattrs.append((b'rust/target/release/rhg', 'with_rhg')) | pathandattrs.append((b'rust/target/release/rhg', 'with_rhg')) | ||||
| for relpath, attr in pathandattrs: | for relpath, attr in pathandattrs: | ||||
| binpath = os.path.join(reporootdir, relpath) | binpath = os.path.join(reporootdir, relpath) | ||||
| if os.name != 'nt' and not os.access(binpath, os.X_OK): | if not (WINDOWS or os.access(binpath, os.X_OK)): | ||||
| parser.error( | parser.error( | ||||
| '--local specified, but %r not found or ' | '--local specified, but %r not found or ' | ||||
| 'not executable' % binpath | 'not executable' % binpath | ||||
| ) | ) | ||||
| setattr(options, attr, _bytes2sys(binpath)) | setattr(options, attr, _bytes2sys(binpath)) | ||||
| if options.with_hg: | if options.with_hg: | ||||
| options.with_hg = canonpath(_sys2bytes(options.with_hg)) | options.with_hg = canonpath(_sys2bytes(options.with_hg)) | ||||
| if not ( | if not ( | ||||
| os.path.isfile(options.with_hg) | os.path.isfile(options.with_hg) | ||||
| and os.access(options.with_hg, os.X_OK) | and os.access(options.with_hg, os.X_OK) | ||||
| ): | ): | ||||
| parser.error('--with-hg must specify an executable hg script') | parser.error('--with-hg must specify an executable hg script') | ||||
| if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']: | 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') | sys.stderr.write('warning: --with-hg should specify an hg script\n') | ||||
| sys.stderr.flush() | sys.stderr.flush() | ||||
| if (options.chg or options.with_chg) and os.name == 'nt': | if (options.chg or options.with_chg) and WINDOWS: | ||||
| parser.error('chg does not work on %s' % os.name) | parser.error('chg does not work on %s' % os.name) | ||||
| if (options.rhg or options.with_rhg) and os.name == 'nt': | if (options.rhg or options.with_rhg) and WINDOWS: | ||||
| parser.error('rhg does not work on %s' % os.name) | parser.error('rhg does not work on %s' % os.name) | ||||
| if options.with_chg: | if options.with_chg: | ||||
| options.chg = False # no installation to temporary location | options.chg = False # no installation to temporary location | ||||
| options.with_chg = canonpath(_sys2bytes(options.with_chg)) | options.with_chg = canonpath(_sys2bytes(options.with_chg)) | ||||
| if not ( | if not ( | ||||
| os.path.isfile(options.with_chg) | os.path.isfile(options.with_chg) | ||||
| and os.access(options.with_chg, os.X_OK) | and os.access(options.with_chg, os.X_OK) | ||||
| ): | ): | ||||
| # This list should be parallel to defineport in _getenv | # This list should be parallel to defineport in _getenv | ||||
| self._portmap(0), | self._portmap(0), | ||||
| self._portmap(1), | self._portmap(1), | ||||
| self._portmap(2), | self._portmap(2), | ||||
| (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'), | (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$'), | (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'), | ||||
| ] | ] | ||||
| r.append((self._escapepath(self._testtmp), b'$TESTTMP')) | r.append((self._escapepath(self._testtmp), b'$TESTTMP')) | ||||
| if os.name == 'nt': | if WINDOWS: | ||||
| # JSON output escapes backslashes in Windows paths, so also catch a | # JSON output escapes backslashes in Windows paths, so also catch a | ||||
| # double-escape. | # double-escape. | ||||
| replaced = self._testtmp.replace(b'\\', br'\\') | replaced = self._testtmp.replace(b'\\', br'\\') | ||||
| r.append((self._escapepath(replaced), b'$STR_REPR_TESTTMP')) | r.append((self._escapepath(replaced), b'$STR_REPR_TESTTMP')) | ||||
| replacementfile = os.path.join(self._testdir, b'common-pattern.py') | replacementfile = os.path.join(self._testdir, b'common-pattern.py') | ||||
| if os.path.exists(replacementfile): | if os.path.exists(replacementfile): | ||||
| data = {} | data = {} | ||||
| with open(replacementfile, mode='rb') as source: | with open(replacementfile, mode='rb') as source: | ||||
| # the intermediate 'compile' step help with debugging | # the intermediate 'compile' step help with debugging | ||||
| code = compile(source.read(), replacementfile, 'exec') | code = compile(source.read(), replacementfile, 'exec') | ||||
| exec(code, data) | exec(code, data) | ||||
| for value in data.get('substitutions', ()): | for value in data.get('substitutions', ()): | ||||
| if len(value) != 2: | if len(value) != 2: | ||||
| msg = 'malformatted substitution in %s: %r' | msg = 'malformatted substitution in %s: %r' | ||||
| msg %= (replacementfile, value) | msg %= (replacementfile, value) | ||||
| raise ValueError(msg) | raise ValueError(msg) | ||||
| r.append(value) | r.append(value) | ||||
| return r | return r | ||||
| def _escapepath(self, p): | def _escapepath(self, p): | ||||
| if os.name == 'nt': | if WINDOWS: | ||||
| return b''.join( | return b''.join( | ||||
| c.isalpha() | c.isalpha() | ||||
| and b'[%s%s]' % (c.lower(), c.upper()) | and b'[%s%s]' % (c.lower(), c.upper()) | ||||
| or c in b'/\\' | or c in b'/\\' | ||||
| and br'[/\\]' | and br'[/\\]' | ||||
| or c.isdigit() | or c.isdigit() | ||||
| and c | and c | ||||
| or b'\\' + c | or b'\\' + c | ||||
| env = os.environ.copy() | env = os.environ.copy() | ||||
| env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or '' | env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or '' | ||||
| env['HGEMITWARNINGS'] = '1' | env['HGEMITWARNINGS'] = '1' | ||||
| env['TESTTMP'] = _bytes2sys(self._testtmp) | env['TESTTMP'] = _bytes2sys(self._testtmp) | ||||
| uid_file = os.path.join(_bytes2sys(self._testtmp), 'UID') | uid_file = os.path.join(_bytes2sys(self._testtmp), 'UID') | ||||
| env['HGTEST_UUIDFILE'] = uid_file | env['HGTEST_UUIDFILE'] = uid_file | ||||
| env['TESTNAME'] = self.name | env['TESTNAME'] = self.name | ||||
| env['HOME'] = _bytes2sys(self._testtmp) | env['HOME'] = _bytes2sys(self._testtmp) | ||||
| if os.name == 'nt': | if WINDOWS: | ||||
| env['REALUSERPROFILE'] = env['USERPROFILE'] | env['REALUSERPROFILE'] = env['USERPROFILE'] | ||||
| # py3.8+ ignores HOME: https://bugs.python.org/issue36264 | # py3.8+ ignores HOME: https://bugs.python.org/issue36264 | ||||
| env['USERPROFILE'] = env['HOME'] | env['USERPROFILE'] = env['HOME'] | ||||
| formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1]) | formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1]) | ||||
| env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout | env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout | ||||
| env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout) | env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout) | ||||
| # This number should match portneeded in _getport | # This number should match portneeded in _getport | ||||
| for port in xrange(3): | for port in xrange(3): | ||||
| # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw | # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw | ||||
| # IP addresses. | # IP addresses. | ||||
| env['LOCALIP'] = _bytes2sys(self._localip()) | env['LOCALIP'] = _bytes2sys(self._localip()) | ||||
| # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c, | # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c, | ||||
| # but this is needed for testing python instances like dummyssh, | # but this is needed for testing python instances like dummyssh, | ||||
| # dummysmtpd.py, and dumbhttp.py. | # dummysmtpd.py, and dumbhttp.py. | ||||
| if PYTHON3 and os.name == 'nt': | if PYTHON3 and WINDOWS: | ||||
| env['PYTHONLEGACYWINDOWSSTDIO'] = '1' | env['PYTHONLEGACYWINDOWSSTDIO'] = '1' | ||||
| # Modified HOME in test environment can confuse Rust tools. So set | # Modified HOME in test environment can confuse Rust tools. So set | ||||
| # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is | # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is | ||||
| # present and these variables aren't already defined. | # present and these variables aren't already defined. | ||||
| cargo_home_path = os.path.expanduser('~/.cargo') | cargo_home_path = os.path.expanduser('~/.cargo') | ||||
| rustup_home_path = os.path.expanduser('~/.rustup') | rustup_home_path = os.path.expanduser('~/.rustup') | ||||
| @property | @property | ||||
| def refpath(self): | def refpath(self): | ||||
| return os.path.join(self._testdir, b'%s.out' % self.bname) | return os.path.join(self._testdir, b'%s.out' % self.bname) | ||||
| def _run(self, env): | def _run(self, env): | ||||
| # Quote the python(3) executable for Windows | # Quote the python(3) executable for Windows | ||||
| cmd = b'"%s" "%s"' % (PYTHON, self.path) | cmd = b'"%s" "%s"' % (PYTHON, self.path) | ||||
| vlog("# Running", cmd.decode("utf-8")) | vlog("# Running", cmd.decode("utf-8")) | ||||
| normalizenewlines = os.name == 'nt' | result = self._runcommand(cmd, env, normalizenewlines=WINDOWS) | ||||
| result = self._runcommand(cmd, env, normalizenewlines=normalizenewlines) | |||||
| if self._aborted: | if self._aborted: | ||||
| raise KeyboardInterrupt() | raise KeyboardInterrupt() | ||||
| return result | return result | ||||
| # Some glob patterns apply only in some circumstances, so the script | # Some glob patterns apply only in some circumstances, so the script | ||||
| # might want to remove (glob) annotations that otherwise should be | # might want to remove (glob) annotations that otherwise should be | ||||
| def rematch(el, l): | def rematch(el, l): | ||||
| try: | try: | ||||
| # parse any flags at the beginning of the regex. Only 'i' is | # parse any flags at the beginning of the regex. Only 'i' is | ||||
| # supported right now, but this should be easy to extend. | # supported right now, but this should be easy to extend. | ||||
| flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2] | flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2] | ||||
| flags = flags or b'' | flags = flags or b'' | ||||
| el = flags + b'(?:' + el + b')' | el = flags + b'(?:' + el + b')' | ||||
| # use \Z to ensure that the regex matches to the end of the string | # use \Z to ensure that the regex matches to the end of the string | ||||
| if os.name == 'nt': | if WINDOWS: | ||||
| return re.match(el + br'\r?\n\Z', l) | return re.match(el + br'\r?\n\Z', l) | ||||
| return re.match(el + br'\n\Z', l) | return re.match(el + br'\n\Z', l) | ||||
| except re.error: | except re.error: | ||||
| # el is an invalid regex | # el is an invalid regex | ||||
| return False | return False | ||||
| @staticmethod | @staticmethod | ||||
| def globmatch(el, l): | def globmatch(el, l): | ||||
| return "retry", False | return "retry", False | ||||
| if el.endswith(b" (esc)\n"): | if el.endswith(b" (esc)\n"): | ||||
| if PYTHON3: | if PYTHON3: | ||||
| el = el[:-7].decode('unicode_escape') + '\n' | el = el[:-7].decode('unicode_escape') + '\n' | ||||
| el = el.encode('latin-1') | el = el.encode('latin-1') | ||||
| else: | else: | ||||
| el = el[:-7].decode('string-escape') + '\n' | el = el[:-7].decode('string-escape') + '\n' | ||||
| if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l: | if el == l or WINDOWS and el[:-1] + b'\r\n' == l: | ||||
| return True, True | return True, True | ||||
| if el.endswith(b" (re)\n"): | if el.endswith(b" (re)\n"): | ||||
| return (TTest.rematch(el[:-6], l) or retry), False | return (TTest.rematch(el[:-6], l) or retry), False | ||||
| if el.endswith(b" (glob)\n"): | if el.endswith(b" (glob)\n"): | ||||
| # ignore '(glob)' added to l by 'replacements' | # ignore '(glob)' added to l by 'replacements' | ||||
| if l.endswith(b" (glob)\n"): | if l.endswith(b" (glob)\n"): | ||||
| l = l[:-8] + b"\n" | l = l[:-8] + b"\n" | ||||
| return (TTest.globmatch(el[:-8], l) or retry), False | return (TTest.globmatch(el[:-8], l) or retry), False | ||||
| if os.altsep: | if os.altsep: | ||||
| _l = l.replace(b'\\', b'/') | _l = l.replace(b'\\', b'/') | ||||
| if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l: | if el == _l or WINDOWS and el[:-1] + b'\r\n' == _l: | ||||
| return True, True | return True, True | ||||
| return retry, True | return retry, True | ||||
| @staticmethod | @staticmethod | ||||
| def parsehghaveoutput(lines): | def parsehghaveoutput(lines): | ||||
| """Parse hghave log lines. | """Parse hghave log lines. | ||||
| Return tuple of lists (missing, failed): | Return tuple of lists (missing, failed): | ||||
| self.successes = [] | self.successes = [] | ||||
| self.faildata = {} | self.faildata = {} | ||||
| if options.color == 'auto': | if options.color == 'auto': | ||||
| isatty = self.stream.isatty() | isatty = self.stream.isatty() | ||||
| # For some reason, redirecting stdout on Windows disables the ANSI | # For some reason, redirecting stdout on Windows disables the ANSI | ||||
| # color processing of stderr, which is what is used to print the | # color processing of stderr, which is what is used to print the | ||||
| # output. Therefore, both must be tty on Windows to enable color. | # output. Therefore, both must be tty on Windows to enable color. | ||||
| if os.name == 'nt': | if WINDOWS: | ||||
| isatty = isatty and sys.stdout.isatty() | isatty = isatty and sys.stdout.isatty() | ||||
| self.color = pygmentspresent and isatty | self.color = pygmentspresent and isatty | ||||
| elif options.color == 'never': | elif options.color == 'never': | ||||
| self.color = False | self.color = False | ||||
| else: # 'always', for testing purposes | else: # 'always', for testing purposes | ||||
| self.color = pygmentspresent | self.color = pygmentspresent | ||||
| def onStart(self, test): | def onStart(self, test): | ||||
| # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if | # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if | ||||
| # tmpdir already exists. | # tmpdir already exists. | ||||
| print("error: temp dir %r already exists" % tmpdir) | print("error: temp dir %r already exists" % tmpdir) | ||||
| return 1 | return 1 | ||||
| os.makedirs(tmpdir) | os.makedirs(tmpdir) | ||||
| else: | else: | ||||
| d = None | d = None | ||||
| if os.name == 'nt': | if WINDOWS: | ||||
| # without this, we get the default temp dir location, but | # without this, we get the default temp dir location, but | ||||
| # in all lowercase, which causes troubles with paths (issue3490) | # in all lowercase, which causes troubles with paths (issue3490) | ||||
| d = osenvironb.get(b'TMP', None) | d = osenvironb.get(b'TMP', None) | ||||
| tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d) | 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: | if self.options.with_hg: | ||||
| self._hgcommand = b'hg' | self._hgcommand = b'hg' | ||||
| self._tmpbindir = self._bindir | self._tmpbindir = self._bindir | ||||
| self._pythondir = os.path.join(self._installdir, b"lib", b"python") | self._pythondir = os.path.join(self._installdir, b"lib", b"python") | ||||
| # Force the use of hg.exe instead of relying on MSYS to recognize hg is | # Force the use of hg.exe instead of relying on MSYS to recognize hg is | ||||
| # a python script and feed it to python.exe. Legacy stdio is force | # a python script and feed it to python.exe. Legacy stdio is force | ||||
| # enabled by hg.exe, and this is a more realistic way to launch hg | # enabled by hg.exe, and this is a more realistic way to launch hg | ||||
| # anyway. | # anyway. | ||||
| if os.name == 'nt' and not self._hgcommand.endswith(b'.exe'): | if WINDOWS and not self._hgcommand.endswith(b'.exe'): | ||||
| self._hgcommand += b'.exe' | self._hgcommand += b'.exe' | ||||
| # set CHGHG, then replace "hg" command by "chg" | # set CHGHG, then replace "hg" command by "chg" | ||||
| chgbindir = self._bindir | chgbindir = self._bindir | ||||
| if self.options.chg or self.options.with_chg: | if self.options.chg or self.options.with_chg: | ||||
| osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand) | osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand) | ||||
| else: | else: | ||||
| osenvironb.pop(b'CHGHG', None) # drop flag for hghave | osenvironb.pop(b'CHGHG', None) # drop flag for hghave | ||||
| pyexe_names = [b'python', b'python.exe'] | pyexe_names = [b'python', b'python.exe'] | ||||
| elif sys.version_info[0] < 3: | elif sys.version_info[0] < 3: | ||||
| pyexe_names = [b'python', b'python2'] | pyexe_names = [b'python', b'python2'] | ||||
| else: | else: | ||||
| pyexe_names = [b'python', b'python3'] | pyexe_names = [b'python', b'python3'] | ||||
| # os.symlink() is a thing with py3 on Windows, but it requires | # os.symlink() is a thing with py3 on Windows, but it requires | ||||
| # Administrator rights. | # Administrator rights. | ||||
| if getattr(os, 'symlink', None) and os.name != 'nt': | if getattr(os, 'symlink', None) and not WINDOWS: | ||||
| msg = "# Making python executable in test path a symlink to '%s'" | msg = "# Making python executable in test path a symlink to '%s'" | ||||
| msg %= sysexecutable | msg %= sysexecutable | ||||
| vlog(msg) | vlog(msg) | ||||
| for pyexename in pyexe_names: | for pyexename in pyexe_names: | ||||
| mypython = os.path.join(self._tmpbindir, pyexename) | mypython = os.path.join(self._tmpbindir, pyexename) | ||||
| try: | try: | ||||
| if os.readlink(mypython) == sysexecutable: | if os.readlink(mypython) == sysexecutable: | ||||
| continue | continue | ||||
| if PYTHON3: | if PYTHON3: | ||||
| compiler = _sys2bytes(compiler) | compiler = _sys2bytes(compiler) | ||||
| script = _sys2bytes(script) | script = _sys2bytes(script) | ||||
| exe = _sys2bytes(exe) | exe = _sys2bytes(exe) | ||||
| hgroot = os.path.dirname(os.path.dirname(script)) | hgroot = os.path.dirname(os.path.dirname(script)) | ||||
| self._hgroot = hgroot | self._hgroot = hgroot | ||||
| os.chdir(hgroot) | os.chdir(hgroot) | ||||
| nohome = b'--home=""' | nohome = b'--home=""' | ||||
| if os.name == 'nt': | if WINDOWS: | ||||
| # The --home="" trick works only on OS where os.sep == '/' | # The --home="" trick works only on OS where os.sep == '/' | ||||
| # because of a distutils convert_path() fast-path. Avoid it at | # because of a distutils convert_path() fast-path. Avoid it at | ||||
| # least on Windows for now, deal with .pydistutils.cfg bugs | # least on Windows for now, deal with .pydistutils.cfg bugs | ||||
| # when they happen. | # when they happen. | ||||
| nohome = b'' | nohome = b'' | ||||
| cmd = ( | cmd = ( | ||||
| b'"%(exe)s" setup.py %(setup_opts)s clean --all' | b'"%(exe)s" setup.py %(setup_opts)s clean --all' | ||||
| b' build %(compiler)s --build-base="%(base)s"' | b' build %(compiler)s --build-base="%(base)s"' | ||||
| cov.annotate(directory=adir, omit=omit) | cov.annotate(directory=adir, omit=omit) | ||||
| def _findprogram(self, program): | def _findprogram(self, program): | ||||
| """Search PATH for a executable program""" | """Search PATH for a executable program""" | ||||
| dpb = _sys2bytes(os.defpath) | dpb = _sys2bytes(os.defpath) | ||||
| sepb = _sys2bytes(os.pathsep) | sepb = _sys2bytes(os.pathsep) | ||||
| for p in osenvironb.get(b'PATH', dpb).split(sepb): | for p in osenvironb.get(b'PATH', dpb).split(sepb): | ||||
| name = os.path.join(p, program) | name = os.path.join(p, program) | ||||
| if os.name == 'nt' or os.access(name, os.X_OK): | if WINDOWS or os.access(name, os.X_OK): | ||||
| return _bytes2sys(name) | return _bytes2sys(name) | ||||
| return None | return None | ||||
| def _checktools(self): | def _checktools(self): | ||||
| """Ensure tools required to run tests are present.""" | """Ensure tools required to run tests are present.""" | ||||
| for p in self.REQUIREDTOOLS: | for p in self.REQUIREDTOOLS: | ||||
| if os.name == 'nt' and not p.endswith(b'.exe'): | if WINDOWS and not p.endswith(b'.exe'): | ||||
| p += b'.exe' | p += b'.exe' | ||||
| found = self._findprogram(p) | found = self._findprogram(p) | ||||
| p = p.decode("utf-8") | p = p.decode("utf-8") | ||||
| if found: | if found: | ||||
| vlog("# Found prerequisite", p, "at", found) | vlog("# Found prerequisite", p, "at", found) | ||||
| else: | else: | ||||
| print("WARNING: Did not find prerequisite tool: %s " % p) | print("WARNING: Did not find prerequisite tool: %s " % p) | ||||
| def wintests(): | def wintests(): | ||||
| r"""test matching like running on windows | r"""test matching like running on windows | ||||
| enable windows matching on any os | enable windows matching on any os | ||||
| >>> _osaltsep = os.altsep | >>> _osaltsep = os.altsep | ||||
| >>> os.altsep = True | >>> os.altsep = True | ||||
| >>> _osname = os.name | >>> _osname = os.name | ||||
| >>> os.name = 'nt' | >>> os.name = 'nt' | ||||
| >>> _old_windows = run_tests.WINDOWS | |||||
| >>> run_tests.WINDOWS = True | |||||
| valid match on windows | valid match on windows | ||||
| >>> lm(b'g/a*/d (glob)\n', b'g\\abc/d\n') | >>> lm(b'g/a*/d (glob)\n', b'g\\abc/d\n') | ||||
| True | True | ||||
| direct matching, glob unnecessary | direct matching, glob unnecessary | ||||
| >>> lm(b'g/b (glob)\n', b'g/b\n') | >>> lm(b'g/b (glob)\n', b'g/b\n') | ||||
| 'special: -glob' | 'special: -glob' | ||||
| missing glob | missing glob | ||||
| >>> lm(b'/g/c/d/fg\n', b'\\g\\c\\d/fg\n') | >>> lm(b'/g/c/d/fg\n', b'\\g\\c\\d/fg\n') | ||||
| True | True | ||||
| >>> lm(b'/g/c/d/fg\n', b'\\g\\c\\d\\fg\r\n') | >>> lm(b'/g/c/d/fg\n', b'\\g\\c\\d\\fg\r\n') | ||||
| True | True | ||||
| restore os.altsep | restore os.altsep | ||||
| >>> os.altsep = _osaltsep | >>> os.altsep = _osaltsep | ||||
| >>> os.name = _osname | >>> os.name = _osname | ||||
| >>> run_tests.WINDOWS = _old_windows | |||||
| """ | """ | ||||
| pass | pass | ||||
| def otherostests(): | def otherostests(): | ||||
| r"""test matching like running on non-windows os | r"""test matching like running on non-windows os | ||||
| disable windows matching on any os | disable windows matching on any os | ||||