comparison tests/run-tests.py @ 43076:2372284d9457

formatting: blacken the codebase This is using my patch to black (https://github.com/psf/black/pull/826) so we don't un-wrap collection literals. Done with: hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S # skip-blame mass-reformatting only # no-check-commit reformats foo_bar functions Differential Revision: https://phab.mercurial-scm.org/D6971
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:45:02 -0400
parents 5c9c71cde1c9
children 96eb9ef777a8
comparison
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
73 except ImportError: 73 except ImportError:
74 import queue 74 import queue
75 75
76 try: 76 try:
77 import shlex 77 import shlex
78
78 shellquote = shlex.quote 79 shellquote = shlex.quote
79 except (ImportError, AttributeError): 80 except (ImportError, AttributeError):
80 import pipes 81 import pipes
82
81 shellquote = pipes.quote 83 shellquote = pipes.quote
82 84
83 processlock = threading.Lock() 85 processlock = threading.Lock()
84 86
85 pygmentspresent = False 87 pygmentspresent = False
86 # ANSI color is unsupported prior to Windows 10 88 # ANSI color is unsupported prior to Windows 10
87 if os.name != 'nt': 89 if os.name != 'nt':
88 try: # is pygments installed 90 try: # is pygments installed
89 import pygments 91 import pygments
90 import pygments.lexers as lexers 92 import pygments.lexers as lexers
91 import pygments.lexer as lexer 93 import pygments.lexer as lexer
92 import pygments.formatters as formatters 94 import pygments.formatters as formatters
93 import pygments.token as token 95 import pygments.token as token
94 import pygments.style as style 96 import pygments.style as style
97
95 pygmentspresent = True 98 pygmentspresent = True
96 difflexer = lexers.DiffLexer() 99 difflexer = lexers.DiffLexer()
97 terminal256formatter = formatters.Terminal256Formatter() 100 terminal256formatter = formatters.Terminal256Formatter()
98 except ImportError: 101 except ImportError:
99 pass 102 pass
100 103
101 if pygmentspresent: 104 if pygmentspresent:
105
102 class TestRunnerStyle(style.Style): 106 class TestRunnerStyle(style.Style):
103 default_style = "" 107 default_style = ""
104 skipped = token.string_to_tokentype("Token.Generic.Skipped") 108 skipped = token.string_to_tokentype("Token.Generic.Skipped")
105 failed = token.string_to_tokentype("Token.Generic.Failed") 109 failed = token.string_to_tokentype("Token.Generic.Failed")
106 skippedname = token.string_to_tokentype("Token.Generic.SName") 110 skippedname = token.string_to_tokentype("Token.Generic.SName")
107 failedname = token.string_to_tokentype("Token.Generic.FName") 111 failedname = token.string_to_tokentype("Token.Generic.FName")
108 styles = { 112 styles = {
109 skipped: '#e5e5e5', 113 skipped: '#e5e5e5',
110 skippedname: '#00ffff', 114 skippedname: '#00ffff',
111 failed: '#7f0000', 115 failed: '#7f0000',
112 failedname: '#ff0000', 116 failedname: '#ff0000',
113 } 117 }
114 118
115 class TestRunnerLexer(lexer.RegexLexer): 119 class TestRunnerLexer(lexer.RegexLexer):
116 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?' 120 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
117 tokens = { 121 tokens = {
125 (r':.*', token.Generic.Skipped), 129 (r':.*', token.Generic.Skipped),
126 ], 130 ],
127 'failed': [ 131 'failed': [
128 (testpattern, token.Generic.FName), 132 (testpattern, token.Generic.FName),
129 (r'(:| ).*', token.Generic.Failed), 133 (r'(:| ).*', token.Generic.Failed),
130 ] 134 ],
131 } 135 }
132 136
133 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle) 137 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
134 runnerlexer = TestRunnerLexer() 138 runnerlexer = TestRunnerLexer()
135 139
136 origenviron = os.environ.copy() 140 origenviron = os.environ.copy()
137 141
138 if sys.version_info > (3, 5, 0): 142 if sys.version_info > (3, 5, 0):
139 PYTHON3 = True 143 PYTHON3 = True
140 xrange = range # we use xrange in one place, and we'd rather not use range 144 xrange = range # we use xrange in one place, and we'd rather not use range
145
141 def _bytespath(p): 146 def _bytespath(p):
142 if p is None: 147 if p is None:
143 return p 148 return p
144 return p.encode('utf-8') 149 return p.encode('utf-8')
145 150
156 class environbytes(object): 161 class environbytes(object):
157 def __init__(self, strenv): 162 def __init__(self, strenv):
158 self.__len__ = strenv.__len__ 163 self.__len__ = strenv.__len__
159 self.clear = strenv.clear 164 self.clear = strenv.clear
160 self._strenv = strenv 165 self._strenv = strenv
166
161 def __getitem__(self, k): 167 def __getitem__(self, k):
162 v = self._strenv.__getitem__(_strpath(k)) 168 v = self._strenv.__getitem__(_strpath(k))
163 return _bytespath(v) 169 return _bytespath(v)
170
164 def __setitem__(self, k, v): 171 def __setitem__(self, k, v):
165 self._strenv.__setitem__(_strpath(k), _strpath(v)) 172 self._strenv.__setitem__(_strpath(k), _strpath(v))
173
166 def __delitem__(self, k): 174 def __delitem__(self, k):
167 self._strenv.__delitem__(_strpath(k)) 175 self._strenv.__delitem__(_strpath(k))
176
168 def __contains__(self, k): 177 def __contains__(self, k):
169 return self._strenv.__contains__(_strpath(k)) 178 return self._strenv.__contains__(_strpath(k))
179
170 def __iter__(self): 180 def __iter__(self):
171 return iter([_bytespath(k) for k in iter(self._strenv)]) 181 return iter([_bytespath(k) for k in iter(self._strenv)])
182
172 def get(self, k, default=None): 183 def get(self, k, default=None):
173 v = self._strenv.get(_strpath(k), _strpath(default)) 184 v = self._strenv.get(_strpath(k), _strpath(default))
174 return _bytespath(v) 185 return _bytespath(v)
186
175 def pop(self, k, default=None): 187 def pop(self, k, default=None):
176 v = self._strenv.pop(_strpath(k), _strpath(default)) 188 v = self._strenv.pop(_strpath(k), _strpath(default))
177 return _bytespath(v) 189 return _bytespath(v)
178 190
179 osenvironb = environbytes(os.environ) 191 osenvironb = environbytes(os.environ)
181 getcwdb = getattr(os, 'getcwdb') 193 getcwdb = getattr(os, 'getcwdb')
182 if not getcwdb or os.name == 'nt': 194 if not getcwdb or os.name == 'nt':
183 getcwdb = lambda: _bytespath(os.getcwd()) 195 getcwdb = lambda: _bytespath(os.getcwd())
184 196
185 elif sys.version_info >= (3, 0, 0): 197 elif sys.version_info >= (3, 0, 0):
186 print('%s is only supported on Python 3.5+ and 2.7, not %s' % 198 print(
187 (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))) 199 '%s is only supported on Python 3.5+ and 2.7, not %s'
188 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit` 200 % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))
201 )
202 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
189 else: 203 else:
190 PYTHON3 = False 204 PYTHON3 = False
191 205
192 # In python 2.x, path operations are generally done using 206 # In python 2.x, path operations are generally done using
193 # bytestrings by default, so we don't have to do any extra 207 # bytestrings by default, so we don't have to do any extra
226 else: 240 else:
227 raise 241 raise
228 else: 242 else:
229 return False 243 return False
230 244
245
231 # useipv6 will be set by parseargs 246 # useipv6 will be set by parseargs
232 useipv6 = None 247 useipv6 = None
248
233 249
234 def checkportisavailable(port): 250 def checkportisavailable(port):
235 """return true if a port seems free to bind on localhost""" 251 """return true if a port seems free to bind on localhost"""
236 if useipv6: 252 if useipv6:
237 family = socket.AF_INET6 253 family = socket.AF_INET6
241 s = socket.socket(family, socket.SOCK_STREAM) 257 s = socket.socket(family, socket.SOCK_STREAM)
242 s.bind(('localhost', port)) 258 s.bind(('localhost', port))
243 s.close() 259 s.close()
244 return True 260 return True
245 except socket.error as exc: 261 except socket.error as exc:
246 if exc.errno not in (errno.EADDRINUSE, errno.EADDRNOTAVAIL, 262 if exc.errno not in (
247 errno.EPROTONOSUPPORT): 263 errno.EADDRINUSE,
264 errno.EADDRNOTAVAIL,
265 errno.EPROTONOSUPPORT,
266 ):
248 raise 267 raise
249 return False 268 return False
250 269
270
251 closefds = os.name == 'posix' 271 closefds = os.name == 'posix'
272
273
252 def Popen4(cmd, wd, timeout, env=None): 274 def Popen4(cmd, wd, timeout, env=None):
253 processlock.acquire() 275 processlock.acquire()
254 p = subprocess.Popen(_strpath(cmd), shell=True, bufsize=-1, 276 p = subprocess.Popen(
255 cwd=_strpath(wd), env=env, 277 _strpath(cmd),
256 close_fds=closefds, 278 shell=True,
257 stdin=subprocess.PIPE, stdout=subprocess.PIPE, 279 bufsize=-1,
258 stderr=subprocess.STDOUT) 280 cwd=_strpath(wd),
281 env=env,
282 close_fds=closefds,
283 stdin=subprocess.PIPE,
284 stdout=subprocess.PIPE,
285 stderr=subprocess.STDOUT,
286 )
259 processlock.release() 287 processlock.release()
260 288
261 p.fromchild = p.stdout 289 p.fromchild = p.stdout
262 p.tochild = p.stdin 290 p.tochild = p.stdin
263 p.childerr = p.stderr 291 p.childerr = p.stderr
264 292
265 p.timeout = False 293 p.timeout = False
266 if timeout: 294 if timeout:
295
267 def t(): 296 def t():
268 start = time.time() 297 start = time.time()
269 while time.time() - start < timeout and p.returncode is None: 298 while time.time() - start < timeout and p.returncode is None:
270 time.sleep(.1) 299 time.sleep(0.1)
271 p.timeout = True 300 p.timeout = True
272 if p.returncode is None: 301 if p.returncode is None:
273 terminate(p) 302 terminate(p)
303
274 threading.Thread(target=t).start() 304 threading.Thread(target=t).start()
275 305
276 return p 306 return p
307
277 308
278 if sys.executable: 309 if sys.executable:
279 sysexecutable = sys.executable 310 sysexecutable = sys.executable
280 elif os.environ.get('PYTHONEXECUTABLE'): 311 elif os.environ.get('PYTHONEXECUTABLE'):
281 sysexecutable = os.environ['PYTHONEXECUTABLE'] 312 sysexecutable = os.environ['PYTHONEXECUTABLE']
295 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500), 326 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
296 'port': ('HGTEST_PORT', 20059), 327 'port': ('HGTEST_PORT', 20059),
297 'shell': ('HGTEST_SHELL', 'sh'), 328 'shell': ('HGTEST_SHELL', 'sh'),
298 } 329 }
299 330
331
300 def canonpath(path): 332 def canonpath(path):
301 return os.path.realpath(os.path.expanduser(path)) 333 return os.path.realpath(os.path.expanduser(path))
334
302 335
303 def parselistfiles(files, listtype, warn=True): 336 def parselistfiles(files, listtype, warn=True):
304 entries = dict() 337 entries = dict()
305 for filename in files: 338 for filename in files:
306 try: 339 try:
319 entries[line] = filename 352 entries[line] = filename
320 353
321 f.close() 354 f.close()
322 return entries 355 return entries
323 356
357
324 def parsettestcases(path): 358 def parsettestcases(path):
325 """read a .t test file, return a set of test case names 359 """read a .t test file, return a set of test case names
326 360
327 If path does not exist, return an empty set. 361 If path does not exist, return an empty set.
328 """ 362 """
335 except IOError as ex: 369 except IOError as ex:
336 if ex.errno != errno.ENOENT: 370 if ex.errno != errno.ENOENT:
337 raise 371 raise
338 return cases 372 return cases
339 373
374
340 def getparser(): 375 def getparser():
341 """Obtain the OptionParser used by the CLI.""" 376 """Obtain the OptionParser used by the CLI."""
342 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]') 377 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
343 378
344 selection = parser.add_argument_group('Test Selection') 379 selection = parser.add_argument_group('Test Selection')
345 selection.add_argument('--allow-slow-tests', action='store_true', 380 selection.add_argument(
346 help='allow extremely slow tests') 381 '--allow-slow-tests',
347 selection.add_argument("--blacklist", action="append", 382 action='store_true',
348 help="skip tests listed in the specified blacklist file") 383 help='allow extremely slow tests',
349 selection.add_argument("--changed", 384 )
350 help="run tests that are changed in parent rev or working directory") 385 selection.add_argument(
351 selection.add_argument("-k", "--keywords", 386 "--blacklist",
352 help="run tests matching keywords") 387 action="append",
353 selection.add_argument("-r", "--retest", action="store_true", 388 help="skip tests listed in the specified blacklist file",
354 help = "retest failed tests") 389 )
355 selection.add_argument("--test-list", action="append", 390 selection.add_argument(
356 help="read tests to run from the specified file") 391 "--changed",
357 selection.add_argument("--whitelist", action="append", 392 help="run tests that are changed in parent rev or working directory",
358 help="always run tests listed in the specified whitelist file") 393 )
359 selection.add_argument('tests', metavar='TESTS', nargs='*', 394 selection.add_argument(
360 help='Tests to run') 395 "-k", "--keywords", help="run tests matching keywords"
396 )
397 selection.add_argument(
398 "-r", "--retest", action="store_true", help="retest failed tests"
399 )
400 selection.add_argument(
401 "--test-list",
402 action="append",
403 help="read tests to run from the specified file",
404 )
405 selection.add_argument(
406 "--whitelist",
407 action="append",
408 help="always run tests listed in the specified whitelist file",
409 )
410 selection.add_argument(
411 'tests', metavar='TESTS', nargs='*', help='Tests to run'
412 )
361 413
362 harness = parser.add_argument_group('Test Harness Behavior') 414 harness = parser.add_argument_group('Test Harness Behavior')
363 harness.add_argument('--bisect-repo', 415 harness.add_argument(
364 metavar='bisect_repo', 416 '--bisect-repo',
365 help=("Path of a repo to bisect. Use together with " 417 metavar='bisect_repo',
366 "--known-good-rev")) 418 help=(
367 harness.add_argument("-d", "--debug", action="store_true", 419 "Path of a repo to bisect. Use together with " "--known-good-rev"
420 ),
421 )
422 harness.add_argument(
423 "-d",
424 "--debug",
425 action="store_true",
368 help="debug mode: write output of test scripts to console" 426 help="debug mode: write output of test scripts to console"
369 " rather than capturing and diffing it (disables timeout)") 427 " rather than capturing and diffing it (disables timeout)",
370 harness.add_argument("-f", "--first", action="store_true", 428 )
371 help="exit on the first test failure") 429 harness.add_argument(
372 harness.add_argument("-i", "--interactive", action="store_true", 430 "-f",
373 help="prompt to accept changed output") 431 "--first",
374 harness.add_argument("-j", "--jobs", type=int, 432 action="store_true",
433 help="exit on the first test failure",
434 )
435 harness.add_argument(
436 "-i",
437 "--interactive",
438 action="store_true",
439 help="prompt to accept changed output",
440 )
441 harness.add_argument(
442 "-j",
443 "--jobs",
444 type=int,
375 help="number of jobs to run in parallel" 445 help="number of jobs to run in parallel"
376 " (default: $%s or %d)" % defaults['jobs']) 446 " (default: $%s or %d)" % defaults['jobs'],
377 harness.add_argument("--keep-tmpdir", action="store_true", 447 )
378 help="keep temporary directory after running tests") 448 harness.add_argument(
379 harness.add_argument('--known-good-rev', 449 "--keep-tmpdir",
380 metavar="known_good_rev", 450 action="store_true",
381 help=("Automatically bisect any failures using this " 451 help="keep temporary directory after running tests",
382 "revision as a known-good revision.")) 452 )
383 harness.add_argument("--list-tests", action="store_true", 453 harness.add_argument(
384 help="list tests instead of running them") 454 '--known-good-rev',
385 harness.add_argument("--loop", action="store_true", 455 metavar="known_good_rev",
386 help="loop tests repeatedly") 456 help=(
387 harness.add_argument('--random', action="store_true", 457 "Automatically bisect any failures using this "
388 help='run tests in random order') 458 "revision as a known-good revision."
389 harness.add_argument('--order-by-runtime', action="store_true", 459 ),
390 help='run slowest tests first, according to .testtimes') 460 )
391 harness.add_argument("-p", "--port", type=int, 461 harness.add_argument(
462 "--list-tests",
463 action="store_true",
464 help="list tests instead of running them",
465 )
466 harness.add_argument(
467 "--loop", action="store_true", help="loop tests repeatedly"
468 )
469 harness.add_argument(
470 '--random', action="store_true", help='run tests in random order'
471 )
472 harness.add_argument(
473 '--order-by-runtime',
474 action="store_true",
475 help='run slowest tests first, according to .testtimes',
476 )
477 harness.add_argument(
478 "-p",
479 "--port",
480 type=int,
392 help="port on which servers should listen" 481 help="port on which servers should listen"
393 " (default: $%s or %d)" % defaults['port']) 482 " (default: $%s or %d)" % defaults['port'],
394 harness.add_argument('--profile-runner', action='store_true', 483 )
395 help='run statprof on run-tests') 484 harness.add_argument(
396 harness.add_argument("-R", "--restart", action="store_true", 485 '--profile-runner',
397 help="restart at last error") 486 action='store_true',
398 harness.add_argument("--runs-per-test", type=int, dest="runs_per_test", 487 help='run statprof on run-tests',
399 help="run each test N times (default=1)", default=1) 488 )
400 harness.add_argument("--shell", 489 harness.add_argument(
401 help="shell to use (default: $%s or %s)" % defaults['shell']) 490 "-R", "--restart", action="store_true", help="restart at last error"
402 harness.add_argument('--showchannels', action='store_true', 491 )
403 help='show scheduling channels') 492 harness.add_argument(
404 harness.add_argument("--slowtimeout", type=int, 493 "--runs-per-test",
494 type=int,
495 dest="runs_per_test",
496 help="run each test N times (default=1)",
497 default=1,
498 )
499 harness.add_argument(
500 "--shell", help="shell to use (default: $%s or %s)" % defaults['shell']
501 )
502 harness.add_argument(
503 '--showchannels', action='store_true', help='show scheduling channels'
504 )
505 harness.add_argument(
506 "--slowtimeout",
507 type=int,
405 help="kill errant slow tests after SLOWTIMEOUT seconds" 508 help="kill errant slow tests after SLOWTIMEOUT seconds"
406 " (default: $%s or %d)" % defaults['slowtimeout']) 509 " (default: $%s or %d)" % defaults['slowtimeout'],
407 harness.add_argument("-t", "--timeout", type=int, 510 )
511 harness.add_argument(
512 "-t",
513 "--timeout",
514 type=int,
408 help="kill errant tests after TIMEOUT seconds" 515 help="kill errant tests after TIMEOUT seconds"
409 " (default: $%s or %d)" % defaults['timeout']) 516 " (default: $%s or %d)" % defaults['timeout'],
410 harness.add_argument("--tmpdir", 517 )
518 harness.add_argument(
519 "--tmpdir",
411 help="run tests in the given temporary directory" 520 help="run tests in the given temporary directory"
412 " (implies --keep-tmpdir)") 521 " (implies --keep-tmpdir)",
413 harness.add_argument("-v", "--verbose", action="store_true", 522 )
414 help="output verbose messages") 523 harness.add_argument(
524 "-v", "--verbose", action="store_true", help="output verbose messages"
525 )
415 526
416 hgconf = parser.add_argument_group('Mercurial Configuration') 527 hgconf = parser.add_argument_group('Mercurial Configuration')
417 hgconf.add_argument("--chg", action="store_true", 528 hgconf.add_argument(
418 help="install and use chg wrapper in place of hg") 529 "--chg",
419 hgconf.add_argument("--compiler", 530 action="store_true",
420 help="compiler to build with") 531 help="install and use chg wrapper in place of hg",
421 hgconf.add_argument('--extra-config-opt', action="append", default=[], 532 )
422 help='set the given config opt in the test hgrc') 533 hgconf.add_argument("--compiler", help="compiler to build with")
423 hgconf.add_argument("-l", "--local", action="store_true", 534 hgconf.add_argument(
535 '--extra-config-opt',
536 action="append",
537 default=[],
538 help='set the given config opt in the test hgrc',
539 )
540 hgconf.add_argument(
541 "-l",
542 "--local",
543 action="store_true",
424 help="shortcut for --with-hg=<testdir>/../hg, " 544 help="shortcut for --with-hg=<testdir>/../hg, "
425 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set") 545 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set",
426 hgconf.add_argument("--ipv6", action="store_true", 546 )
427 help="prefer IPv6 to IPv4 for network related tests") 547 hgconf.add_argument(
428 hgconf.add_argument("--pure", action="store_true", 548 "--ipv6",
429 help="use pure Python code instead of C extensions") 549 action="store_true",
430 hgconf.add_argument("-3", "--py3-warnings", action="store_true", 550 help="prefer IPv6 to IPv4 for network related tests",
431 help="enable Py3k warnings on Python 2.7+") 551 )
432 hgconf.add_argument("--with-chg", metavar="CHG", 552 hgconf.add_argument(
433 help="use specified chg wrapper in place of hg") 553 "--pure",
434 hgconf.add_argument("--with-hg", 554 action="store_true",
555 help="use pure Python code instead of C extensions",
556 )
557 hgconf.add_argument(
558 "-3",
559 "--py3-warnings",
560 action="store_true",
561 help="enable Py3k warnings on Python 2.7+",
562 )
563 hgconf.add_argument(
564 "--with-chg",
565 metavar="CHG",
566 help="use specified chg wrapper in place of hg",
567 )
568 hgconf.add_argument(
569 "--with-hg",
435 metavar="HG", 570 metavar="HG",
436 help="test using specified hg script rather than a " 571 help="test using specified hg script rather than a "
437 "temporary installation") 572 "temporary installation",
573 )
438 574
439 reporting = parser.add_argument_group('Results Reporting') 575 reporting = parser.add_argument_group('Results Reporting')
440 reporting.add_argument("-C", "--annotate", action="store_true", 576 reporting.add_argument(
441 help="output files annotated with coverage") 577 "-C",
442 reporting.add_argument("--color", choices=["always", "auto", "never"], 578 "--annotate",
579 action="store_true",
580 help="output files annotated with coverage",
581 )
582 reporting.add_argument(
583 "--color",
584 choices=["always", "auto", "never"],
443 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'), 585 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
444 help="colorisation: always|auto|never (default: auto)") 586 help="colorisation: always|auto|never (default: auto)",
445 reporting.add_argument("-c", "--cover", action="store_true", 587 )
446 help="print a test coverage report") 588 reporting.add_argument(
447 reporting.add_argument('--exceptions', action='store_true', 589 "-c",
448 help='log all exceptions and generate an exception report') 590 "--cover",
449 reporting.add_argument("-H", "--htmlcov", action="store_true", 591 action="store_true",
450 help="create an HTML report of the coverage of the files") 592 help="print a test coverage report",
451 reporting.add_argument("--json", action="store_true", 593 )
452 help="store test result data in 'report.json' file") 594 reporting.add_argument(
453 reporting.add_argument("--outputdir", 595 '--exceptions',
454 help="directory to write error logs to (default=test directory)") 596 action='store_true',
455 reporting.add_argument("-n", "--nodiff", action="store_true", 597 help='log all exceptions and generate an exception report',
456 help="skip showing test changes") 598 )
457 reporting.add_argument("-S", "--noskips", action="store_true", 599 reporting.add_argument(
458 help="don't report skip tests verbosely") 600 "-H",
459 reporting.add_argument("--time", action="store_true", 601 "--htmlcov",
460 help="time how long each test takes") 602 action="store_true",
461 reporting.add_argument("--view", 603 help="create an HTML report of the coverage of the files",
462 help="external diff viewer") 604 )
463 reporting.add_argument("--xunit", 605 reporting.add_argument(
464 help="record xunit results at specified path") 606 "--json",
607 action="store_true",
608 help="store test result data in 'report.json' file",
609 )
610 reporting.add_argument(
611 "--outputdir",
612 help="directory to write error logs to (default=test directory)",
613 )
614 reporting.add_argument(
615 "-n", "--nodiff", action="store_true", help="skip showing test changes"
616 )
617 reporting.add_argument(
618 "-S",
619 "--noskips",
620 action="store_true",
621 help="don't report skip tests verbosely",
622 )
623 reporting.add_argument(
624 "--time", action="store_true", help="time how long each test takes"
625 )
626 reporting.add_argument("--view", help="external diff viewer")
627 reporting.add_argument(
628 "--xunit", help="record xunit results at specified path"
629 )
465 630
466 for option, (envvar, default) in defaults.items(): 631 for option, (envvar, default) in defaults.items():
467 defaults[option] = type(default)(os.environ.get(envvar, default)) 632 defaults[option] = type(default)(os.environ.get(envvar, default))
468 parser.set_defaults(**defaults) 633 parser.set_defaults(**defaults)
469 634
470 return parser 635 return parser
636
471 637
472 def parseargs(args, parser): 638 def parseargs(args, parser):
473 """Parse arguments with our OptionParser and validate results.""" 639 """Parse arguments with our OptionParser and validate results."""
474 options = parser.parse_args(args) 640 options = parser.parse_args(args)
475 641
486 if options.chg: 652 if options.chg:
487 pathandattrs.append((b'contrib/chg/chg', 'with_chg')) 653 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
488 for relpath, attr in pathandattrs: 654 for relpath, attr in pathandattrs:
489 binpath = os.path.join(reporootdir, relpath) 655 binpath = os.path.join(reporootdir, relpath)
490 if os.name != 'nt' and not os.access(binpath, os.X_OK): 656 if os.name != 'nt' and not os.access(binpath, os.X_OK):
491 parser.error('--local specified, but %r not found or ' 657 parser.error(
492 'not executable' % binpath) 658 '--local specified, but %r not found or '
659 'not executable' % binpath
660 )
493 setattr(options, attr, _strpath(binpath)) 661 setattr(options, attr, _strpath(binpath))
494 662
495 if options.with_hg: 663 if options.with_hg:
496 options.with_hg = canonpath(_bytespath(options.with_hg)) 664 options.with_hg = canonpath(_bytespath(options.with_hg))
497 if not (os.path.isfile(options.with_hg) and 665 if not (
498 os.access(options.with_hg, os.X_OK)): 666 os.path.isfile(options.with_hg)
667 and os.access(options.with_hg, os.X_OK)
668 ):
499 parser.error('--with-hg must specify an executable hg script') 669 parser.error('--with-hg must specify an executable hg script')
500 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']: 670 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
501 sys.stderr.write('warning: --with-hg should specify an hg script\n') 671 sys.stderr.write('warning: --with-hg should specify an hg script\n')
502 sys.stderr.flush() 672 sys.stderr.flush()
503 673
504 if (options.chg or options.with_chg) and os.name == 'nt': 674 if (options.chg or options.with_chg) and os.name == 'nt':
505 parser.error('chg does not work on %s' % os.name) 675 parser.error('chg does not work on %s' % os.name)
506 if options.with_chg: 676 if options.with_chg:
507 options.chg = False # no installation to temporary location 677 options.chg = False # no installation to temporary location
508 options.with_chg = canonpath(_bytespath(options.with_chg)) 678 options.with_chg = canonpath(_bytespath(options.with_chg))
509 if not (os.path.isfile(options.with_chg) and 679 if not (
510 os.access(options.with_chg, os.X_OK)): 680 os.path.isfile(options.with_chg)
681 and os.access(options.with_chg, os.X_OK)
682 ):
511 parser.error('--with-chg must specify a chg executable') 683 parser.error('--with-chg must specify a chg executable')
512 if options.chg and options.with_hg: 684 if options.chg and options.with_hg:
513 # chg shares installation location with hg 685 # chg shares installation location with hg
514 parser.error('--chg does not work when --with-hg is specified ' 686 parser.error(
515 '(use --with-chg instead)') 687 '--chg does not work when --with-hg is specified '
688 '(use --with-chg instead)'
689 )
516 690
517 if options.color == 'always' and not pygmentspresent: 691 if options.color == 'always' and not pygmentspresent:
518 sys.stderr.write('warning: --color=always ignored because ' 692 sys.stderr.write(
519 'pygments is not installed\n') 693 'warning: --color=always ignored because '
694 'pygments is not installed\n'
695 )
520 696
521 if options.bisect_repo and not options.known_good_rev: 697 if options.bisect_repo and not options.known_good_rev:
522 parser.error("--bisect-repo cannot be used without --known-good-rev") 698 parser.error("--bisect-repo cannot be used without --known-good-rev")
523 699
524 global useipv6 700 global useipv6
525 if options.ipv6: 701 if options.ipv6:
526 useipv6 = checksocketfamily('AF_INET6') 702 useipv6 = checksocketfamily('AF_INET6')
527 else: 703 else:
528 # only use IPv6 if IPv4 is unavailable and IPv6 is available 704 # only use IPv6 if IPv4 is unavailable and IPv6 is available
529 useipv6 = ((not checksocketfamily('AF_INET')) 705 useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily(
530 and checksocketfamily('AF_INET6')) 706 'AF_INET6'
707 )
531 708
532 options.anycoverage = options.cover or options.annotate or options.htmlcov 709 options.anycoverage = options.cover or options.annotate or options.htmlcov
533 if options.anycoverage: 710 if options.anycoverage:
534 try: 711 try:
535 import coverage 712 import coverage
713
536 covver = version.StrictVersion(coverage.__version__).version 714 covver = version.StrictVersion(coverage.__version__).version
537 if covver < (3, 3): 715 if covver < (3, 3):
538 parser.error('coverage options require coverage 3.3 or later') 716 parser.error('coverage options require coverage 3.3 or later')
539 except ImportError: 717 except ImportError:
540 parser.error('coverage options now require the coverage package') 718 parser.error('coverage options now require the coverage package')
541 719
542 if options.anycoverage and options.local: 720 if options.anycoverage and options.local:
543 # this needs some path mangling somewhere, I guess 721 # this needs some path mangling somewhere, I guess
544 parser.error("sorry, coverage options do not work when --local " 722 parser.error(
545 "is specified") 723 "sorry, coverage options do not work when --local " "is specified"
724 )
546 725
547 if options.anycoverage and options.with_hg: 726 if options.anycoverage and options.with_hg:
548 parser.error("sorry, coverage options do not work when --with-hg " 727 parser.error(
549 "is specified") 728 "sorry, coverage options do not work when --with-hg " "is specified"
729 )
550 730
551 global verbose 731 global verbose
552 if options.verbose: 732 if options.verbose:
553 verbose = '' 733 verbose = ''
554 734
559 parser.error('--jobs must be positive') 739 parser.error('--jobs must be positive')
560 if options.interactive and options.debug: 740 if options.interactive and options.debug:
561 parser.error("-i/--interactive and -d/--debug are incompatible") 741 parser.error("-i/--interactive and -d/--debug are incompatible")
562 if options.debug: 742 if options.debug:
563 if options.timeout != defaults['timeout']: 743 if options.timeout != defaults['timeout']:
564 sys.stderr.write( 744 sys.stderr.write('warning: --timeout option ignored with --debug\n')
565 'warning: --timeout option ignored with --debug\n')
566 if options.slowtimeout != defaults['slowtimeout']: 745 if options.slowtimeout != defaults['slowtimeout']:
567 sys.stderr.write( 746 sys.stderr.write(
568 'warning: --slowtimeout option ignored with --debug\n') 747 'warning: --slowtimeout option ignored with --debug\n'
748 )
569 options.timeout = 0 749 options.timeout = 0
570 options.slowtimeout = 0 750 options.slowtimeout = 0
571 if options.py3_warnings: 751 if options.py3_warnings:
572 if PYTHON3: 752 if PYTHON3:
573 parser.error( 753 parser.error('--py3-warnings can only be used on Python 2.7')
574 '--py3-warnings can only be used on Python 2.7')
575 754
576 if options.blacklist: 755 if options.blacklist:
577 options.blacklist = parselistfiles(options.blacklist, 'blacklist') 756 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
578 if options.whitelist: 757 if options.whitelist:
579 options.whitelisted = parselistfiles(options.whitelist, 'whitelist') 758 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
583 if options.showchannels: 762 if options.showchannels:
584 options.nodiff = True 763 options.nodiff = True
585 764
586 return options 765 return options
587 766
767
588 def rename(src, dst): 768 def rename(src, dst):
589 """Like os.rename(), trade atomicity and opened files friendliness 769 """Like os.rename(), trade atomicity and opened files friendliness
590 for existing destination support. 770 for existing destination support.
591 """ 771 """
592 shutil.copy(src, dst) 772 shutil.copy(src, dst)
593 os.remove(src) 773 os.remove(src)
774
594 775
595 def makecleanable(path): 776 def makecleanable(path):
596 """Try to fix directory permission recursively so that the entire tree 777 """Try to fix directory permission recursively so that the entire tree
597 can be deleted""" 778 can be deleted"""
598 for dirpath, dirnames, _filenames in os.walk(path, topdown=True): 779 for dirpath, dirnames, _filenames in os.walk(path, topdown=True):
601 try: 782 try:
602 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx 783 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx
603 except OSError: 784 except OSError:
604 pass 785 pass
605 786
787
606 _unified_diff = difflib.unified_diff 788 _unified_diff = difflib.unified_diff
607 if PYTHON3: 789 if PYTHON3:
608 import functools 790 import functools
791
609 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff) 792 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
793
610 794
611 def getdiff(expected, output, ref, err): 795 def getdiff(expected, output, ref, err):
612 servefail = False 796 servefail = False
613 lines = [] 797 lines = []
614 for line in _unified_diff(expected, output, ref, err): 798 for line in _unified_diff(expected, output, ref, err):
616 line = line.replace(b'\\', b'/') 800 line = line.replace(b'\\', b'/')
617 if line.endswith(b' \n'): 801 if line.endswith(b' \n'):
618 line = line[:-2] + b'\n' 802 line = line[:-2] + b'\n'
619 lines.append(line) 803 lines.append(line)
620 if not servefail and line.startswith( 804 if not servefail and line.startswith(
621 b'+ abort: child process failed to start'): 805 b'+ abort: child process failed to start'
806 ):
622 servefail = True 807 servefail = True
623 808
624 return servefail, lines 809 return servefail, lines
625 810
811
626 verbose = False 812 verbose = False
813
814
627 def vlog(*msg): 815 def vlog(*msg):
628 """Log only when in verbose mode.""" 816 """Log only when in verbose mode."""
629 if verbose is False: 817 if verbose is False:
630 return 818 return
631 819
632 return log(*msg) 820 return log(*msg)
821
633 822
634 # Bytes that break XML even in a CDATA block: control characters 0-31 823 # Bytes that break XML even in a CDATA block: control characters 0-31
635 # sans \t, \n and \r 824 # sans \t, \n and \r
636 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]") 825 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
637 826
638 # Match feature conditionalized output lines in the form, capturing the feature 827 # Match feature conditionalized output lines in the form, capturing the feature
639 # list in group 2, and the preceeding line output in group 1: 828 # list in group 2, and the preceeding line output in group 1:
640 # 829 #
641 # output..output (feature !)\n 830 # output..output (feature !)\n
642 optline = re.compile(br'(.*) \((.+?) !\)\n$') 831 optline = re.compile(br'(.*) \((.+?) !\)\n$')
832
643 833
644 def cdatasafe(data): 834 def cdatasafe(data):
645 """Make a string safe to include in a CDATA block. 835 """Make a string safe to include in a CDATA block.
646 836
647 Certain control characters are illegal in a CDATA block, and 837 Certain control characters are illegal in a CDATA block, and
648 there's no way to include a ]]> in a CDATA either. This function 838 there's no way to include a ]]> in a CDATA either. This function
649 replaces illegal bytes with ? and adds a space between the ]] so 839 replaces illegal bytes with ? and adds a space between the ]] so
650 that it won't break the CDATA block. 840 that it won't break the CDATA block.
651 """ 841 """
652 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>') 842 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
843
653 844
654 def log(*msg): 845 def log(*msg):
655 """Log something to stdout. 846 """Log something to stdout.
656 847
657 Arguments are strings to print. 848 Arguments are strings to print.
662 for m in msg: 853 for m in msg:
663 print(m, end=' ') 854 print(m, end=' ')
664 print() 855 print()
665 sys.stdout.flush() 856 sys.stdout.flush()
666 857
858
667 def highlightdiff(line, color): 859 def highlightdiff(line, color):
668 if not color: 860 if not color:
669 return line 861 return line
670 assert pygmentspresent 862 assert pygmentspresent
671 return pygments.highlight(line.decode('latin1'), difflexer, 863 return pygments.highlight(
672 terminal256formatter).encode('latin1') 864 line.decode('latin1'), difflexer, terminal256formatter
865 ).encode('latin1')
866
673 867
674 def highlightmsg(msg, color): 868 def highlightmsg(msg, color):
675 if not color: 869 if not color:
676 return msg 870 return msg
677 assert pygmentspresent 871 assert pygmentspresent
678 return pygments.highlight(msg, runnerlexer, runnerformatter) 872 return pygments.highlight(msg, runnerlexer, runnerformatter)
873
679 874
680 def terminate(proc): 875 def terminate(proc):
681 """Terminate subprocess""" 876 """Terminate subprocess"""
682 vlog('# Terminating process %d' % proc.pid) 877 vlog('# Terminating process %d' % proc.pid)
683 try: 878 try:
684 proc.terminate() 879 proc.terminate()
685 except OSError: 880 except OSError:
686 pass 881 pass
687 882
883
688 def killdaemons(pidfile): 884 def killdaemons(pidfile):
689 import killdaemons as killmod 885 import killdaemons as killmod
690 return killmod.killdaemons(pidfile, tryhard=False, remove=True, 886
691 logfn=vlog) 887 return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog)
888
692 889
693 class Test(unittest.TestCase): 890 class Test(unittest.TestCase):
694 """Encapsulates a single, runnable test. 891 """Encapsulates a single, runnable test.
695 892
696 While this class conforms to the unittest.TestCase API, it differs in that 893 While this class conforms to the unittest.TestCase API, it differs in that
699 """ 896 """
700 897
701 # Status code reserved for skipped tests (used by hghave). 898 # Status code reserved for skipped tests (used by hghave).
702 SKIPPED_STATUS = 80 899 SKIPPED_STATUS = 80
703 900
704 def __init__(self, path, outputdir, tmpdir, keeptmpdir=False, 901 def __init__(
705 debug=False, 902 self,
706 first=False, 903 path,
707 timeout=None, 904 outputdir,
708 startport=None, extraconfigopts=None, 905 tmpdir,
709 py3warnings=False, shell=None, hgcommand=None, 906 keeptmpdir=False,
710 slowtimeout=None, usechg=False, 907 debug=False,
711 useipv6=False): 908 first=False,
909 timeout=None,
910 startport=None,
911 extraconfigopts=None,
912 py3warnings=False,
913 shell=None,
914 hgcommand=None,
915 slowtimeout=None,
916 usechg=False,
917 useipv6=False,
918 ):
712 """Create a test from parameters. 919 """Create a test from parameters.
713 920
714 path is the full path to the file defining the test. 921 path is the full path to the file defining the test.
715 922
716 tmpdir is the main temporary directory to use for this test. 923 tmpdir is the main temporary directory to use for this test.
781 def readrefout(self): 988 def readrefout(self):
782 """read reference output""" 989 """read reference output"""
783 # If we're not in --debug mode and reference output file exists, 990 # If we're not in --debug mode and reference output file exists,
784 # check test output against it. 991 # check test output against it.
785 if self._debug: 992 if self._debug:
786 return None # to match "out is None" 993 return None # to match "out is None"
787 elif os.path.exists(self.refpath): 994 elif os.path.exists(self.refpath):
788 with open(self.refpath, 'rb') as f: 995 with open(self.refpath, 'rb') as f:
789 return f.read().splitlines(True) 996 return f.read().splitlines(True)
790 else: 997 else:
791 return [] 998 return []
828 # file. 1035 # file.
829 if e.errno != errno.ENOENT: 1036 if e.errno != errno.ENOENT:
830 raise 1037 raise
831 1038
832 if self._usechg: 1039 if self._usechg:
833 self._chgsockdir = os.path.join(self._threadtmp, 1040 self._chgsockdir = os.path.join(
834 b'%s.chgsock' % name) 1041 self._threadtmp, b'%s.chgsock' % name
1042 )
835 os.mkdir(self._chgsockdir) 1043 os.mkdir(self._chgsockdir)
836 1044
837 def run(self, result): 1045 def run(self, result):
838 """Run this test and report results against a TestResult instance.""" 1046 """Run this test and report results against a TestResult instance."""
839 # This function is extremely similar to unittest.TestCase.run(). Once 1047 # This function is extremely similar to unittest.TestCase.run(). Once
912 return 'returned error code %d' % ret 1120 return 'returned error code %d' % ret
913 1121
914 self._skipped = False 1122 self._skipped = False
915 1123
916 if ret == self.SKIPPED_STATUS: 1124 if ret == self.SKIPPED_STATUS:
917 if out is None: # Debug mode, nothing to parse. 1125 if out is None: # Debug mode, nothing to parse.
918 missing = ['unknown'] 1126 missing = ['unknown']
919 failed = None 1127 failed = None
920 else: 1128 else:
921 missing, failed = TTest.parsehghaveoutput(out) 1129 missing, failed = TTest.parsehghaveoutput(out)
922 1130
932 self.fail('timed out') 1140 self.fail('timed out')
933 elif ret is False: 1141 elif ret is False:
934 self.fail('no result code from test') 1142 self.fail('no result code from test')
935 elif out != self._refout: 1143 elif out != self._refout:
936 # Diff generation may rely on written .err file. 1144 # Diff generation may rely on written .err file.
937 if ((ret != 0 or out != self._refout) and not self._skipped 1145 if (
938 and not self._debug): 1146 (ret != 0 or out != self._refout)
1147 and not self._skipped
1148 and not self._debug
1149 ):
939 with open(self.errpath, 'wb') as f: 1150 with open(self.errpath, 'wb') as f:
940 for line in out: 1151 for line in out:
941 f.write(line) 1152 f.write(line)
942 1153
943 # The result object handles diff calculation for us. 1154 # The result object handles diff calculation for us.
963 for entry in self._daemonpids: 1174 for entry in self._daemonpids:
964 killdaemons(entry) 1175 killdaemons(entry)
965 self._daemonpids = [] 1176 self._daemonpids = []
966 1177
967 if self._keeptmpdir: 1178 if self._keeptmpdir:
968 log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' % 1179 log(
969 (self._testtmp.decode('utf-8'), 1180 '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s'
970 self._threadtmp.decode('utf-8'))) 1181 % (
1182 self._testtmp.decode('utf-8'),
1183 self._threadtmp.decode('utf-8'),
1184 )
1185 )
971 else: 1186 else:
972 try: 1187 try:
973 shutil.rmtree(self._testtmp) 1188 shutil.rmtree(self._testtmp)
974 except OSError: 1189 except OSError:
975 # unreadable directory may be left in $TESTTMP; fix permission 1190 # unreadable directory may be left in $TESTTMP; fix permission
981 if self._usechg: 1196 if self._usechg:
982 # chgservers will stop automatically after they find the socket 1197 # chgservers will stop automatically after they find the socket
983 # files are deleted 1198 # files are deleted
984 shutil.rmtree(self._chgsockdir, True) 1199 shutil.rmtree(self._chgsockdir, True)
985 1200
986 if ((self._ret != 0 or self._out != self._refout) and not self._skipped 1201 if (
987 and not self._debug and self._out): 1202 (self._ret != 0 or self._out != self._refout)
1203 and not self._skipped
1204 and not self._debug
1205 and self._out
1206 ):
988 with open(self.errpath, 'wb') as f: 1207 with open(self.errpath, 'wb') as f:
989 for line in self._out: 1208 for line in self._out:
990 f.write(line) 1209 f.write(line)
991 1210
992 vlog("# Ret was:", self._ret, '(%s)' % self.name) 1211 vlog("# Ret was:", self._ret, '(%s)' % self.name)
1015 self._portmap(0), 1234 self._portmap(0),
1016 self._portmap(1), 1235 self._portmap(1),
1017 self._portmap(2), 1236 self._portmap(2),
1018 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'), 1237 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
1019 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'), 1238 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
1020 ] 1239 ]
1021 r.append((self._escapepath(self._testtmp), b'$TESTTMP')) 1240 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
1022 1241
1023 replacementfile = os.path.join(self._testdir, b'common-pattern.py') 1242 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1024 1243
1025 if os.path.exists(replacementfile): 1244 if os.path.exists(replacementfile):
1036 r.append(value) 1255 r.append(value)
1037 return r 1256 return r
1038 1257
1039 def _escapepath(self, p): 1258 def _escapepath(self, p):
1040 if os.name == 'nt': 1259 if os.name == 'nt':
1041 return ( 1260 return b''.join(
1042 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or 1261 c.isalpha()
1043 c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c 1262 and b'[%s%s]' % (c.lower(), c.upper())
1044 for c in [p[i:i + 1] for i in range(len(p))])) 1263 or c in b'/\\'
1264 and br'[/\\]'
1265 or c.isdigit()
1266 and c
1267 or b'\\' + c
1268 for c in [p[i : i + 1] for i in range(len(p))]
1045 ) 1269 )
1046 else: 1270 else:
1047 return re.escape(p) 1271 return re.escape(p)
1048 1272
1049 def _localip(self): 1273 def _localip(self):
1081 continue 1305 continue
1082 envf.write('unset %s\n' % (name,)) 1306 envf.write('unset %s\n' % (name,))
1083 1307
1084 def _getenv(self): 1308 def _getenv(self):
1085 """Obtain environment variables to use during test execution.""" 1309 """Obtain environment variables to use during test execution."""
1310
1086 def defineport(i): 1311 def defineport(i):
1087 offset = '' if i == 0 else '%s' % i 1312 offset = '' if i == 0 else '%s' % i
1088 env["HGPORT%s" % offset] = '%s' % (self._startport + i) 1313 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1314
1089 env = os.environ.copy() 1315 env = os.environ.copy()
1090 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or '' 1316 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1091 env['HGEMITWARNINGS'] = '1' 1317 env['HGEMITWARNINGS'] = '1'
1092 env['TESTTMP'] = _strpath(self._testtmp) 1318 env['TESTTMP'] = _strpath(self._testtmp)
1093 env['TESTNAME'] = self.name 1319 env['TESTNAME'] = self.name
1095 # This number should match portneeded in _getport 1321 # This number should match portneeded in _getport
1096 for port in xrange(3): 1322 for port in xrange(3):
1097 # This list should be parallel to _portmap in _getreplacements 1323 # This list should be parallel to _portmap in _getreplacements
1098 defineport(port) 1324 defineport(port)
1099 env["HGRCPATH"] = _strpath(os.path.join(self._threadtmp, b'.hgrc')) 1325 env["HGRCPATH"] = _strpath(os.path.join(self._threadtmp, b'.hgrc'))
1100 env["DAEMON_PIDS"] = _strpath(os.path.join(self._threadtmp, 1326 env["DAEMON_PIDS"] = _strpath(
1101 b'daemon.pids')) 1327 os.path.join(self._threadtmp, b'daemon.pids')
1102 env["HGEDITOR"] = ('"' + sysexecutable + '"' 1328 )
1103 + ' -c "import sys; sys.exit(0)"') 1329 env["HGEDITOR"] = (
1104 env["HGUSER"] = "test" 1330 '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
1331 )
1332 env["HGUSER"] = "test"
1105 env["HGENCODING"] = "ascii" 1333 env["HGENCODING"] = "ascii"
1106 env["HGENCODINGMODE"] = "strict" 1334 env["HGENCODINGMODE"] = "strict"
1107 env["HGHOSTNAME"] = "test-hostname" 1335 env["HGHOSTNAME"] = "test-hostname"
1108 env['HGIPV6'] = str(int(self._useipv6)) 1336 env['HGIPV6'] = str(int(self._useipv6))
1109 # See contrib/catapipe.py for how to use this functionality. 1337 # See contrib/catapipe.py for how to use this functionality.
1110 if 'HGTESTCATAPULTSERVERPIPE' not in env: 1338 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1111 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the 1339 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1112 # non-test one in as a default, otherwise set to devnull 1340 # non-test one in as a default, otherwise set to devnull
1113 env['HGTESTCATAPULTSERVERPIPE'] = env.get( 1341 env['HGTESTCATAPULTSERVERPIPE'] = env.get(
1114 'HGCATAPULTSERVERPIPE', os.devnull) 1342 'HGCATAPULTSERVERPIPE', os.devnull
1343 )
1115 1344
1116 extraextensions = [] 1345 extraextensions = []
1117 for opt in self._extraconfigopts: 1346 for opt in self._extraconfigopts:
1118 section, key = opt.encode('utf-8').split(b'.', 1) 1347 section, key = opt.encode('utf-8').split(b'.', 1)
1119 if section != 'extensions': 1348 if section != 'extensions':
1185 hgrc.write(b'[defaults]\n') 1414 hgrc.write(b'[defaults]\n')
1186 hgrc.write(b'[devel]\n') 1415 hgrc.write(b'[devel]\n')
1187 hgrc.write(b'all-warnings = true\n') 1416 hgrc.write(b'all-warnings = true\n')
1188 hgrc.write(b'default-date = 0 0\n') 1417 hgrc.write(b'default-date = 0 0\n')
1189 hgrc.write(b'[largefiles]\n') 1418 hgrc.write(b'[largefiles]\n')
1190 hgrc.write(b'usercache = %s\n' % 1419 hgrc.write(
1191 (os.path.join(self._testtmp, b'.cache/largefiles'))) 1420 b'usercache = %s\n'
1421 % (os.path.join(self._testtmp, b'.cache/largefiles'))
1422 )
1192 hgrc.write(b'[lfs]\n') 1423 hgrc.write(b'[lfs]\n')
1193 hgrc.write(b'usercache = %s\n' % 1424 hgrc.write(
1194 (os.path.join(self._testtmp, b'.cache/lfs'))) 1425 b'usercache = %s\n'
1426 % (os.path.join(self._testtmp, b'.cache/lfs'))
1427 )
1195 hgrc.write(b'[web]\n') 1428 hgrc.write(b'[web]\n')
1196 hgrc.write(b'address = localhost\n') 1429 hgrc.write(b'address = localhost\n')
1197 hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii')) 1430 hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii'))
1198 hgrc.write(b'server-header = testing stub value\n') 1431 hgrc.write(b'server-header = testing stub value\n')
1199 1432
1200 for opt in self._extraconfigopts: 1433 for opt in self._extraconfigopts:
1201 section, key = opt.encode('utf-8').split(b'.', 1) 1434 section, key = opt.encode('utf-8').split(b'.', 1)
1202 assert b'=' in key, ('extra config opt %s must ' 1435 assert b'=' in key, (
1203 'have an = for assignment' % opt) 1436 'extra config opt %s must ' 'have an = for assignment' % opt
1437 )
1204 hgrc.write(b'[%s]\n%s\n' % (section, key)) 1438 hgrc.write(b'[%s]\n%s\n' % (section, key))
1205 1439
1206 def fail(self, msg): 1440 def fail(self, msg):
1207 # unittest differentiates between errored and failed. 1441 # unittest differentiates between errored and failed.
1208 # Failed is denoted by AssertionError (by default at least). 1442 # Failed is denoted by AssertionError (by default at least).
1213 stderr). 1447 stderr).
1214 1448
1215 Return a tuple (exitcode, output). output is None in debug mode. 1449 Return a tuple (exitcode, output). output is None in debug mode.
1216 """ 1450 """
1217 if self._debug: 1451 if self._debug:
1218 proc = subprocess.Popen(_strpath(cmd), shell=True, 1452 proc = subprocess.Popen(
1219 cwd=_strpath(self._testtmp), 1453 _strpath(cmd), shell=True, cwd=_strpath(self._testtmp), env=env
1220 env=env) 1454 )
1221 ret = proc.wait() 1455 ret = proc.wait()
1222 return (ret, None) 1456 return (ret, None)
1223 1457
1224 proc = Popen4(cmd, self._testtmp, self._timeout, env) 1458 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1459
1225 def cleanup(): 1460 def cleanup():
1226 terminate(proc) 1461 terminate(proc)
1227 ret = proc.wait() 1462 ret = proc.wait()
1228 if ret == 0: 1463 if ret == 0:
1229 ret = signal.SIGTERM << 8 1464 ret = signal.SIGTERM << 8
1255 if normalizenewlines: 1490 if normalizenewlines:
1256 output = output.replace(b'\r\n', b'\n') 1491 output = output.replace(b'\r\n', b'\n')
1257 1492
1258 return ret, output.splitlines(True) 1493 return ret, output.splitlines(True)
1259 1494
1495
1260 class PythonTest(Test): 1496 class PythonTest(Test):
1261 """A Python-based test.""" 1497 """A Python-based test."""
1262 1498
1263 @property 1499 @property
1264 def refpath(self): 1500 def refpath(self):
1268 py3switch = self._py3warnings and b' -3' or b'' 1504 py3switch = self._py3warnings and b' -3' or b''
1269 # Quote the python(3) executable for Windows 1505 # Quote the python(3) executable for Windows
1270 cmd = b'"%s"%s "%s"' % (PYTHON, py3switch, self.path) 1506 cmd = b'"%s"%s "%s"' % (PYTHON, py3switch, self.path)
1271 vlog("# Running", cmd) 1507 vlog("# Running", cmd)
1272 normalizenewlines = os.name == 'nt' 1508 normalizenewlines = os.name == 'nt'
1273 result = self._runcommand(cmd, env, 1509 result = self._runcommand(cmd, env, normalizenewlines=normalizenewlines)
1274 normalizenewlines=normalizenewlines)
1275 if self._aborted: 1510 if self._aborted:
1276 raise KeyboardInterrupt() 1511 raise KeyboardInterrupt()
1277 1512
1278 return result 1513 return result
1514
1279 1515
1280 # Some glob patterns apply only in some circumstances, so the script 1516 # Some glob patterns apply only in some circumstances, so the script
1281 # might want to remove (glob) annotations that otherwise should be 1517 # might want to remove (glob) annotations that otherwise should be
1282 # retained. 1518 # retained.
1283 checkcodeglobpats = [ 1519 checkcodeglobpats = [
1299 WARN_YES = 2 1535 WARN_YES = 2
1300 WARN_NO = 3 1536 WARN_NO = 3
1301 1537
1302 MARK_OPTIONAL = b" (?)\n" 1538 MARK_OPTIONAL = b" (?)\n"
1303 1539
1540
1304 def isoptional(line): 1541 def isoptional(line):
1305 return line.endswith(MARK_OPTIONAL) 1542 return line.endswith(MARK_OPTIONAL)
1543
1306 1544
1307 class TTest(Test): 1545 class TTest(Test):
1308 """A "t test" is a test backed by a .t file.""" 1546 """A "t test" is a test backed by a .t file."""
1309 1547
1310 SKIPPED_PREFIX = b'skipped: ' 1548 SKIPPED_PREFIX = b'skipped: '
1374 return self._have.get(allreqs) 1612 return self._have.get(allreqs)
1375 1613
1376 # TODO do something smarter when all other uses of hghave are gone. 1614 # TODO do something smarter when all other uses of hghave are gone.
1377 runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__))) 1615 runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
1378 tdir = runtestdir.replace(b'\\', b'/') 1616 tdir = runtestdir.replace(b'\\', b'/')
1379 proc = Popen4(b'%s -c "%s/hghave %s"' % 1617 proc = Popen4(
1380 (self._shell, tdir, allreqs), 1618 b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
1381 self._testtmp, 0, self._getenv()) 1619 self._testtmp,
1620 0,
1621 self._getenv(),
1622 )
1382 stdout, stderr = proc.communicate() 1623 stdout, stderr = proc.communicate()
1383 ret = proc.wait() 1624 ret = proc.wait()
1384 if wifexited(ret): 1625 if wifexited(ret):
1385 ret = os.WEXITSTATUS(ret) 1626 ret = os.WEXITSTATUS(ret)
1386 if ret == 2: 1627 if ret == 2:
1417 def _parsetest(self, lines): 1658 def _parsetest(self, lines):
1418 # We generate a shell script which outputs unique markers to line 1659 # We generate a shell script which outputs unique markers to line
1419 # up script results with our source. These markers include input 1660 # up script results with our source. These markers include input
1420 # line number and the last return code. 1661 # line number and the last return code.
1421 salt = b"SALT%d" % time.time() 1662 salt = b"SALT%d" % time.time()
1663
1422 def addsalt(line, inpython): 1664 def addsalt(line, inpython):
1423 if inpython: 1665 if inpython:
1424 script.append(b'%s %d 0\n' % (salt, line)) 1666 script.append(b'%s %d 0\n' % (salt, line))
1425 else: 1667 else:
1426 script.append(b'echo %s %d $?\n' % (salt, line)) 1668 script.append(b'echo %s %d $?\n' % (salt, line))
1669
1427 activetrace = [] 1670 activetrace = []
1428 session = str(uuid.uuid4()) 1671 session = str(uuid.uuid4())
1429 if PYTHON3: 1672 if PYTHON3:
1430 session = session.encode('ascii') 1673 session = session.encode('ascii')
1431 hgcatapult = (os.getenv('HGTESTCATAPULTSERVERPIPE') or 1674 hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
1432 os.getenv('HGCATAPULTSERVERPIPE')) 1675 'HGCATAPULTSERVERPIPE'
1676 )
1677
1433 def toggletrace(cmd=None): 1678 def toggletrace(cmd=None):
1434 if not hgcatapult or hgcatapult == os.devnull: 1679 if not hgcatapult or hgcatapult == os.devnull:
1435 return 1680 return
1436 1681
1437 if activetrace: 1682 if activetrace:
1438 script.append( 1683 script.append(
1439 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n' % ( 1684 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1440 session, activetrace[0])) 1685 % (session, activetrace[0])
1686 )
1441 if cmd is None: 1687 if cmd is None:
1442 return 1688 return
1443 1689
1444 if isinstance(cmd, str): 1690 if isinstance(cmd, str):
1445 quoted = shellquote(cmd.strip()) 1691 quoted = shellquote(cmd.strip())
1446 else: 1692 else:
1447 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8') 1693 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1448 quoted = quoted.replace(b'\\', b'\\\\') 1694 quoted = quoted.replace(b'\\', b'\\\\')
1449 script.append( 1695 script.append(
1450 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n' % ( 1696 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1451 session, quoted)) 1697 % (session, quoted)
1698 )
1452 activetrace[0:] = [quoted] 1699 activetrace[0:] = [quoted]
1453 1700
1454 script = [] 1701 script = []
1455 1702
1456 # After we run the shell script, we re-unify the script output 1703 # After we run the shell script, we re-unify the script output
1548 after.setdefault(pos, []).append(' !!! missing #if\n') 1795 after.setdefault(pos, []).append(' !!! missing #if\n')
1549 skipping = None 1796 skipping = None
1550 after.setdefault(pos, []).append(l) 1797 after.setdefault(pos, []).append(l)
1551 elif skipping: 1798 elif skipping:
1552 after.setdefault(pos, []).append(l) 1799 after.setdefault(pos, []).append(l)
1553 elif l.startswith(b' >>> '): # python inlines 1800 elif l.startswith(b' >>> '): # python inlines
1554 after.setdefault(pos, []).append(l) 1801 after.setdefault(pos, []).append(l)
1555 prepos = pos 1802 prepos = pos
1556 pos = n 1803 pos = n
1557 if not inpython: 1804 if not inpython:
1558 # We've just entered a Python block. Add the header. 1805 # We've just entered a Python block. Add the header.
1559 inpython = True 1806 inpython = True
1560 addsalt(prepos, False) # Make sure we report the exit code. 1807 addsalt(prepos, False) # Make sure we report the exit code.
1561 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON) 1808 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1562 addsalt(n, True) 1809 addsalt(n, True)
1563 script.append(l[2:]) 1810 script.append(l[2:])
1564 elif l.startswith(b' ... '): # python inlines 1811 elif l.startswith(b' ... '): # python inlines
1565 after.setdefault(prepos, []).append(l) 1812 after.setdefault(prepos, []).append(l)
1566 script.append(l[2:]) 1813 script.append(l[2:])
1567 elif l.startswith(b' $ '): # commands 1814 elif l.startswith(b' $ '): # commands
1568 if inpython: 1815 if inpython:
1569 script.append(b'EOF\n') 1816 script.append(b'EOF\n')
1570 inpython = False 1817 inpython = False
1571 after.setdefault(pos, []).append(l) 1818 after.setdefault(pos, []).append(l)
1572 prepos = pos 1819 prepos = pos
1576 cmd = rawcmd.split() 1823 cmd = rawcmd.split()
1577 toggletrace(rawcmd) 1824 toggletrace(rawcmd)
1578 if len(cmd) == 2 and cmd[0] == b'cd': 1825 if len(cmd) == 2 and cmd[0] == b'cd':
1579 l = b' $ cd %s || exit 1\n' % cmd[1] 1826 l = b' $ cd %s || exit 1\n' % cmd[1]
1580 script.append(rawcmd) 1827 script.append(rawcmd)
1581 elif l.startswith(b' > '): # continuations 1828 elif l.startswith(b' > '): # continuations
1582 after.setdefault(prepos, []).append(l) 1829 after.setdefault(prepos, []).append(l)
1583 script.append(l[4:]) 1830 script.append(l[4:])
1584 elif l.startswith(b' '): # results 1831 elif l.startswith(b' '): # results
1585 # Queue up a list of expected results. 1832 # Queue up a list of expected results.
1586 expected.setdefault(pos, []).append(l[2:]) 1833 expected.setdefault(pos, []).append(l[2:])
1587 else: 1834 else:
1588 if inpython: 1835 if inpython:
1589 script.append(b'EOF\n') 1836 script.append(b'EOF\n')
1601 toggletrace() 1848 toggletrace()
1602 return salt, script, after, expected 1849 return salt, script, after, expected
1603 1850
1604 def _processoutput(self, exitcode, output, salt, after, expected): 1851 def _processoutput(self, exitcode, output, salt, after, expected):
1605 # Merge the script output back into a unified test. 1852 # Merge the script output back into a unified test.
1606 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not 1853 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
1607 if exitcode != 0: 1854 if exitcode != 0:
1608 warnonly = WARN_NO 1855 warnonly = WARN_NO
1609 1856
1610 pos = -1 1857 pos = -1
1611 postout = [] 1858 postout = []
1612 for out_rawline in output: 1859 for out_rawline in output:
1613 out_line, cmd_line = out_rawline, None 1860 out_line, cmd_line = out_rawline, None
1614 if salt in out_rawline: 1861 if salt in out_rawline:
1615 out_line, cmd_line = out_rawline.split(salt, 1) 1862 out_line, cmd_line = out_rawline.split(salt, 1)
1616 1863
1617 pos, postout, warnonly = self._process_out_line(out_line, 1864 pos, postout, warnonly = self._process_out_line(
1618 pos, 1865 out_line, pos, postout, expected, warnonly
1619 postout, 1866 )
1620 expected, 1867 pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
1621 warnonly)
1622 pos, postout = self._process_cmd_line(cmd_line, pos, postout,
1623 after)
1624 1868
1625 if pos in after: 1869 if pos in after:
1626 postout += after.pop(pos) 1870 postout += after.pop(pos)
1627 1871
1628 if warnonly == WARN_YES: 1872 if warnonly == WARN_YES:
1629 exitcode = False # Set exitcode to warned. 1873 exitcode = False # Set exitcode to warned.
1630 1874
1631 return exitcode, postout 1875 return exitcode, postout
1632 1876
1633 def _process_out_line(self, out_line, pos, postout, expected, warnonly): 1877 def _process_out_line(self, out_line, pos, postout, expected, warnonly):
1634 while out_line: 1878 while out_line:
1646 if el: 1890 if el:
1647 r, exact = self.linematch(el, out_line) 1891 r, exact = self.linematch(el, out_line)
1648 if isinstance(r, str): 1892 if isinstance(r, str):
1649 if r == '-glob': 1893 if r == '-glob':
1650 out_line = ''.join(el.rsplit(' (glob)', 1)) 1894 out_line = ''.join(el.rsplit(' (glob)', 1))
1651 r = '' # Warn only this line. 1895 r = '' # Warn only this line.
1652 elif r == "retry": 1896 elif r == "retry":
1653 postout.append(b' ' + el) 1897 postout.append(b' ' + el)
1654 else: 1898 else:
1655 log('\ninfo, unknown linematch result: %r\n' % r) 1899 log('\ninfo, unknown linematch result: %r\n' % r)
1656 r = False 1900 r = False
1661 if isoptional(el): 1905 if isoptional(el):
1662 optional.append(i) 1906 optional.append(i)
1663 else: 1907 else:
1664 m = optline.match(el) 1908 m = optline.match(el)
1665 if m: 1909 if m:
1666 conditions = [ 1910 conditions = [c for c in m.group(2).split(b' ')]
1667 c for c in m.group(2).split(b' ')]
1668 1911
1669 if not self._iftest(conditions): 1912 if not self._iftest(conditions):
1670 optional.append(i) 1913 optional.append(i)
1671 if exact: 1914 if exact:
1672 # Don't allow line to be matches against a later 1915 # Don't allow line to be matches against a later
1683 for i in reversed(optional): 1926 for i in reversed(optional):
1684 del els[i] 1927 del els[i]
1685 postout.append(b' ' + el) 1928 postout.append(b' ' + el)
1686 else: 1929 else:
1687 if self.NEEDESCAPE(out_line): 1930 if self.NEEDESCAPE(out_line):
1688 out_line = TTest._stringescape(b'%s (esc)\n' % 1931 out_line = TTest._stringescape(
1689 out_line.rstrip(b'\n')) 1932 b'%s (esc)\n' % out_line.rstrip(b'\n')
1690 postout.append(b' ' + out_line) # Let diff deal with it. 1933 )
1691 if r != '': # If line failed. 1934 postout.append(b' ' + out_line) # Let diff deal with it.
1935 if r != '': # If line failed.
1692 warnonly = WARN_NO 1936 warnonly = WARN_NO
1693 elif warnonly == WARN_UNDEFINED: 1937 elif warnonly == WARN_UNDEFINED:
1694 warnonly = WARN_YES 1938 warnonly = WARN_YES
1695 break 1939 break
1696 else: 1940 else:
1750 return True 1994 return True
1751 el = el.replace(b'$LOCALIP', b'*') 1995 el = el.replace(b'$LOCALIP', b'*')
1752 i, n = 0, len(el) 1996 i, n = 0, len(el)
1753 res = b'' 1997 res = b''
1754 while i < n: 1998 while i < n:
1755 c = el[i:i + 1] 1999 c = el[i : i + 1]
1756 i += 1 2000 i += 1
1757 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/': 2001 if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
1758 res += el[i - 1:i + 1] 2002 res += el[i - 1 : i + 1]
1759 i += 1 2003 i += 1
1760 elif c == b'*': 2004 elif c == b'*':
1761 res += b'.*' 2005 res += b'.*'
1762 elif c == b'?': 2006 elif c == b'?':
1763 res += b'.' 2007 res += b'.'
1766 else: 2010 else:
1767 res += re.escape(c) 2011 res += re.escape(c)
1768 return TTest.rematch(res, l) 2012 return TTest.rematch(res, l)
1769 2013
1770 def linematch(self, el, l): 2014 def linematch(self, el, l):
1771 if el == l: # perfect match (fast) 2015 if el == l: # perfect match (fast)
1772 return True, True 2016 return True, True
1773 retry = False 2017 retry = False
1774 if isoptional(el): 2018 if isoptional(el):
1775 retry = "retry" 2019 retry = "retry"
1776 el = el[:-len(MARK_OPTIONAL)] + b"\n" 2020 el = el[: -len(MARK_OPTIONAL)] + b"\n"
1777 else: 2021 else:
1778 m = optline.match(el) 2022 m = optline.match(el)
1779 if m: 2023 if m:
1780 conditions = [c for c in m.group(2).split(b' ')] 2024 conditions = [c for c in m.group(2).split(b' ')]
1781 2025
1815 missing = [] 2059 missing = []
1816 failed = [] 2060 failed = []
1817 for line in lines: 2061 for line in lines:
1818 if line.startswith(TTest.SKIPPED_PREFIX): 2062 if line.startswith(TTest.SKIPPED_PREFIX):
1819 line = line.splitlines()[0] 2063 line = line.splitlines()[0]
1820 missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8')) 2064 missing.append(
2065 line[len(TTest.SKIPPED_PREFIX) :].decode('utf-8')
2066 )
1821 elif line.startswith(TTest.FAILED_PREFIX): 2067 elif line.startswith(TTest.FAILED_PREFIX):
1822 line = line.splitlines()[0] 2068 line = line.splitlines()[0]
1823 failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8')) 2069 failed.append(line[len(TTest.FAILED_PREFIX) :].decode('utf-8'))
1824 2070
1825 return missing, failed 2071 return missing, failed
1826 2072
1827 @staticmethod 2073 @staticmethod
1828 def _escapef(m): 2074 def _escapef(m):
1830 2076
1831 @staticmethod 2077 @staticmethod
1832 def _stringescape(s): 2078 def _stringescape(s):
1833 return TTest.ESCAPESUB(TTest._escapef, s) 2079 return TTest.ESCAPESUB(TTest._escapef, s)
1834 2080
2081
1835 iolock = threading.RLock() 2082 iolock = threading.RLock()
1836 firstlock = threading.RLock() 2083 firstlock = threading.RLock()
1837 firsterror = False 2084 firsterror = False
1838 2085
2086
1839 class TestResult(unittest._TextTestResult): 2087 class TestResult(unittest._TextTestResult):
1840 """Holds results when executing via unittest.""" 2088 """Holds results when executing via unittest."""
2089
1841 # Don't worry too much about accessing the non-public _TextTestResult. 2090 # Don't worry too much about accessing the non-public _TextTestResult.
1842 # It is relatively common in Python testing tools. 2091 # It is relatively common in Python testing tools.
1843 def __init__(self, options, *args, **kwargs): 2092 def __init__(self, options, *args, **kwargs):
1844 super(TestResult, self).__init__(*args, **kwargs) 2093 super(TestResult, self).__init__(*args, **kwargs)
1845 2094
1862 2111
1863 if options.color == 'auto': 2112 if options.color == 'auto':
1864 self.color = pygmentspresent and self.stream.isatty() 2113 self.color = pygmentspresent and self.stream.isatty()
1865 elif options.color == 'never': 2114 elif options.color == 'never':
1866 self.color = False 2115 self.color = False
1867 else: # 'always', for testing purposes 2116 else: # 'always', for testing purposes
1868 self.color = pygmentspresent 2117 self.color = pygmentspresent
1869 2118
1870 def onStart(self, test): 2119 def onStart(self, test):
1871 """ Can be overriden by custom TestResult 2120 """ Can be overriden by custom TestResult
1872 """ 2121 """
1940 with iolock: 2189 with iolock:
1941 if self._options.nodiff: 2190 if self._options.nodiff:
1942 pass 2191 pass
1943 elif self._options.view: 2192 elif self._options.view:
1944 v = self._options.view 2193 v = self._options.view
1945 subprocess.call(r'"%s" "%s" "%s"' % 2194 subprocess.call(
1946 (v, _strpath(test.refpath), 2195 r'"%s" "%s" "%s"'
1947 _strpath(test.errpath)), shell=True) 2196 % (v, _strpath(test.refpath), _strpath(test.errpath)),
2197 shell=True,
2198 )
1948 else: 2199 else:
1949 servefail, lines = getdiff(expected, got, 2200 servefail, lines = getdiff(
1950 test.refpath, test.errpath) 2201 expected, got, test.refpath, test.errpath
2202 )
1951 self.stream.write('\n') 2203 self.stream.write('\n')
1952 for line in lines: 2204 for line in lines:
1953 line = highlightdiff(line, self.color) 2205 line = highlightdiff(line, self.color)
1954 if PYTHON3: 2206 if PYTHON3:
1955 self.stream.flush() 2207 self.stream.flush()
1959 self.stream.write(line) 2211 self.stream.write(line)
1960 self.stream.flush() 2212 self.stream.flush()
1961 2213
1962 if servefail: 2214 if servefail:
1963 raise test.failureException( 2215 raise test.failureException(
1964 'server failed to start (HGPORT=%s)' % test._startport) 2216 'server failed to start (HGPORT=%s)' % test._startport
2217 )
1965 2218
1966 # handle interactive prompt without releasing iolock 2219 # handle interactive prompt without releasing iolock
1967 if self._options.interactive: 2220 if self._options.interactive:
1968 if test.readrefout() != expected: 2221 if test.readrefout() != expected:
1969 self.stream.write( 2222 self.stream.write(
1970 'Reference output has changed (run again to prompt ' 2223 'Reference output has changed (run again to prompt '
1971 'changes)') 2224 'changes)'
2225 )
1972 else: 2226 else:
1973 self.stream.write('Accept this change? [n] ') 2227 self.stream.write('Accept this change? [n] ')
1974 self.stream.flush() 2228 self.stream.flush()
1975 answer = sys.stdin.readline().strip() 2229 answer = sys.stdin.readline().strip()
1976 if answer.lower() in ('y', 'yes'): 2230 if answer.lower() in ('y', 'yes'):
1990 # os.times module computes the user time and system time spent by 2244 # os.times module computes the user time and system time spent by
1991 # child's processes along with real elapsed time taken by a process. 2245 # child's processes along with real elapsed time taken by a process.
1992 # This module has one limitation. It can only work for Linux user 2246 # This module has one limitation. It can only work for Linux user
1993 # and not for Windows. 2247 # and not for Windows.
1994 test.started = os.times() 2248 test.started = os.times()
1995 if self._firststarttime is None: # thread racy but irrelevant 2249 if self._firststarttime is None: # thread racy but irrelevant
1996 self._firststarttime = test.started[4] 2250 self._firststarttime = test.started[4]
1997 2251
1998 def stopTest(self, test, interrupted=False): 2252 def stopTest(self, test, interrupted=False):
1999 super(TestResult, self).stopTest(test) 2253 super(TestResult, self).stopTest(test)
2000 2254
2001 test.stopped = os.times() 2255 test.stopped = os.times()
2002 2256
2003 starttime = test.started 2257 starttime = test.started
2004 endtime = test.stopped 2258 endtime = test.stopped
2005 origin = self._firststarttime 2259 origin = self._firststarttime
2006 self.times.append((test.name, 2260 self.times.append(
2007 endtime[2] - starttime[2], # user space CPU time 2261 (
2008 endtime[3] - starttime[3], # sys space CPU time 2262 test.name,
2009 endtime[4] - starttime[4], # real time 2263 endtime[2] - starttime[2], # user space CPU time
2010 starttime[4] - origin, # start date in run context 2264 endtime[3] - starttime[3], # sys space CPU time
2011 endtime[4] - origin, # end date in run context 2265 endtime[4] - starttime[4], # real time
2012 )) 2266 starttime[4] - origin, # start date in run context
2267 endtime[4] - origin, # end date in run context
2268 )
2269 )
2013 2270
2014 if interrupted: 2271 if interrupted:
2015 with iolock: 2272 with iolock:
2016 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % ( 2273 self.stream.writeln(
2017 test.name, self.times[-1][3])) 2274 'INTERRUPTED: %s (after %d seconds)'
2275 % (test.name, self.times[-1][3])
2276 )
2277
2018 2278
2019 def getTestResult(): 2279 def getTestResult():
2020 """ 2280 """
2021 Returns the relevant test result 2281 Returns the relevant test result
2022 """ 2282 """
2024 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"]) 2284 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
2025 return testresultmodule.TestResult 2285 return testresultmodule.TestResult
2026 else: 2286 else:
2027 return TestResult 2287 return TestResult
2028 2288
2289
2029 class TestSuite(unittest.TestSuite): 2290 class TestSuite(unittest.TestSuite):
2030 """Custom unittest TestSuite that knows how to execute Mercurial tests.""" 2291 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
2031 2292
2032 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None, 2293 def __init__(
2033 retest=False, keywords=None, loop=False, runs_per_test=1, 2294 self,
2034 loadtest=None, showchannels=False, 2295 testdir,
2035 *args, **kwargs): 2296 jobs=1,
2297 whitelist=None,
2298 blacklist=None,
2299 retest=False,
2300 keywords=None,
2301 loop=False,
2302 runs_per_test=1,
2303 loadtest=None,
2304 showchannels=False,
2305 *args,
2306 **kwargs
2307 ):
2036 """Create a new instance that can run tests with a configuration. 2308 """Create a new instance that can run tests with a configuration.
2037 2309
2038 testdir specifies the directory where tests are executed from. This 2310 testdir specifies the directory where tests are executed from. This
2039 is typically the ``tests`` directory from Mercurial's source 2311 is typically the ``tests`` directory from Mercurial's source
2040 repository. 2312 repository.
2077 # here instead of inside Test because it makes the running logic for 2349 # here instead of inside Test because it makes the running logic for
2078 # Test simpler. 2350 # Test simpler.
2079 tests = [] 2351 tests = []
2080 num_tests = [0] 2352 num_tests = [0]
2081 for test in self._tests: 2353 for test in self._tests:
2354
2082 def get(): 2355 def get():
2083 num_tests[0] += 1 2356 num_tests[0] += 1
2084 if getattr(test, 'should_reload', False): 2357 if getattr(test, 'should_reload', False):
2085 return self._loadtest(test, num_tests[0]) 2358 return self._loadtest(test, num_tests[0])
2086 return test 2359 return test
2360
2087 if not os.path.exists(test.path): 2361 if not os.path.exists(test.path):
2088 result.addSkip(test, "Doesn't exist") 2362 result.addSkip(test, "Doesn't exist")
2089 continue 2363 continue
2090 2364
2091 if not (self._whitelist and test.bname in self._whitelist): 2365 if not (self._whitelist and test.bname in self._whitelist):
2129 try: 2403 try:
2130 test(result) 2404 test(result)
2131 done.put(None) 2405 done.put(None)
2132 except KeyboardInterrupt: 2406 except KeyboardInterrupt:
2133 pass 2407 pass
2134 except: # re-raises 2408 except: # re-raises
2135 done.put(('!', test, 'run-test raised an error, see traceback')) 2409 done.put(('!', test, 'run-test raised an error, see traceback'))
2136 raise 2410 raise
2137 finally: 2411 finally:
2138 try: 2412 try:
2139 channels[channel] = '' 2413 channels[channel] = ''
2154 with iolock: 2428 with iolock:
2155 sys.stdout.write(d + ' ') 2429 sys.stdout.write(d + ' ')
2156 sys.stdout.flush() 2430 sys.stdout.flush()
2157 for x in xrange(10): 2431 for x in xrange(10):
2158 if channels: 2432 if channels:
2159 time.sleep(.1) 2433 time.sleep(0.1)
2160 count += 1 2434 count += 1
2161 2435
2162 stoppedearly = False 2436 stoppedearly = False
2163 2437
2164 if self._showchannels: 2438 if self._showchannels:
2179 if tests and not running == self._jobs: 2453 if tests and not running == self._jobs:
2180 test = tests.pop(0) 2454 test = tests.pop(0)
2181 if self._loop: 2455 if self._loop:
2182 if getattr(test, 'should_reload', False): 2456 if getattr(test, 'should_reload', False):
2183 num_tests[0] += 1 2457 num_tests[0] += 1
2184 tests.append( 2458 tests.append(self._loadtest(test, num_tests[0]))
2185 self._loadtest(test, num_tests[0]))
2186 else: 2459 else:
2187 tests.append(test) 2460 tests.append(test)
2188 if self._jobs == 1: 2461 if self._jobs == 1:
2189 job(test, result) 2462 job(test, result)
2190 else: 2463 else:
2191 t = threading.Thread(target=job, name=test.name, 2464 t = threading.Thread(
2192 args=(test, result)) 2465 target=job, name=test.name, args=(test, result)
2466 )
2193 t.start() 2467 t.start()
2194 running += 1 2468 running += 1
2195 2469
2196 # If we stop early we still need to wait on started tests to 2470 # If we stop early we still need to wait on started tests to
2197 # finish. Otherwise, there is a race between the test completing 2471 # finish. Otherwise, there is a race between the test completing
2210 2484
2211 channels = [] 2485 channels = []
2212 2486
2213 return result 2487 return result
2214 2488
2489
2215 # Save the most recent 5 wall-clock runtimes of each test to a 2490 # Save the most recent 5 wall-clock runtimes of each test to a
2216 # human-readable text file named .testtimes. Tests are sorted 2491 # human-readable text file named .testtimes. Tests are sorted
2217 # alphabetically, while times for each test are listed from oldest to 2492 # alphabetically, while times for each test are listed from oldest to
2218 # newest. 2493 # newest.
2494
2219 2495
2220 def loadtimes(outputdir): 2496 def loadtimes(outputdir):
2221 times = [] 2497 times = []
2222 try: 2498 try:
2223 with open(os.path.join(outputdir, b'.testtimes')) as fp: 2499 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2224 for line in fp: 2500 for line in fp:
2225 m = re.match('(.*?) ([0-9. ]+)', line) 2501 m = re.match('(.*?) ([0-9. ]+)', line)
2226 times.append((m.group(1), 2502 times.append(
2227 [float(t) for t in m.group(2).split()])) 2503 (m.group(1), [float(t) for t in m.group(2).split()])
2504 )
2228 except IOError as err: 2505 except IOError as err:
2229 if err.errno != errno.ENOENT: 2506 if err.errno != errno.ENOENT:
2230 raise 2507 raise
2231 return times 2508 return times
2509
2232 2510
2233 def savetimes(outputdir, result): 2511 def savetimes(outputdir, result):
2234 saved = dict(loadtimes(outputdir)) 2512 saved = dict(loadtimes(outputdir))
2235 maxruns = 5 2513 maxruns = 5
2236 skipped = set([str(t[0]) for t in result.skipped]) 2514 skipped = set([str(t[0]) for t in result.skipped])
2239 if test not in skipped: 2517 if test not in skipped:
2240 ts = saved.setdefault(test, []) 2518 ts = saved.setdefault(test, [])
2241 ts.append(real) 2519 ts.append(real)
2242 ts[:] = ts[-maxruns:] 2520 ts[:] = ts[-maxruns:]
2243 2521
2244 fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes', 2522 fd, tmpname = tempfile.mkstemp(
2245 dir=outputdir, text=True) 2523 prefix=b'.testtimes', dir=outputdir, text=True
2524 )
2246 with os.fdopen(fd, 'w') as fp: 2525 with os.fdopen(fd, 'w') as fp:
2247 for name, ts in sorted(saved.items()): 2526 for name, ts in sorted(saved.items()):
2248 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts]))) 2527 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2249 timepath = os.path.join(outputdir, b'.testtimes') 2528 timepath = os.path.join(outputdir, b'.testtimes')
2250 try: 2529 try:
2254 try: 2533 try:
2255 os.rename(tmpname, timepath) 2534 os.rename(tmpname, timepath)
2256 except OSError: 2535 except OSError:
2257 pass 2536 pass
2258 2537
2538
2259 class TextTestRunner(unittest.TextTestRunner): 2539 class TextTestRunner(unittest.TextTestRunner):
2260 """Custom unittest test runner that uses appropriate settings.""" 2540 """Custom unittest test runner that uses appropriate settings."""
2261 2541
2262 def __init__(self, runner, *args, **kwargs): 2542 def __init__(self, runner, *args, **kwargs):
2263 super(TextTestRunner, self).__init__(*args, **kwargs) 2543 super(TextTestRunner, self).__init__(*args, **kwargs)
2264 2544
2265 self._runner = runner 2545 self._runner = runner
2266 2546
2267 self._result = getTestResult()(self._runner.options, self.stream, 2547 self._result = getTestResult()(
2268 self.descriptions, self.verbosity) 2548 self._runner.options, self.stream, self.descriptions, self.verbosity
2549 )
2269 2550
2270 def listtests(self, test): 2551 def listtests(self, test):
2271 test = sorted(test, key=lambda t: t.name) 2552 test = sorted(test, key=lambda t: t.name)
2272 2553
2273 self._result.onStart(test) 2554 self._result.onStart(test)
2297 2578
2298 with iolock: 2579 with iolock:
2299 self.stream.writeln('') 2580 self.stream.writeln('')
2300 2581
2301 if not self._runner.options.noskips: 2582 if not self._runner.options.noskips:
2302 for test, msg in sorted(self._result.skipped, 2583 for test, msg in sorted(
2303 key=lambda s: s[0].name): 2584 self._result.skipped, key=lambda s: s[0].name
2585 ):
2304 formatted = 'Skipped %s: %s\n' % (test.name, msg) 2586 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2305 msg = highlightmsg(formatted, self._result.color) 2587 msg = highlightmsg(formatted, self._result.color)
2306 self.stream.write(msg) 2588 self.stream.write(msg)
2307 for test, msg in sorted(self._result.failures, 2589 for test, msg in sorted(
2308 key=lambda f: f[0].name): 2590 self._result.failures, key=lambda f: f[0].name
2591 ):
2309 formatted = 'Failed %s: %s\n' % (test.name, msg) 2592 formatted = 'Failed %s: %s\n' % (test.name, msg)
2310 self.stream.write(highlightmsg(formatted, self._result.color)) 2593 self.stream.write(highlightmsg(formatted, self._result.color))
2311 for test, msg in sorted(self._result.errors, 2594 for test, msg in sorted(
2312 key=lambda e: e[0].name): 2595 self._result.errors, key=lambda e: e[0].name
2596 ):
2313 self.stream.writeln('Errored %s: %s' % (test.name, msg)) 2597 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2314 2598
2315 if self._runner.options.xunit: 2599 if self._runner.options.xunit:
2316 with open(self._runner.options.xunit, "wb") as xuf: 2600 with open(self._runner.options.xunit, "wb") as xuf:
2317 self._writexunit(self._result, xuf) 2601 self._writexunit(self._result, xuf)
2327 2611
2328 if failed and self._runner.options.known_good_rev: 2612 if failed and self._runner.options.known_good_rev:
2329 self._bisecttests(t for t, m in self._result.failures) 2613 self._bisecttests(t for t, m in self._result.failures)
2330 self.stream.writeln( 2614 self.stream.writeln(
2331 '# Ran %d tests, %d skipped, %d failed.' 2615 '# Ran %d tests, %d skipped, %d failed.'
2332 % (self._result.testsRun, skipped + ignored, failed)) 2616 % (self._result.testsRun, skipped + ignored, failed)
2617 )
2333 if failed: 2618 if failed:
2334 self.stream.writeln('python hash seed: %s' % 2619 self.stream.writeln(
2335 os.environ['PYTHONHASHSEED']) 2620 'python hash seed: %s' % os.environ['PYTHONHASHSEED']
2621 )
2336 if self._runner.options.time: 2622 if self._runner.options.time:
2337 self.printtimes(self._result.times) 2623 self.printtimes(self._result.times)
2338 2624
2339 if self._runner.options.exceptions: 2625 if self._runner.options.exceptions:
2340 exceptions = aggregateexceptions( 2626 exceptions = aggregateexceptions(
2341 os.path.join(self._runner._outputdir, b'exceptions')) 2627 os.path.join(self._runner._outputdir, b'exceptions')
2628 )
2342 2629
2343 self.stream.writeln('Exceptions Report:') 2630 self.stream.writeln('Exceptions Report:')
2344 self.stream.writeln('%d total from %d frames' % 2631 self.stream.writeln(
2345 (exceptions['total'], 2632 '%d total from %d frames'
2346 len(exceptions['exceptioncounts']))) 2633 % (exceptions['total'], len(exceptions['exceptioncounts']))
2634 )
2347 combined = exceptions['combined'] 2635 combined = exceptions['combined']
2348 for key in sorted(combined, key=combined.get, reverse=True): 2636 for key in sorted(combined, key=combined.get, reverse=True):
2349 frame, line, exc = key 2637 frame, line, exc = key
2350 totalcount, testcount, leastcount, leasttest = combined[key] 2638 totalcount, testcount, leastcount, leasttest = combined[key]
2351 2639
2352 self.stream.writeln('%d (%d tests)\t%s: %s (%s - %d total)' 2640 self.stream.writeln(
2353 % (totalcount, 2641 '%d (%d tests)\t%s: %s (%s - %d total)'
2354 testcount, 2642 % (
2355 frame, exc, 2643 totalcount,
2356 leasttest, leastcount)) 2644 testcount,
2645 frame,
2646 exc,
2647 leasttest,
2648 leastcount,
2649 )
2650 )
2357 2651
2358 self.stream.flush() 2652 self.stream.flush()
2359 2653
2360 return self._result 2654 return self._result
2361 2655
2362 def _bisecttests(self, tests): 2656 def _bisecttests(self, tests):
2363 bisectcmd = ['hg', 'bisect'] 2657 bisectcmd = ['hg', 'bisect']
2364 bisectrepo = self._runner.options.bisect_repo 2658 bisectrepo = self._runner.options.bisect_repo
2365 if bisectrepo: 2659 if bisectrepo:
2366 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)]) 2660 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2661
2367 def pread(args): 2662 def pread(args):
2368 env = os.environ.copy() 2663 env = os.environ.copy()
2369 env['HGPLAIN'] = '1' 2664 env['HGPLAIN'] = '1'
2370 p = subprocess.Popen(args, stderr=subprocess.STDOUT, 2665 p = subprocess.Popen(
2371 stdout=subprocess.PIPE, env=env) 2666 args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
2667 )
2372 data = p.stdout.read() 2668 data = p.stdout.read()
2373 p.wait() 2669 p.wait()
2374 return data 2670 return data
2671
2375 for test in tests: 2672 for test in tests:
2376 pread(bisectcmd + ['--reset']), 2673 pread(bisectcmd + ['--reset']),
2377 pread(bisectcmd + ['--bad', '.']) 2674 pread(bisectcmd + ['--bad', '.'])
2378 pread(bisectcmd + ['--good', self._runner.options.known_good_rev]) 2675 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2379 # TODO: we probably need to forward more options 2676 # TODO: we probably need to forward more options
2380 # that alter hg's behavior inside the tests. 2677 # that alter hg's behavior inside the tests.
2381 opts = '' 2678 opts = ''
2382 withhg = self._runner.options.with_hg 2679 withhg = self._runner.options.with_hg
2383 if withhg: 2680 if withhg:
2384 opts += ' --with-hg=%s ' % shellquote(_strpath(withhg)) 2681 opts += ' --with-hg=%s ' % shellquote(_strpath(withhg))
2385 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, 2682 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
2386 test)
2387 data = pread(bisectcmd + ['--command', rtc]) 2683 data = pread(bisectcmd + ['--command', rtc])
2388 m = re.search( 2684 m = re.search(
2389 (br'\nThe first (?P<goodbad>bad|good) revision ' 2685 (
2390 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n' 2686 br'\nThe first (?P<goodbad>bad|good) revision '
2391 br'summary: +(?P<summary>[^\n]+)\n'), 2687 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2392 data, (re.MULTILINE | re.DOTALL)) 2688 br'summary: +(?P<summary>[^\n]+)\n'
2689 ),
2690 data,
2691 (re.MULTILINE | re.DOTALL),
2692 )
2393 if m is None: 2693 if m is None:
2394 self.stream.writeln( 2694 self.stream.writeln(
2395 'Failed to identify failure point for %s' % test) 2695 'Failed to identify failure point for %s' % test
2696 )
2396 continue 2697 continue
2397 dat = m.groupdict() 2698 dat = m.groupdict()
2398 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed' 2699 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2399 self.stream.writeln( 2700 self.stream.writeln(
2400 '%s %s by %s (%s)' % ( 2701 '%s %s by %s (%s)'
2401 test, verb, dat['node'].decode('ascii'), 2702 % (
2402 dat['summary'].decode('utf8', 'ignore'))) 2703 test,
2704 verb,
2705 dat['node'].decode('ascii'),
2706 dat['summary'].decode('utf8', 'ignore'),
2707 )
2708 )
2403 2709
2404 def printtimes(self, times): 2710 def printtimes(self, times):
2405 # iolock held by run 2711 # iolock held by run
2406 self.stream.writeln('# Producing time report') 2712 self.stream.writeln('# Producing time report')
2407 times.sort(key=lambda t: (t[3])) 2713 times.sort(key=lambda t: (t[3]))
2408 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s' 2714 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2409 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' % 2715 self.stream.writeln(
2410 ('start', 'end', 'cuser', 'csys', 'real', 'Test')) 2716 '%-7s %-7s %-7s %-7s %-7s %s'
2717 % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
2718 )
2411 for tdata in times: 2719 for tdata in times:
2412 test = tdata[0] 2720 test = tdata[0]
2413 cuser, csys, real, start, end = tdata[1:6] 2721 cuser, csys, real, start, end = tdata[1:6]
2414 self.stream.writeln(cols % (start, end, cuser, csys, real, test)) 2722 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2415 2723
2417 def _writexunit(result, outf): 2725 def _writexunit(result, outf):
2418 # See http://llg.cubic.org/docs/junit/ for a reference. 2726 # See http://llg.cubic.org/docs/junit/ for a reference.
2419 timesd = dict((t[0], t[3]) for t in result.times) 2727 timesd = dict((t[0], t[3]) for t in result.times)
2420 doc = minidom.Document() 2728 doc = minidom.Document()
2421 s = doc.createElement('testsuite') 2729 s = doc.createElement('testsuite')
2422 s.setAttribute('errors', "0") # TODO 2730 s.setAttribute('errors', "0") # TODO
2423 s.setAttribute('failures', str(len(result.failures))) 2731 s.setAttribute('failures', str(len(result.failures)))
2424 s.setAttribute('name', 'run-tests') 2732 s.setAttribute('name', 'run-tests')
2425 s.setAttribute('skipped', str(len(result.skipped) + 2733 s.setAttribute(
2426 len(result.ignored))) 2734 'skipped', str(len(result.skipped) + len(result.ignored))
2735 )
2427 s.setAttribute('tests', str(result.testsRun)) 2736 s.setAttribute('tests', str(result.testsRun))
2428 doc.appendChild(s) 2737 doc.appendChild(s)
2429 for tc in result.successes: 2738 for tc in result.successes:
2430 t = doc.createElement('testcase') 2739 t = doc.createElement('testcase')
2431 t.setAttribute('name', tc.name) 2740 t.setAttribute('name', tc.name)
2472 for tdata in result.times: 2781 for tdata in result.times:
2473 test = tdata[0] 2782 test = tdata[0]
2474 timesd[test] = tdata[1:] 2783 timesd[test] = tdata[1:]
2475 2784
2476 outcome = {} 2785 outcome = {}
2477 groups = [('success', ((tc, None) 2786 groups = [
2478 for tc in result.successes)), 2787 ('success', ((tc, None) for tc in result.successes)),
2479 ('failure', result.failures), 2788 ('failure', result.failures),
2480 ('skip', result.skipped)] 2789 ('skip', result.skipped),
2790 ]
2481 for res, testcases in groups: 2791 for res, testcases in groups:
2482 for tc, __ in testcases: 2792 for tc, __ in testcases:
2483 if tc.name in timesd: 2793 if tc.name in timesd:
2484 diff = result.faildata.get(tc.name, b'') 2794 diff = result.faildata.get(tc.name, b'')
2485 try: 2795 try:
2486 diff = diff.decode('unicode_escape') 2796 diff = diff.decode('unicode_escape')
2487 except UnicodeDecodeError as e: 2797 except UnicodeDecodeError as e:
2488 diff = '%r decoding diff, sorry' % e 2798 diff = '%r decoding diff, sorry' % e
2489 tres = {'result': res, 2799 tres = {
2490 'time': ('%0.3f' % timesd[tc.name][2]), 2800 'result': res,
2491 'cuser': ('%0.3f' % timesd[tc.name][0]), 2801 'time': ('%0.3f' % timesd[tc.name][2]),
2492 'csys': ('%0.3f' % timesd[tc.name][1]), 2802 'cuser': ('%0.3f' % timesd[tc.name][0]),
2493 'start': ('%0.3f' % timesd[tc.name][3]), 2803 'csys': ('%0.3f' % timesd[tc.name][1]),
2494 'end': ('%0.3f' % timesd[tc.name][4]), 2804 'start': ('%0.3f' % timesd[tc.name][3]),
2495 'diff': diff, 2805 'end': ('%0.3f' % timesd[tc.name][4]),
2496 } 2806 'diff': diff,
2807 }
2497 else: 2808 else:
2498 # blacklisted test 2809 # blacklisted test
2499 tres = {'result': res} 2810 tres = {'result': res}
2500 2811
2501 outcome[tc.name] = tres 2812 outcome[tc.name] = tres
2502 jsonout = json.dumps(outcome, sort_keys=True, indent=4, 2813 jsonout = json.dumps(
2503 separators=(',', ': ')) 2814 outcome, sort_keys=True, indent=4, separators=(',', ': ')
2815 )
2504 outf.writelines(("testreport =", jsonout)) 2816 outf.writelines(("testreport =", jsonout))
2817
2505 2818
2506 def sorttests(testdescs, previoustimes, shuffle=False): 2819 def sorttests(testdescs, previoustimes, shuffle=False):
2507 """Do an in-place sort of tests.""" 2820 """Do an in-place sort of tests."""
2508 if shuffle: 2821 if shuffle:
2509 random.shuffle(testdescs) 2822 random.shuffle(testdescs)
2510 return 2823 return
2511 2824
2512 if previoustimes: 2825 if previoustimes:
2826
2513 def sortkey(f): 2827 def sortkey(f):
2514 f = f['path'] 2828 f = f['path']
2515 if f in previoustimes: 2829 if f in previoustimes:
2516 # Use most recent time as estimate 2830 # Use most recent time as estimate
2517 return -previoustimes[f][-1] 2831 return -(previoustimes[f][-1])
2518 else: 2832 else:
2519 # Default to a rather arbitrary value of 1 second for new tests 2833 # Default to a rather arbitrary value of 1 second for new tests
2520 return -1.0 2834 return -1.0
2835
2521 else: 2836 else:
2522 # keywords for slow tests 2837 # keywords for slow tests
2523 slow = {b'svn': 10, 2838 slow = {
2524 b'cvs': 10, 2839 b'svn': 10,
2525 b'hghave': 10, 2840 b'cvs': 10,
2526 b'largefiles-update': 10, 2841 b'hghave': 10,
2527 b'run-tests': 10, 2842 b'largefiles-update': 10,
2528 b'corruption': 10, 2843 b'run-tests': 10,
2529 b'race': 10, 2844 b'corruption': 10,
2530 b'i18n': 10, 2845 b'race': 10,
2531 b'check': 100, 2846 b'i18n': 10,
2532 b'gendoc': 100, 2847 b'check': 100,
2533 b'contrib-perf': 200, 2848 b'gendoc': 100,
2534 b'merge-combination': 100, 2849 b'contrib-perf': 200,
2535 } 2850 b'merge-combination': 100,
2851 }
2536 perf = {} 2852 perf = {}
2537 2853
2538 def sortkey(f): 2854 def sortkey(f):
2539 # run largest tests first, as they tend to take the longest 2855 # run largest tests first, as they tend to take the longest
2540 f = f['path'] 2856 f = f['path']
2556 perf[f] = val / 1000.0 2872 perf[f] = val / 1000.0
2557 return perf[f] 2873 return perf[f]
2558 2874
2559 testdescs.sort(key=sortkey) 2875 testdescs.sort(key=sortkey)
2560 2876
2877
2561 class TestRunner(object): 2878 class TestRunner(object):
2562 """Holds context for executing tests. 2879 """Holds context for executing tests.
2563 2880
2564 Tests rely on a lot of state. This object holds it for them. 2881 Tests rely on a lot of state. This object holds it for them.
2565 """ 2882 """
2612 2929
2613 self._checktools() 2930 self._checktools()
2614 testdescs = self.findtests(tests) 2931 testdescs = self.findtests(tests)
2615 if options.profile_runner: 2932 if options.profile_runner:
2616 import statprof 2933 import statprof
2934
2617 statprof.start() 2935 statprof.start()
2618 result = self._run(testdescs) 2936 result = self._run(testdescs)
2619 if options.profile_runner: 2937 if options.profile_runner:
2620 statprof.stop() 2938 statprof.stop()
2621 statprof.display() 2939 statprof.display()
2666 # without this, we get the default temp dir location, but 2984 # without this, we get the default temp dir location, but
2667 # in all lowercase, which causes troubles with paths (issue3490) 2985 # in all lowercase, which causes troubles with paths (issue3490)
2668 d = osenvironb.get(b'TMP', None) 2986 d = osenvironb.get(b'TMP', None)
2669 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d) 2987 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
2670 2988
2671 self._hgtmp = osenvironb[b'HGTMP'] = ( 2989 self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir)
2672 os.path.realpath(tmpdir))
2673 2990
2674 if self.options.with_hg: 2991 if self.options.with_hg:
2675 self._installdir = None 2992 self._installdir = None
2676 whg = self.options.with_hg 2993 whg = self.options.with_hg
2677 self._bindir = os.path.dirname(os.path.realpath(whg)) 2994 self._bindir = os.path.dirname(os.path.realpath(whg))
2789 os.unlink(os.path.join(exceptionsdir, f)) 3106 os.unlink(os.path.join(exceptionsdir, f))
2790 3107
2791 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir 3108 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
2792 logexceptions = os.path.join(self._testdir, b'logexceptions.py') 3109 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
2793 self.options.extra_config_opt.append( 3110 self.options.extra_config_opt.append(
2794 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')) 3111 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
3112 )
2795 3113
2796 vlog("# Using TESTDIR", self._testdir) 3114 vlog("# Using TESTDIR", self._testdir)
2797 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR']) 3115 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2798 vlog("# Using HGTMP", self._hgtmp) 3116 vlog("# Using HGTMP", self._hgtmp)
2799 vlog("# Using PATH", os.environ["PATH"]) 3117 vlog("# Using PATH", os.environ["PATH"])
2801 vlog("# Writing to directory", self._outputdir) 3119 vlog("# Writing to directory", self._outputdir)
2802 3120
2803 try: 3121 try:
2804 return self._runtests(testdescs) or 0 3122 return self._runtests(testdescs) or 0
2805 finally: 3123 finally:
2806 time.sleep(.1) 3124 time.sleep(0.1)
2807 self._cleanup() 3125 self._cleanup()
2808 3126
2809 def findtests(self, args): 3127 def findtests(self, args):
2810 """Finds possible test files from arguments. 3128 """Finds possible test files from arguments.
2811 3129
2812 If you wish to inject custom tests into the test harness, this would 3130 If you wish to inject custom tests into the test harness, this would
2813 be a good function to monkeypatch or override in a derived class. 3131 be a good function to monkeypatch or override in a derived class.
2814 """ 3132 """
2815 if not args: 3133 if not args:
2816 if self.options.changed: 3134 if self.options.changed:
2817 proc = Popen4(b'hg st --rev "%s" -man0 .' % 3135 proc = Popen4(
2818 _bytespath(self.options.changed), None, 0) 3136 b'hg st --rev "%s" -man0 .'
3137 % _bytespath(self.options.changed),
3138 None,
3139 0,
3140 )
2819 stdout, stderr = proc.communicate() 3141 stdout, stderr = proc.communicate()
2820 args = stdout.strip(b'\0').split(b'\0') 3142 args = stdout.strip(b'\0').split(b'\0')
2821 else: 3143 else:
2822 args = os.listdir(b'.') 3144 args = os.listdir(b'.')
2823 3145
2830 else: 3152 else:
2831 expanded_args.append(arg) 3153 expanded_args.append(arg)
2832 args = expanded_args 3154 args = expanded_args
2833 3155
2834 testcasepattern = re.compile( 3156 testcasepattern = re.compile(
2835 br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-\.#]+))') 3157 br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-\.#]+))'
3158 )
2836 tests = [] 3159 tests = []
2837 for t in args: 3160 for t in args:
2838 case = [] 3161 case = []
2839 3162
2840 if not (os.path.basename(t).startswith(b'test-') 3163 if not (
2841 and (t.endswith(b'.py') or t.endswith(b'.t'))): 3164 os.path.basename(t).startswith(b'test-')
3165 and (t.endswith(b'.py') or t.endswith(b'.t'))
3166 ):
2842 3167
2843 m = testcasepattern.match(os.path.basename(t)) 3168 m = testcasepattern.match(os.path.basename(t))
2844 if m is not None: 3169 if m is not None:
2845 t_basename, casestr = m.groups() 3170 t_basename, casestr = m.groups()
2846 t = os.path.join(os.path.dirname(t), t_basename) 3171 t = os.path.join(os.path.dirname(t), t_basename)
2852 if t.endswith(b'.t'): 3177 if t.endswith(b'.t'):
2853 # .t file may contain multiple test cases 3178 # .t file may contain multiple test cases
2854 casedimensions = parsettestcases(t) 3179 casedimensions = parsettestcases(t)
2855 if casedimensions: 3180 if casedimensions:
2856 cases = [] 3181 cases = []
3182
2857 def addcases(case, casedimensions): 3183 def addcases(case, casedimensions):
2858 if not casedimensions: 3184 if not casedimensions:
2859 cases.append(case) 3185 cases.append(case)
2860 else: 3186 else:
2861 for c in casedimensions[0]: 3187 for c in casedimensions[0]:
2862 addcases(case + [c], casedimensions[1:]) 3188 addcases(case + [c], casedimensions[1:])
3189
2863 addcases([], casedimensions) 3190 addcases([], casedimensions)
2864 if case and case in cases: 3191 if case and case in cases:
2865 cases = [case] 3192 cases = [case]
2866 elif case: 3193 elif case:
2867 # Ignore invalid cases 3194 # Ignore invalid cases
2911 failed = False 3238 failed = False
2912 kws = self.options.keywords 3239 kws = self.options.keywords
2913 if kws is not None and PYTHON3: 3240 if kws is not None and PYTHON3:
2914 kws = kws.encode('utf-8') 3241 kws = kws.encode('utf-8')
2915 3242
2916 suite = TestSuite(self._testdir, 3243 suite = TestSuite(
2917 jobs=jobs, 3244 self._testdir,
2918 whitelist=self.options.whitelisted, 3245 jobs=jobs,
2919 blacklist=self.options.blacklist, 3246 whitelist=self.options.whitelisted,
2920 retest=self.options.retest, 3247 blacklist=self.options.blacklist,
2921 keywords=kws, 3248 retest=self.options.retest,
2922 loop=self.options.loop, 3249 keywords=kws,
2923 runs_per_test=self.options.runs_per_test, 3250 loop=self.options.loop,
2924 showchannels=self.options.showchannels, 3251 runs_per_test=self.options.runs_per_test,
2925 tests=tests, loadtest=_reloadtest) 3252 showchannels=self.options.showchannels,
3253 tests=tests,
3254 loadtest=_reloadtest,
3255 )
2926 verbosity = 1 3256 verbosity = 1
2927 if self.options.list_tests: 3257 if self.options.list_tests:
2928 verbosity = 0 3258 verbosity = 0
2929 elif self.options.verbose: 3259 elif self.options.verbose:
2930 verbosity = 2 3260 verbosity = 2
2940 self._usecorrectpython() 3270 self._usecorrectpython()
2941 if self.options.chg: 3271 if self.options.chg:
2942 assert self._installdir 3272 assert self._installdir
2943 self._installchg() 3273 self._installchg()
2944 3274
2945 log('running %d tests using %d parallel processes' % ( 3275 log(
2946 num_tests, jobs)) 3276 'running %d tests using %d parallel processes'
3277 % (num_tests, jobs)
3278 )
2947 3279
2948 result = runner.run(suite) 3280 result = runner.run(suite)
2949 3281
2950 if result.failures or result.errors: 3282 if result.failures or result.errors:
2951 failed = True 3283 failed = True
2960 3292
2961 if failed: 3293 if failed:
2962 return 1 3294 return 1
2963 3295
2964 def _getport(self, count): 3296 def _getport(self, count):
2965 port = self._ports.get(count) # do we have a cached entry? 3297 port = self._ports.get(count) # do we have a cached entry?
2966 if port is None: 3298 if port is None:
2967 portneeded = 3 3299 portneeded = 3
2968 # above 100 tries we just give up and let test reports failure 3300 # above 100 tries we just give up and let test reports failure
2969 for tries in xrange(100): 3301 for tries in xrange(100):
2970 allfree = True 3302 allfree = True
2998 tmpdir = os.path.join(self._hgtmp, b'child%d' % count) 3330 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
2999 3331
3000 # extra keyword parameters. 'case' is used by .t tests 3332 # extra keyword parameters. 'case' is used by .t tests
3001 kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc) 3333 kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc)
3002 3334
3003 t = testcls(refpath, self._outputdir, tmpdir, 3335 t = testcls(
3004 keeptmpdir=self.options.keep_tmpdir, 3336 refpath,
3005 debug=self.options.debug, 3337 self._outputdir,
3006 first=self.options.first, 3338 tmpdir,
3007 timeout=self.options.timeout, 3339 keeptmpdir=self.options.keep_tmpdir,
3008 startport=self._getport(count), 3340 debug=self.options.debug,
3009 extraconfigopts=self.options.extra_config_opt, 3341 first=self.options.first,
3010 py3warnings=self.options.py3_warnings, 3342 timeout=self.options.timeout,
3011 shell=self.options.shell, 3343 startport=self._getport(count),
3012 hgcommand=self._hgcommand, 3344 extraconfigopts=self.options.extra_config_opt,
3013 usechg=bool(self.options.with_chg or self.options.chg), 3345 py3warnings=self.options.py3_warnings,
3014 useipv6=useipv6, **kwds) 3346 shell=self.options.shell,
3347 hgcommand=self._hgcommand,
3348 usechg=bool(self.options.with_chg or self.options.chg),
3349 useipv6=useipv6,
3350 **kwds
3351 )
3015 t.should_reload = True 3352 t.should_reload = True
3016 return t 3353 return t
3017 3354
3018 def _cleanup(self): 3355 def _cleanup(self):
3019 """Clean up state from this test invocation.""" 3356 """Clean up state from this test invocation."""
3034 pyexename = sys.platform == 'win32' and b'python.exe' or b'python' 3371 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
3035 3372
3036 # os.symlink() is a thing with py3 on Windows, but it requires 3373 # os.symlink() is a thing with py3 on Windows, but it requires
3037 # Administrator rights. 3374 # Administrator rights.
3038 if getattr(os, 'symlink', None) and os.name != 'nt': 3375 if getattr(os, 'symlink', None) and os.name != 'nt':
3039 vlog("# Making python executable in test path a symlink to '%s'" % 3376 vlog(
3040 sysexecutable) 3377 "# Making python executable in test path a symlink to '%s'"
3378 % sysexecutable
3379 )
3041 mypython = os.path.join(self._tmpbindir, pyexename) 3380 mypython = os.path.join(self._tmpbindir, pyexename)
3042 try: 3381 try:
3043 if os.readlink(mypython) == sysexecutable: 3382 if os.readlink(mypython) == sysexecutable:
3044 return 3383 return
3045 os.unlink(mypython) 3384 os.unlink(mypython)
3054 # child processes may race, which is harmless 3393 # child processes may race, which is harmless
3055 if err.errno != errno.EEXIST: 3394 if err.errno != errno.EEXIST:
3056 raise 3395 raise
3057 else: 3396 else:
3058 exedir, exename = os.path.split(sysexecutable) 3397 exedir, exename = os.path.split(sysexecutable)
3059 vlog("# Modifying search path to find %s as %s in '%s'" % 3398 vlog(
3060 (exename, pyexename, exedir)) 3399 "# Modifying search path to find %s as %s in '%s'"
3400 % (exename, pyexename, exedir)
3401 )
3061 path = os.environ['PATH'].split(os.pathsep) 3402 path = os.environ['PATH'].split(os.pathsep)
3062 while exedir in path: 3403 while exedir in path:
3063 path.remove(exedir) 3404 path.remove(exedir)
3064 os.environ['PATH'] = os.pathsep.join([exedir] + path) 3405 os.environ['PATH'] = os.pathsep.join([exedir] + path)
3065 if not self._findprogram(pyexename): 3406 if not self._findprogram(pyexename):
3095 # The --home="" trick works only on OS where os.sep == '/' 3436 # The --home="" trick works only on OS where os.sep == '/'
3096 # because of a distutils convert_path() fast-path. Avoid it at 3437 # because of a distutils convert_path() fast-path. Avoid it at
3097 # least on Windows for now, deal with .pydistutils.cfg bugs 3438 # least on Windows for now, deal with .pydistutils.cfg bugs
3098 # when they happen. 3439 # when they happen.
3099 nohome = b'' 3440 nohome = b''
3100 cmd = (b'"%(exe)s" setup.py %(pure)s clean --all' 3441 cmd = (
3101 b' build %(compiler)s --build-base="%(base)s"' 3442 b'"%(exe)s" setup.py %(pure)s clean --all'
3102 b' install --force --prefix="%(prefix)s"' 3443 b' build %(compiler)s --build-base="%(base)s"'
3103 b' --install-lib="%(libdir)s"' 3444 b' install --force --prefix="%(prefix)s"'
3104 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1' 3445 b' --install-lib="%(libdir)s"'
3105 % {b'exe': exe, b'pure': pure, 3446 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3106 b'compiler': compiler, 3447 % {
3107 b'base': os.path.join(self._hgtmp, b"build"), 3448 b'exe': exe,
3108 b'prefix': self._installdir, b'libdir': self._pythondir, 3449 b'pure': pure,
3109 b'bindir': self._bindir, 3450 b'compiler': compiler,
3110 b'nohome': nohome, b'logfile': installerrs}) 3451 b'base': os.path.join(self._hgtmp, b"build"),
3452 b'prefix': self._installdir,
3453 b'libdir': self._pythondir,
3454 b'bindir': self._bindir,
3455 b'nohome': nohome,
3456 b'logfile': installerrs,
3457 }
3458 )
3111 3459
3112 # setuptools requires install directories to exist. 3460 # setuptools requires install directories to exist.
3113 def makedirs(p): 3461 def makedirs(p):
3114 try: 3462 try:
3115 os.makedirs(p) 3463 os.makedirs(p)
3116 except OSError as e: 3464 except OSError as e:
3117 if e.errno != errno.EEXIST: 3465 if e.errno != errno.EEXIST:
3118 raise 3466 raise
3467
3119 makedirs(self._pythondir) 3468 makedirs(self._pythondir)
3120 makedirs(self._bindir) 3469 makedirs(self._bindir)
3121 3470
3122 vlog("# Running", cmd) 3471 vlog("# Running", cmd)
3123 if subprocess.call(_strpath(cmd), shell=True) == 0: 3472 if subprocess.call(_strpath(cmd), shell=True) == 0:
3153 # hg.bat expects to be put in bin/scripts while run-tests.py 3502 # hg.bat expects to be put in bin/scripts while run-tests.py
3154 # installation layout put it in bin/ directly. Fix it 3503 # installation layout put it in bin/ directly. Fix it
3155 with open(hgbat, 'rb') as f: 3504 with open(hgbat, 'rb') as f:
3156 data = f.read() 3505 data = f.read()
3157 if br'"%~dp0..\python" "%~dp0hg" %*' in data: 3506 if br'"%~dp0..\python" "%~dp0hg" %*' in data:
3158 data = data.replace(br'"%~dp0..\python" "%~dp0hg" %*', 3507 data = data.replace(
3159 b'"%~dp0python" "%~dp0hg" %*') 3508 br'"%~dp0..\python" "%~dp0hg" %*',
3509 b'"%~dp0python" "%~dp0hg" %*',
3510 )
3160 with open(hgbat, 'wb') as f: 3511 with open(hgbat, 'wb') as f:
3161 f.write(data) 3512 f.write(data)
3162 else: 3513 else:
3163 print('WARNING: cannot fix hg.bat reference to python.exe') 3514 print('WARNING: cannot fix hg.bat reference to python.exe')
3164 3515
3180 os.environ['COVERAGE_DIR'] = covdir 3531 os.environ['COVERAGE_DIR'] = covdir
3181 3532
3182 def _checkhglib(self, verb): 3533 def _checkhglib(self, verb):
3183 """Ensure that the 'mercurial' package imported by python is 3534 """Ensure that the 'mercurial' package imported by python is
3184 the one we expect it to be. If not, print a warning to stderr.""" 3535 the one we expect it to be. If not, print a warning to stderr."""
3185 if ((self._bindir == self._pythondir) and 3536 if (self._bindir == self._pythondir) and (
3186 (self._bindir != self._tmpbindir)): 3537 self._bindir != self._tmpbindir
3538 ):
3187 # The pythondir has been inferred from --with-hg flag. 3539 # The pythondir has been inferred from --with-hg flag.
3188 # We cannot expect anything sensible here. 3540 # We cannot expect anything sensible here.
3189 return 3541 return
3190 expecthg = os.path.join(self._pythondir, b'mercurial') 3542 expecthg = os.path.join(self._pythondir, b'mercurial')
3191 actualhg = self._gethgpath() 3543 actualhg = self._gethgpath()
3192 if os.path.abspath(actualhg) != os.path.abspath(expecthg): 3544 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3193 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n' 3545 sys.stderr.write(
3194 ' (expected %s)\n' 3546 'warning: %s with unexpected mercurial lib: %s\n'
3195 % (verb, actualhg, expecthg)) 3547 ' (expected %s)\n' % (verb, actualhg, expecthg)
3548 )
3549
3196 def _gethgpath(self): 3550 def _gethgpath(self):
3197 """Return the path to the mercurial package that is actually found by 3551 """Return the path to the mercurial package that is actually found by
3198 the current Python interpreter.""" 3552 the current Python interpreter."""
3199 if self._hgpath is not None: 3553 if self._hgpath is not None:
3200 return self._hgpath 3554 return self._hgpath
3214 def _installchg(self): 3568 def _installchg(self):
3215 """Install chg into the test environment""" 3569 """Install chg into the test environment"""
3216 vlog('# Performing temporary installation of CHG') 3570 vlog('# Performing temporary installation of CHG')
3217 assert os.path.dirname(self._bindir) == self._installdir 3571 assert os.path.dirname(self._bindir) == self._installdir
3218 assert self._hgroot, 'must be called after _installhg()' 3572 assert self._hgroot, 'must be called after _installhg()'
3219 cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"' 3573 cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
3220 % {b'make': b'make', # TODO: switch by option or environment? 3574 b'make': b'make', # TODO: switch by option or environment?
3221 b'prefix': self._installdir}) 3575 b'prefix': self._installdir,
3576 }
3222 cwd = os.path.join(self._hgroot, b'contrib', b'chg') 3577 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3223 vlog("# Running", cmd) 3578 vlog("# Running", cmd)
3224 proc = subprocess.Popen(cmd, shell=True, cwd=cwd, 3579 proc = subprocess.Popen(
3225 stdin=subprocess.PIPE, stdout=subprocess.PIPE, 3580 cmd,
3226 stderr=subprocess.STDOUT) 3581 shell=True,
3582 cwd=cwd,
3583 stdin=subprocess.PIPE,
3584 stdout=subprocess.PIPE,
3585 stderr=subprocess.STDOUT,
3586 )
3227 out, _err = proc.communicate() 3587 out, _err = proc.communicate()
3228 if proc.returncode != 0: 3588 if proc.returncode != 0:
3229 if PYTHON3: 3589 if PYTHON3:
3230 sys.stdout.buffer.write(out) 3590 sys.stdout.buffer.write(out)
3231 else: 3591 else:
3233 sys.exit(1) 3593 sys.exit(1)
3234 3594
3235 def _outputcoverage(self): 3595 def _outputcoverage(self):
3236 """Produce code coverage output.""" 3596 """Produce code coverage output."""
3237 import coverage 3597 import coverage
3598
3238 coverage = coverage.coverage 3599 coverage = coverage.coverage
3239 3600
3240 vlog('# Producing coverage report') 3601 vlog('# Producing coverage report')
3241 # chdir is the easiest way to get short, relative paths in the 3602 # chdir is the easiest way to get short, relative paths in the
3242 # output. 3603 # output.
3278 p += b'.exe' 3639 p += b'.exe'
3279 found = self._findprogram(p) 3640 found = self._findprogram(p)
3280 if found: 3641 if found:
3281 vlog("# Found prerequisite", p, "at", found) 3642 vlog("# Found prerequisite", p, "at", found)
3282 else: 3643 else:
3283 print("WARNING: Did not find prerequisite tool: %s " % 3644 print(
3284 p.decode("utf-8")) 3645 "WARNING: Did not find prerequisite tool: %s "
3646 % p.decode("utf-8")
3647 )
3648
3285 3649
3286 def aggregateexceptions(path): 3650 def aggregateexceptions(path):
3287 exceptioncounts = collections.Counter() 3651 exceptioncounts = collections.Counter()
3288 testsbyfailure = collections.defaultdict(set) 3652 testsbyfailure = collections.defaultdict(set)
3289 failuresbytest = collections.defaultdict(set) 3653 failuresbytest = collections.defaultdict(set)
3320 3684
3321 # Create a combined counter so we can sort by total occurrences and 3685 # Create a combined counter so we can sort by total occurrences and
3322 # impacted tests. 3686 # impacted tests.
3323 combined = {} 3687 combined = {}
3324 for key in exceptioncounts: 3688 for key in exceptioncounts:
3325 combined[key] = (exceptioncounts[key], 3689 combined[key] = (
3326 len(testsbyfailure[key]), 3690 exceptioncounts[key],
3327 leastfailing[key][0], 3691 len(testsbyfailure[key]),
3328 leastfailing[key][1]) 3692 leastfailing[key][0],
3693 leastfailing[key][1],
3694 )
3329 3695
3330 return { 3696 return {
3331 'exceptioncounts': exceptioncounts, 3697 'exceptioncounts': exceptioncounts,
3332 'total': sum(exceptioncounts.values()), 3698 'total': sum(exceptioncounts.values()),
3333 'combined': combined, 3699 'combined': combined,
3334 'leastfailing': leastfailing, 3700 'leastfailing': leastfailing,
3335 'byfailure': testsbyfailure, 3701 'byfailure': testsbyfailure,
3336 'bytest': failuresbytest, 3702 'bytest': failuresbytest,
3337 } 3703 }
3338 3704
3705
3339 if __name__ == '__main__': 3706 if __name__ == '__main__':
3340 runner = TestRunner() 3707 runner = TestRunner()
3341 3708
3342 try: 3709 try:
3343 import msvcrt 3710 import msvcrt
3711
3344 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) 3712 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
3345 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) 3713 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3346 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY) 3714 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
3347 except ImportError: 3715 except ImportError:
3348 pass 3716 pass