Skip to content

bpo-37531: Fix regrtest timeout for subprocesses #14679

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Lib/test/libregrtest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
5 changes: 4 additions & 1 deletion Lib/test/libregrtest/runtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
21 changes: 17 additions & 4 deletions Lib/test/libregrtest/runtest_mp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand Down Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
@@ -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.