diff options
author | Victor Stinner <vstinner@redhat.com> | 2019-05-13 17:17:54 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-05-13 17:17:54 (GMT) |
commit | b0917df329ba14b7bc6fa782c1b61e7a2163af0b (patch) | |
tree | dc5890e4e93dc4306b545a52d6c523366f69f55a | |
parent | 85c69d5c4c5682a70201612128e838d438c01499 (diff) | |
download | cpython-b0917df329ba14b7bc6fa782c1b61e7a2163af0b.zip cpython-b0917df329ba14b7bc6fa782c1b61e7a2163af0b.tar.gz cpython-b0917df329ba14b7bc6fa782c1b61e7a2163af0b.tar.bz2 |
bpo-36719: regrtest -jN no longer stops on crash (GH-13231)
"python3 -m test -jN ..." now continues the execution of next tests
when a worker process crash (CHILD_ERROR state). Previously, the test
suite stopped immediately. Use --failfast to stop at the first error.
Moreover, --forever now also implies --failfast.
-rw-r--r-- | Lib/test/libregrtest/cmdline.py | 5 | ||||
-rw-r--r-- | Lib/test/libregrtest/main.py | 7 | ||||
-rw-r--r-- | Lib/test/libregrtest/runtest.py | 11 | ||||
-rw-r--r-- | Lib/test/libregrtest/runtest_mp.py | 41 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Tests/2019-05-10-01-50-30.bpo-36719.O84ZWv.rst | 3 |
5 files changed, 49 insertions, 18 deletions
diff --git a/Lib/test/libregrtest/cmdline.py b/Lib/test/libregrtest/cmdline.py index cb09ee0..dc0d880 100644 --- a/Lib/test/libregrtest/cmdline.py +++ b/Lib/test/libregrtest/cmdline.py @@ -256,7 +256,7 @@ def _create_parser(): help='suppress error message boxes on Windows') group.add_argument('-F', '--forever', action='store_true', help='run the specified tests in a loop, until an ' - 'error happens') + 'error happens; imply --failfast') group.add_argument('--list-tests', action='store_true', help="only write the name of tests that will be run, " "don't execute them") @@ -389,5 +389,8 @@ def _parse_args(args, **kwargs): with open(ns.match_filename) as fp: for line in fp: ns.match_tests.append(line.strip()) + if ns.forever: + # --forever implies --failfast + ns.failfast = True return ns diff --git a/Lib/test/libregrtest/main.py b/Lib/test/libregrtest/main.py index c19ea44..02717d8c 100644 --- a/Lib/test/libregrtest/main.py +++ b/Lib/test/libregrtest/main.py @@ -16,7 +16,7 @@ from test.libregrtest.runtest import ( findtests, runtest, get_abs_module, STDTESTS, NOTTESTS, PASSED, FAILED, ENV_CHANGED, SKIPPED, RESOURCE_DENIED, INTERRUPTED, CHILD_ERROR, TEST_DID_NOT_RUN, - PROGRESS_MIN_TIME, format_test_result) + PROGRESS_MIN_TIME, format_test_result, is_failed) from test.libregrtest.setup import setup_tests from test.libregrtest.utils import removepy, count, format_duration, printlist from test import support @@ -404,7 +404,7 @@ class Regrtest: test_time = time.monotonic() - start_time if test_time >= PROGRESS_MIN_TIME: previous_test = "%s in %s" % (previous_test, format_duration(test_time)) - elif result[0] == PASSED: + elif result.result == PASSED: # be quiet: say nothing if the test passed shortly previous_test = None @@ -413,6 +413,9 @@ class Regrtest: if module not in save_modules and module.startswith("test."): support.unload(module) + if self.ns.failfast and is_failed(result, self.ns): + break + if previous_test: print(previous_test) diff --git a/Lib/test/libregrtest/runtest.py b/Lib/test/libregrtest/runtest.py index a957492..a43b766 100644 --- a/Lib/test/libregrtest/runtest.py +++ b/Lib/test/libregrtest/runtest.py @@ -24,7 +24,7 @@ SKIPPED = -2 RESOURCE_DENIED = -3 INTERRUPTED = -4 CHILD_ERROR = -5 # error in a child process -TEST_DID_NOT_RUN = -6 # error in a child process +TEST_DID_NOT_RUN = -6 _FORMAT_TEST_RESULT = { PASSED: '%s passed', @@ -64,6 +64,15 @@ NOTTESTS = set() FOUND_GARBAGE = [] +def is_failed(result, ns): + ok = result.result + if ok in (PASSED, RESOURCE_DENIED, SKIPPED, TEST_DID_NOT_RUN): + return False + if ok == ENV_CHANGED: + return ns.fail_env_changed + return True + + def format_test_result(result): fmt = _FORMAT_TEST_RESULT.get(result.result, "%s") return fmt % result.test_name diff --git a/Lib/test/libregrtest/runtest_mp.py b/Lib/test/libregrtest/runtest_mp.py index dbab695..ced7f86 100644 --- a/Lib/test/libregrtest/runtest_mp.py +++ b/Lib/test/libregrtest/runtest_mp.py @@ -13,7 +13,7 @@ from test import support from test.libregrtest.runtest import ( runtest, INTERRUPTED, CHILD_ERROR, PROGRESS_MIN_TIME, - format_test_result, TestResult) + format_test_result, TestResult, is_failed) from test.libregrtest.setup import setup_tests from test.libregrtest.utils import format_duration @@ -22,8 +22,12 @@ from test.libregrtest.utils import format_duration PROGRESS_UPDATE = 30.0 # seconds -def must_stop(result): - return result.result in (INTERRUPTED, CHILD_ERROR) +def must_stop(result, ns): + if result.result == INTERRUPTED: + return True + if ns.failfast and is_failed(result, ns): + return True + return False def run_test_in_subprocess(testname, ns): @@ -66,16 +70,22 @@ class MultiprocessIterator: """A thread-safe iterator over tests for multiprocess mode.""" - def __init__(self, tests): + def __init__(self, tests_iter): self.lock = threading.Lock() - self.tests = tests + self.tests_iter = tests_iter def __iter__(self): return self def __next__(self): with self.lock: - return next(self.tests) + if self.tests_iter is None: + raise StopIteration + return next(self.tests_iter) + + def stop(self): + with self.lock: + self.tests_iter = None MultiprocessResult = collections.namedtuple('MultiprocessResult', @@ -92,23 +102,24 @@ class MultiprocessThread(threading.Thread): self._popen = None def kill(self): - if not self.is_alive(): + popen = self._popen + if popen is None: return - if self._popen is not None: - self._popen.kill() + print("Kill regrtest worker process %s" % popen.pid) + popen.kill() def _runtest(self, test_name): try: self.start_time = time.monotonic() self.current_test_name = test_name - popen = run_test_in_subprocess(test_name, self.ns) - self._popen = popen + self._popen = run_test_in_subprocess(test_name, self.ns) + popen = self._popen with popen: try: stdout, stderr = popen.communicate() except: - popen.kill() + self.kill() popen.wait() raise @@ -153,7 +164,7 @@ class MultiprocessThread(threading.Thread): mp_result = self._runtest(test_name) self.output.put((False, mp_result)) - if must_stop(mp_result.result): + if must_stop(mp_result.result, self.ns): break except BaseException: self.output.put((True, traceback.format_exc())) @@ -255,7 +266,7 @@ class MultiprocessRunner: if mp_result.stderr and not self.ns.pgo: print(mp_result.stderr, file=sys.stderr, flush=True) - if must_stop(mp_result.result): + if must_stop(mp_result.result, self.ns): return True return False @@ -280,6 +291,8 @@ class MultiprocessRunner: if self.test_timeout is not None: faulthandler.cancel_dump_traceback_later() + # a test failed (and --failfast is set) or all tests completed + self.pending.stop() self.wait_workers() diff --git a/Misc/NEWS.d/next/Tests/2019-05-10-01-50-30.bpo-36719.O84ZWv.rst b/Misc/NEWS.d/next/Tests/2019-05-10-01-50-30.bpo-36719.O84ZWv.rst new file mode 100644 index 0000000..9f60145 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2019-05-10-01-50-30.bpo-36719.O84ZWv.rst @@ -0,0 +1,3 @@ +"python3 -m test -jN ..." now continues the execution of next tests when a +worker process crash (CHILD_ERROR state). Previously, the test suite stopped +immediately. Use --failfast to stop at the first error. |