--- a/tests/run-tests.py Sat Oct 05 10:29:34 2019 -0400
+++ b/tests/run-tests.py Sun Oct 06 09:45:02 2019 -0400
@@ -75,9 +75,11 @@
try:
import shlex
+
shellquote = shlex.quote
except (ImportError, AttributeError):
import pipes
+
shellquote = pipes.quote
processlock = threading.Lock()
@@ -85,13 +87,14 @@
pygmentspresent = False
# ANSI color is unsupported prior to Windows 10
if os.name != 'nt':
- try: # is pygments installed
+ try: # is pygments installed
import pygments
import pygments.lexers as lexers
import pygments.lexer as lexer
import pygments.formatters as formatters
import pygments.token as token
import pygments.style as style
+
pygmentspresent = True
difflexer = lexers.DiffLexer()
terminal256formatter = formatters.Terminal256Formatter()
@@ -99,6 +102,7 @@
pass
if pygmentspresent:
+
class TestRunnerStyle(style.Style):
default_style = ""
skipped = token.string_to_tokentype("Token.Generic.Skipped")
@@ -106,10 +110,10 @@
skippedname = token.string_to_tokentype("Token.Generic.SName")
failedname = token.string_to_tokentype("Token.Generic.FName")
styles = {
- skipped: '#e5e5e5',
- skippedname: '#00ffff',
- failed: '#7f0000',
- failedname: '#ff0000',
+ skipped: '#e5e5e5',
+ skippedname: '#00ffff',
+ failed: '#7f0000',
+ failedname: '#ff0000',
}
class TestRunnerLexer(lexer.RegexLexer):
@@ -127,7 +131,7 @@
'failed': [
(testpattern, token.Generic.FName),
(r'(:| ).*', token.Generic.Failed),
- ]
+ ],
}
runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
@@ -137,7 +141,8 @@
if sys.version_info > (3, 5, 0):
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 _bytespath(p):
if p is None:
return p
@@ -158,20 +163,27 @@
self.__len__ = strenv.__len__
self.clear = strenv.clear
self._strenv = strenv
+
def __getitem__(self, k):
v = self._strenv.__getitem__(_strpath(k))
return _bytespath(v)
+
def __setitem__(self, k, v):
self._strenv.__setitem__(_strpath(k), _strpath(v))
+
def __delitem__(self, k):
self._strenv.__delitem__(_strpath(k))
+
def __contains__(self, k):
return self._strenv.__contains__(_strpath(k))
+
def __iter__(self):
return iter([_bytespath(k) for k in iter(self._strenv)])
+
def get(self, k, default=None):
v = self._strenv.get(_strpath(k), _strpath(default))
return _bytespath(v)
+
def pop(self, k, default=None):
v = self._strenv.pop(_strpath(k), _strpath(default))
return _bytespath(v)
@@ -183,9 +195,11 @@
getcwdb = lambda: _bytespath(os.getcwd())
elif sys.version_info >= (3, 0, 0):
- print('%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.exit(70) # EX_SOFTWARE from `man 3 sysexit`
+ print(
+ '%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.exit(70) # EX_SOFTWARE from `man 3 sysexit`
else:
PYTHON3 = False
@@ -228,9 +242,11 @@
else:
return False
+
# useipv6 will be set by parseargs
useipv6 = None
+
def checkportisavailable(port):
"""return true if a port seems free to bind on localhost"""
if useipv6:
@@ -243,19 +259,31 @@
s.close()
return True
except socket.error as exc:
- if exc.errno not in (errno.EADDRINUSE, errno.EADDRNOTAVAIL,
- errno.EPROTONOSUPPORT):
+ if exc.errno not in (
+ errno.EADDRINUSE,
+ errno.EADDRNOTAVAIL,
+ errno.EPROTONOSUPPORT,
+ ):
raise
return False
+
closefds = os.name == 'posix'
+
+
def Popen4(cmd, wd, timeout, env=None):
processlock.acquire()
- p = subprocess.Popen(_strpath(cmd), shell=True, bufsize=-1,
- cwd=_strpath(wd), env=env,
- close_fds=closefds,
- stdin=subprocess.PIPE, stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
+ p = subprocess.Popen(
+ _strpath(cmd),
+ shell=True,
+ bufsize=-1,
+ cwd=_strpath(wd),
+ env=env,
+ close_fds=closefds,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ )
processlock.release()
p.fromchild = p.stdout
@@ -264,17 +292,20 @@
p.timeout = False
if timeout:
+
def t():
start = time.time()
while time.time() - start < timeout and p.returncode is None:
- time.sleep(.1)
+ time.sleep(0.1)
p.timeout = True
if p.returncode is None:
terminate(p)
+
threading.Thread(target=t).start()
return p
+
if sys.executable:
sysexecutable = sys.executable
elif os.environ.get('PYTHONEXECUTABLE'):
@@ -297,9 +328,11 @@
'shell': ('HGTEST_SHELL', 'sh'),
}
+
def canonpath(path):
return os.path.realpath(os.path.expanduser(path))
+
def parselistfiles(files, listtype, warn=True):
entries = dict()
for filename in files:
@@ -321,6 +354,7 @@
f.close()
return entries
+
def parsettestcases(path):
"""read a .t test file, return a set of test case names
@@ -337,131 +371,262 @@
raise
return cases
+
def getparser():
"""Obtain the OptionParser used by the CLI."""
parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
selection = parser.add_argument_group('Test Selection')
- selection.add_argument('--allow-slow-tests', action='store_true',
- help='allow extremely slow tests')
- selection.add_argument("--blacklist", action="append",
- help="skip tests listed in the specified blacklist file")
- selection.add_argument("--changed",
- help="run tests that are changed in parent rev or working directory")
- selection.add_argument("-k", "--keywords",
- help="run tests matching keywords")
- selection.add_argument("-r", "--retest", action="store_true",
- help = "retest failed tests")
- selection.add_argument("--test-list", action="append",
- help="read tests to run from the specified file")
- selection.add_argument("--whitelist", action="append",
- help="always run tests listed in the specified whitelist file")
- selection.add_argument('tests', metavar='TESTS', nargs='*',
- help='Tests to run')
+ selection.add_argument(
+ '--allow-slow-tests',
+ action='store_true',
+ help='allow extremely slow tests',
+ )
+ selection.add_argument(
+ "--blacklist",
+ action="append",
+ help="skip tests listed in the specified blacklist file",
+ )
+ selection.add_argument(
+ "--changed",
+ help="run tests that are changed in parent rev or working directory",
+ )
+ selection.add_argument(
+ "-k", "--keywords", help="run tests matching keywords"
+ )
+ selection.add_argument(
+ "-r", "--retest", action="store_true", help="retest failed tests"
+ )
+ selection.add_argument(
+ "--test-list",
+ action="append",
+ help="read tests to run from the specified file",
+ )
+ selection.add_argument(
+ "--whitelist",
+ action="append",
+ help="always run tests listed in the specified whitelist file",
+ )
+ selection.add_argument(
+ 'tests', metavar='TESTS', nargs='*', help='Tests to run'
+ )
harness = parser.add_argument_group('Test Harness Behavior')
- harness.add_argument('--bisect-repo',
- metavar='bisect_repo',
- help=("Path of a repo to bisect. Use together with "
- "--known-good-rev"))
- harness.add_argument("-d", "--debug", action="store_true",
+ harness.add_argument(
+ '--bisect-repo',
+ metavar='bisect_repo',
+ help=(
+ "Path of a repo to bisect. Use together with " "--known-good-rev"
+ ),
+ )
+ harness.add_argument(
+ "-d",
+ "--debug",
+ action="store_true",
help="debug mode: write output of test scripts to console"
- " rather than capturing and diffing it (disables timeout)")
- harness.add_argument("-f", "--first", action="store_true",
- help="exit on the first test failure")
- harness.add_argument("-i", "--interactive", action="store_true",
- help="prompt to accept changed output")
- harness.add_argument("-j", "--jobs", type=int,
+ " rather than capturing and diffing it (disables timeout)",
+ )
+ harness.add_argument(
+ "-f",
+ "--first",
+ action="store_true",
+ help="exit on the first test failure",
+ )
+ harness.add_argument(
+ "-i",
+ "--interactive",
+ action="store_true",
+ help="prompt to accept changed output",
+ )
+ harness.add_argument(
+ "-j",
+ "--jobs",
+ type=int,
help="number of jobs to run in parallel"
- " (default: $%s or %d)" % defaults['jobs'])
- harness.add_argument("--keep-tmpdir", action="store_true",
- help="keep temporary directory after running tests")
- harness.add_argument('--known-good-rev',
- metavar="known_good_rev",
- help=("Automatically bisect any failures using this "
- "revision as a known-good revision."))
- harness.add_argument("--list-tests", action="store_true",
- help="list tests instead of running them")
- harness.add_argument("--loop", action="store_true",
- help="loop tests repeatedly")
- harness.add_argument('--random', action="store_true",
- help='run tests in random order')
- harness.add_argument('--order-by-runtime', action="store_true",
- help='run slowest tests first, according to .testtimes')
- harness.add_argument("-p", "--port", type=int,
+ " (default: $%s or %d)" % defaults['jobs'],
+ )
+ harness.add_argument(
+ "--keep-tmpdir",
+ action="store_true",
+ help="keep temporary directory after running tests",
+ )
+ harness.add_argument(
+ '--known-good-rev',
+ metavar="known_good_rev",
+ help=(
+ "Automatically bisect any failures using this "
+ "revision as a known-good revision."
+ ),
+ )
+ harness.add_argument(
+ "--list-tests",
+ action="store_true",
+ help="list tests instead of running them",
+ )
+ harness.add_argument(
+ "--loop", action="store_true", help="loop tests repeatedly"
+ )
+ harness.add_argument(
+ '--random', action="store_true", help='run tests in random order'
+ )
+ harness.add_argument(
+ '--order-by-runtime',
+ action="store_true",
+ help='run slowest tests first, according to .testtimes',
+ )
+ harness.add_argument(
+ "-p",
+ "--port",
+ type=int,
help="port on which servers should listen"
- " (default: $%s or %d)" % defaults['port'])
- harness.add_argument('--profile-runner', action='store_true',
- help='run statprof on run-tests')
- harness.add_argument("-R", "--restart", action="store_true",
- help="restart at last error")
- harness.add_argument("--runs-per-test", type=int, dest="runs_per_test",
- help="run each test N times (default=1)", default=1)
- harness.add_argument("--shell",
- help="shell to use (default: $%s or %s)" % defaults['shell'])
- harness.add_argument('--showchannels', action='store_true',
- help='show scheduling channels')
- harness.add_argument("--slowtimeout", type=int,
+ " (default: $%s or %d)" % defaults['port'],
+ )
+ harness.add_argument(
+ '--profile-runner',
+ action='store_true',
+ help='run statprof on run-tests',
+ )
+ harness.add_argument(
+ "-R", "--restart", action="store_true", help="restart at last error"
+ )
+ harness.add_argument(
+ "--runs-per-test",
+ type=int,
+ dest="runs_per_test",
+ help="run each test N times (default=1)",
+ default=1,
+ )
+ harness.add_argument(
+ "--shell", help="shell to use (default: $%s or %s)" % defaults['shell']
+ )
+ harness.add_argument(
+ '--showchannels', action='store_true', help='show scheduling channels'
+ )
+ harness.add_argument(
+ "--slowtimeout",
+ type=int,
help="kill errant slow tests after SLOWTIMEOUT seconds"
- " (default: $%s or %d)" % defaults['slowtimeout'])
- harness.add_argument("-t", "--timeout", type=int,
+ " (default: $%s or %d)" % defaults['slowtimeout'],
+ )
+ harness.add_argument(
+ "-t",
+ "--timeout",
+ type=int,
help="kill errant tests after TIMEOUT seconds"
- " (default: $%s or %d)" % defaults['timeout'])
- harness.add_argument("--tmpdir",
+ " (default: $%s or %d)" % defaults['timeout'],
+ )
+ harness.add_argument(
+ "--tmpdir",
help="run tests in the given temporary directory"
- " (implies --keep-tmpdir)")
- harness.add_argument("-v", "--verbose", action="store_true",
- help="output verbose messages")
+ " (implies --keep-tmpdir)",
+ )
+ harness.add_argument(
+ "-v", "--verbose", action="store_true", help="output verbose messages"
+ )
hgconf = parser.add_argument_group('Mercurial Configuration')
- hgconf.add_argument("--chg", action="store_true",
- help="install and use chg wrapper in place of hg")
- hgconf.add_argument("--compiler",
- help="compiler to build with")
- hgconf.add_argument('--extra-config-opt', action="append", default=[],
- help='set the given config opt in the test hgrc')
- hgconf.add_argument("-l", "--local", action="store_true",
+ hgconf.add_argument(
+ "--chg",
+ action="store_true",
+ help="install and use chg wrapper in place of hg",
+ )
+ hgconf.add_argument("--compiler", help="compiler to build with")
+ hgconf.add_argument(
+ '--extra-config-opt',
+ action="append",
+ default=[],
+ help='set the given config opt in the test hgrc',
+ )
+ hgconf.add_argument(
+ "-l",
+ "--local",
+ action="store_true",
help="shortcut for --with-hg=<testdir>/../hg, "
- "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set")
- hgconf.add_argument("--ipv6", action="store_true",
- help="prefer IPv6 to IPv4 for network related tests")
- hgconf.add_argument("--pure", action="store_true",
- help="use pure Python code instead of C extensions")
- hgconf.add_argument("-3", "--py3-warnings", action="store_true",
- help="enable Py3k warnings on Python 2.7+")
- hgconf.add_argument("--with-chg", metavar="CHG",
- help="use specified chg wrapper in place of hg")
- hgconf.add_argument("--with-hg",
+ "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set",
+ )
+ hgconf.add_argument(
+ "--ipv6",
+ action="store_true",
+ help="prefer IPv6 to IPv4 for network related tests",
+ )
+ hgconf.add_argument(
+ "--pure",
+ action="store_true",
+ help="use pure Python code instead of C extensions",
+ )
+ hgconf.add_argument(
+ "-3",
+ "--py3-warnings",
+ action="store_true",
+ help="enable Py3k warnings on Python 2.7+",
+ )
+ hgconf.add_argument(
+ "--with-chg",
+ metavar="CHG",
+ help="use specified chg wrapper in place of hg",
+ )
+ hgconf.add_argument(
+ "--with-hg",
metavar="HG",
help="test using specified hg script rather than a "
- "temporary installation")
+ "temporary installation",
+ )
reporting = parser.add_argument_group('Results Reporting')
- reporting.add_argument("-C", "--annotate", action="store_true",
- help="output files annotated with coverage")
- reporting.add_argument("--color", choices=["always", "auto", "never"],
+ reporting.add_argument(
+ "-C",
+ "--annotate",
+ action="store_true",
+ help="output files annotated with coverage",
+ )
+ reporting.add_argument(
+ "--color",
+ choices=["always", "auto", "never"],
default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
- help="colorisation: always|auto|never (default: auto)")
- reporting.add_argument("-c", "--cover", action="store_true",
- help="print a test coverage report")
- reporting.add_argument('--exceptions', action='store_true',
- help='log all exceptions and generate an exception report')
- reporting.add_argument("-H", "--htmlcov", action="store_true",
- help="create an HTML report of the coverage of the files")
- reporting.add_argument("--json", action="store_true",
- help="store test result data in 'report.json' file")
- reporting.add_argument("--outputdir",
- help="directory to write error logs to (default=test directory)")
- reporting.add_argument("-n", "--nodiff", action="store_true",
- help="skip showing test changes")
- reporting.add_argument("-S", "--noskips", action="store_true",
- help="don't report skip tests verbosely")
- reporting.add_argument("--time", action="store_true",
- help="time how long each test takes")
- reporting.add_argument("--view",
- help="external diff viewer")
- reporting.add_argument("--xunit",
- help="record xunit results at specified path")
+ help="colorisation: always|auto|never (default: auto)",
+ )
+ reporting.add_argument(
+ "-c",
+ "--cover",
+ action="store_true",
+ help="print a test coverage report",
+ )
+ reporting.add_argument(
+ '--exceptions',
+ action='store_true',
+ help='log all exceptions and generate an exception report',
+ )
+ reporting.add_argument(
+ "-H",
+ "--htmlcov",
+ action="store_true",
+ help="create an HTML report of the coverage of the files",
+ )
+ reporting.add_argument(
+ "--json",
+ action="store_true",
+ help="store test result data in 'report.json' file",
+ )
+ reporting.add_argument(
+ "--outputdir",
+ help="directory to write error logs to (default=test directory)",
+ )
+ reporting.add_argument(
+ "-n", "--nodiff", action="store_true", help="skip showing test changes"
+ )
+ reporting.add_argument(
+ "-S",
+ "--noskips",
+ action="store_true",
+ help="don't report skip tests verbosely",
+ )
+ reporting.add_argument(
+ "--time", action="store_true", help="time how long each test takes"
+ )
+ reporting.add_argument("--view", help="external diff viewer")
+ reporting.add_argument(
+ "--xunit", help="record xunit results at specified path"
+ )
for option, (envvar, default) in defaults.items():
defaults[option] = type(default)(os.environ.get(envvar, default))
@@ -469,6 +634,7 @@
return parser
+
def parseargs(args, parser):
"""Parse arguments with our OptionParser and validate results."""
options = parser.parse_args(args)
@@ -488,14 +654,18 @@
for relpath, attr in pathandattrs:
binpath = os.path.join(reporootdir, relpath)
if os.name != 'nt' and not os.access(binpath, os.X_OK):
- parser.error('--local specified, but %r not found or '
- 'not executable' % binpath)
+ parser.error(
+ '--local specified, but %r not found or '
+ 'not executable' % binpath
+ )
setattr(options, attr, _strpath(binpath))
if options.with_hg:
options.with_hg = canonpath(_bytespath(options.with_hg))
- if not (os.path.isfile(options.with_hg) and
- os.access(options.with_hg, os.X_OK)):
+ if not (
+ os.path.isfile(options.with_hg)
+ and os.access(options.with_hg, os.X_OK)
+ ):
parser.error('--with-hg must specify an executable hg script')
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')
@@ -506,17 +676,23 @@
if options.with_chg:
options.chg = False # no installation to temporary location
options.with_chg = canonpath(_bytespath(options.with_chg))
- if not (os.path.isfile(options.with_chg) and
- os.access(options.with_chg, os.X_OK)):
+ if not (
+ os.path.isfile(options.with_chg)
+ and os.access(options.with_chg, os.X_OK)
+ ):
parser.error('--with-chg must specify a chg executable')
if options.chg and options.with_hg:
# chg shares installation location with hg
- parser.error('--chg does not work when --with-hg is specified '
- '(use --with-chg instead)')
+ parser.error(
+ '--chg does not work when --with-hg is specified '
+ '(use --with-chg instead)'
+ )
if options.color == 'always' and not pygmentspresent:
- sys.stderr.write('warning: --color=always ignored because '
- 'pygments is not installed\n')
+ sys.stderr.write(
+ 'warning: --color=always ignored because '
+ 'pygments is not installed\n'
+ )
if options.bisect_repo and not options.known_good_rev:
parser.error("--bisect-repo cannot be used without --known-good-rev")
@@ -526,13 +702,15 @@
useipv6 = checksocketfamily('AF_INET6')
else:
# only use IPv6 if IPv4 is unavailable and IPv6 is available
- useipv6 = ((not checksocketfamily('AF_INET'))
- and checksocketfamily('AF_INET6'))
+ useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily(
+ 'AF_INET6'
+ )
options.anycoverage = options.cover or options.annotate or options.htmlcov
if options.anycoverage:
try:
import coverage
+
covver = version.StrictVersion(coverage.__version__).version
if covver < (3, 3):
parser.error('coverage options require coverage 3.3 or later')
@@ -541,12 +719,14 @@
if options.anycoverage and options.local:
# this needs some path mangling somewhere, I guess
- parser.error("sorry, coverage options do not work when --local "
- "is specified")
+ parser.error(
+ "sorry, coverage options do not work when --local " "is specified"
+ )
if options.anycoverage and options.with_hg:
- parser.error("sorry, coverage options do not work when --with-hg "
- "is specified")
+ parser.error(
+ "sorry, coverage options do not work when --with-hg " "is specified"
+ )
global verbose
if options.verbose:
@@ -561,17 +741,16 @@
parser.error("-i/--interactive and -d/--debug are incompatible")
if options.debug:
if options.timeout != defaults['timeout']:
- sys.stderr.write(
- 'warning: --timeout option ignored with --debug\n')
+ sys.stderr.write('warning: --timeout option ignored with --debug\n')
if options.slowtimeout != defaults['slowtimeout']:
sys.stderr.write(
- 'warning: --slowtimeout option ignored with --debug\n')
+ 'warning: --slowtimeout option ignored with --debug\n'
+ )
options.timeout = 0
options.slowtimeout = 0
if options.py3_warnings:
if PYTHON3:
- parser.error(
- '--py3-warnings can only be used on Python 2.7')
+ parser.error('--py3-warnings can only be used on Python 2.7')
if options.blacklist:
options.blacklist = parselistfiles(options.blacklist, 'blacklist')
@@ -585,6 +764,7 @@
return options
+
def rename(src, dst):
"""Like os.rename(), trade atomicity and opened files friendliness
for existing destination support.
@@ -592,6 +772,7 @@
shutil.copy(src, dst)
os.remove(src)
+
def makecleanable(path):
"""Try to fix directory permission recursively so that the entire tree
can be deleted"""
@@ -603,11 +784,14 @@
except OSError:
pass
+
_unified_diff = difflib.unified_diff
if PYTHON3:
import functools
+
_unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
+
def getdiff(expected, output, ref, err):
servefail = False
lines = []
@@ -618,12 +802,16 @@
line = line[:-2] + b'\n'
lines.append(line)
if not servefail and line.startswith(
- b'+ abort: child process failed to start'):
+ b'+ abort: child process failed to start'
+ ):
servefail = True
return servefail, lines
+
verbose = False
+
+
def vlog(*msg):
"""Log only when in verbose mode."""
if verbose is False:
@@ -631,6 +819,7 @@
return log(*msg)
+
# Bytes that break XML even in a CDATA block: control characters 0-31
# sans \t, \n and \r
CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
@@ -641,6 +830,7 @@
# output..output (feature !)\n
optline = re.compile(br'(.*) \((.+?) !\)\n$')
+
def cdatasafe(data):
"""Make a string safe to include in a CDATA block.
@@ -651,6 +841,7 @@
"""
return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
+
def log(*msg):
"""Log something to stdout.
@@ -664,12 +855,15 @@
print()
sys.stdout.flush()
+
def highlightdiff(line, color):
if not color:
return line
assert pygmentspresent
- return pygments.highlight(line.decode('latin1'), difflexer,
- terminal256formatter).encode('latin1')
+ return pygments.highlight(
+ line.decode('latin1'), difflexer, terminal256formatter
+ ).encode('latin1')
+
def highlightmsg(msg, color):
if not color:
@@ -677,6 +871,7 @@
assert pygmentspresent
return pygments.highlight(msg, runnerlexer, runnerformatter)
+
def terminate(proc):
"""Terminate subprocess"""
vlog('# Terminating process %d' % proc.pid)
@@ -685,10 +880,12 @@
except OSError:
pass
+
def killdaemons(pidfile):
import killdaemons as killmod
- return killmod.killdaemons(pidfile, tryhard=False, remove=True,
- logfn=vlog)
+
+ return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog)
+
class Test(unittest.TestCase):
"""Encapsulates a single, runnable test.
@@ -701,14 +898,24 @@
# Status code reserved for skipped tests (used by hghave).
SKIPPED_STATUS = 80
- def __init__(self, path, outputdir, tmpdir, keeptmpdir=False,
- debug=False,
- first=False,
- timeout=None,
- startport=None, extraconfigopts=None,
- py3warnings=False, shell=None, hgcommand=None,
- slowtimeout=None, usechg=False,
- useipv6=False):
+ def __init__(
+ self,
+ path,
+ outputdir,
+ tmpdir,
+ keeptmpdir=False,
+ debug=False,
+ first=False,
+ timeout=None,
+ startport=None,
+ extraconfigopts=None,
+ py3warnings=False,
+ shell=None,
+ hgcommand=None,
+ slowtimeout=None,
+ usechg=False,
+ useipv6=False,
+ ):
"""Create a test from parameters.
path is the full path to the file defining the test.
@@ -783,7 +990,7 @@
# If we're not in --debug mode and reference output file exists,
# check test output against it.
if self._debug:
- return None # to match "out is None"
+ return None # to match "out is None"
elif os.path.exists(self.refpath):
with open(self.refpath, 'rb') as f:
return f.read().splitlines(True)
@@ -830,8 +1037,9 @@
raise
if self._usechg:
- self._chgsockdir = os.path.join(self._threadtmp,
- b'%s.chgsock' % name)
+ self._chgsockdir = os.path.join(
+ self._threadtmp, b'%s.chgsock' % name
+ )
os.mkdir(self._chgsockdir)
def run(self, result):
@@ -914,7 +1122,7 @@
self._skipped = False
if ret == self.SKIPPED_STATUS:
- if out is None: # Debug mode, nothing to parse.
+ if out is None: # Debug mode, nothing to parse.
missing = ['unknown']
failed = None
else:
@@ -934,8 +1142,11 @@
self.fail('no result code from test')
elif out != self._refout:
# Diff generation may rely on written .err file.
- if ((ret != 0 or out != self._refout) and not self._skipped
- and not self._debug):
+ if (
+ (ret != 0 or out != self._refout)
+ and not self._skipped
+ and not self._debug
+ ):
with open(self.errpath, 'wb') as f:
for line in out:
f.write(line)
@@ -965,9 +1176,13 @@
self._daemonpids = []
if self._keeptmpdir:
- log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' %
- (self._testtmp.decode('utf-8'),
- self._threadtmp.decode('utf-8')))
+ log(
+ '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s'
+ % (
+ self._testtmp.decode('utf-8'),
+ self._threadtmp.decode('utf-8'),
+ )
+ )
else:
try:
shutil.rmtree(self._testtmp)
@@ -983,8 +1198,12 @@
# files are deleted
shutil.rmtree(self._chgsockdir, True)
- if ((self._ret != 0 or self._out != self._refout) and not self._skipped
- and not self._debug and self._out):
+ if (
+ (self._ret != 0 or self._out != self._refout)
+ and not self._skipped
+ and not self._debug
+ and self._out
+ ):
with open(self.errpath, 'wb') as f:
for line in self._out:
f.write(line)
@@ -1017,7 +1236,7 @@
self._portmap(2),
(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$'),
- ]
+ ]
r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
replacementfile = os.path.join(self._testdir, b'common-pattern.py')
@@ -1038,10 +1257,15 @@
def _escapepath(self, p):
if os.name == 'nt':
- return (
- (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
- c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
- for c in [p[i:i + 1] for i in range(len(p))]))
+ return b''.join(
+ c.isalpha()
+ and b'[%s%s]' % (c.lower(), c.upper())
+ or c in b'/\\'
+ and br'[/\\]'
+ or c.isdigit()
+ and c
+ or b'\\' + c
+ for c in [p[i : i + 1] for i in range(len(p))]
)
else:
return re.escape(p)
@@ -1083,9 +1307,11 @@
def _getenv(self):
"""Obtain environment variables to use during test execution."""
+
def defineport(i):
offset = '' if i == 0 else '%s' % i
env["HGPORT%s" % offset] = '%s' % (self._startport + i)
+
env = os.environ.copy()
env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
env['HGEMITWARNINGS'] = '1'
@@ -1097,11 +1323,13 @@
# This list should be parallel to _portmap in _getreplacements
defineport(port)
env["HGRCPATH"] = _strpath(os.path.join(self._threadtmp, b'.hgrc'))
- env["DAEMON_PIDS"] = _strpath(os.path.join(self._threadtmp,
- b'daemon.pids'))
- env["HGEDITOR"] = ('"' + sysexecutable + '"'
- + ' -c "import sys; sys.exit(0)"')
- env["HGUSER"] = "test"
+ env["DAEMON_PIDS"] = _strpath(
+ os.path.join(self._threadtmp, b'daemon.pids')
+ )
+ env["HGEDITOR"] = (
+ '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
+ )
+ env["HGUSER"] = "test"
env["HGENCODING"] = "ascii"
env["HGENCODINGMODE"] = "strict"
env["HGHOSTNAME"] = "test-hostname"
@@ -1111,7 +1339,8 @@
# If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
# non-test one in as a default, otherwise set to devnull
env['HGTESTCATAPULTSERVERPIPE'] = env.get(
- 'HGCATAPULTSERVERPIPE', os.devnull)
+ 'HGCATAPULTSERVERPIPE', os.devnull
+ )
extraextensions = []
for opt in self._extraconfigopts:
@@ -1187,11 +1416,15 @@
hgrc.write(b'all-warnings = true\n')
hgrc.write(b'default-date = 0 0\n')
hgrc.write(b'[largefiles]\n')
- hgrc.write(b'usercache = %s\n' %
- (os.path.join(self._testtmp, b'.cache/largefiles')))
+ hgrc.write(
+ b'usercache = %s\n'
+ % (os.path.join(self._testtmp, b'.cache/largefiles'))
+ )
hgrc.write(b'[lfs]\n')
- hgrc.write(b'usercache = %s\n' %
- (os.path.join(self._testtmp, b'.cache/lfs')))
+ hgrc.write(
+ b'usercache = %s\n'
+ % (os.path.join(self._testtmp, b'.cache/lfs'))
+ )
hgrc.write(b'[web]\n')
hgrc.write(b'address = localhost\n')
hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii'))
@@ -1199,8 +1432,9 @@
for opt in self._extraconfigopts:
section, key = opt.encode('utf-8').split(b'.', 1)
- assert b'=' in key, ('extra config opt %s must '
- 'have an = for assignment' % opt)
+ assert b'=' in key, (
+ 'extra config opt %s must ' 'have an = for assignment' % opt
+ )
hgrc.write(b'[%s]\n%s\n' % (section, key))
def fail(self, msg):
@@ -1215,13 +1449,14 @@
Return a tuple (exitcode, output). output is None in debug mode.
"""
if self._debug:
- proc = subprocess.Popen(_strpath(cmd), shell=True,
- cwd=_strpath(self._testtmp),
- env=env)
+ proc = subprocess.Popen(
+ _strpath(cmd), shell=True, cwd=_strpath(self._testtmp), env=env
+ )
ret = proc.wait()
return (ret, None)
proc = Popen4(cmd, self._testtmp, self._timeout, env)
+
def cleanup():
terminate(proc)
ret = proc.wait()
@@ -1257,6 +1492,7 @@
return ret, output.splitlines(True)
+
class PythonTest(Test):
"""A Python-based test."""
@@ -1270,13 +1506,13 @@
cmd = b'"%s"%s "%s"' % (PYTHON, py3switch, self.path)
vlog("# Running", cmd)
normalizenewlines = os.name == 'nt'
- result = self._runcommand(cmd, env,
- normalizenewlines=normalizenewlines)
+ result = self._runcommand(cmd, env, normalizenewlines=normalizenewlines)
if self._aborted:
raise KeyboardInterrupt()
return result
+
# Some glob patterns apply only in some circumstances, so the script
# might want to remove (glob) annotations that otherwise should be
# retained.
@@ -1301,9 +1537,11 @@
MARK_OPTIONAL = b" (?)\n"
+
def isoptional(line):
return line.endswith(MARK_OPTIONAL)
+
class TTest(Test):
"""A "t test" is a test backed by a .t file."""
@@ -1376,9 +1614,12 @@
# TODO do something smarter when all other uses of hghave are gone.
runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
tdir = runtestdir.replace(b'\\', b'/')
- proc = Popen4(b'%s -c "%s/hghave %s"' %
- (self._shell, tdir, allreqs),
- self._testtmp, 0, self._getenv())
+ proc = Popen4(
+ b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
+ self._testtmp,
+ 0,
+ self._getenv(),
+ )
stdout, stderr = proc.communicate()
ret = proc.wait()
if wifexited(ret):
@@ -1419,25 +1660,30 @@
# up script results with our source. These markers include input
# line number and the last return code.
salt = b"SALT%d" % time.time()
+
def addsalt(line, inpython):
if inpython:
script.append(b'%s %d 0\n' % (salt, line))
else:
script.append(b'echo %s %d $?\n' % (salt, line))
+
activetrace = []
session = str(uuid.uuid4())
if PYTHON3:
session = session.encode('ascii')
- hgcatapult = (os.getenv('HGTESTCATAPULTSERVERPIPE') or
- os.getenv('HGCATAPULTSERVERPIPE'))
+ hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
+ 'HGCATAPULTSERVERPIPE'
+ )
+
def toggletrace(cmd=None):
if not hgcatapult or hgcatapult == os.devnull:
return
if activetrace:
script.append(
- b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n' % (
- session, activetrace[0]))
+ b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
+ % (session, activetrace[0])
+ )
if cmd is None:
return
@@ -1447,8 +1693,9 @@
quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
quoted = quoted.replace(b'\\', b'\\\\')
script.append(
- b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n' % (
- session, quoted))
+ b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
+ % (session, quoted)
+ )
activetrace[0:] = [quoted]
script = []
@@ -1550,21 +1797,21 @@
after.setdefault(pos, []).append(l)
elif skipping:
after.setdefault(pos, []).append(l)
- elif l.startswith(b' >>> '): # python inlines
+ elif l.startswith(b' >>> '): # python inlines
after.setdefault(pos, []).append(l)
prepos = pos
pos = n
if not inpython:
# We've just entered a Python block. Add the header.
inpython = True
- addsalt(prepos, False) # Make sure we report the exit code.
+ addsalt(prepos, False) # Make sure we report the exit code.
script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
addsalt(n, True)
script.append(l[2:])
- elif l.startswith(b' ... '): # python inlines
+ elif l.startswith(b' ... '): # python inlines
after.setdefault(prepos, []).append(l)
script.append(l[2:])
- elif l.startswith(b' $ '): # commands
+ elif l.startswith(b' $ '): # commands
if inpython:
script.append(b'EOF\n')
inpython = False
@@ -1578,10 +1825,10 @@
if len(cmd) == 2 and cmd[0] == b'cd':
l = b' $ cd %s || exit 1\n' % cmd[1]
script.append(rawcmd)
- elif l.startswith(b' > '): # continuations
+ elif l.startswith(b' > '): # continuations
after.setdefault(prepos, []).append(l)
script.append(l[4:])
- elif l.startswith(b' '): # results
+ elif l.startswith(b' '): # results
# Queue up a list of expected results.
expected.setdefault(pos, []).append(l[2:])
else:
@@ -1603,7 +1850,7 @@
def _processoutput(self, exitcode, output, salt, after, expected):
# Merge the script output back into a unified test.
- warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
+ warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
if exitcode != 0:
warnonly = WARN_NO
@@ -1614,19 +1861,16 @@
if salt in out_rawline:
out_line, cmd_line = out_rawline.split(salt, 1)
- pos, postout, warnonly = self._process_out_line(out_line,
- pos,
- postout,
- expected,
- warnonly)
- pos, postout = self._process_cmd_line(cmd_line, pos, postout,
- after)
+ pos, postout, warnonly = self._process_out_line(
+ out_line, pos, postout, expected, warnonly
+ )
+ pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
if pos in after:
postout += after.pop(pos)
if warnonly == WARN_YES:
- exitcode = False # Set exitcode to warned.
+ exitcode = False # Set exitcode to warned.
return exitcode, postout
@@ -1648,7 +1892,7 @@
if isinstance(r, str):
if r == '-glob':
out_line = ''.join(el.rsplit(' (glob)', 1))
- r = '' # Warn only this line.
+ r = '' # Warn only this line.
elif r == "retry":
postout.append(b' ' + el)
else:
@@ -1663,8 +1907,7 @@
else:
m = optline.match(el)
if m:
- conditions = [
- c for c in m.group(2).split(b' ')]
+ conditions = [c for c in m.group(2).split(b' ')]
if not self._iftest(conditions):
optional.append(i)
@@ -1685,10 +1928,11 @@
postout.append(b' ' + el)
else:
if self.NEEDESCAPE(out_line):
- out_line = TTest._stringescape(b'%s (esc)\n' %
- out_line.rstrip(b'\n'))
- postout.append(b' ' + out_line) # Let diff deal with it.
- if r != '': # If line failed.
+ out_line = TTest._stringescape(
+ b'%s (esc)\n' % out_line.rstrip(b'\n')
+ )
+ postout.append(b' ' + out_line) # Let diff deal with it.
+ if r != '': # If line failed.
warnonly = WARN_NO
elif warnonly == WARN_UNDEFINED:
warnonly = WARN_YES
@@ -1752,10 +1996,10 @@
i, n = 0, len(el)
res = b''
while i < n:
- c = el[i:i + 1]
+ c = el[i : i + 1]
i += 1
- if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
- res += el[i - 1:i + 1]
+ if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
+ res += el[i - 1 : i + 1]
i += 1
elif c == b'*':
res += b'.*'
@@ -1768,12 +2012,12 @@
return TTest.rematch(res, l)
def linematch(self, el, l):
- if el == l: # perfect match (fast)
+ if el == l: # perfect match (fast)
return True, True
retry = False
if isoptional(el):
retry = "retry"
- el = el[:-len(MARK_OPTIONAL)] + b"\n"
+ el = el[: -len(MARK_OPTIONAL)] + b"\n"
else:
m = optline.match(el)
if m:
@@ -1817,10 +2061,12 @@
for line in lines:
if line.startswith(TTest.SKIPPED_PREFIX):
line = line.splitlines()[0]
- missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8'))
+ missing.append(
+ line[len(TTest.SKIPPED_PREFIX) :].decode('utf-8')
+ )
elif line.startswith(TTest.FAILED_PREFIX):
line = line.splitlines()[0]
- failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8'))
+ failed.append(line[len(TTest.FAILED_PREFIX) :].decode('utf-8'))
return missing, failed
@@ -1832,12 +2078,15 @@
def _stringescape(s):
return TTest.ESCAPESUB(TTest._escapef, s)
+
iolock = threading.RLock()
firstlock = threading.RLock()
firsterror = False
+
class TestResult(unittest._TextTestResult):
"""Holds results when executing via unittest."""
+
# Don't worry too much about accessing the non-public _TextTestResult.
# It is relatively common in Python testing tools.
def __init__(self, options, *args, **kwargs):
@@ -1864,7 +2113,7 @@
self.color = pygmentspresent and self.stream.isatty()
elif options.color == 'never':
self.color = False
- else: # 'always', for testing purposes
+ else: # 'always', for testing purposes
self.color = pygmentspresent
def onStart(self, test):
@@ -1942,12 +2191,15 @@
pass
elif self._options.view:
v = self._options.view
- subprocess.call(r'"%s" "%s" "%s"' %
- (v, _strpath(test.refpath),
- _strpath(test.errpath)), shell=True)
+ subprocess.call(
+ r'"%s" "%s" "%s"'
+ % (v, _strpath(test.refpath), _strpath(test.errpath)),
+ shell=True,
+ )
else:
- servefail, lines = getdiff(expected, got,
- test.refpath, test.errpath)
+ servefail, lines = getdiff(
+ expected, got, test.refpath, test.errpath
+ )
self.stream.write('\n')
for line in lines:
line = highlightdiff(line, self.color)
@@ -1961,14 +2213,16 @@
if servefail:
raise test.failureException(
- 'server failed to start (HGPORT=%s)' % test._startport)
+ 'server failed to start (HGPORT=%s)' % test._startport
+ )
# handle interactive prompt without releasing iolock
if self._options.interactive:
if test.readrefout() != expected:
self.stream.write(
'Reference output has changed (run again to prompt '
- 'changes)')
+ 'changes)'
+ )
else:
self.stream.write('Accept this change? [n] ')
self.stream.flush()
@@ -1992,7 +2246,7 @@
# This module has one limitation. It can only work for Linux user
# and not for Windows.
test.started = os.times()
- if self._firststarttime is None: # thread racy but irrelevant
+ if self._firststarttime is None: # thread racy but irrelevant
self._firststarttime = test.started[4]
def stopTest(self, test, interrupted=False):
@@ -2003,18 +2257,24 @@
starttime = test.started
endtime = test.stopped
origin = self._firststarttime
- self.times.append((test.name,
- endtime[2] - starttime[2], # user space CPU time
- endtime[3] - starttime[3], # sys space CPU time
- endtime[4] - starttime[4], # real time
- starttime[4] - origin, # start date in run context
- endtime[4] - origin, # end date in run context
- ))
+ self.times.append(
+ (
+ test.name,
+ endtime[2] - starttime[2], # user space CPU time
+ endtime[3] - starttime[3], # sys space CPU time
+ endtime[4] - starttime[4], # real time
+ starttime[4] - origin, # start date in run context
+ endtime[4] - origin, # end date in run context
+ )
+ )
if interrupted:
with iolock:
- self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
- test.name, self.times[-1][3]))
+ self.stream.writeln(
+ 'INTERRUPTED: %s (after %d seconds)'
+ % (test.name, self.times[-1][3])
+ )
+
def getTestResult():
"""
@@ -2026,13 +2286,25 @@
else:
return TestResult
+
class TestSuite(unittest.TestSuite):
"""Custom unittest TestSuite that knows how to execute Mercurial tests."""
- def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
- retest=False, keywords=None, loop=False, runs_per_test=1,
- loadtest=None, showchannels=False,
- *args, **kwargs):
+ def __init__(
+ self,
+ testdir,
+ jobs=1,
+ whitelist=None,
+ blacklist=None,
+ retest=False,
+ keywords=None,
+ loop=False,
+ runs_per_test=1,
+ loadtest=None,
+ showchannels=False,
+ *args,
+ **kwargs
+ ):
"""Create a new instance that can run tests with a configuration.
testdir specifies the directory where tests are executed from. This
@@ -2079,11 +2351,13 @@
tests = []
num_tests = [0]
for test in self._tests:
+
def get():
num_tests[0] += 1
if getattr(test, 'should_reload', False):
return self._loadtest(test, num_tests[0])
return test
+
if not os.path.exists(test.path):
result.addSkip(test, "Doesn't exist")
continue
@@ -2131,7 +2405,7 @@
done.put(None)
except KeyboardInterrupt:
pass
- except: # re-raises
+ except: # re-raises
done.put(('!', test, 'run-test raised an error, see traceback'))
raise
finally:
@@ -2156,7 +2430,7 @@
sys.stdout.flush()
for x in xrange(10):
if channels:
- time.sleep(.1)
+ time.sleep(0.1)
count += 1
stoppedearly = False
@@ -2181,15 +2455,15 @@
if self._loop:
if getattr(test, 'should_reload', False):
num_tests[0] += 1
- tests.append(
- self._loadtest(test, num_tests[0]))
+ tests.append(self._loadtest(test, num_tests[0]))
else:
tests.append(test)
if self._jobs == 1:
job(test, result)
else:
- t = threading.Thread(target=job, name=test.name,
- args=(test, result))
+ t = threading.Thread(
+ target=job, name=test.name, args=(test, result)
+ )
t.start()
running += 1
@@ -2212,24 +2486,28 @@
return result
+
# Save the most recent 5 wall-clock runtimes of each test to a
# human-readable text file named .testtimes. Tests are sorted
# alphabetically, while times for each test are listed from oldest to
# newest.
+
def loadtimes(outputdir):
times = []
try:
with open(os.path.join(outputdir, b'.testtimes')) as fp:
for line in fp:
m = re.match('(.*?) ([0-9. ]+)', line)
- times.append((m.group(1),
- [float(t) for t in m.group(2).split()]))
+ times.append(
+ (m.group(1), [float(t) for t in m.group(2).split()])
+ )
except IOError as err:
if err.errno != errno.ENOENT:
raise
return times
+
def savetimes(outputdir, result):
saved = dict(loadtimes(outputdir))
maxruns = 5
@@ -2241,8 +2519,9 @@
ts.append(real)
ts[:] = ts[-maxruns:]
- fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes',
- dir=outputdir, text=True)
+ fd, tmpname = tempfile.mkstemp(
+ prefix=b'.testtimes', dir=outputdir, text=True
+ )
with os.fdopen(fd, 'w') as fp:
for name, ts in sorted(saved.items()):
fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
@@ -2256,6 +2535,7 @@
except OSError:
pass
+
class TextTestRunner(unittest.TextTestRunner):
"""Custom unittest test runner that uses appropriate settings."""
@@ -2264,8 +2544,9 @@
self._runner = runner
- self._result = getTestResult()(self._runner.options, self.stream,
- self.descriptions, self.verbosity)
+ self._result = getTestResult()(
+ self._runner.options, self.stream, self.descriptions, self.verbosity
+ )
def listtests(self, test):
test = sorted(test, key=lambda t: t.name)
@@ -2299,17 +2580,20 @@
self.stream.writeln('')
if not self._runner.options.noskips:
- for test, msg in sorted(self._result.skipped,
- key=lambda s: s[0].name):
+ for test, msg in sorted(
+ self._result.skipped, key=lambda s: s[0].name
+ ):
formatted = 'Skipped %s: %s\n' % (test.name, msg)
msg = highlightmsg(formatted, self._result.color)
self.stream.write(msg)
- for test, msg in sorted(self._result.failures,
- key=lambda f: f[0].name):
+ for test, msg in sorted(
+ self._result.failures, key=lambda f: f[0].name
+ ):
formatted = 'Failed %s: %s\n' % (test.name, msg)
self.stream.write(highlightmsg(formatted, self._result.color))
- for test, msg in sorted(self._result.errors,
- key=lambda e: e[0].name):
+ for test, msg in sorted(
+ self._result.errors, key=lambda e: e[0].name
+ ):
self.stream.writeln('Errored %s: %s' % (test.name, msg))
if self._runner.options.xunit:
@@ -2329,31 +2613,41 @@
self._bisecttests(t for t, m in self._result.failures)
self.stream.writeln(
'# Ran %d tests, %d skipped, %d failed.'
- % (self._result.testsRun, skipped + ignored, failed))
+ % (self._result.testsRun, skipped + ignored, failed)
+ )
if failed:
- self.stream.writeln('python hash seed: %s' %
- os.environ['PYTHONHASHSEED'])
+ self.stream.writeln(
+ 'python hash seed: %s' % os.environ['PYTHONHASHSEED']
+ )
if self._runner.options.time:
self.printtimes(self._result.times)
if self._runner.options.exceptions:
exceptions = aggregateexceptions(
- os.path.join(self._runner._outputdir, b'exceptions'))
+ os.path.join(self._runner._outputdir, b'exceptions')
+ )
self.stream.writeln('Exceptions Report:')
- self.stream.writeln('%d total from %d frames' %
- (exceptions['total'],
- len(exceptions['exceptioncounts'])))
+ self.stream.writeln(
+ '%d total from %d frames'
+ % (exceptions['total'], len(exceptions['exceptioncounts']))
+ )
combined = exceptions['combined']
for key in sorted(combined, key=combined.get, reverse=True):
frame, line, exc = key
totalcount, testcount, leastcount, leasttest = combined[key]
- self.stream.writeln('%d (%d tests)\t%s: %s (%s - %d total)'
- % (totalcount,
- testcount,
- frame, exc,
- leasttest, leastcount))
+ self.stream.writeln(
+ '%d (%d tests)\t%s: %s (%s - %d total)'
+ % (
+ totalcount,
+ testcount,
+ frame,
+ exc,
+ leasttest,
+ leastcount,
+ )
+ )
self.stream.flush()
@@ -2364,14 +2658,17 @@
bisectrepo = self._runner.options.bisect_repo
if bisectrepo:
bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
+
def pread(args):
env = os.environ.copy()
env['HGPLAIN'] = '1'
- p = subprocess.Popen(args, stderr=subprocess.STDOUT,
- stdout=subprocess.PIPE, env=env)
+ p = subprocess.Popen(
+ args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
+ )
data = p.stdout.read()
p.wait()
return data
+
for test in tests:
pread(bisectcmd + ['--reset']),
pread(bisectcmd + ['--bad', '.'])
@@ -2382,32 +2679,43 @@
withhg = self._runner.options.with_hg
if withhg:
opts += ' --with-hg=%s ' % shellquote(_strpath(withhg))
- rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts,
- test)
+ rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
data = pread(bisectcmd + ['--command', rtc])
m = re.search(
- (br'\nThe first (?P<goodbad>bad|good) revision '
- br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
- br'summary: +(?P<summary>[^\n]+)\n'),
- data, (re.MULTILINE | re.DOTALL))
+ (
+ br'\nThe first (?P<goodbad>bad|good) revision '
+ br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
+ br'summary: +(?P<summary>[^\n]+)\n'
+ ),
+ data,
+ (re.MULTILINE | re.DOTALL),
+ )
if m is None:
self.stream.writeln(
- 'Failed to identify failure point for %s' % test)
+ 'Failed to identify failure point for %s' % test
+ )
continue
dat = m.groupdict()
verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
self.stream.writeln(
- '%s %s by %s (%s)' % (
- test, verb, dat['node'].decode('ascii'),
- dat['summary'].decode('utf8', 'ignore')))
+ '%s %s by %s (%s)'
+ % (
+ test,
+ verb,
+ dat['node'].decode('ascii'),
+ dat['summary'].decode('utf8', 'ignore'),
+ )
+ )
def printtimes(self, times):
# iolock held by run
self.stream.writeln('# Producing time report')
times.sort(key=lambda t: (t[3]))
cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
- self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
- ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
+ self.stream.writeln(
+ '%-7s %-7s %-7s %-7s %-7s %s'
+ % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
+ )
for tdata in times:
test = tdata[0]
cuser, csys, real, start, end = tdata[1:6]
@@ -2419,11 +2727,12 @@
timesd = dict((t[0], t[3]) for t in result.times)
doc = minidom.Document()
s = doc.createElement('testsuite')
- s.setAttribute('errors', "0") # TODO
+ s.setAttribute('errors', "0") # TODO
s.setAttribute('failures', str(len(result.failures)))
s.setAttribute('name', 'run-tests')
- s.setAttribute('skipped', str(len(result.skipped) +
- len(result.ignored)))
+ s.setAttribute(
+ 'skipped', str(len(result.skipped) + len(result.ignored))
+ )
s.setAttribute('tests', str(result.testsRun))
doc.appendChild(s)
for tc in result.successes:
@@ -2474,10 +2783,11 @@
timesd[test] = tdata[1:]
outcome = {}
- groups = [('success', ((tc, None)
- for tc in result.successes)),
- ('failure', result.failures),
- ('skip', result.skipped)]
+ groups = [
+ ('success', ((tc, None) for tc in result.successes)),
+ ('failure', result.failures),
+ ('skip', result.skipped),
+ ]
for res, testcases in groups:
for tc, __ in testcases:
if tc.name in timesd:
@@ -2486,23 +2796,26 @@
diff = diff.decode('unicode_escape')
except UnicodeDecodeError as e:
diff = '%r decoding diff, sorry' % e
- tres = {'result': res,
- 'time': ('%0.3f' % timesd[tc.name][2]),
- 'cuser': ('%0.3f' % timesd[tc.name][0]),
- 'csys': ('%0.3f' % timesd[tc.name][1]),
- 'start': ('%0.3f' % timesd[tc.name][3]),
- 'end': ('%0.3f' % timesd[tc.name][4]),
- 'diff': diff,
- }
+ tres = {
+ 'result': res,
+ 'time': ('%0.3f' % timesd[tc.name][2]),
+ 'cuser': ('%0.3f' % timesd[tc.name][0]),
+ 'csys': ('%0.3f' % timesd[tc.name][1]),
+ 'start': ('%0.3f' % timesd[tc.name][3]),
+ 'end': ('%0.3f' % timesd[tc.name][4]),
+ 'diff': diff,
+ }
else:
# blacklisted test
tres = {'result': res}
outcome[tc.name] = tres
- jsonout = json.dumps(outcome, sort_keys=True, indent=4,
- separators=(',', ': '))
+ jsonout = json.dumps(
+ outcome, sort_keys=True, indent=4, separators=(',', ': ')
+ )
outf.writelines(("testreport =", jsonout))
+
def sorttests(testdescs, previoustimes, shuffle=False):
"""Do an in-place sort of tests."""
if shuffle:
@@ -2510,29 +2823,32 @@
return
if previoustimes:
+
def sortkey(f):
f = f['path']
if f in previoustimes:
# Use most recent time as estimate
- return -previoustimes[f][-1]
+ return -(previoustimes[f][-1])
else:
# Default to a rather arbitrary value of 1 second for new tests
return -1.0
+
else:
# keywords for slow tests
- slow = {b'svn': 10,
- b'cvs': 10,
- b'hghave': 10,
- b'largefiles-update': 10,
- b'run-tests': 10,
- b'corruption': 10,
- b'race': 10,
- b'i18n': 10,
- b'check': 100,
- b'gendoc': 100,
- b'contrib-perf': 200,
- b'merge-combination': 100,
- }
+ slow = {
+ b'svn': 10,
+ b'cvs': 10,
+ b'hghave': 10,
+ b'largefiles-update': 10,
+ b'run-tests': 10,
+ b'corruption': 10,
+ b'race': 10,
+ b'i18n': 10,
+ b'check': 100,
+ b'gendoc': 100,
+ b'contrib-perf': 200,
+ b'merge-combination': 100,
+ }
perf = {}
def sortkey(f):
@@ -2558,6 +2874,7 @@
testdescs.sort(key=sortkey)
+
class TestRunner(object):
"""Holds context for executing tests.
@@ -2614,6 +2931,7 @@
testdescs = self.findtests(tests)
if options.profile_runner:
import statprof
+
statprof.start()
result = self._run(testdescs)
if options.profile_runner:
@@ -2668,8 +2986,7 @@
d = osenvironb.get(b'TMP', None)
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:
self._installdir = None
@@ -2791,7 +3108,8 @@
osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
logexceptions = os.path.join(self._testdir, b'logexceptions.py')
self.options.extra_config_opt.append(
- 'extensions.logexceptions=%s' % logexceptions.decode('utf-8'))
+ 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
+ )
vlog("# Using TESTDIR", self._testdir)
vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
@@ -2803,7 +3121,7 @@
try:
return self._runtests(testdescs) or 0
finally:
- time.sleep(.1)
+ time.sleep(0.1)
self._cleanup()
def findtests(self, args):
@@ -2814,8 +3132,12 @@
"""
if not args:
if self.options.changed:
- proc = Popen4(b'hg st --rev "%s" -man0 .' %
- _bytespath(self.options.changed), None, 0)
+ proc = Popen4(
+ b'hg st --rev "%s" -man0 .'
+ % _bytespath(self.options.changed),
+ None,
+ 0,
+ )
stdout, stderr = proc.communicate()
args = stdout.strip(b'\0').split(b'\0')
else:
@@ -2832,13 +3154,16 @@
args = expanded_args
testcasepattern = re.compile(
- br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-\.#]+))')
+ br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-\.#]+))'
+ )
tests = []
for t in args:
case = []
- if not (os.path.basename(t).startswith(b'test-')
- and (t.endswith(b'.py') or t.endswith(b'.t'))):
+ if not (
+ os.path.basename(t).startswith(b'test-')
+ and (t.endswith(b'.py') or t.endswith(b'.t'))
+ ):
m = testcasepattern.match(os.path.basename(t))
if m is not None:
@@ -2854,12 +3179,14 @@
casedimensions = parsettestcases(t)
if casedimensions:
cases = []
+
def addcases(case, casedimensions):
if not casedimensions:
cases.append(case)
else:
for c in casedimensions[0]:
addcases(case + [c], casedimensions[1:])
+
addcases([], casedimensions)
if case and case in cases:
cases = [case]
@@ -2913,16 +3240,19 @@
if kws is not None and PYTHON3:
kws = kws.encode('utf-8')
- suite = TestSuite(self._testdir,
- jobs=jobs,
- whitelist=self.options.whitelisted,
- blacklist=self.options.blacklist,
- retest=self.options.retest,
- keywords=kws,
- loop=self.options.loop,
- runs_per_test=self.options.runs_per_test,
- showchannels=self.options.showchannels,
- tests=tests, loadtest=_reloadtest)
+ suite = TestSuite(
+ self._testdir,
+ jobs=jobs,
+ whitelist=self.options.whitelisted,
+ blacklist=self.options.blacklist,
+ retest=self.options.retest,
+ keywords=kws,
+ loop=self.options.loop,
+ runs_per_test=self.options.runs_per_test,
+ showchannels=self.options.showchannels,
+ tests=tests,
+ loadtest=_reloadtest,
+ )
verbosity = 1
if self.options.list_tests:
verbosity = 0
@@ -2942,8 +3272,10 @@
assert self._installdir
self._installchg()
- log('running %d tests using %d parallel processes' % (
- num_tests, jobs))
+ log(
+ 'running %d tests using %d parallel processes'
+ % (num_tests, jobs)
+ )
result = runner.run(suite)
@@ -2962,7 +3294,7 @@
return 1
def _getport(self, count):
- port = self._ports.get(count) # do we have a cached entry?
+ port = self._ports.get(count) # do we have a cached entry?
if port is None:
portneeded = 3
# above 100 tries we just give up and let test reports failure
@@ -3000,18 +3332,23 @@
# extra keyword parameters. 'case' is used by .t tests
kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc)
- t = testcls(refpath, self._outputdir, tmpdir,
- keeptmpdir=self.options.keep_tmpdir,
- debug=self.options.debug,
- first=self.options.first,
- timeout=self.options.timeout,
- startport=self._getport(count),
- extraconfigopts=self.options.extra_config_opt,
- py3warnings=self.options.py3_warnings,
- shell=self.options.shell,
- hgcommand=self._hgcommand,
- usechg=bool(self.options.with_chg or self.options.chg),
- useipv6=useipv6, **kwds)
+ t = testcls(
+ refpath,
+ self._outputdir,
+ tmpdir,
+ keeptmpdir=self.options.keep_tmpdir,
+ debug=self.options.debug,
+ first=self.options.first,
+ timeout=self.options.timeout,
+ startport=self._getport(count),
+ extraconfigopts=self.options.extra_config_opt,
+ py3warnings=self.options.py3_warnings,
+ shell=self.options.shell,
+ hgcommand=self._hgcommand,
+ usechg=bool(self.options.with_chg or self.options.chg),
+ useipv6=useipv6,
+ **kwds
+ )
t.should_reload = True
return t
@@ -3036,8 +3373,10 @@
# os.symlink() is a thing with py3 on Windows, but it requires
# Administrator rights.
if getattr(os, 'symlink', None) and os.name != 'nt':
- vlog("# Making python executable in test path a symlink to '%s'" %
- sysexecutable)
+ vlog(
+ "# Making python executable in test path a symlink to '%s'"
+ % sysexecutable
+ )
mypython = os.path.join(self._tmpbindir, pyexename)
try:
if os.readlink(mypython) == sysexecutable:
@@ -3056,8 +3395,10 @@
raise
else:
exedir, exename = os.path.split(sysexecutable)
- vlog("# Modifying search path to find %s as %s in '%s'" %
- (exename, pyexename, exedir))
+ vlog(
+ "# Modifying search path to find %s as %s in '%s'"
+ % (exename, pyexename, exedir)
+ )
path = os.environ['PATH'].split(os.pathsep)
while exedir in path:
path.remove(exedir)
@@ -3097,17 +3438,24 @@
# least on Windows for now, deal with .pydistutils.cfg bugs
# when they happen.
nohome = b''
- cmd = (b'"%(exe)s" setup.py %(pure)s clean --all'
- b' build %(compiler)s --build-base="%(base)s"'
- b' install --force --prefix="%(prefix)s"'
- b' --install-lib="%(libdir)s"'
- b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
- % {b'exe': exe, b'pure': pure,
- b'compiler': compiler,
- b'base': os.path.join(self._hgtmp, b"build"),
- b'prefix': self._installdir, b'libdir': self._pythondir,
- b'bindir': self._bindir,
- b'nohome': nohome, b'logfile': installerrs})
+ cmd = (
+ b'"%(exe)s" setup.py %(pure)s clean --all'
+ b' build %(compiler)s --build-base="%(base)s"'
+ b' install --force --prefix="%(prefix)s"'
+ b' --install-lib="%(libdir)s"'
+ b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
+ % {
+ b'exe': exe,
+ b'pure': pure,
+ b'compiler': compiler,
+ b'base': os.path.join(self._hgtmp, b"build"),
+ b'prefix': self._installdir,
+ b'libdir': self._pythondir,
+ b'bindir': self._bindir,
+ b'nohome': nohome,
+ b'logfile': installerrs,
+ }
+ )
# setuptools requires install directories to exist.
def makedirs(p):
@@ -3116,6 +3464,7 @@
except OSError as e:
if e.errno != errno.EEXIST:
raise
+
makedirs(self._pythondir)
makedirs(self._bindir)
@@ -3155,8 +3504,10 @@
with open(hgbat, 'rb') as f:
data = f.read()
if br'"%~dp0..\python" "%~dp0hg" %*' in data:
- data = data.replace(br'"%~dp0..\python" "%~dp0hg" %*',
- b'"%~dp0python" "%~dp0hg" %*')
+ data = data.replace(
+ br'"%~dp0..\python" "%~dp0hg" %*',
+ b'"%~dp0python" "%~dp0hg" %*',
+ )
with open(hgbat, 'wb') as f:
f.write(data)
else:
@@ -3182,17 +3533,20 @@
def _checkhglib(self, verb):
"""Ensure that the 'mercurial' package imported by python is
the one we expect it to be. If not, print a warning to stderr."""
- if ((self._bindir == self._pythondir) and
- (self._bindir != self._tmpbindir)):
+ if (self._bindir == self._pythondir) and (
+ self._bindir != self._tmpbindir
+ ):
# The pythondir has been inferred from --with-hg flag.
# We cannot expect anything sensible here.
return
expecthg = os.path.join(self._pythondir, b'mercurial')
actualhg = self._gethgpath()
if os.path.abspath(actualhg) != os.path.abspath(expecthg):
- sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
- ' (expected %s)\n'
- % (verb, actualhg, expecthg))
+ sys.stderr.write(
+ 'warning: %s with unexpected mercurial lib: %s\n'
+ ' (expected %s)\n' % (verb, actualhg, expecthg)
+ )
+
def _gethgpath(self):
"""Return the path to the mercurial package that is actually found by
the current Python interpreter."""
@@ -3216,14 +3570,20 @@
vlog('# Performing temporary installation of CHG')
assert os.path.dirname(self._bindir) == self._installdir
assert self._hgroot, 'must be called after _installhg()'
- cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
- % {b'make': b'make', # TODO: switch by option or environment?
- b'prefix': self._installdir})
+ cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
+ b'make': b'make', # TODO: switch by option or environment?
+ b'prefix': self._installdir,
+ }
cwd = os.path.join(self._hgroot, b'contrib', b'chg')
vlog("# Running", cmd)
- proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
- stdin=subprocess.PIPE, stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
+ proc = subprocess.Popen(
+ cmd,
+ shell=True,
+ cwd=cwd,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ )
out, _err = proc.communicate()
if proc.returncode != 0:
if PYTHON3:
@@ -3235,6 +3595,7 @@
def _outputcoverage(self):
"""Produce code coverage output."""
import coverage
+
coverage = coverage.coverage
vlog('# Producing coverage report')
@@ -3280,8 +3641,11 @@
if found:
vlog("# Found prerequisite", p, "at", found)
else:
- print("WARNING: Did not find prerequisite tool: %s " %
- p.decode("utf-8"))
+ print(
+ "WARNING: Did not find prerequisite tool: %s "
+ % p.decode("utf-8")
+ )
+
def aggregateexceptions(path):
exceptioncounts = collections.Counter()
@@ -3322,10 +3686,12 @@
# impacted tests.
combined = {}
for key in exceptioncounts:
- combined[key] = (exceptioncounts[key],
- len(testsbyfailure[key]),
- leastfailing[key][0],
- leastfailing[key][1])
+ combined[key] = (
+ exceptioncounts[key],
+ len(testsbyfailure[key]),
+ leastfailing[key][0],
+ leastfailing[key][1],
+ )
return {
'exceptioncounts': exceptioncounts,
@@ -3336,11 +3702,13 @@
'bytest': failuresbytest,
}
+
if __name__ == '__main__':
runner = TestRunner()
try:
import msvcrt
+
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)