Mercurial > hg-git
comparison tests/run-tests.py @ 1106:cca56bbea143
tests: copy hg test infra from hg repo @5cfdf6137af8
author | Tony Tung <ttung@chanzuckerberg.com <mailto:ttung@chanzuckerberg.com>> |
---|---|
date | Tue, 06 Feb 2018 16:34:28 -0800 |
parents | e085b381e8e2 |
children |
comparison
equal
deleted
inserted
replaced
1105:bb255eaf3d14 | 1106:cca56bbea143 |
---|---|
43 # completes fairly quickly, includes both shell and Python scripts, and | 43 # completes fairly quickly, includes both shell and Python scripts, and |
44 # includes some scripts that run daemon processes.) | 44 # includes some scripts that run daemon processes.) |
45 | 45 |
46 from __future__ import absolute_import, print_function | 46 from __future__ import absolute_import, print_function |
47 | 47 |
48 import argparse | |
49 import collections | |
48 import difflib | 50 import difflib |
49 import distutils.version as version | 51 import distutils.version as version |
50 import errno | 52 import errno |
51 import json | 53 import json |
52 import optparse | |
53 import os | 54 import os |
54 import random | 55 import random |
55 import re | 56 import re |
56 import shutil | 57 import shutil |
57 import signal | 58 import signal |
117 failed: '#7f0000', | 118 failed: '#7f0000', |
118 failedname: '#ff0000', | 119 failedname: '#ff0000', |
119 } | 120 } |
120 | 121 |
121 class TestRunnerLexer(lexer.RegexLexer): | 122 class TestRunnerLexer(lexer.RegexLexer): |
123 testpattern = r'[\w-]+\.(t|py)( \(case [\w-]+\))?' | |
122 tokens = { | 124 tokens = { |
123 'root': [ | 125 'root': [ |
124 (r'^Skipped', token.Generic.Skipped, 'skipped'), | 126 (r'^Skipped', token.Generic.Skipped, 'skipped'), |
125 (r'^Failed ', token.Generic.Failed, 'failed'), | 127 (r'^Failed ', token.Generic.Failed, 'failed'), |
126 (r'^ERROR: ', token.Generic.Failed, 'failed'), | 128 (r'^ERROR: ', token.Generic.Failed, 'failed'), |
127 ], | 129 ], |
128 'skipped': [ | 130 'skipped': [ |
129 (r'[\w-]+\.(t|py)', token.Generic.SName), | 131 (testpattern, token.Generic.SName), |
130 (r':.*', token.Generic.Skipped), | 132 (r':.*', token.Generic.Skipped), |
131 ], | 133 ], |
132 'failed': [ | 134 'failed': [ |
133 (r'[\w-]+\.(t|py)', token.Generic.FName), | 135 (testpattern, token.Generic.FName), |
134 (r'(:| ).*', token.Generic.Failed), | 136 (r'(:| ).*', token.Generic.Failed), |
135 ] | 137 ] |
136 } | 138 } |
137 | 139 |
138 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle) | 140 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle) |
294 raise | 296 raise |
295 return cases | 297 return cases |
296 | 298 |
297 def getparser(): | 299 def getparser(): |
298 """Obtain the OptionParser used by the CLI.""" | 300 """Obtain the OptionParser used by the CLI.""" |
299 parser = optparse.OptionParser("%prog [options] [tests]") | 301 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]') |
300 | 302 |
301 # keep these sorted | 303 selection = parser.add_argument_group('Test Selection') |
302 parser.add_option("--blacklist", action="append", | 304 selection.add_argument('--allow-slow-tests', action='store_true', |
305 help='allow extremely slow tests') | |
306 selection.add_argument("--blacklist", action="append", | |
303 help="skip tests listed in the specified blacklist file") | 307 help="skip tests listed in the specified blacklist file") |
304 parser.add_option("--whitelist", action="append", | 308 selection.add_argument("--changed", |
309 help="run tests that are changed in parent rev or working directory") | |
310 selection.add_argument("-k", "--keywords", | |
311 help="run tests matching keywords") | |
312 selection.add_argument("-r", "--retest", action="store_true", | |
313 help = "retest failed tests") | |
314 selection.add_argument("--test-list", action="append", | |
315 help="read tests to run from the specified file") | |
316 selection.add_argument("--whitelist", action="append", | |
305 help="always run tests listed in the specified whitelist file") | 317 help="always run tests listed in the specified whitelist file") |
306 parser.add_option("--test-list", action="append", | 318 selection.add_argument('tests', metavar='TESTS', nargs='*', |
307 help="read tests to run from the specified file") | 319 help='Tests to run') |
308 parser.add_option("--changed", type="string", | 320 |
309 help="run tests that are changed in parent rev or working directory") | 321 harness = parser.add_argument_group('Test Harness Behavior') |
310 parser.add_option("-C", "--annotate", action="store_true", | 322 harness.add_argument('--bisect-repo', |
311 help="output files annotated with coverage") | 323 metavar='bisect_repo', |
312 parser.add_option("-c", "--cover", action="store_true", | 324 help=("Path of a repo to bisect. Use together with " |
313 help="print a test coverage report") | 325 "--known-good-rev")) |
314 parser.add_option("--color", choices=["always", "auto", "never"], | 326 harness.add_argument("-d", "--debug", action="store_true", |
315 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'), | |
316 help="colorisation: always|auto|never (default: auto)") | |
317 parser.add_option("-d", "--debug", action="store_true", | |
318 help="debug mode: write output of test scripts to console" | 327 help="debug mode: write output of test scripts to console" |
319 " rather than capturing and diffing it (disables timeout)") | 328 " rather than capturing and diffing it (disables timeout)") |
320 parser.add_option("-f", "--first", action="store_true", | 329 harness.add_argument("-f", "--first", action="store_true", |
321 help="exit on the first test failure") | 330 help="exit on the first test failure") |
322 parser.add_option("-H", "--htmlcov", action="store_true", | 331 harness.add_argument("-i", "--interactive", action="store_true", |
323 help="create an HTML report of the coverage of the files") | |
324 parser.add_option("-i", "--interactive", action="store_true", | |
325 help="prompt to accept changed output") | 332 help="prompt to accept changed output") |
326 parser.add_option("-j", "--jobs", type="int", | 333 harness.add_argument("-j", "--jobs", type=int, |
327 help="number of jobs to run in parallel" | 334 help="number of jobs to run in parallel" |
328 " (default: $%s or %d)" % defaults['jobs']) | 335 " (default: $%s or %d)" % defaults['jobs']) |
329 parser.add_option("--keep-tmpdir", action="store_true", | 336 harness.add_argument("--keep-tmpdir", action="store_true", |
330 help="keep temporary directory after running tests") | 337 help="keep temporary directory after running tests") |
331 parser.add_option("-k", "--keywords", | 338 harness.add_argument('--known-good-rev', |
332 help="run tests matching keywords") | 339 metavar="known_good_rev", |
333 parser.add_option("--list-tests", action="store_true", | 340 help=("Automatically bisect any failures using this " |
341 "revision as a known-good revision.")) | |
342 harness.add_argument("--list-tests", action="store_true", | |
334 help="list tests instead of running them") | 343 help="list tests instead of running them") |
335 parser.add_option("-l", "--local", action="store_true", | 344 harness.add_argument("--loop", action="store_true", |
345 help="loop tests repeatedly") | |
346 harness.add_argument('--random', action="store_true", | |
347 help='run tests in random order') | |
348 harness.add_argument("-p", "--port", type=int, | |
349 help="port on which servers should listen" | |
350 " (default: $%s or %d)" % defaults['port']) | |
351 harness.add_argument('--profile-runner', action='store_true', | |
352 help='run statprof on run-tests') | |
353 harness.add_argument("-R", "--restart", action="store_true", | |
354 help="restart at last error") | |
355 harness.add_argument("--runs-per-test", type=int, dest="runs_per_test", | |
356 help="run each test N times (default=1)", default=1) | |
357 harness.add_argument("--shell", | |
358 help="shell to use (default: $%s or %s)" % defaults['shell']) | |
359 harness.add_argument('--showchannels', action='store_true', | |
360 help='show scheduling channels') | |
361 harness.add_argument("--slowtimeout", type=int, | |
362 help="kill errant slow tests after SLOWTIMEOUT seconds" | |
363 " (default: $%s or %d)" % defaults['slowtimeout']) | |
364 harness.add_argument("-t", "--timeout", type=int, | |
365 help="kill errant tests after TIMEOUT seconds" | |
366 " (default: $%s or %d)" % defaults['timeout']) | |
367 harness.add_argument("--tmpdir", | |
368 help="run tests in the given temporary directory" | |
369 " (implies --keep-tmpdir)") | |
370 harness.add_argument("-v", "--verbose", action="store_true", | |
371 help="output verbose messages") | |
372 | |
373 hgconf = parser.add_argument_group('Mercurial Configuration') | |
374 hgconf.add_argument("--chg", action="store_true", | |
375 help="install and use chg wrapper in place of hg") | |
376 hgconf.add_argument("--compiler", | |
377 help="compiler to build with") | |
378 hgconf.add_argument('--extra-config-opt', action="append", default=[], | |
379 help='set the given config opt in the test hgrc') | |
380 hgconf.add_argument("-l", "--local", action="store_true", | |
336 help="shortcut for --with-hg=<testdir>/../hg, " | 381 help="shortcut for --with-hg=<testdir>/../hg, " |
337 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set") | 382 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set") |
338 parser.add_option("--loop", action="store_true", | 383 hgconf.add_argument("--ipv6", action="store_true", |
339 help="loop tests repeatedly") | 384 help="prefer IPv6 to IPv4 for network related tests") |
340 parser.add_option("--runs-per-test", type="int", dest="runs_per_test", | 385 hgconf.add_argument("--pure", action="store_true", |
341 help="run each test N times (default=1)", default=1) | |
342 parser.add_option("-n", "--nodiff", action="store_true", | |
343 help="skip showing test changes") | |
344 parser.add_option("--outputdir", type="string", | |
345 help="directory to write error logs to (default=test directory)") | |
346 parser.add_option("-p", "--port", type="int", | |
347 help="port on which servers should listen" | |
348 " (default: $%s or %d)" % defaults['port']) | |
349 parser.add_option("--compiler", type="string", | |
350 help="compiler to build with") | |
351 parser.add_option("--pure", action="store_true", | |
352 help="use pure Python code instead of C extensions") | 386 help="use pure Python code instead of C extensions") |
353 parser.add_option("-R", "--restart", action="store_true", | 387 hgconf.add_argument("-3", "--py3k-warnings", action="store_true", |
354 help="restart at last error") | 388 help="enable Py3k warnings on Python 2.7+") |
355 parser.add_option("-r", "--retest", action="store_true", | 389 hgconf.add_argument("--with-chg", metavar="CHG", |
356 help="retest failed tests") | 390 help="use specified chg wrapper in place of hg") |
357 parser.add_option("-S", "--noskips", action="store_true", | 391 hgconf.add_argument("--with-hg", |
358 help="don't report skip tests verbosely") | |
359 parser.add_option("--shell", type="string", | |
360 help="shell to use (default: $%s or %s)" % defaults['shell']) | |
361 parser.add_option("-t", "--timeout", type="int", | |
362 help="kill errant tests after TIMEOUT seconds" | |
363 " (default: $%s or %d)" % defaults['timeout']) | |
364 parser.add_option("--slowtimeout", type="int", | |
365 help="kill errant slow tests after SLOWTIMEOUT seconds" | |
366 " (default: $%s or %d)" % defaults['slowtimeout']) | |
367 parser.add_option("--time", action="store_true", | |
368 help="time how long each test takes") | |
369 parser.add_option("--json", action="store_true", | |
370 help="store test result data in 'report.json' file") | |
371 parser.add_option("--tmpdir", type="string", | |
372 help="run tests in the given temporary directory" | |
373 " (implies --keep-tmpdir)") | |
374 parser.add_option("-v", "--verbose", action="store_true", | |
375 help="output verbose messages") | |
376 parser.add_option("--xunit", type="string", | |
377 help="record xunit results at specified path") | |
378 parser.add_option("--view", type="string", | |
379 help="external diff viewer") | |
380 parser.add_option("--with-hg", type="string", | |
381 metavar="HG", | 392 metavar="HG", |
382 help="test using specified hg script rather than a " | 393 help="test using specified hg script rather than a " |
383 "temporary installation") | 394 "temporary installation") |
384 parser.add_option("--chg", action="store_true", | |
385 help="install and use chg wrapper in place of hg") | |
386 parser.add_option("--with-chg", metavar="CHG", | |
387 help="use specified chg wrapper in place of hg") | |
388 parser.add_option("--ipv6", action="store_true", | |
389 help="prefer IPv6 to IPv4 for network related tests") | |
390 parser.add_option("-3", "--py3k-warnings", action="store_true", | |
391 help="enable Py3k warnings on Python 2.7+") | |
392 # This option should be deleted once test-check-py3-compat.t and other | 395 # This option should be deleted once test-check-py3-compat.t and other |
393 # Python 3 tests run with Python 3. | 396 # Python 3 tests run with Python 3. |
394 parser.add_option("--with-python3", metavar="PYTHON3", | 397 hgconf.add_argument("--with-python3", metavar="PYTHON3", |
395 help="Python 3 interpreter (if running under Python 2)" | 398 help="Python 3 interpreter (if running under Python 2)" |
396 " (TEMPORARY)") | 399 " (TEMPORARY)") |
397 parser.add_option('--extra-config-opt', action="append", | 400 |
398 help='set the given config opt in the test hgrc') | 401 reporting = parser.add_argument_group('Results Reporting') |
399 parser.add_option('--random', action="store_true", | 402 reporting.add_argument("-C", "--annotate", action="store_true", |
400 help='run tests in random order') | 403 help="output files annotated with coverage") |
401 parser.add_option('--profile-runner', action='store_true', | 404 reporting.add_argument("--color", choices=["always", "auto", "never"], |
402 help='run statprof on run-tests') | 405 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'), |
403 parser.add_option('--allow-slow-tests', action='store_true', | 406 help="colorisation: always|auto|never (default: auto)") |
404 help='allow extremely slow tests') | 407 reporting.add_argument("-c", "--cover", action="store_true", |
405 parser.add_option('--showchannels', action='store_true', | 408 help="print a test coverage report") |
406 help='show scheduling channels') | 409 reporting.add_argument('--exceptions', action='store_true', |
407 parser.add_option('--known-good-rev', type="string", | 410 help='log all exceptions and generate an exception report') |
408 metavar="known_good_rev", | 411 reporting.add_argument("-H", "--htmlcov", action="store_true", |
409 help=("Automatically bisect any failures using this " | 412 help="create an HTML report of the coverage of the files") |
410 "revision as a known-good revision.")) | 413 reporting.add_argument("--json", action="store_true", |
411 parser.add_option('--bisect-repo', type="string", | 414 help="store test result data in 'report.json' file") |
412 metavar='bisect_repo', | 415 reporting.add_argument("--outputdir", |
413 help=("Path of a repo to bisect. Use together with " | 416 help="directory to write error logs to (default=test directory)") |
414 "--known-good-rev")) | 417 reporting.add_argument("-n", "--nodiff", action="store_true", |
418 help="skip showing test changes") | |
419 reporting.add_argument("-S", "--noskips", action="store_true", | |
420 help="don't report skip tests verbosely") | |
421 reporting.add_argument("--time", action="store_true", | |
422 help="time how long each test takes") | |
423 reporting.add_argument("--view", | |
424 help="external diff viewer") | |
425 reporting.add_argument("--xunit", | |
426 help="record xunit results at specified path") | |
415 | 427 |
416 for option, (envvar, default) in defaults.items(): | 428 for option, (envvar, default) in defaults.items(): |
417 defaults[option] = type(default)(os.environ.get(envvar, default)) | 429 defaults[option] = type(default)(os.environ.get(envvar, default)) |
418 parser.set_defaults(**defaults) | 430 parser.set_defaults(**defaults) |
419 | 431 |
420 return parser | 432 return parser |
421 | 433 |
422 def parseargs(args, parser): | 434 def parseargs(args, parser): |
423 """Parse arguments with our OptionParser and validate results.""" | 435 """Parse arguments with our OptionParser and validate results.""" |
424 (options, args) = parser.parse_args(args) | 436 options = parser.parse_args(args) |
425 | 437 |
426 # jython is always pure | 438 # jython is always pure |
427 if 'java' in sys.platform or '__pypy__' in sys.modules: | 439 if 'java' in sys.platform or '__pypy__' in sys.modules: |
428 options.pure = True | 440 options.pure = True |
429 | 441 |
548 options.whitelisted = {} | 560 options.whitelisted = {} |
549 | 561 |
550 if options.showchannels: | 562 if options.showchannels: |
551 options.nodiff = True | 563 options.nodiff = True |
552 | 564 |
553 return (options, args) | 565 return options |
554 | 566 |
555 def rename(src, dst): | 567 def rename(src, dst): |
556 """Like os.rename(), trade atomicity and opened files friendliness | 568 """Like os.rename(), trade atomicity and opened files friendliness |
557 for existing destination support. | 569 for existing destination support. |
558 """ | 570 """ |
657 # Status code reserved for skipped tests (used by hghave). | 669 # Status code reserved for skipped tests (used by hghave). |
658 SKIPPED_STATUS = 80 | 670 SKIPPED_STATUS = 80 |
659 | 671 |
660 def __init__(self, path, outputdir, tmpdir, keeptmpdir=False, | 672 def __init__(self, path, outputdir, tmpdir, keeptmpdir=False, |
661 debug=False, | 673 debug=False, |
674 first=False, | |
662 timeout=None, | 675 timeout=None, |
663 startport=None, extraconfigopts=None, | 676 startport=None, extraconfigopts=None, |
664 py3kwarnings=False, shell=None, hgcommand=None, | 677 py3kwarnings=False, shell=None, hgcommand=None, |
665 slowtimeout=None, usechg=False, | 678 slowtimeout=None, usechg=False, |
666 useipv6=False): | 679 useipv6=False): |
709 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname) | 722 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname) |
710 | 723 |
711 self._threadtmp = tmpdir | 724 self._threadtmp = tmpdir |
712 self._keeptmpdir = keeptmpdir | 725 self._keeptmpdir = keeptmpdir |
713 self._debug = debug | 726 self._debug = debug |
727 self._first = first | |
714 self._timeout = timeout | 728 self._timeout = timeout |
715 self._slowtimeout = slowtimeout | 729 self._slowtimeout = slowtimeout |
716 self._startport = startport | 730 self._startport = startport |
717 self._extraconfigopts = extraconfigopts or [] | 731 self._extraconfigopts = extraconfigopts or [] |
718 self._py3kwarnings = py3kwarnings | 732 self._py3kwarnings = py3kwarnings |
888 self.fail('no result code from test') | 902 self.fail('no result code from test') |
889 elif out != self._refout: | 903 elif out != self._refout: |
890 # Diff generation may rely on written .err file. | 904 # Diff generation may rely on written .err file. |
891 if (ret != 0 or out != self._refout) and not self._skipped \ | 905 if (ret != 0 or out != self._refout) and not self._skipped \ |
892 and not self._debug: | 906 and not self._debug: |
893 f = open(self.errpath, 'wb') | 907 with open(self.errpath, 'wb') as f: |
894 for line in out: | 908 for line in out: |
895 f.write(line) | 909 f.write(line) |
896 f.close() | |
897 | 910 |
898 # The result object handles diff calculation for us. | 911 # The result object handles diff calculation for us. |
899 if self._result.addOutputMismatch(self, ret, out, self._refout): | 912 with firstlock: |
900 # change was accepted, skip failing | 913 if self._result.addOutputMismatch(self, ret, out, self._refout): |
901 return | 914 # change was accepted, skip failing |
915 return | |
916 if self._first: | |
917 global firsterror | |
918 firsterror = True | |
902 | 919 |
903 if ret: | 920 if ret: |
904 msg = 'output changed and ' + describe(ret) | 921 msg = 'output changed and ' + describe(ret) |
905 else: | 922 else: |
906 msg = 'output changed' | 923 msg = 'output changed' |
928 # files are deleted | 945 # files are deleted |
929 shutil.rmtree(self._chgsockdir, True) | 946 shutil.rmtree(self._chgsockdir, True) |
930 | 947 |
931 if (self._ret != 0 or self._out != self._refout) and not self._skipped \ | 948 if (self._ret != 0 or self._out != self._refout) and not self._skipped \ |
932 and not self._debug and self._out: | 949 and not self._debug and self._out: |
933 f = open(self.errpath, 'wb') | 950 with open(self.errpath, 'wb') as f: |
934 for line in self._out: | 951 for line in self._out: |
935 f.write(line) | 952 f.write(line) |
936 f.close() | |
937 | 953 |
938 vlog("# Ret was:", self._ret, '(%s)' % self.name) | 954 vlog("# Ret was:", self._ret, '(%s)' % self.name) |
939 | 955 |
940 def _run(self, env): | 956 def _run(self, env): |
941 # This should be implemented in child classes to run tests. | 957 # This should be implemented in child classes to run tests. |
959 r = [ | 975 r = [ |
960 # This list should be parallel to defineport in _getenv | 976 # This list should be parallel to defineport in _getenv |
961 self._portmap(0), | 977 self._portmap(0), |
962 self._portmap(1), | 978 self._portmap(1), |
963 self._portmap(2), | 979 self._portmap(2), |
964 (br'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$', | |
965 br'\1 (glob)'), | |
966 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'), | 980 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'), |
967 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'), | 981 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'), |
968 ] | 982 ] |
969 r.append((self._escapepath(self._testtmp), b'$TESTTMP')) | 983 r.append((self._escapepath(self._testtmp), b'$TESTTMP')) |
970 | 984 |
985 replacementfile = os.path.join(self._testdir, b'common-pattern.py') | |
986 | |
987 if os.path.exists(replacementfile): | |
988 data = {} | |
989 with open(replacementfile, mode='rb') as source: | |
990 # the intermediate 'compile' step help with debugging | |
991 code = compile(source.read(), replacementfile, 'exec') | |
992 exec(code, data) | |
993 r.extend(data.get('substitutions', ())) | |
971 return r | 994 return r |
972 | 995 |
973 def _escapepath(self, p): | 996 def _escapepath(self, p): |
974 if os.name == 'nt': | 997 if os.name == 'nt': |
975 return ( | 998 return ( |
1019 """Obtain environment variables to use during test execution.""" | 1042 """Obtain environment variables to use during test execution.""" |
1020 def defineport(i): | 1043 def defineport(i): |
1021 offset = '' if i == 0 else '%s' % i | 1044 offset = '' if i == 0 else '%s' % i |
1022 env["HGPORT%s" % offset] = '%s' % (self._startport + i) | 1045 env["HGPORT%s" % offset] = '%s' % (self._startport + i) |
1023 env = os.environ.copy() | 1046 env = os.environ.copy() |
1024 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') | 1047 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or '' |
1025 env['HGEMITWARNINGS'] = '1' | 1048 env['HGEMITWARNINGS'] = '1' |
1026 env['TESTTMP'] = self._testtmp | 1049 env['TESTTMP'] = self._testtmp |
1027 env['HOME'] = self._testtmp | 1050 env['HOME'] = self._testtmp |
1028 # This number should match portneeded in _getport | 1051 # This number should match portneeded in _getport |
1029 for port in xrange(3): | 1052 for port in xrange(3): |
1067 | 1090 |
1068 return env | 1091 return env |
1069 | 1092 |
1070 def _createhgrc(self, path): | 1093 def _createhgrc(self, path): |
1071 """Create an hgrc file for this test.""" | 1094 """Create an hgrc file for this test.""" |
1072 hgrc = open(path, 'wb') | 1095 with open(path, 'wb') as hgrc: |
1073 hgrc.write(b'[ui]\n') | 1096 hgrc.write(b'[ui]\n') |
1074 hgrc.write(b'slash = True\n') | 1097 hgrc.write(b'slash = True\n') |
1075 hgrc.write(b'interactive = False\n') | 1098 hgrc.write(b'interactive = False\n') |
1076 hgrc.write(b'mergemarkers = detailed\n') | 1099 hgrc.write(b'mergemarkers = detailed\n') |
1077 hgrc.write(b'promptecho = True\n') | 1100 hgrc.write(b'promptecho = True\n') |
1078 hgrc.write(b'[defaults]\n') | 1101 hgrc.write(b'[defaults]\n') |
1079 hgrc.write(b'[devel]\n') | 1102 hgrc.write(b'[devel]\n') |
1080 hgrc.write(b'all-warnings = true\n') | 1103 hgrc.write(b'all-warnings = true\n') |
1081 hgrc.write(b'default-date = 0 0\n') | 1104 hgrc.write(b'default-date = 0 0\n') |
1082 hgrc.write(b'[largefiles]\n') | 1105 hgrc.write(b'[largefiles]\n') |
1083 hgrc.write(b'usercache = %s\n' % | 1106 hgrc.write(b'usercache = %s\n' % |
1084 (os.path.join(self._testtmp, b'.cache/largefiles'))) | 1107 (os.path.join(self._testtmp, b'.cache/largefiles'))) |
1085 hgrc.write(b'[web]\n') | 1108 hgrc.write(b'[lfs]\n') |
1086 hgrc.write(b'address = localhost\n') | 1109 hgrc.write(b'usercache = %s\n' % |
1087 hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii')) | 1110 (os.path.join(self._testtmp, b'.cache/lfs'))) |
1088 | 1111 hgrc.write(b'[web]\n') |
1089 for opt in self._extraconfigopts: | 1112 hgrc.write(b'address = localhost\n') |
1090 section, key = opt.split('.', 1) | 1113 hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii')) |
1091 assert '=' in key, ('extra config opt %s must ' | 1114 |
1092 'have an = for assignment' % opt) | 1115 for opt in self._extraconfigopts: |
1093 hgrc.write(b'[%s]\n%s\n' % (section, key)) | 1116 section, key = opt.encode('utf-8').split(b'.', 1) |
1094 hgrc.close() | 1117 assert b'=' in key, ('extra config opt %s must ' |
1118 'have an = for assignment' % opt) | |
1119 hgrc.write(b'[%s]\n%s\n' % (section, key)) | |
1095 | 1120 |
1096 def fail(self, msg): | 1121 def fail(self, msg): |
1097 # unittest differentiates between errored and failed. | 1122 # unittest differentiates between errored and failed. |
1098 # Failed is denoted by AssertionError (by default at least). | 1123 # Failed is denoted by AssertionError (by default at least). |
1099 raise AssertionError(msg) | 1124 raise AssertionError(msg) |
1195 ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256)) | 1220 ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256)) |
1196 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'}) | 1221 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'}) |
1197 | 1222 |
1198 def __init__(self, path, *args, **kwds): | 1223 def __init__(self, path, *args, **kwds): |
1199 # accept an extra "case" parameter | 1224 # accept an extra "case" parameter |
1200 case = None | 1225 case = kwds.pop('case', None) |
1201 if 'case' in kwds: | |
1202 case = kwds.pop('case') | |
1203 self._case = case | 1226 self._case = case |
1204 self._allcases = parsettestcases(path) | 1227 self._allcases = parsettestcases(path) |
1205 super(TTest, self).__init__(path, *args, **kwds) | 1228 super(TTest, self).__init__(path, *args, **kwds) |
1206 if case: | 1229 if case: |
1207 self.name = '%s (case %s)' % (self.name, _strpath(case)) | 1230 self.name = '%s (case %s)' % (self.name, _strpath(case)) |
1211 @property | 1234 @property |
1212 def refpath(self): | 1235 def refpath(self): |
1213 return os.path.join(self._testdir, self.bname) | 1236 return os.path.join(self._testdir, self.bname) |
1214 | 1237 |
1215 def _run(self, env): | 1238 def _run(self, env): |
1216 f = open(self.path, 'rb') | 1239 with open(self.path, 'rb') as f: |
1217 lines = f.readlines() | 1240 lines = f.readlines() |
1218 f.close() | |
1219 | 1241 |
1220 # .t file is both reference output and the test input, keep reference | 1242 # .t file is both reference output and the test input, keep reference |
1221 # output updated with the the test input. This avoids some race | 1243 # output updated with the the test input. This avoids some race |
1222 # conditions where the reference output does not match the actual test. | 1244 # conditions where the reference output does not match the actual test. |
1223 if self._refout is not None: | 1245 if self._refout is not None: |
1225 | 1247 |
1226 salt, script, after, expected = self._parsetest(lines) | 1248 salt, script, after, expected = self._parsetest(lines) |
1227 | 1249 |
1228 # Write out the generated script. | 1250 # Write out the generated script. |
1229 fname = b'%s.sh' % self._testtmp | 1251 fname = b'%s.sh' % self._testtmp |
1230 f = open(fname, 'wb') | 1252 with open(fname, 'wb') as f: |
1231 for l in script: | 1253 for l in script: |
1232 f.write(l) | 1254 f.write(l) |
1233 f.close() | |
1234 | 1255 |
1235 cmd = b'%s "%s"' % (self._shell, fname) | 1256 cmd = b'%s "%s"' % (self._shell, fname) |
1236 vlog("# Running", cmd) | 1257 vlog("# Running", cmd) |
1237 | 1258 |
1238 exitcode, output = self._runcommand(cmd, env) | 1259 exitcode, output = self._runcommand(cmd, env) |
1318 script.append(b'set -x\n') | 1339 script.append(b'set -x\n') |
1319 if self._hgcommand != b'hg': | 1340 if self._hgcommand != b'hg': |
1320 script.append(b'alias hg="%s"\n' % self._hgcommand) | 1341 script.append(b'alias hg="%s"\n' % self._hgcommand) |
1321 if os.getenv('MSYSTEM'): | 1342 if os.getenv('MSYSTEM'): |
1322 script.append(b'alias pwd="pwd -W"\n') | 1343 script.append(b'alias pwd="pwd -W"\n') |
1344 if self._case: | |
1345 if isinstance(self._case, str): | |
1346 quoted = shellquote(self._case) | |
1347 else: | |
1348 quoted = shellquote(self._case.decode('utf8')).encode('utf8') | |
1349 script.append(b'TESTCASE=%s\n' % quoted) | |
1350 script.append(b'export TESTCASE\n') | |
1323 | 1351 |
1324 n = 0 | 1352 n = 0 |
1325 for n, l in enumerate(lines): | 1353 for n, l in enumerate(lines): |
1326 if not l.endswith(b'\n'): | 1354 if not l.endswith(b'\n'): |
1327 l += b'\n' | 1355 l += b'\n' |
1428 while i < len(els): | 1456 while i < len(els): |
1429 el = els[i] | 1457 el = els[i] |
1430 | 1458 |
1431 r = self.linematch(el, lout) | 1459 r = self.linematch(el, lout) |
1432 if isinstance(r, str): | 1460 if isinstance(r, str): |
1433 if r == '+glob': | 1461 if r == '-glob': |
1434 lout = el[:-1] + ' (glob)\n' | |
1435 r = '' # Warn only this line. | |
1436 elif r == '-glob': | |
1437 lout = ''.join(el.rsplit(' (glob)', 1)) | 1462 lout = ''.join(el.rsplit(' (glob)', 1)) |
1438 r = '' # Warn only this line. | 1463 r = '' # Warn only this line. |
1439 elif r == "retry": | 1464 elif r == "retry": |
1440 postout.append(b' ' + el) | 1465 postout.append(b' ' + el) |
1441 els.pop(i) | 1466 els.pop(i) |
1515 return exitcode, postout | 1540 return exitcode, postout |
1516 | 1541 |
1517 @staticmethod | 1542 @staticmethod |
1518 def rematch(el, l): | 1543 def rematch(el, l): |
1519 try: | 1544 try: |
1545 el = b'(?:' + el + b')' | |
1520 # use \Z to ensure that the regex matches to the end of the string | 1546 # use \Z to ensure that the regex matches to the end of the string |
1521 if os.name == 'nt': | 1547 if os.name == 'nt': |
1522 return re.match(el + br'\r?\n\Z', l) | 1548 return re.match(el + br'\r?\n\Z', l) |
1523 return re.match(el + br'\n\Z', l) | 1549 return re.match(el + br'\n\Z', l) |
1524 except re.error: | 1550 except re.error: |
1586 if el.endswith(b" (glob)\n"): | 1612 if el.endswith(b" (glob)\n"): |
1587 # ignore '(glob)' added to l by 'replacements' | 1613 # ignore '(glob)' added to l by 'replacements' |
1588 if l.endswith(b" (glob)\n"): | 1614 if l.endswith(b" (glob)\n"): |
1589 l = l[:-8] + b"\n" | 1615 l = l[:-8] + b"\n" |
1590 return TTest.globmatch(el[:-8], l) or retry | 1616 return TTest.globmatch(el[:-8], l) or retry |
1591 if os.altsep and l.replace(b'\\', b'/') == el: | 1617 if os.altsep: |
1592 return b'+glob' | 1618 _l = l.replace(b'\\', b'/') |
1619 if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l: | |
1620 return True | |
1593 return retry | 1621 return retry |
1594 | 1622 |
1595 @staticmethod | 1623 @staticmethod |
1596 def parsehghaveoutput(lines): | 1624 def parsehghaveoutput(lines): |
1597 '''Parse hghave log lines. | 1625 '''Parse hghave log lines. |
1618 @staticmethod | 1646 @staticmethod |
1619 def _stringescape(s): | 1647 def _stringescape(s): |
1620 return TTest.ESCAPESUB(TTest._escapef, s) | 1648 return TTest.ESCAPESUB(TTest._escapef, s) |
1621 | 1649 |
1622 iolock = threading.RLock() | 1650 iolock = threading.RLock() |
1651 firstlock = threading.RLock() | |
1652 firsterror = False | |
1623 | 1653 |
1624 class TestResult(unittest._TextTestResult): | 1654 class TestResult(unittest._TextTestResult): |
1625 """Holds results when executing via unittest.""" | 1655 """Holds results when executing via unittest.""" |
1626 # Don't worry too much about accessing the non-public _TextTestResult. | 1656 # Don't worry too much about accessing the non-public _TextTestResult. |
1627 # It is relatively common in Python testing tools. | 1657 # It is relatively common in Python testing tools. |
1703 self.testsRun += 1 | 1733 self.testsRun += 1 |
1704 self.stream.flush() | 1734 self.stream.flush() |
1705 | 1735 |
1706 def addOutputMismatch(self, test, ret, got, expected): | 1736 def addOutputMismatch(self, test, ret, got, expected): |
1707 """Record a mismatch in test output for a particular test.""" | 1737 """Record a mismatch in test output for a particular test.""" |
1708 if self.shouldStop: | 1738 if self.shouldStop or firsterror: |
1709 # don't print, some other test case already failed and | 1739 # don't print, some other test case already failed and |
1710 # printed, we're just stale and probably failed due to our | 1740 # printed, we're just stale and probably failed due to our |
1711 # temp dir getting cleaned up. | 1741 # temp dir getting cleaned up. |
1712 return | 1742 return |
1713 | 1743 |
1863 if self._retest and not os.path.exists(test.errpath): | 1893 if self._retest and not os.path.exists(test.errpath): |
1864 result.addIgnore(test, 'not retesting') | 1894 result.addIgnore(test, 'not retesting') |
1865 continue | 1895 continue |
1866 | 1896 |
1867 if self._keywords: | 1897 if self._keywords: |
1868 f = open(test.path, 'rb') | 1898 with open(test.path, 'rb') as f: |
1869 t = f.read().lower() + test.bname.lower() | 1899 t = f.read().lower() + test.bname.lower() |
1870 f.close() | |
1871 ignored = False | 1900 ignored = False |
1872 for k in self._keywords.lower().split(): | 1901 for k in self._keywords.lower().split(): |
1873 if k not in t: | 1902 if k not in t: |
1874 result.addIgnore(test, "doesn't match keyword") | 1903 result.addIgnore(test, "doesn't match keyword") |
1875 ignored = True | 1904 ignored = True |
2094 if failed: | 2123 if failed: |
2095 self.stream.writeln('python hash seed: %s' % | 2124 self.stream.writeln('python hash seed: %s' % |
2096 os.environ['PYTHONHASHSEED']) | 2125 os.environ['PYTHONHASHSEED']) |
2097 if self._runner.options.time: | 2126 if self._runner.options.time: |
2098 self.printtimes(result.times) | 2127 self.printtimes(result.times) |
2128 | |
2129 if self._runner.options.exceptions: | |
2130 exceptions = aggregateexceptions( | |
2131 os.path.join(self._runner._outputdir, b'exceptions')) | |
2132 total = sum(exceptions.values()) | |
2133 | |
2134 self.stream.writeln('Exceptions Report:') | |
2135 self.stream.writeln('%d total from %d frames' % | |
2136 (total, len(exceptions))) | |
2137 for (frame, line, exc), count in exceptions.most_common(): | |
2138 self.stream.writeln('%d\t%s: %s' % (count, frame, exc)) | |
2139 | |
2099 self.stream.flush() | 2140 self.stream.flush() |
2100 | 2141 |
2101 return result | 2142 return result |
2102 | 2143 |
2103 def _bisecttests(self, tests): | 2144 def _bisecttests(self, tests): |
2241 outcome[tc.name] = tres | 2282 outcome[tc.name] = tres |
2242 jsonout = json.dumps(outcome, sort_keys=True, indent=4, | 2283 jsonout = json.dumps(outcome, sort_keys=True, indent=4, |
2243 separators=(',', ': ')) | 2284 separators=(',', ': ')) |
2244 outf.writelines(("testreport =", jsonout)) | 2285 outf.writelines(("testreport =", jsonout)) |
2245 | 2286 |
2287 def sorttests(testdescs, shuffle=False): | |
2288 """Do an in-place sort of tests.""" | |
2289 if shuffle: | |
2290 random.shuffle(testdescs) | |
2291 return | |
2292 | |
2293 # keywords for slow tests | |
2294 slow = {b'svn': 10, | |
2295 b'cvs': 10, | |
2296 b'hghave': 10, | |
2297 b'largefiles-update': 10, | |
2298 b'run-tests': 10, | |
2299 b'corruption': 10, | |
2300 b'race': 10, | |
2301 b'i18n': 10, | |
2302 b'check': 100, | |
2303 b'gendoc': 100, | |
2304 b'contrib-perf': 200, | |
2305 } | |
2306 perf = {} | |
2307 | |
2308 def sortkey(f): | |
2309 # run largest tests first, as they tend to take the longest | |
2310 f = f['path'] | |
2311 try: | |
2312 return perf[f] | |
2313 except KeyError: | |
2314 try: | |
2315 val = -os.stat(f).st_size | |
2316 except OSError as e: | |
2317 if e.errno != errno.ENOENT: | |
2318 raise | |
2319 perf[f] = -1e9 # file does not exist, tell early | |
2320 return -1e9 | |
2321 for kw, mul in slow.items(): | |
2322 if kw in f: | |
2323 val *= mul | |
2324 if f.endswith(b'.py'): | |
2325 val /= 10.0 | |
2326 perf[f] = val / 1000.0 | |
2327 return perf[f] | |
2328 | |
2329 testdescs.sort(key=sortkey) | |
2330 | |
2246 class TestRunner(object): | 2331 class TestRunner(object): |
2247 """Holds context for executing tests. | 2332 """Holds context for executing tests. |
2248 | 2333 |
2249 Tests rely on a lot of state. This object holds it for them. | 2334 Tests rely on a lot of state. This object holds it for them. |
2250 """ | 2335 """ |
2285 def run(self, args, parser=None): | 2370 def run(self, args, parser=None): |
2286 """Run the test suite.""" | 2371 """Run the test suite.""" |
2287 oldmask = os.umask(0o22) | 2372 oldmask = os.umask(0o22) |
2288 try: | 2373 try: |
2289 parser = parser or getparser() | 2374 parser = parser or getparser() |
2290 options, args = parseargs(args, parser) | 2375 options = parseargs(args, parser) |
2291 # positional arguments are paths to test files to run, so | 2376 tests = [_bytespath(a) for a in options.tests] |
2292 # we make sure they're all bytestrings | |
2293 args = [_bytespath(a) for a in args] | |
2294 if options.test_list is not None: | 2377 if options.test_list is not None: |
2295 for listfile in options.test_list: | 2378 for listfile in options.test_list: |
2296 with open(listfile, 'rb') as f: | 2379 with open(listfile, 'rb') as f: |
2297 args.extend(t for t in f.read().splitlines() if t) | 2380 tests.extend(t for t in f.read().splitlines() if t) |
2298 self.options = options | 2381 self.options = options |
2299 | 2382 |
2300 self._checktools() | 2383 self._checktools() |
2301 testdescs = self.findtests(args) | 2384 testdescs = self.findtests(tests) |
2302 if options.profile_runner: | 2385 if options.profile_runner: |
2303 import statprof | 2386 import statprof |
2304 statprof.start() | 2387 statprof.start() |
2305 result = self._run(testdescs) | 2388 result = self._run(testdescs) |
2306 if options.profile_runner: | 2389 if options.profile_runner: |
2310 | 2393 |
2311 finally: | 2394 finally: |
2312 os.umask(oldmask) | 2395 os.umask(oldmask) |
2313 | 2396 |
2314 def _run(self, testdescs): | 2397 def _run(self, testdescs): |
2315 if self.options.random: | 2398 sorttests(testdescs, shuffle=self.options.random) |
2316 random.shuffle(testdescs) | |
2317 else: | |
2318 # keywords for slow tests | |
2319 slow = {b'svn': 10, | |
2320 b'cvs': 10, | |
2321 b'hghave': 10, | |
2322 b'largefiles-update': 10, | |
2323 b'run-tests': 10, | |
2324 b'corruption': 10, | |
2325 b'race': 10, | |
2326 b'i18n': 10, | |
2327 b'check': 100, | |
2328 b'gendoc': 100, | |
2329 b'contrib-perf': 200, | |
2330 } | |
2331 perf = {} | |
2332 def sortkey(f): | |
2333 # run largest tests first, as they tend to take the longest | |
2334 f = f['path'] | |
2335 try: | |
2336 return perf[f] | |
2337 except KeyError: | |
2338 try: | |
2339 val = -os.stat(f).st_size | |
2340 except OSError as e: | |
2341 if e.errno != errno.ENOENT: | |
2342 raise | |
2343 perf[f] = -1e9 # file does not exist, tell early | |
2344 return -1e9 | |
2345 for kw, mul in slow.items(): | |
2346 if kw in f: | |
2347 val *= mul | |
2348 if f.endswith(b'.py'): | |
2349 val /= 10.0 | |
2350 perf[f] = val / 1000.0 | |
2351 return perf[f] | |
2352 testdescs.sort(key=sortkey) | |
2353 | 2399 |
2354 self._testdir = osenvironb[b'TESTDIR'] = getattr( | 2400 self._testdir = osenvironb[b'TESTDIR'] = getattr( |
2355 os, 'getcwdb', os.getcwd)() | 2401 os, 'getcwdb', os.getcwd)() |
2402 # assume all tests in same folder for now | |
2403 if testdescs: | |
2404 pathname = os.path.dirname(testdescs[0]['path']) | |
2405 if pathname: | |
2406 osenvironb[b'TESTDIR'] = os.path.join(osenvironb[b'TESTDIR'], | |
2407 pathname) | |
2356 if self.options.outputdir: | 2408 if self.options.outputdir: |
2357 self._outputdir = canonpath(_bytespath(self.options.outputdir)) | 2409 self._outputdir = canonpath(_bytespath(self.options.outputdir)) |
2358 else: | 2410 else: |
2359 self._outputdir = self._testdir | 2411 self._outputdir = self._testdir |
2412 if testdescs and pathname: | |
2413 self._outputdir = os.path.join(self._outputdir, pathname) | |
2360 | 2414 |
2361 if 'PYTHONHASHSEED' not in os.environ: | 2415 if 'PYTHONHASHSEED' not in os.environ: |
2362 # use a random python hash seed all the time | 2416 # use a random python hash seed all the time |
2363 # we do the randomness ourself to know what seed is used | 2417 # we do the randomness ourself to know what seed is used |
2364 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32)) | 2418 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32)) |
2371 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if | 2425 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if |
2372 # tmpdir already exists. | 2426 # tmpdir already exists. |
2373 print("error: temp dir %r already exists" % tmpdir) | 2427 print("error: temp dir %r already exists" % tmpdir) |
2374 return 1 | 2428 return 1 |
2375 | 2429 |
2376 # Automatically removing tmpdir sounds convenient, but could | |
2377 # really annoy anyone in the habit of using "--tmpdir=/tmp" | |
2378 # or "--tmpdir=$HOME". | |
2379 #vlog("# Removing temp dir", tmpdir) | |
2380 #shutil.rmtree(tmpdir) | |
2381 os.makedirs(tmpdir) | 2430 os.makedirs(tmpdir) |
2382 else: | 2431 else: |
2383 d = None | 2432 d = None |
2384 if os.name == 'nt': | 2433 if os.name == 'nt': |
2385 # without this, we get the default temp dir location, but | 2434 # without this, we get the default temp dir location, but |
2397 assert isinstance(self._bindir, bytes) | 2446 assert isinstance(self._bindir, bytes) |
2398 self._hgcommand = os.path.basename(whg) | 2447 self._hgcommand = os.path.basename(whg) |
2399 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin') | 2448 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin') |
2400 os.makedirs(self._tmpbindir) | 2449 os.makedirs(self._tmpbindir) |
2401 | 2450 |
2402 # This looks redundant with how Python initializes sys.path from | 2451 normbin = os.path.normpath(os.path.abspath(whg)) |
2403 # the location of the script being executed. Needed because the | 2452 normbin = normbin.replace(os.sep.encode('ascii'), b'/') |
2404 # "hg" specified by --with-hg is not the only Python script | 2453 |
2405 # executed in the test suite that needs to import 'mercurial' | 2454 # Other Python scripts in the test harness need to |
2406 # ... which means it's not really redundant at all. | 2455 # `import mercurial`. If `hg` is a Python script, we assume |
2407 self._pythondir = self._bindir | 2456 # the Mercurial modules are relative to its path and tell the tests |
2457 # to load Python modules from its directory. | |
2458 with open(whg, 'rb') as fh: | |
2459 initial = fh.read(1024) | |
2460 | |
2461 if re.match(b'#!.*python', initial): | |
2462 self._pythondir = self._bindir | |
2463 # If it looks like our in-repo Rust binary, use the source root. | |
2464 # This is a bit hacky. But rhg is still not supported outside the | |
2465 # source directory. So until it is, do the simple thing. | |
2466 elif re.search(b'/rust/target/[^/]+/hg', normbin): | |
2467 self._pythondir = os.path.dirname(self._testdir) | |
2468 # Fall back to the legacy behavior. | |
2469 else: | |
2470 self._pythondir = self._bindir | |
2471 | |
2408 else: | 2472 else: |
2409 self._installdir = os.path.join(self._hgtmp, b"install") | 2473 self._installdir = os.path.join(self._hgtmp, b"install") |
2410 self._bindir = os.path.join(self._installdir, b"bin") | 2474 self._bindir = os.path.join(self._installdir, b"bin") |
2411 self._hgcommand = b'hg' | 2475 self._hgcommand = b'hg' |
2412 self._tmpbindir = self._bindir | 2476 self._tmpbindir = self._bindir |
2474 elif 'HGTEST_SLOW' in os.environ: | 2538 elif 'HGTEST_SLOW' in os.environ: |
2475 del os.environ['HGTEST_SLOW'] | 2539 del os.environ['HGTEST_SLOW'] |
2476 | 2540 |
2477 self._coveragefile = os.path.join(self._testdir, b'.coverage') | 2541 self._coveragefile = os.path.join(self._testdir, b'.coverage') |
2478 | 2542 |
2543 if self.options.exceptions: | |
2544 exceptionsdir = os.path.join(self._outputdir, b'exceptions') | |
2545 try: | |
2546 os.makedirs(exceptionsdir) | |
2547 except OSError as e: | |
2548 if e.errno != errno.EEXIST: | |
2549 raise | |
2550 | |
2551 # Remove all existing exception reports. | |
2552 for f in os.listdir(exceptionsdir): | |
2553 os.unlink(os.path.join(exceptionsdir, f)) | |
2554 | |
2555 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir | |
2556 logexceptions = os.path.join(self._testdir, b'logexceptions.py') | |
2557 self.options.extra_config_opt.append( | |
2558 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')) | |
2559 | |
2479 vlog("# Using TESTDIR", self._testdir) | 2560 vlog("# Using TESTDIR", self._testdir) |
2480 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR']) | 2561 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR']) |
2481 vlog("# Using HGTMP", self._hgtmp) | 2562 vlog("# Using HGTMP", self._hgtmp) |
2482 vlog("# Using PATH", os.environ["PATH"]) | 2563 vlog("# Using PATH", os.environ["PATH"]) |
2483 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH]) | 2564 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH]) |
2501 self.options.changed, None, 0) | 2582 self.options.changed, None, 0) |
2502 stdout, stderr = proc.communicate() | 2583 stdout, stderr = proc.communicate() |
2503 args = stdout.strip(b'\0').split(b'\0') | 2584 args = stdout.strip(b'\0').split(b'\0') |
2504 else: | 2585 else: |
2505 args = os.listdir(b'.') | 2586 args = os.listdir(b'.') |
2587 | |
2588 expanded_args = [] | |
2589 for arg in args: | |
2590 if os.path.isdir(arg): | |
2591 if not arg.endswith(b'/'): | |
2592 arg += b'/' | |
2593 expanded_args.extend([arg + a for a in os.listdir(arg)]) | |
2594 else: | |
2595 expanded_args.append(arg) | |
2596 args = expanded_args | |
2506 | 2597 |
2507 tests = [] | 2598 tests = [] |
2508 for t in args: | 2599 for t in args: |
2509 if not (os.path.basename(t).startswith(b'test-') | 2600 if not (os.path.basename(t).startswith(b'test-') |
2510 and (t.endswith(b'.py') or t.endswith(b'.t'))): | 2601 and (t.endswith(b'.py') or t.endswith(b'.t'))): |
2635 kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc) | 2726 kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc) |
2636 | 2727 |
2637 t = testcls(refpath, self._outputdir, tmpdir, | 2728 t = testcls(refpath, self._outputdir, tmpdir, |
2638 keeptmpdir=self.options.keep_tmpdir, | 2729 keeptmpdir=self.options.keep_tmpdir, |
2639 debug=self.options.debug, | 2730 debug=self.options.debug, |
2731 first=self.options.first, | |
2640 timeout=self.options.timeout, | 2732 timeout=self.options.timeout, |
2641 startport=self._getport(count), | 2733 startport=self._getport(count), |
2642 extraconfigopts=self.options.extra_config_opt, | 2734 extraconfigopts=self.options.extra_config_opt, |
2643 py3kwarnings=self.options.py3k_warnings, | 2735 py3kwarnings=self.options.py3k_warnings, |
2644 shell=self.options.shell, | 2736 shell=self.options.shell, |
2756 os.remove(installerrs) | 2848 os.remove(installerrs) |
2757 except OSError as e: | 2849 except OSError as e: |
2758 if e.errno != errno.ENOENT: | 2850 if e.errno != errno.ENOENT: |
2759 raise | 2851 raise |
2760 else: | 2852 else: |
2761 f = open(installerrs, 'rb') | 2853 with open(installerrs, 'rb') as f: |
2762 for line in f: | 2854 for line in f: |
2763 if PYTHON3: | 2855 if PYTHON3: |
2764 sys.stdout.buffer.write(line) | 2856 sys.stdout.buffer.write(line) |
2765 else: | 2857 else: |
2766 sys.stdout.write(line) | 2858 sys.stdout.write(line) |
2767 f.close() | |
2768 sys.exit(1) | 2859 sys.exit(1) |
2769 os.chdir(self._testdir) | 2860 os.chdir(self._testdir) |
2770 | 2861 |
2771 self._usecorrectpython() | 2862 self._usecorrectpython() |
2772 | 2863 |
2773 if self.options.py3k_warnings and not self.options.anycoverage: | 2864 if self.options.py3k_warnings and not self.options.anycoverage: |
2774 vlog("# Updating hg command to enable Py3k Warnings switch") | 2865 vlog("# Updating hg command to enable Py3k Warnings switch") |
2775 f = open(os.path.join(self._bindir, 'hg'), 'rb') | 2866 with open(os.path.join(self._bindir, 'hg'), 'rb') as f: |
2776 lines = [line.rstrip() for line in f] | 2867 lines = [line.rstrip() for line in f] |
2777 lines[0] += ' -3' | 2868 lines[0] += ' -3' |
2778 f.close() | 2869 with open(os.path.join(self._bindir, 'hg'), 'wb') as f: |
2779 f = open(os.path.join(self._bindir, 'hg'), 'wb') | 2870 for line in lines: |
2780 for line in lines: | 2871 f.write(line + '\n') |
2781 f.write(line + '\n') | |
2782 f.close() | |
2783 | 2872 |
2784 hgbat = os.path.join(self._bindir, b'hg.bat') | 2873 hgbat = os.path.join(self._bindir, b'hg.bat') |
2785 if os.path.isfile(hgbat): | 2874 if os.path.isfile(hgbat): |
2786 # hg.bat expects to be put in bin/scripts while run-tests.py | 2875 # hg.bat expects to be put in bin/scripts while run-tests.py |
2787 # installation layout put it in bin/ directly. Fix it | 2876 # installation layout put it in bin/ directly. Fix it |
2788 f = open(hgbat, 'rb') | 2877 with open(hgbat, 'rb') as f: |
2789 data = f.read() | 2878 data = f.read() |
2790 f.close() | |
2791 if b'"%~dp0..\python" "%~dp0hg" %*' in data: | 2879 if b'"%~dp0..\python" "%~dp0hg" %*' in data: |
2792 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*', | 2880 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*', |
2793 b'"%~dp0python" "%~dp0hg" %*') | 2881 b'"%~dp0python" "%~dp0hg" %*') |
2794 f = open(hgbat, 'wb') | 2882 with open(hgbat, 'wb') as f: |
2795 f.write(data) | 2883 f.write(data) |
2796 f.close() | |
2797 else: | 2884 else: |
2798 print('WARNING: cannot fix hg.bat reference to python.exe') | 2885 print('WARNING: cannot fix hg.bat reference to python.exe') |
2799 | 2886 |
2800 if self.options.anycoverage: | 2887 if self.options.anycoverage: |
2801 custom = os.path.join(self._testdir, 'sitecustomize.py') | 2888 custom = os.path.join(self._testdir, 'sitecustomize.py') |
2916 vlog("# Found prerequisite", p, "at", found) | 3003 vlog("# Found prerequisite", p, "at", found) |
2917 else: | 3004 else: |
2918 print("WARNING: Did not find prerequisite tool: %s " % | 3005 print("WARNING: Did not find prerequisite tool: %s " % |
2919 p.decode("utf-8")) | 3006 p.decode("utf-8")) |
2920 | 3007 |
3008 def aggregateexceptions(path): | |
3009 exceptions = collections.Counter() | |
3010 | |
3011 for f in os.listdir(path): | |
3012 with open(os.path.join(path, f), 'rb') as fh: | |
3013 data = fh.read().split(b'\0') | |
3014 if len(data) != 4: | |
3015 continue | |
3016 | |
3017 exc, mainframe, hgframe, hgline = data | |
3018 exc = exc.decode('utf-8') | |
3019 mainframe = mainframe.decode('utf-8') | |
3020 hgframe = hgframe.decode('utf-8') | |
3021 hgline = hgline.decode('utf-8') | |
3022 exceptions[(hgframe, hgline, exc)] += 1 | |
3023 | |
3024 return exceptions | |
3025 | |
2921 if __name__ == '__main__': | 3026 if __name__ == '__main__': |
2922 runner = TestRunner() | 3027 runner = TestRunner() |
2923 | 3028 |
2924 try: | 3029 try: |
2925 import msvcrt | 3030 import msvcrt |