Skip to content

Commit 8a62de0

Browse files
committed
pythongh-109276: libregrtest: add RunTests.work_dir
* WorkerThread now always creates a temporary directory, even on Emscripten and WASI: it's used as the working directory of the test worker process. * Fix Emscripten and WASI: start the test worker process in the Python source code directory, where 'python.js' and 'python.wasm' can be found. Then worker_process() goes to the temporary directory created to run tests.
1 parent 1ee50e2 commit 8a62de0

File tree

5 files changed

+55
-44
lines changed

5 files changed

+55
-44
lines changed

Diff for: Lib/test/libregrtest/main.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
StrPath, StrJSON, TestName, TestList, TestTuple, FilterTuple,
2121
strip_py_suffix, count, format_duration,
2222
printlist, get_temp_dir, get_work_dir, exit_timeout,
23-
display_header, cleanup_temp_dir)
23+
display_header, cleanup_temp_dir, set_temp_dir_environ)
2424

2525

2626
class Regrtest:
@@ -393,9 +393,12 @@ def create_run_tests(self, tests: TestTuple):
393393
gc_threshold=self.gc_threshold,
394394
use_resources=self.use_resources,
395395
python_cmd=self.python_cmd,
396+
work_dir=os.getcwd(),
396397
)
397398

398399
def _run_tests(self, selected: TestTuple, tests: TestList | None) -> int:
400+
set_temp_dir_environ(os.environ, os.getcwd())
401+
399402
if self.hunt_refleak and self.hunt_refleak.warmups < 3:
400403
msg = ("WARNING: Running tests with --huntrleaks/-R and "
401404
"less than 3 warmup repetitions can give false positives!")

Diff for: Lib/test/libregrtest/run_workers.py

+27-21
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import threading
1010
import time
1111
import traceback
12-
from typing import Literal, TextIO
12+
from typing import Literal
1313

1414
from test import support
1515
from test.support import os_helper
@@ -156,10 +156,10 @@ def mp_result_error(
156156
return MultiprocessResult(test_result, stdout, err_msg)
157157

158158
def _run_process(self, runtests: RunTests, output_fd: int, json_fd: int,
159-
tmp_dir: StrPath | None = None) -> int:
159+
start_work_dir: StrPath | None = None) -> int:
160160
try:
161161
popen = create_worker_process(runtests, output_fd, json_fd,
162-
tmp_dir)
162+
start_work_dir)
163163

164164
self._killed = False
165165
self._popen = popen
@@ -235,33 +235,39 @@ def _runtest(self, test_name: TestName) -> MultiprocessResult:
235235
if MS_WINDOWS:
236236
json_fd = msvcrt.get_osfhandle(json_fd)
237237

238+
# gh-93353: Check for leaked temporary files in the parent process,
239+
# since the deletion of temporary files can happen late during
240+
# Python finalization: too late for libregrtest.
241+
tmp_dir = tempfile.mkdtemp(prefix="worker_", dir=os.getcwd())
242+
tmp_dir = os.path.abspath(tmp_dir)
243+
238244
kwargs = {}
239245
if match_tests:
240246
kwargs['match_tests'] = match_tests
241247
worker_runtests = self.runtests.copy(
242248
tests=tests,
243249
json_fd=json_fd,
250+
work_dir=tmp_dir,
244251
**kwargs)
245252

