changeset 43076 | 2372284d9457 |
parent 43073 | 5c9c71cde1c9 |
child 43283 | 96eb9ef777a8 |
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 |