336 """ |
336 """ |
337 |
337 |
338 # Status code reserved for skipped tests (used by hghave). |
338 # Status code reserved for skipped tests (used by hghave). |
339 SKIPPED_STATUS = 80 |
339 SKIPPED_STATUS = 80 |
340 |
340 |
341 def __init__(self, path, tmpdir, abort, keeptmpdir=False, |
341 def __init__(self, path, tmpdir, keeptmpdir=False, |
342 debug=False, nodiff=False, diffviewer=None, |
342 debug=False, nodiff=False, diffviewer=None, |
343 interactive=False, timeout=defaults['timeout'], |
343 interactive=False, timeout=defaults['timeout'], |
344 startport=defaults['port'], extraconfigopts=None, |
344 startport=defaults['port'], extraconfigopts=None, |
345 py3kwarnings=False, shell=None): |
345 py3kwarnings=False, shell=None): |
346 """Create a test from parameters. |
346 """Create a test from parameters. |
347 |
347 |
348 path is the full path to the file defining the test. |
348 path is the full path to the file defining the test. |
349 |
349 |
350 tmpdir is the main temporary directory to use for this test. |
350 tmpdir is the main temporary directory to use for this test. |
351 |
351 |
352 abort is a flag that turns to True if test execution should be aborted. |
|
353 It is consulted periodically during the execution of tests. |
|
354 |
|
355 keeptmpdir determines whether to keep the test's temporary directory |
352 keeptmpdir determines whether to keep the test's temporary directory |
356 after execution. It defaults to removal (False). |
353 after execution. It defaults to removal (False). |
357 |
354 |
358 debug mode will make the test execute verbosely, with unfiltered |
355 debug mode will make the test execute verbosely, with unfiltered |
359 output. |
356 output. |
386 self.name = os.path.basename(path) |
383 self.name = os.path.basename(path) |
387 self._testdir = os.path.dirname(path) |
384 self._testdir = os.path.dirname(path) |
388 self.errpath = os.path.join(self._testdir, '%s.err' % self.name) |
385 self.errpath = os.path.join(self._testdir, '%s.err' % self.name) |
389 |
386 |
390 self._threadtmp = tmpdir |
387 self._threadtmp = tmpdir |
391 self._abort = abort |
|
392 self._keeptmpdir = keeptmpdir |
388 self._keeptmpdir = keeptmpdir |
393 self._debug = debug |
389 self._debug = debug |
394 self._nodiff = nodiff |
390 self._nodiff = nodiff |
395 self._diffviewer = diffviewer |
391 self._diffviewer = diffviewer |
396 self._interactive = interactive |
392 self._interactive = interactive |
397 self._timeout = timeout |
393 self._timeout = timeout |
398 self._startport = startport |
394 self._startport = startport |
399 self._extraconfigopts = extraconfigopts or [] |
395 self._extraconfigopts = extraconfigopts or [] |
400 self._py3kwarnings = py3kwarnings |
396 self._py3kwarnings = py3kwarnings |
401 self._shell = shell |
397 self._shell = shell |
|
398 |
|
399 self._aborted = False |
402 self._daemonpids = [] |
400 self._daemonpids = [] |
403 |
|
404 self._finished = None |
401 self._finished = None |
405 self._ret = None |
402 self._ret = None |
406 self._out = None |
403 self._out = None |
407 self._skipped = None |
404 self._skipped = None |
408 self._testtmp = None |
405 self._testtmp = None |
445 if os.path.exists(self.errpath): |
442 if os.path.exists(self.errpath): |
446 os.remove(self.errpath) |
443 os.remove(self.errpath) |
447 |
444 |
448 def run(self, result): |
445 def run(self, result): |
449 result.startTest(self) |
446 result.startTest(self) |
450 interrupted = False |
|
451 try: |
447 try: |
452 try: |
448 try: |
453 self.setUp() |
449 self.setUp() |
454 except (KeyboardInterrupt, SystemExit): |
450 except (KeyboardInterrupt, SystemExit): |
455 interrupted = True |
451 self._aborted = True |
456 raise |
452 raise |
457 except Exception: |
453 except Exception: |
458 result.addError(self, sys.exc_info()) |
454 result.addError(self, sys.exc_info()) |
459 return |
455 return |
460 |
456 |
461 success = False |
457 success = False |
462 try: |
458 try: |
463 self.runTest() |
459 self.runTest() |
464 except KeyboardInterrupt: |
460 except KeyboardInterrupt: |
465 interrupted = True |
461 self._aborted = True |
466 raise |
462 raise |
467 except SkipTest, e: |
463 except SkipTest, e: |
468 result.addSkip(self, str(e)) |
464 result.addSkip(self, str(e)) |
469 except IgnoreTest, e: |
465 except IgnoreTest, e: |
470 result.addIgnore(self, str(e)) |
466 result.addIgnore(self, str(e)) |
482 success = True |
478 success = True |
483 |
479 |
484 try: |
480 try: |
485 self.tearDown() |
481 self.tearDown() |
486 except (KeyboardInterrupt, SystemExit): |
482 except (KeyboardInterrupt, SystemExit): |
487 interrupted = True |
483 self._aborted = True |
488 raise |
484 raise |
489 except Exception: |
485 except Exception: |
490 result.addError(self, sys.exc_info()) |
486 result.addError(self, sys.exc_info()) |
491 success = False |
487 success = False |
492 |
488 |
493 if success: |
489 if success: |
494 result.addSuccess(self) |
490 result.addSuccess(self) |
495 finally: |
491 finally: |
496 result.stopTest(self, interrupted=interrupted) |
492 result.stopTest(self, interrupted=self._aborted) |
497 |
493 |
498 def runTest(self): |
494 def runTest(self): |
499 """Run this test instance. |
495 """Run this test instance. |
500 |
496 |
501 This will return a tuple describing the result of the test. |
497 This will return a tuple describing the result of the test. |
587 |
583 |
588 def _run(self, replacements, env): |
584 def _run(self, replacements, env): |
589 # This should be implemented in child classes to run tests. |
585 # This should be implemented in child classes to run tests. |
590 raise SkipTest('unknown test type') |
586 raise SkipTest('unknown test type') |
591 |
587 |
|
588 def abort(self): |
|
589 """Terminate execution of this test.""" |
|
590 self._aborted = True |
|
591 |
592 def _getreplacements(self): |
592 def _getreplacements(self): |
593 r = [ |
593 r = [ |
594 (r':%s\b' % self._startport, ':$HGPORT'), |
594 (r':%s\b' % self._startport, ':$HGPORT'), |
595 (r':%s\b' % (self._startport + 1), ':$HGPORT1'), |
595 (r':%s\b' % (self._startport + 1), ':$HGPORT1'), |
596 (r':%s\b' % (self._startport + 2), ':$HGPORT2'), |
596 (r':%s\b' % (self._startport + 2), ':$HGPORT2'), |
696 py3kswitch = self._py3kwarnings and ' -3' or '' |
696 py3kswitch = self._py3kwarnings and ' -3' or '' |
697 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self.path) |
697 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self.path) |
698 vlog("# Running", cmd) |
698 vlog("# Running", cmd) |
699 if os.name == 'nt': |
699 if os.name == 'nt': |
700 replacements.append((r'\r\n', '\n')) |
700 replacements.append((r'\r\n', '\n')) |
701 return run(cmd, self._testtmp, replacements, env, self._abort, |
701 result = run(cmd, self._testtmp, replacements, env, |
702 debug=self._debug, timeout=self._timeout) |
702 debug=self._debug, timeout=self._timeout) |
|
703 if self._aborted: |
|
704 raise KeyboardInterrupt() |
|
705 |
|
706 return result |
703 |
707 |
704 class TTest(Test): |
708 class TTest(Test): |
705 """A "t test" is a test backed by a .t file.""" |
709 """A "t test" is a test backed by a .t file.""" |
706 |
710 |
707 SKIPPED_PREFIX = 'skipped: ' |
711 SKIPPED_PREFIX = 'skipped: ' |
732 |
736 |
733 cmd = '%s "%s"' % (self._shell, fname) |
737 cmd = '%s "%s"' % (self._shell, fname) |
734 vlog("# Running", cmd) |
738 vlog("# Running", cmd) |
735 |
739 |
736 exitcode, output = run(cmd, self._testtmp, replacements, env, |
740 exitcode, output = run(cmd, self._testtmp, replacements, env, |
737 self._abort, debug=self._debug, |
741 debug=self._debug, timeout=self._timeout) |
738 timeout=self._timeout) |
742 |
|
743 if self._aborted: |
|
744 raise KeyboardInterrupt() |
|
745 |
739 # Do not merge output if skipped. Return hghave message instead. |
746 # Do not merge output if skipped. Return hghave message instead. |
740 # Similarly, with --debug, output is None. |
747 # Similarly, with --debug, output is None. |
741 if exitcode == self.SKIPPED_STATUS or output is None: |
748 if exitcode == self.SKIPPED_STATUS or output is None: |
742 return exitcode, output |
749 return exitcode, output |
743 |
750 |
1010 def _stringescape(s): |
1017 def _stringescape(s): |
1011 return TTest.ESCAPESUB(TTest._escapef, s) |
1018 return TTest.ESCAPESUB(TTest._escapef, s) |
1012 |
1019 |
1013 |
1020 |
1014 wifexited = getattr(os, "WIFEXITED", lambda x: False) |
1021 wifexited = getattr(os, "WIFEXITED", lambda x: False) |
1015 def run(cmd, wd, replacements, env, abort, debug=False, timeout=None): |
1022 def run(cmd, wd, replacements, env, debug=False, timeout=None): |
1016 """Run command in a sub-process, capturing the output (stdout and stderr). |
1023 """Run command in a sub-process, capturing the output (stdout and stderr). |
1017 Return a tuple (exitcode, output). output is None in debug mode.""" |
1024 Return a tuple (exitcode, output). output is None in debug mode.""" |
1018 if debug: |
1025 if debug: |
1019 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env) |
1026 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env) |
1020 ret = proc.wait() |
1027 ret = proc.wait() |
1046 if proc.timeout: |
1053 if proc.timeout: |
1047 ret = 'timeout' |
1054 ret = 'timeout' |
1048 |
1055 |
1049 if ret: |
1056 if ret: |
1050 killdaemons(env['DAEMON_PIDS']) |
1057 killdaemons(env['DAEMON_PIDS']) |
1051 |
|
1052 if abort[0]: |
|
1053 raise KeyboardInterrupt() |
|
1054 |
1058 |
1055 for s, r in replacements: |
1059 for s, r in replacements: |
1056 output = re.sub(s, r, output) |
1060 output = re.sub(s, r, output) |
1057 return ret, output.splitlines(True) |
1061 return ret, output.splitlines(True) |
1058 |
1062 |
1227 t = threading.Thread(target=job, name=test.name, |
1232 t = threading.Thread(target=job, name=test.name, |
1228 args=(test, result)) |
1233 args=(test, result)) |
1229 t.start() |
1234 t.start() |
1230 running += 1 |
1235 running += 1 |
1231 except KeyboardInterrupt: |
1236 except KeyboardInterrupt: |
1232 self._runner.abort[0] = True |
1237 for test in runtests: |
|
1238 test.abort() |
1233 |
1239 |
1234 return result |
1240 return result |
1235 |
1241 |
1236 class TextTestRunner(unittest.TextTestRunner): |
1242 class TextTestRunner(unittest.TextTestRunner): |
1237 """Custom unittest test runner that uses appropriate settings.""" |
1243 """Custom unittest test runner that uses appropriate settings.""" |
1313 self.inst = None |
1319 self.inst = None |
1314 self.bindir = None |
1320 self.bindir = None |
1315 self.tmpbinddir = None |
1321 self.tmpbinddir = None |
1316 self.pythondir = None |
1322 self.pythondir = None |
1317 self.coveragefile = None |
1323 self.coveragefile = None |
1318 self.abort = [False] |
|
1319 self._createdfiles = [] |
1324 self._createdfiles = [] |
1320 self._hgpath = None |
1325 self._hgpath = None |
1321 |
1326 |
1322 def run(self, args, parser=None): |
1327 def run(self, args, parser=None): |
1323 """Run the test suite.""" |
1328 """Run the test suite.""" |
1516 break |
1521 break |
1517 |
1522 |
1518 refpath = os.path.join(self.testdir, test) |
1523 refpath = os.path.join(self.testdir, test) |
1519 tmpdir = os.path.join(self.hgtmp, 'child%d' % count) |
1524 tmpdir = os.path.join(self.hgtmp, 'child%d' % count) |
1520 |
1525 |
1521 return testcls(refpath, tmpdir, self.abort, |
1526 return testcls(refpath, tmpdir, |
1522 keeptmpdir=self.options.keep_tmpdir, |
1527 keeptmpdir=self.options.keep_tmpdir, |
1523 debug=self.options.debug, |
1528 debug=self.options.debug, |
1524 nodiff = self.options.nodiff, |
1529 nodiff = self.options.nodiff, |
1525 diffviewer=self.options.view, |
1530 diffviewer=self.options.view, |
1526 interactive=self.options.interactive, |
1531 interactive=self.options.interactive, |