246-
# gh-93353: Check for leaked temporary files in the parent process,
247-
# since the deletion of temporary files can happen late during
248-
# Python finalization: too late for libregrtest.
249-
if not support.is_wasi:
250-
# Don't check for leaked temporary files and directories if Python is
251-
# run on WASI. WASI don't pass environment variables like TMPDIR to
252-
# worker processes.
253-
tmp_dir = tempfile.mkdtemp(prefix="test_python_")
254-
tmp_dir = os.path.abspath(tmp_dir)
255-
try:
256-
retcode = self._run_process(worker_runtests,
257-
stdout_fd, json_fd, tmp_dir)
258-
finally:
259-
tmp_files = os.listdir(tmp_dir)
260-
os_helper.rmtree(tmp_dir)
253+
# Starting working directory of the worker process.
254+
#
255+
# Emscripten and WASI Python must start in the Python source code
256+
# directory to get 'python.js' or 'python.wasm' file.
257+
#
258+
# Then worker_process() calls change_cwd(runtests.work_dir).
259+
if support.is_emscripten or support.is_wasi:
260+
start_work_dir = os_helper.SAVEDCWD
261261
else:
262+
start_work_dir = tmp_dir
263+
264+
try:
262265
retcode = self._run_process(worker_runtests,
263-
stdout_fd, json_fd)
264-
tmp_files = ()
266+
stdout_fd, json_fd,
267+
start_work_dir)
268+
finally:
269+
tmp_files = os.listdir(tmp_dir)
270+
os_helper.rmtree(tmp_dir)
265271
stdout_file.seek(0)
266272

267273
try:
@@ -280,7 +286,7 @@ def _runtest(self, test_name: TestName) -> MultiprocessResult:
280286
if worker_json:
281287
result = TestResult.from_json(worker_json)
282288
else:
283-
err_msg = f"empty JSON"
289+
err_msg = "empty JSON"
284290
except Exception as exc:
285291
# gh-101634: Catch UnicodeDecodeError if stdout cannot be
286292
# decoded from encoding

Diff for: Lib/test/libregrtest/runtests.py

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class RunTests:
3939
# On Unix, it's a file descriptor.
4040
# On Windows, it's a handle.
4141
json_fd: int | None = None
42+
work_dir: StrPath = None
4243

4344
def copy(self, **override):
4445
state = dataclasses.asdict(self)

Diff for: Lib/test/libregrtest/utils.py

+11-7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818

1919
MS_WINDOWS = (sys.platform == 'win32')
20+
WORK_DIR_PREFIX = 'test_python_'
2021

2122
# bpo-38203: Maximum delay in seconds to exit Python (call Py_Finalize()).
2223
# Used to protect against threading._shutdown() hang.
@@ -359,6 +360,12 @@ def get_temp_dir(tmp_dir):
359360
return os.path.abspath(tmp_dir)
360361

361362

363+
def set_temp_dir_environ(environ: dict, tmp_dir: StrPath) -> None:
364+
environ['TMPDIR'] = tmp_dir
365+
environ['TEMP'] = tmp_dir
366+
environ['TMP'] = tmp_dir
367+
368+
362369
def fix_umask():
363370
if support.is_emscripten:
364371
# Emscripten has default umask 0o777, which breaks some tests.
@@ -370,21 +377,18 @@ def fix_umask():
370377
os.umask(old_mask)
371378

372379

373-
def get_work_dir(*, parent_dir: StrPath = '', worker: bool = False):
380+
def get_work_dir(*, parent_dir: StrPath | None = None) -> StrPath:
374381
# Define a writable temp dir that will be used as cwd while running
375382
# the tests. The name of the dir includes the pid to allow parallel
376383
# testing (see the -j option).
377384
# Emscripten and WASI have stubbed getpid(), Emscripten has only
378385
# milisecond clock resolution. Use randint() instead.
379-
if sys.platform in {"emscripten", "wasi"}:
386+
if support.is_emscripten or support.is_wasi:
380387
nounce = random.randint(0, 1_000_000)
381388
else:
382389
nounce = os.getpid()
383390

384-
if worker:
385-
work_dir = 'test_python_worker_{}'.format(nounce)
386-
else:
387-
work_dir = 'test_python_{}'.format(nounce)
391+
work_dir = WORK_DIR_PREFIX + str(nounce)
388392
work_dir += os_helper.FS_NONASCII
389393
if parent_dir:
390394
work_dir = os.path.join(parent_dir, work_dir)
@@ -570,7 +574,7 @@ def display_header():
570574
def cleanup_temp_dir(tmp_dir: StrPath):
571575
import glob
572576

