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 |