diff --git a/Lib/test/libregrtest/main.py b/Lib/test/libregrtest/main.py index e2274254fdb89c..7d6a62863599bd 100644 --- a/Lib/test/libregrtest/main.py +++ b/Lib/test/libregrtest/main.py @@ -14,7 +14,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, + INTERRUPTED, CHILD_ERROR, TEST_DID_NOT_RUN, TIMEOUT, 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 @@ -114,6 +114,8 @@ def accumulate_result(self, result, rerun=False): self.run_no_tests.append(test_name) elif ok == INTERRUPTED: self.interrupted = True + elif ok == TIMEOUT: + self.bad.append(test_name) else: raise ValueError("invalid test result: %r" % ok) diff --git a/Lib/test/libregrtest/runtest.py b/Lib/test/libregrtest/runtest.py index a43b7666cd1e35..b24015af009a09 100644 --- a/Lib/test/libregrtest/runtest.py +++ b/Lib/test/libregrtest/runtest.py @@ -25,6 +25,7 @@ INTERRUPTED = -4 CHILD_ERROR = -5 # error in a child process TEST_DID_NOT_RUN = -6 +TIMEOUT = -7 _FORMAT_TEST_RESULT = { PASSED: '%s passed', @@ -35,6 +36,7 @@ INTERRUPTED: '%s interrupted', CHILD_ERROR: '%s crashed', TEST_DID_NOT_RUN: '%s run no tests', + TIMEOUT: '%s timed out', } # Minimum duration of a test to display its duration or to mention that @@ -66,7 +68,7 @@ def is_failed(result, ns): ok = result.result - if ok in (PASSED, RESOURCE_DENIED, SKIPPED, TEST_DID_NOT_RUN): + if ok in (PASSED, RESOURCE_DENIED, SKIPPED, TEST_DID_NOT_RUN, TIMEOUT): return False if ok == ENV_CHANGED: return ns.fail_env_changed @@ -179,6 +181,7 @@ def runtest(ns, test_name): FAILED test failed PASSED test passed EMPTY_TEST_SUITE test ran no subtests. + TIMEOUT test timd out. If ns.xmlpath is not None, xml_data is a list containing each generated testsuite element. diff --git a/Lib/test/libregrtest/runtest_mp.py b/Lib/test/libregrtest/runtest_mp.py index aa2409b4ef7985..10d7c7ce0edd68 100644 --- a/Lib/test/libregrtest/runtest_mp.py +++ b/Lib/test/libregrtest/runtest_mp.py @@ -13,7 +13,7 @@ from test.libregrtest.runtest import ( runtest, INTERRUPTED, CHILD_ERROR, PROGRESS_MIN_TIME, - format_test_result, TestResult, is_failed) + format_test_result, TestResult, is_failed, TIMEOUT) from test.libregrtest.setup import setup_tests from test.libregrtest.utils import format_duration @@ -137,6 +137,13 @@ def kill(self): popen.stdout.close() popen.stderr.close() + def time_result(self, test_name, error_type): + test_time = time.monotonic() - self.start_time + result = TestResult(test_name, error_type, test_time, None) + stdout = stderr = '' + err_msg = None + return result, stdout, stderr, err_msg + def _runtest(self, test_name): try: self.start_time = time.monotonic() @@ -154,7 +161,14 @@ def _runtest(self, test_name): raise ExitThread try: - stdout, stderr = popen.communicate() + stdout, stderr = popen.communicate(timeout=self.ns.timeout) + except subprocess.TimeoutExpired: + if self._killed: + # kill() has been called: communicate() fails + # on reading closed stdout/stderr + raise ExitThread + result, stdout, stderr, err_msg = self.time_result(test_name, TIMEOUT) + raise except OSError: if self._killed: # kill() has been called: communicate() fails @@ -191,8 +205,7 @@ def _runtest(self, test_name): err_msg = "Failed to parse worker JSON: %s" % exc if err_msg is not None: - test_time = time.monotonic() - self.start_time - result = TestResult(test_name, CHILD_ERROR, test_time, None) + result, stdout, stderr, err_msg = self.time_result(test_name, CHILD_ERROR) return MultiprocessResult(result, stdout, stderr, err_msg) diff --git a/Misc/NEWS.d/next/Library/2019-07-09-19-38-26.bpo-37531.GX7s8S.rst b/Misc/NEWS.d/next/Library/2019-07-09-19-38-26.bpo-37531.GX7s8S.rst new file mode 100644 index 00000000000000..aa5f0f495ed01e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-07-09-19-38-26.bpo-37531.GX7s8S.rst @@ -0,0 +1,3 @@ +The change only impacts "python3 -m test -jN --timeout=TIMEOUT", +it ensures that a worker process is stopped with a timeout if it +runs longer than `TIMEOUT` seconds.