Skip to content

Commit ecd668d

Browse files
bpo-36915: regrtest always remove tempdir of worker processes (GH-13312)
When using multiprocessing (-jN option), worker processes now create their temporary directory inside the temporary directory of the main process. So the main process is able to remove temporary directories of worker processes even if they crash or when they are killed by regrtest on KeyboardInterrupt (CTRL+c). Rework also how multiprocessing arguments are parsed in main.py. (cherry picked from commit 3c93153) Co-authored-by: Victor Stinner <vstinner@redhat.com>
1 parent 8b3823a commit ecd668d

File tree

3 files changed

+69
-44
lines changed

3 files changed

+69
-44
lines changed

Lib/test/libregrtest/main.py

Lines changed: 54 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import datetime
22
import faulthandler
3-
import json
43
import locale
54
import os
65
import platform
@@ -22,22 +21,6 @@
2221
from test import support
2322

2423

25-
# When tests are run from the Python build directory, it is best practice
26-
# to keep the test files in a subfolder. This eases the cleanup of leftover
27-
# files using the "make distclean" command.
28-
if sysconfig.is_python_build():
29-
TEMPDIR = sysconfig.get_config_var('abs_builddir')
30-
if TEMPDIR is None:
31-
# bpo-30284: On Windows, only srcdir is available. Using abs_builddir
32-
# mostly matters on UNIX when building Python out of the source tree,
33-
# especially when the source tree is read only.
34-
TEMPDIR = sysconfig.get_config_var('srcdir')
35-
TEMPDIR = os.path.join(TEMPDIR, 'build')
36-
else:
37-
TEMPDIR = tempfile.gettempdir()
38-
TEMPDIR = os.path.abspath(TEMPDIR)
39-
40-
4124
class Regrtest:
4225
"""Execute a test suite.
4326
@@ -98,7 +81,10 @@ def __init__(self):
9881
# used by --junit-xml
9982
self.testsuite_xml = None
10083

84+
# misc
10185
self.win_load_tracker = None
86+
self.tmp_dir = None
87+
self.worker_test_name = None
10288

10389
def get_executed(self):
10490
return (set(self.good) | set(self.bad) | set(self.skipped)
@@ -177,6 +163,13 @@ def parse_args(self, kwargs):
177163
if ns.xmlpath:
178164
support.junit_xml_list = self.testsuite_xml = []
179165

166+
worker_args = ns.worker_args
167+
if worker_args is not None:
168+
from test.libregrtest.runtest_mp import parse_worker_args
169+
ns, test_name = parse_worker_args(ns.worker_args)
170+
ns.worker_args = worker_args
171+
self.worker_test_name = test_name
172+
180173
# Strip .py extensions.
181174
removepy(ns.args)
182175

@@ -186,7 +179,7 @@ def find_tests(self, tests):
186179
self.tests = tests
187180

188181
if self.ns.single:
189-
self.next_single_filename = os.path.join(TEMPDIR, 'pynexttest')
182+
self.next_single_filename = os.path.join(self.tmp_dir, 'pynexttest')
190183
try:
191184
with open(self.next_single_filename, 'r') as fp:
192185
next_test = fp.read().strip()
@@ -544,29 +537,54 @@ def save_xml_result(self):
544537
for s in ET.tostringlist(root):
545538
f.write(s)
546539

547-
def main(self, tests=None, **kwargs):
548-
global TEMPDIR
549-
self.ns = self.parse_args(kwargs)
550-
540+
def create_temp_dir(self):
551541
if self.ns.tempdir:
552-
TEMPDIR = self.ns.tempdir
553-
elif self.ns.worker_args:
554-
ns_dict, _ = json.loads(self.ns.worker_args)
555-
TEMPDIR = ns_dict.get("tempdir") or TEMPDIR
542+
self.tmp_dir = self.ns.tempdir
543+
544+
if not self.tmp_dir:
545+
# When tests are run from the Python build directory, it is best practice
546+
# to keep the test files in a subfolder. This eases the cleanup of leftover
547+
# files using the "make distclean" command.
548+
if sysconfig.is_python_build():
549+
self.tmp_dir = sysconfig.get_config_var('abs_builddir')
550+
if self.tmp_dir is None:
551+
# bpo-30284: On Windows, only srcdir is available. Using
552+
# abs_builddir mostly matters on UNIX when building Python
553+
# out of the source tree, especially when the source tree
554+
# is read only.
555+
self.tmp_dir = sysconfig.get_config_var('srcdir')
556+
self.tmp_dir = os.path.join(self.tmp_dir, 'build')
557+
else:
558+
self.tmp_dir = tempfile.gettempdir()
556559

557-
os.makedirs(TEMPDIR, exist_ok=True)
560+
self.tmp_dir = os.path.abspath(self.tmp_dir)
561+
os.makedirs(self.tmp_dir, exist_ok=True)
558562

559563
# Define a writable temp dir that will be used as cwd while running
560564
# the tests. The name of the dir includes the pid to allow parallel
561565
# testing (see the -j option).
562-
test_cwd = 'test_python_{}'.format(os.getpid())
563-
test_cwd = os.path.join(TEMPDIR, test_cwd)
566+
pid = os.getpid()
567+
if self.worker_test_name is not None:
568+
test_cwd = 'worker_{}'.format(pid)
569+
else:
570+
test_cwd = 'test_python_{}'.format(pid)
571+
test_cwd = os.path.join(self.tmp_dir, test_cwd)
572+
return test_cwd
573+
574+
def main(self, tests=None, **kwargs):
575+
self.ns = self.parse_args(kwargs)
576+
577+
test_cwd = self.create_temp_dir()
564578

565-
# Run the tests in a context manager that temporarily changes the CWD to a
566-
# temporary and writable directory. If it's not possible to create or
567-
# change the CWD, the original CWD will be used. The original CWD is
568-
# available from support.SAVEDCWD.
579+
# Run the tests in a context manager that temporarily changes the CWD
580+
# to a temporary and writable directory. If it's not possible to
581+
# create or change the CWD, the original CWD will be used.
582+
# The original CWD is available from support.SAVEDCWD.
569583
with support.temp_cwd(test_cwd, quiet=True):
584+
# When using multiprocessing, worker processes will use test_cwd
585+
# as their parent temporary directory. So when the main process
586+
# exit, it removes also subdirectories of worker processes.
587+
self.ns.tempdir = test_cwd
570588
self._main(tests, kwargs)
571589

572590
def getloadavg(self):
@@ -588,9 +606,9 @@ def _main(self, tests, kwargs):
588606
print(msg, file=sys.stderr, flush=True)
589607
sys.exit(2)
590608

591-
if self.ns.worker_args is not None:
609+
if self.worker_test_name is not None:
592610
from test.libregrtest.runtest_mp import run_tests_worker
593-
run_tests_worker(self.ns.worker_args)
611+
run_tests_worker(self.ns, self.worker_test_name)
594612

595613
if self.ns.wait:
596614
input("Press any key to continue...")
@@ -611,7 +629,7 @@ def _main(self, tests, kwargs):
611629

612630
# If we're on windows and this is the parent runner (not a worker),
613631
# track the load average.
614-
if sys.platform == 'win32' and (self.ns.worker_args is None):
632+
if sys.platform == 'win32' and self.worker_test_name is None:
615633
from test.libregrtest.win_utils import WindowsLoadTracker
616634

617635
try:

Lib/test/libregrtest/runtest_mp.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ def must_stop(result, ns):
3333
return False
3434

3535

36+
def parse_worker_args(worker_args):
37+
ns_dict, test_name = json.loads(worker_args)
38+
ns = types.SimpleNamespace(**ns_dict)
39+
return (ns, test_name)
40+
41+
3642
def run_test_in_subprocess(testname, ns):
3743
ns_dict = vars(ns)
3844
worker_args = (ns_dict, testname)
@@ -42,8 +48,6 @@ def run_test_in_subprocess(testname, ns):
4248
'-u', # Unbuffered stdout and stderr
4349
'-m', 'test.regrtest',
4450
'--worker-args', worker_args]
45-
if ns.pgo:
46-
cmd += ['--pgo']
4751

4852
# Running the child from the same working directory as regrtest's original
4953
# invocation ensures that TEMPDIR for the child is the same when
@@ -56,15 +60,15 @@ def run_test_in_subprocess(testname, ns):
5660
cwd=support.SAVEDCWD)
5761

5862

59-
def run_tests_worker(worker_args):
60-
ns_dict, testname = json.loads(worker_args)
61-
ns = types.SimpleNamespace(**ns_dict)
62-
63+
def run_tests_worker(ns, test_name):
6364
setup_tests(ns)
6465

65-
result = runtest(ns, testname)
66+
result = runtest(ns, test_name)
67+
6668
print() # Force a newline (just in case)
67-
print(json.dumps(result), flush=True)
69+
70+
# Serialize TestResult as list in JSON
71+
print(json.dumps(list(result)), flush=True)
6872
sys.exit(0)
6973

7074

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The main regrtest process now always removes all temporary directories of
2+
worker processes even if they crash or if they are killed on
3+
KeyboardInterrupt (CTRL+c).

0 commit comments

Comments
 (0)