diff --git a/Lib/test/regrtest.py b/Lib/test/regrtest.py --- a/Lib/test/regrtest.py +++ b/Lib/test/regrtest.py @@ -21,7 +21,7 @@ Options: -h/--help -- print this text and exit --timeout TIMEOUT - -- dump the traceback and exit if a test takes more + -- dump the traceback and exit if a test function takes more than TIMEOUT seconds (default: 30 minutes); disable the timeout if TIMEOUT is zero @@ -240,7 +240,7 @@ def main(tests=None, testdir=None, verbo findleaks=False, use_resources=None, trace=False, coverdir='coverage', runleaks=False, huntrleaks=False, verbose2=False, print_slow=False, random_seed=None, use_mp=None, verbose3=False, forever=False, - header=False, timeout=60*60): + header=False, timeout=15*60): """Execute a test suite. This also parses command-line options and modifies its behavior @@ -421,6 +421,12 @@ def main(tests=None, testdir=None, verbo if use_mp and findleaks: usage("-l and -j don't go together!") + # set the global timeout of a single test function + if timeout is not None and timeout > 0: + support.TIMEOUT = timeout + else: + support.TIMEOUT = None + good = [] bad = [] skipped = [] @@ -565,7 +571,7 @@ def main(tests=None, testdir=None, verbo args_tuple = ( (test, verbose, quiet), dict(huntrleaks=huntrleaks, use_resources=use_resources, - debug=debug, rerun_failed=verbose3, timeout=timeout) + debug=debug, rerun_failed=verbose3) ) yield (test, args_tuple) pending = tests_and_args() @@ -642,12 +648,12 @@ def main(tests=None, testdir=None, verbo if trace: # If we're tracing code coverage, then we don't exit with status # if on a false return value from main. - tracer.runctx('runtest(test, verbose, quiet, timeout=timeout)', + tracer.runctx('runtest(test, verbose, quiet)', globals=globals(), locals=vars()) else: try: result = runtest(test, verbose, quiet, huntrleaks, debug, - rerun_failed=verbose3, timeout=timeout) + rerun_failed=verbose3) accumulate_result(test, result) except KeyboardInterrupt: interrupted = True @@ -718,7 +724,7 @@ def main(tests=None, testdir=None, verbo sys.stdout.flush() try: verbose = True - ok = runtest(test, True, quiet, huntrleaks, debug, timeout=timeout) + ok = runtest(test, True, quiet, huntrleaks, debug) except KeyboardInterrupt: # print a newline separate from the ^C print() @@ -794,7 +800,7 @@ def replace_stdout(): def runtest(test, verbose, quiet, huntrleaks=False, debug=False, use_resources=None, - rerun_failed=False, timeout=None): + rerun_failed=False): """Run a single test. test -- the name of the test @@ -804,8 +810,6 @@ def runtest(test, verbose, quiet, huntrleaks -- run multiple times to test for leaks; requires a debug build; a triple corresponding to -R's three arguments rerun_failed -- if true, re-run in verbose mode when failed - timeout -- dump the traceback and exit if a test takes more than - timeout seconds Returns one of the test result constants: INTERRUPTED KeyboardInterrupt when run under -j @@ -819,9 +823,6 @@ def runtest(test, verbose, quiet, support.verbose = verbose # Tell tests to be moderately quiet if use_resources is not None: support.use_resources = use_resources - use_timeout = (timeout is not None and timeout > 0) - if use_timeout: - faulthandler.dump_tracebacks_later(timeout, exit=True) try: result = runtest_inner(test, verbose, quiet, huntrleaks, debug) if result[0] == FAILED and rerun_failed: @@ -829,11 +830,9 @@ def runtest(test, verbose, quiet, sys.stdout.flush() sys.stderr.flush() print("Re-running test {} in verbose mode".format(test)) - runtest(test, True, quiet, huntrleaks, debug, timeout=timeout) + runtest(test, True, quiet, huntrleaks, debug) return result finally: - if use_timeout: - faulthandler.cancel_dump_tracebacks_later() cleanup_test_droppings(test, verbose) # Unit tests are supposed to leave the execution environment unchanged diff --git a/Lib/test/support.py b/Lib/test/support.py --- a/Lib/test/support.py +++ b/Lib/test/support.py @@ -5,6 +5,7 @@ if __name__ != 'test.support': import contextlib import errno +import faulthandler import functools import gc import socket @@ -45,6 +46,9 @@ __all__ = [ "swap_item", "swap_attr", "requires_IEEE_754", "TestHandler", "Matcher", "can_symlink", "skip_unless_symlink"] +# Timeout in seconds of a single test function used by run_unittest() +TIMEOUT = None + class Error(Exception): """Base class for regression test exceptions.""" @@ -1073,8 +1077,14 @@ def bigaddrspacetest(f): # unittest integration. class BasicTestRunner: + resultclass = unittest.TestResult + + def __init__(self, resultclass=None): + if resultclass is not None: + self.resultclass = resultclass + def run(self, test): - result = unittest.TestResult() + result = self.resultclass() test(result) return result @@ -1159,9 +1169,26 @@ def refcount_test(test): def _run_suite(suite): """Run tests from a unittest.TestSuite-derived class.""" if verbose: - runner = unittest.TextTestRunner(sys.stdout, verbosity=2) + result_base_class = unittest.TextTestRunner.resultclass else: - runner = BasicTestRunner() + result_base_class = BasicTestRunner.resultclass + + class Result(result_base_class): + def startTest(self, test): + result_base_class.startTest(self, test) + if TIMEOUT: + faulthandler.dump_tracebacks_later(TIMEOUT, exit=True) + + def stopTest(self, test): + if TIMEOUT: + faulthandler.cancel_dump_tracebacks_later() + result_base_class.stopTest(self, test) + + if verbose: + runner = unittest.TextTestRunner(sys.stdout, verbosity=2, + resultclass=Result) + else: + runner = BasicTestRunner(resultclass=Result) result = runner.run(suite) if not result.wasSuccessful():