573-
path = os.path.join(glob.escape(tmp_dir), 'test_python_*')
577+
path = os.path.join(glob.escape(tmp_dir), WORK_DIR_PREFIX + '*')
574578
print("Cleanup %s directory" % tmp_dir)
575579
for name in glob.glob(path):
576580
if os.path.isdir(name):

Diff for: Lib/test/libregrtest/worker.py

+12-15
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import subprocess
22
import sys
33
import os
4-
from typing import TextIO, NoReturn
4+
from typing import NoReturn
55

66
from test import support
77
from test.support import os_helper
@@ -11,15 +11,15 @@
1111
from .single import run_single_test
1212
from .utils import (
1313
StrPath, StrJSON, FilterTuple, MS_WINDOWS,
14-
get_work_dir, exit_timeout)
14+
exit_timeout, set_temp_dir_environ)
1515

1616

1717
USE_PROCESS_GROUP = (hasattr(os, "setsid") and hasattr(os, "killpg"))
1818

1919

2020
def create_worker_process(runtests: RunTests,
2121
output_fd: int, json_fd: int,
22-
tmp_dir: StrPath | None = None) -> subprocess.Popen:
22+
start_work_dir: StrPath | None = None) -> subprocess.Popen:
2323
python_cmd = runtests.python_cmd
2424
worker_json = runtests.as_json()
2525

@@ -33,10 +33,7 @@ def create_worker_process(runtests: RunTests,
3333
worker_json]
3434

3535
env = dict(os.environ)
36-
if tmp_dir is not None:
37-
env['TMPDIR'] = tmp_dir
38-
env['TEMP'] = tmp_dir
39-
env['TMP'] = tmp_dir
36+
set_temp_dir_environ(env, runtests.work_dir)
4037

4138
# Running the child from the same working directory as regrtest's original
4239
# invocation ensures that TEMPDIR for the child is the same when
@@ -48,6 +45,7 @@ def create_worker_process(runtests: RunTests,
4845
stderr=output_fd,
4946
text=True,
5047
close_fds=True,
48+
cwd=start_work_dir,
5149
)
5250
if not MS_WINDOWS:
5351
kwargs['pass_fds'] = [json_fd]
@@ -67,8 +65,9 @@ def create_worker_process(runtests: RunTests,
6765
os.set_handle_inheritable(json_fd, False)
6866

6967

70-
def worker_process(worker_json: StrJSON) -> NoReturn:
71-
runtests = RunTests.from_json(worker_json)
68+
def worker_process(runtests: RunTests) -> NoReturn:
69+
set_temp_dir_environ(os.environ, os.getcwd())
70+
7271
test_name = runtests.tests[0]
7372
match_tests: FilterTuple | None = runtests.match_tests
7473
json_fd: int = runtests.json_fd
@@ -77,7 +76,6 @@ def worker_process(worker_json: StrJSON) -> NoReturn:
7776
import msvcrt
7877
json_fd = msvcrt.open_osfhandle(json_fd, os.O_WRONLY)
7978

80-
8179
setup_test_dir(runtests.test_dir)
8280
setup_process()
8381

@@ -100,13 +98,12 @@ def main():
10098
if len(sys.argv) != 2:
10199
print("usage: python -m test.libregrtest.worker JSON")
102100
sys.exit(1)
103-
worker_json = sys.argv[1]
104-
105-
work_dir = get_work_dir(worker=True)
101+
worker_json: StrJSON = sys.argv[1]
106102

107103
with exit_timeout():
108-
with os_helper.temp_cwd(work_dir, quiet=True):
109-
worker_process(worker_json)
104+
runtests = RunTests.from_json(worker_json)
105+
with os_helper.change_cwd(runtests.work_dir):
106+
worker_process(runtests)
110107

111108

112109
if __name__ == "__main__":

0 commit comments

Comments
 (0)