Mercurial > hg
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 |