Skip to content

Commit 3dde8e4

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.
1 parent 75cdd9a commit 3dde8e4

File tree

3 files changed

+46
-31
lines changed

3 files changed

+46
-31
lines changed

Lib/test/libregrtest/run_workers.py

+22-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, # TMP_PREFIX,
2525
format_duration, print_warning, count, plural)
2626
from .worker import create_worker_process, USE_PROCESS_GROUP
2727

@@ -225,17 +225,20 @@ 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
232+
#elif 0:
233+
# # create an empty file to make the creation atomic
234+
# # (to prevent races with other worker threads)
235+
# prefix = TMP_PREFIX + 'json_'
236+
# json_fd, json_filename = tempfile.mkstemp(prefix=prefix)
237+
# os.close(json_fd)
238+
#
239+
# stack.callback(os_helper.unlink, json_filename)
240+
# json_file = JsonFile(json_filename, JsonFileType.FILENAME)
241+
# json_tmpfile = None
239242
else:
240243
json_tmpfile = tempfile.TemporaryFile('w+', encoding='utf8')
241244
stack.enter_context(json_tmpfile)
@@ -300,11 +303,14 @@ def read_stdout(self, stdout_file: TextIO) -> str:
300303
f"Cannot read process stdout: {exc}", None)
301304

302305
def read_json(self, json_file: JsonFile, json_tmpfile: TextIO | None,
303-
stdout: str) -> TestResult:
306+
stdout: str) -> (TestResult, str):
304307
try:
305308
if json_tmpfile is not None:
306309
json_tmpfile.seek(0)
307310
worker_json: StrJSON = json_tmpfile.read()
311+
elif json_file.file_type == JsonFileType.STDOUT:
312+
stdout, _, worker_json = stdout.rpartition("\n")
313+
stdout = stdout.rstrip()
308314
else:
309315
with json_file.open(encoding='utf8') as json_fp:
310316
worker_json: StrJSON = json_fp.read()
@@ -319,14 +325,16 @@ def read_json(self, json_file: JsonFile, json_tmpfile: TextIO | None,
319325
raise WorkerError(self.test_name, "empty JSON", stdout)
320326

321327
try:
322-
return TestResult.from_json(worker_json)
328+
result = TestResult.from_json(worker_json)
323329
except Exception as exc:
324330
# gh-101634: Catch UnicodeDecodeError if stdout cannot be
325331
# decoded from encoding
326332
err_msg = f"Failed to parse worker process JSON: {exc}"
327333
raise WorkerError(self.test_name, err_msg, stdout,
328334
state=State.MULTIPROCESSING_ERROR)
329335

336+
return (result, stdout)
337+
330338
def _runtest(self, test_name: TestName) -> MultiprocessResult:
331339
with contextlib.ExitStack() as stack:
332340
stdout_file = self.create_stdout(stack)
@@ -341,7 +349,7 @@ def _runtest(self, test_name: TestName) -> MultiprocessResult:
341349
if retcode is None:
342350
raise WorkerError(self.test_name, None, stdout, state=State.TIMEOUT)
343351

344-
result = self.read_json(json_file, json_tmpfile, stdout)
352+
result, stdout = self.read_json(json_file, json_tmpfile, stdout)
345353

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

Lib/test/libregrtest/runtests.py

+17-10
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,17 @@ class JsonFileType:
1515
UNIX_FD = "UNIX_FD"
1616
WINDOWS_HANDLE = "WINDOWS_HANDLE"
1717
FILENAME = "FILENAME"
18+
STDOUT = "STDOUT"
1819

1920

2021
@dataclasses.dataclass(slots=True, frozen=True)
2122
class JsonFile:
22-
# See RunTests.json_file_use_filename()
23-
file: int | StrPath
23+
# file type depends on file_type:
24+
# - UNIX_FD: file descriptor (int)
25+
# - WINDOWS_HANDLE: handle (int)
26+
# - FILENAME: filename (str)
27+
# - STDOUT: use process stdout (None)
28+
file: int | StrPath | None
2429
file_type: str
2530

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

4042
@contextlib.contextmanager
4143
def inherit_subprocess(self):
@@ -49,6 +51,9 @@ def inherit_subprocess(self):
4951
yield
5052

5153
def open(self, mode='r', *, encoding):
54+
if self.file_type == JsonFileType.STDOUT:
55+
raise ValueError("for STDOUT file type, just use sys.stdout")
56+
5257
file = self.file
5358
if self.file_type == JsonFileType.WINDOWS_HANDLE:
5459
import msvcrt
@@ -123,11 +128,13 @@ def as_json(self) -> StrJSON:
123128
def from_json(worker_json: StrJSON) -> 'RunTests':
124129
return json.loads(worker_json, object_hook=_decode_runtests)
125130

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)
131+
def json_file_use_stdout(self) -> bool:
132+
# Use STDOUT in two cases:
133+
#
134+
# - If --python command line option is used;
135+
# - On Emscripten and WASI.
136+
#
137+
# On other platforms, UNIX_FD, WINDOWS_HANDLE or FILENAME can be used.
131138
return (
132139
bool(self.python_cmd)
133140
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

0 commit comments

Comments
 (0)