Skip to content

Commit 6bd1482

Browse files
committed
gh-109276: libregrtest: WASM use stdout for JSON
On Emscripten and WASI, or if --python command line is used, libregrtest now writes JSON into stdout, instead of using a name file. * Add JsonFileType.STDOUT. * Remove JsonFileType.FILENAME. * test.pythoninfo logs environment variables related to cross-compilation and running Python on Emscripten/WASI.
1 parent 75cdd9a commit 6bd1482

File tree

4 files changed

+40
-33
lines changed

4 files changed

+40
-33
lines changed

Lib/test/libregrtest/run_workers.py

+12-14
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from .runtests import RunTests, JsonFile, JsonFileType
2222
from .single import PROGRESS_MIN_TIME
2323
from .utils import (
24-
StrPath, StrJSON, TestName, MS_WINDOWS, TMP_PREFIX,
24+
StrPath, StrJSON, TestName, MS_WINDOWS,
2525
format_duration, print_warning, count, plural)
2626
from .worker import create_worker_process, USE_PROCESS_GROUP
2727

@@ -225,16 +225,9 @@ def create_stdout(self, stack: contextlib.ExitStack) -> TextIO:
225225
def create_json_file(self, stack: contextlib.ExitStack) -> tuple[JsonFile, TextIO | None]:
226226
"""Create JSON file."""
227227

228-
json_file_use_filename = self.runtests.json_file_use_filename()
229-
if json_file_use_filename:
230-
# create an empty file to make the creation atomic
231-
# (to prevent races with other worker threads)
232-
prefix = TMP_PREFIX + 'json_'
233-
json_fd, json_filename = tempfile.mkstemp(prefix=prefix)
234-
os.close(json_fd)
235-
236-
stack.callback(os_helper.unlink, json_filename)
237-
json_file = JsonFile(json_filename, JsonFileType.FILENAME)
228+
json_file_use_stdout = self.runtests.json_file_use_stdout()
229+
if json_file_use_stdout:
230+
json_file = JsonFile(None, JsonFileType.STDOUT)
238231
json_tmpfile = None
239232
else:
240233
json_tmpfile = tempfile.TemporaryFile('w+', encoding='utf8')
@@ -300,11 +293,14 @@ def read_stdout(self, stdout_file: TextIO) -> str:
300293
f"Cannot read process stdout: {exc}", None)
301294

302295
def read_json(self, json_file: JsonFile, json_tmpfile: TextIO | None,
303-
stdout: str) -> TestResult:
296+
stdout: str) -> tuple[TestResult, str]:
304297
try:
305298
if json_tmpfile is not None:
306299
json_tmpfile.seek(0)
307300
worker_json: StrJSON = json_tmpfile.read()
301+
elif json_file.file_type == JsonFileType.STDOUT:
302+
stdout, _, worker_json = stdout.rpartition("\n")
303+
stdout = stdout.rstrip()
308304
else:
309305
with json_file.open(encoding='utf8') as json_fp:
310306
worker_json: StrJSON = json_fp.read()
@@ -319,14 +315,16 @@ def read_json(self, json_file: JsonFile, json_tmpfile: TextIO | None,
319315
raise WorkerError(self.test_name, "empty JSON", stdout)
320316

321317
try:
322-
return TestResult.from_json(worker_json)
318+
result = TestResult.from_json(worker_json)
323319
except Exception as exc:
324320
# gh-101634: Catch UnicodeDecodeError if stdout cannot be
325321
# decoded from encoding
326322
err_msg = f"Failed to parse worker process JSON: {exc}"
327323
raise WorkerError(self.test_name, err_msg, stdout,
328324
state=State.MULTIPROCESSING_ERROR)
329325

326+
return (result, stdout)
327+
330328
def _runtest(self, test_name: TestName) -> MultiprocessResult:
331329
with contextlib.ExitStack() as stack:
332330
stdout_file = self.create_stdout(stack)
@@ -341,7 +339,7 @@ def _runtest(self, test_name: TestName) -> MultiprocessResult:
341339
if retcode is None:
342340
raise WorkerError(self.test_name, None, stdout, state=State.TIMEOUT)
343341

344-
result = self.read_json(json_file, json_tmpfile, stdout)
342+
result, stdout = self.read_json(json_file, json_tmpfile, stdout)
345343

346344
if retcode != 0:
347345
raise WorkerError(self.test_name, f"Exit code {retcode}", stdout)

Lib/test/libregrtest/runtests.py

+16-11
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@
1414
class JsonFileType:
1515
UNIX_FD = "UNIX_FD"
1616
WINDOWS_HANDLE = "WINDOWS_HANDLE"
17-
FILENAME = "FILENAME"
17+
STDOUT = "STDOUT"
1818

1919

2020
@dataclasses.dataclass(slots=True, frozen=True)
2121
class JsonFile:
22-
# See RunTests.json_file_use_filename()
23-
file: int | StrPath
22+
# file type depends on file_type:
23+
# - UNIX_FD: file descriptor (int)
24+
# - WINDOWS_HANDLE: handle (int)
25+
# - STDOUT: use process stdout (None)
26+
file: int | None
2427
file_type: str
2528

2629
def configure_subprocess(self, popen_kwargs: dict) -> None:
@@ -33,9 +36,6 @@ def configure_subprocess(self, popen_kwargs: dict) -> None:
3336
startupinfo = subprocess.STARTUPINFO()
3437
startupinfo.lpAttributeList = {"handle_list": [self.file]}
3538
popen_kwargs['startupinfo'] = startupinfo
36-
case JsonFileType.FILENAME:
37-
# Filename: nothing to do to
38-
pass
3939

4040
@contextlib.contextmanager
4141
def inherit_subprocess(self):
@@ -49,6 +49,9 @@ def inherit_subprocess(self):
4949
yield
5050

5151
def open(self, mode='r', *, encoding):
52+
if self.file_type == JsonFileType.STDOUT:
53+
raise ValueError("for STDOUT file type, just use sys.stdout")
54+
5255
file = self.file
5356
if self.file_type == JsonFileType.WINDOWS_HANDLE:
5457
import msvcrt
@@ -123,11 +126,13 @@ def as_json(self) -> StrJSON:
123126
def from_json(worker_json: StrJSON) -> 'RunTests':
124127
return json.loads(worker_json, object_hook=_decode_runtests)
125128

126-
def json_file_use_filename(self) -> bool:
127-
# json_file type depends on the platform:
128-
# - Unix: file descriptor (int)
129-
# - Windows: handle (int)
130-
# - Emscripten/WASI or if --python is used: filename (str)
129+
def json_file_use_stdout(self) -> bool:
130+
# Use STDOUT in two cases:
131+
#
132+
# - If --python command line option is used;
133+
# - On Emscripten and WASI.
134+
#
135+
# On other platforms, UNIX_FD or WINDOWS_HANDLE can be used.
131136
return (
132137
bool(self.python_cmd)
133138
or support.is_emscripten

Lib/test/libregrtest/worker.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from test.support import os_helper
88

99
from .setup import setup_process, setup_test_dir
10-
from .runtests import RunTests, JsonFile
10+
from .runtests import RunTests, JsonFile, JsonFileType
1111
from .single import run_single_test
1212
from .utils import (
1313
StrPath, StrJSON, FilterTuple,
@@ -67,10 +67,6 @@ def worker_process(worker_json: StrJSON) -> NoReturn:
6767
runtests = RunTests.from_json(worker_json)
6868
test_name = runtests.tests[0]
6969
match_tests: FilterTuple | None = runtests.match_tests
70-
# json_file type depends on the platform:
71-
# - Unix: file descriptor (int)
72-
# - Windows: handle (int)
73-
# - Emscripten/WASI or if --python is used: filename (str)
7470
json_file: JsonFile = runtests.json_file
7571

7672
setup_test_dir(runtests.test_dir)
@@ -85,8 +81,12 @@ def worker_process(worker_json: StrJSON) -> NoReturn:
8581

8682
result = run_single_test(test_name, runtests)
8783

88-
with json_file.open('w', encoding='utf-8') as json_fp:
89-
result.write_json_into(json_fp)
84+
if json_file.file_type == JsonFileType.STDOUT:
85+
print()
86+
result.write_json_into(sys.stdout)
87+
else:
88+
with json_file.open('w', encoding='utf-8') as json_fp:
89+
result.write_json_into(json_fp)
9090

9191
sys.exit(0)
9292

Lib/test/pythoninfo.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ def format_groups(groups):
268268
"ARCHFLAGS",
269269
"ARFLAGS",
270270
"AUDIODEV",
271+
"BUILDPYTHON",
271272
"CC",
272273
"CFLAGS",
273274
"COLUMNS",
@@ -320,6 +321,7 @@ def format_groups(groups):
320321
"VIRTUAL_ENV",
321322
"WAYLAND_DISPLAY",
322323
"WINDIR",
324+
"_PYTHON_HOSTRUNNER",
323325
"_PYTHON_HOST_PLATFORM",
324326
"_PYTHON_PROJECT_BASE",
325327
"_PYTHON_SYSCONFIGDATA_NAME",
@@ -335,7 +337,8 @@ def format_groups(groups):
335337
for name, value in os.environ.items():
336338
uname = name.upper()
337339
if (uname in ENV_VARS
338-
# Copy PYTHON* and LC_* variables
340+
# Copy PYTHON* variables like PYTHONPATH
341+
# Copy LC_* variables like LC_ALL
339342
or uname.startswith(("PYTHON", "LC_"))
340343
# Visual Studio: VS140COMNTOOLS
341344
or (uname.startswith("VS") and uname.endswith("COMNTOOLS"))):
@@ -500,6 +503,7 @@ def collect_sysconfig(info_add):
500503
'CFLAGS',
501504
'CFLAGSFORSHARED',
502505
'CONFIG_ARGS',
506+
'HOSTRUNNER',
503507
'HOST_GNU_TYPE',
504508
'MACHDEP',
505509
'MULTIARCH',

0 commit comments

Comments
 (0)