mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
gh-109276: libregrtest: WASM use stdout for JSON (#109355)
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.
This commit is contained in:
parent
75cdd9a904
commit
715f663258
4 changed files with 40 additions and 33 deletions
|
@ -21,7 +21,7 @@ from .results import TestResults
|
||||||
from .runtests import RunTests, JsonFile, JsonFileType
|
from .runtests import RunTests, JsonFile, JsonFileType
|
||||||
from .single import PROGRESS_MIN_TIME
|
from .single import PROGRESS_MIN_TIME
|
||||||
from .utils import (
|
from .utils import (
|
||||||
StrPath, StrJSON, TestName, MS_WINDOWS, TMP_PREFIX,
|
StrPath, StrJSON, TestName, MS_WINDOWS,
|
||||||
format_duration, print_warning, count, plural)
|
format_duration, print_warning, count, plural)
|
||||||
from .worker import create_worker_process, USE_PROCESS_GROUP
|
from .worker import create_worker_process, USE_PROCESS_GROUP
|
||||||
|
|
||||||
|
@ -225,16 +225,9 @@ class WorkerThread(threading.Thread):
|
||||||
def create_json_file(self, stack: contextlib.ExitStack) -> tuple[JsonFile, TextIO | None]:
|
def create_json_file(self, stack: contextlib.ExitStack) -> tuple[JsonFile, TextIO | None]:
|
||||||
"""Create JSON file."""
|
"""Create JSON file."""
|
||||||
|
|
||||||
json_file_use_filename = self.runtests.json_file_use_filename()
|
json_file_use_stdout = self.runtests.json_file_use_stdout()
|
||||||
if json_file_use_filename:
|
if json_file_use_stdout:
|
||||||
# create an empty file to make the creation atomic
|
json_file = JsonFile(None, JsonFileType.STDOUT)
|
||||||
# (to prevent races with other worker threads)
|
|
||||||
prefix = TMP_PREFIX + 'json_'
|
|
||||||
json_fd, json_filename = tempfile.mkstemp(prefix=prefix)
|
|
||||||
os.close(json_fd)
|
|
||||||
|
|
||||||
stack.callback(os_helper.unlink, json_filename)
|
|
||||||
json_file = JsonFile(json_filename, JsonFileType.FILENAME)
|
|
||||||
json_tmpfile = None
|
json_tmpfile = None
|
||||||
else:
|
else:
|
||||||
json_tmpfile = tempfile.TemporaryFile('w+', encoding='utf8')
|
json_tmpfile = tempfile.TemporaryFile('w+', encoding='utf8')
|
||||||
|
@ -300,11 +293,14 @@ class WorkerThread(threading.Thread):
|
||||||
f"Cannot read process stdout: {exc}", None)
|
f"Cannot read process stdout: {exc}", None)
|
||||||
|
|
||||||
def read_json(self, json_file: JsonFile, json_tmpfile: TextIO | None,
|
def read_json(self, json_file: JsonFile, json_tmpfile: TextIO | None,
|
||||||
stdout: str) -> TestResult:
|
stdout: str) -> tuple[TestResult, str]:
|
||||||
try:
|
try:
|
||||||
if json_tmpfile is not None:
|
if json_tmpfile is not None:
|
||||||
json_tmpfile.seek(0)
|
json_tmpfile.seek(0)
|
||||||
worker_json: StrJSON = json_tmpfile.read()
|
worker_json: StrJSON = json_tmpfile.read()
|
||||||
|
elif json_file.file_type == JsonFileType.STDOUT:
|
||||||
|
stdout, _, worker_json = stdout.rpartition("\n")
|
||||||
|
stdout = stdout.rstrip()
|
||||||
else:
|
else:
|
||||||
with json_file.open(encoding='utf8') as json_fp:
|
with json_file.open(encoding='utf8') as json_fp:
|
||||||
worker_json: StrJSON = json_fp.read()
|
worker_json: StrJSON = json_fp.read()
|
||||||
|
@ -319,7 +315,7 @@ class WorkerThread(threading.Thread):
|
||||||
raise WorkerError(self.test_name, "empty JSON", stdout)
|
raise WorkerError(self.test_name, "empty JSON", stdout)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return TestResult.from_json(worker_json)
|
result = TestResult.from_json(worker_json)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
# gh-101634: Catch UnicodeDecodeError if stdout cannot be
|
# gh-101634: Catch UnicodeDecodeError if stdout cannot be
|
||||||
# decoded from encoding
|
# decoded from encoding
|
||||||
|
@ -327,6 +323,8 @@ class WorkerThread(threading.Thread):
|
||||||
raise WorkerError(self.test_name, err_msg, stdout,
|
raise WorkerError(self.test_name, err_msg, stdout,
|
||||||
state=State.MULTIPROCESSING_ERROR)
|
state=State.MULTIPROCESSING_ERROR)
|
||||||
|
|
||||||
|
return (result, stdout)
|
||||||
|
|
||||||
def _runtest(self, test_name: TestName) -> MultiprocessResult:
|
def _runtest(self, test_name: TestName) -> MultiprocessResult:
|
||||||
with contextlib.ExitStack() as stack:
|
with contextlib.ExitStack() as stack:
|
||||||
stdout_file = self.create_stdout(stack)
|
stdout_file = self.create_stdout(stack)
|
||||||
|
@ -341,7 +339,7 @@ class WorkerThread(threading.Thread):
|
||||||
if retcode is None:
|
if retcode is None:
|
||||||
raise WorkerError(self.test_name, None, stdout, state=State.TIMEOUT)
|
raise WorkerError(self.test_name, None, stdout, state=State.TIMEOUT)
|
||||||
|
|
||||||
result = self.read_json(json_file, json_tmpfile, stdout)
|
result, stdout = self.read_json(json_file, json_tmpfile, stdout)
|
||||||
|
|
||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
raise WorkerError(self.test_name, f"Exit code {retcode}", stdout)
|
raise WorkerError(self.test_name, f"Exit code {retcode}", stdout)
|
||||||
|
|
|
@ -14,13 +14,16 @@ from .utils import (
|
||||||
class JsonFileType:
|
class JsonFileType:
|
||||||
UNIX_FD = "UNIX_FD"
|
UNIX_FD = "UNIX_FD"
|
||||||
WINDOWS_HANDLE = "WINDOWS_HANDLE"
|
WINDOWS_HANDLE = "WINDOWS_HANDLE"
|
||||||
FILENAME = "FILENAME"
|
STDOUT = "STDOUT"
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass(slots=True, frozen=True)
|
@dataclasses.dataclass(slots=True, frozen=True)
|
||||||
class JsonFile:
|
class JsonFile:
|
||||||
# See RunTests.json_file_use_filename()
|
# file type depends on file_type:
|
||||||
file: int | StrPath
|
# - UNIX_FD: file descriptor (int)
|
||||||
|
# - WINDOWS_HANDLE: handle (int)
|
||||||
|
# - STDOUT: use process stdout (None)
|
||||||
|
file: int | None
|
||||||
file_type: str
|
file_type: str
|
||||||
|
|
||||||
def configure_subprocess(self, popen_kwargs: dict) -> None:
|
def configure_subprocess(self, popen_kwargs: dict) -> None:
|
||||||
|
@ -33,9 +36,6 @@ class JsonFile:
|
||||||
startupinfo = subprocess.STARTUPINFO()
|
startupinfo = subprocess.STARTUPINFO()
|
||||||
startupinfo.lpAttributeList = {"handle_list": [self.file]}
|
startupinfo.lpAttributeList = {"handle_list": [self.file]}
|
||||||
popen_kwargs['startupinfo'] = startupinfo
|
popen_kwargs['startupinfo'] = startupinfo
|
||||||
case JsonFileType.FILENAME:
|
|
||||||
# Filename: nothing to do to
|
|
||||||
pass
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def inherit_subprocess(self):
|
def inherit_subprocess(self):
|
||||||
|
@ -49,6 +49,9 @@ class JsonFile:
|
||||||
yield
|
yield
|
||||||
|
|
||||||
def open(self, mode='r', *, encoding):
|
def open(self, mode='r', *, encoding):
|
||||||
|
if self.file_type == JsonFileType.STDOUT:
|
||||||
|
raise ValueError("for STDOUT file type, just use sys.stdout")
|
||||||
|
|
||||||
file = self.file
|
file = self.file
|
||||||
if self.file_type == JsonFileType.WINDOWS_HANDLE:
|
if self.file_type == JsonFileType.WINDOWS_HANDLE:
|
||||||
import msvcrt
|
import msvcrt
|
||||||
|
@ -123,11 +126,13 @@ class RunTests:
|
||||||
def from_json(worker_json: StrJSON) -> 'RunTests':
|
def from_json(worker_json: StrJSON) -> 'RunTests':
|
||||||
return json.loads(worker_json, object_hook=_decode_runtests)
|
return json.loads(worker_json, object_hook=_decode_runtests)
|
||||||
|
|
||||||
def json_file_use_filename(self) -> bool:
|
def json_file_use_stdout(self) -> bool:
|
||||||
# json_file type depends on the platform:
|
# Use STDOUT in two cases:
|
||||||
# - Unix: file descriptor (int)
|
#
|
||||||
# - Windows: handle (int)
|
# - If --python command line option is used;
|
||||||
# - Emscripten/WASI or if --python is used: filename (str)
|
# - On Emscripten and WASI.
|
||||||
|
#
|
||||||
|
# On other platforms, UNIX_FD or WINDOWS_HANDLE can be used.
|
||||||
return (
|
return (
|
||||||
bool(self.python_cmd)
|
bool(self.python_cmd)
|
||||||
or support.is_emscripten
|
or support.is_emscripten
|
||||||
|
|
|
@ -7,7 +7,7 @@ from test import support
|
||||||
from test.support import os_helper
|
from test.support import os_helper
|
||||||
|
|
||||||
from .setup import setup_process, setup_test_dir
|
from .setup import setup_process, setup_test_dir
|
||||||
from .runtests import RunTests, JsonFile
|
from .runtests import RunTests, JsonFile, JsonFileType
|
||||||
from .single import run_single_test
|
from .single import run_single_test
|
||||||
from .utils import (
|
from .utils import (
|
||||||
StrPath, StrJSON, FilterTuple,
|
StrPath, StrJSON, FilterTuple,
|
||||||
|
@ -67,10 +67,6 @@ def worker_process(worker_json: StrJSON) -> NoReturn:
|
||||||
runtests = RunTests.from_json(worker_json)
|
runtests = RunTests.from_json(worker_json)
|
||||||
test_name = runtests.tests[0]
|
test_name = runtests.tests[0]
|
||||||
match_tests: FilterTuple | None = runtests.match_tests
|
match_tests: FilterTuple | None = runtests.match_tests
|
||||||
# json_file type depends on the platform:
|
|
||||||
# - Unix: file descriptor (int)
|
|
||||||
# - Windows: handle (int)
|
|
||||||
# - Emscripten/WASI or if --python is used: filename (str)
|
|
||||||
json_file: JsonFile = runtests.json_file
|
json_file: JsonFile = runtests.json_file
|
||||||
|
|
||||||
setup_test_dir(runtests.test_dir)
|
setup_test_dir(runtests.test_dir)
|
||||||
|
@ -85,6 +81,10 @@ def worker_process(worker_json: StrJSON) -> NoReturn:
|
||||||
|
|
||||||
result = run_single_test(test_name, runtests)
|
result = run_single_test(test_name, runtests)
|
||||||
|
|
||||||
|
if json_file.file_type == JsonFileType.STDOUT:
|
||||||
|
print()
|
||||||
|
result.write_json_into(sys.stdout)
|
||||||
|
else:
|
||||||
with json_file.open('w', encoding='utf-8') as json_fp:
|
with json_file.open('w', encoding='utf-8') as json_fp:
|
||||||
result.write_json_into(json_fp)
|
result.write_json_into(json_fp)
|
||||||
|
|
||||||
|
|
|
@ -268,6 +268,7 @@ def collect_os(info_add):
|
||||||
"ARCHFLAGS",
|
"ARCHFLAGS",
|
||||||
"ARFLAGS",
|
"ARFLAGS",
|
||||||
"AUDIODEV",
|
"AUDIODEV",
|
||||||
|
"BUILDPYTHON",
|
||||||
"CC",
|
"CC",
|
||||||
"CFLAGS",
|
"CFLAGS",
|
||||||
"COLUMNS",
|
"COLUMNS",
|
||||||
|
@ -320,6 +321,7 @@ def collect_os(info_add):
|
||||||
"VIRTUAL_ENV",
|
"VIRTUAL_ENV",
|
||||||
"WAYLAND_DISPLAY",
|
"WAYLAND_DISPLAY",
|
||||||
"WINDIR",
|
"WINDIR",
|
||||||
|
"_PYTHON_HOSTRUNNER",
|
||||||
"_PYTHON_HOST_PLATFORM",
|
"_PYTHON_HOST_PLATFORM",
|
||||||
"_PYTHON_PROJECT_BASE",
|
"_PYTHON_PROJECT_BASE",
|
||||||
"_PYTHON_SYSCONFIGDATA_NAME",
|
"_PYTHON_SYSCONFIGDATA_NAME",
|
||||||
|
@ -335,7 +337,8 @@ def collect_os(info_add):
|
||||||
for name, value in os.environ.items():
|
for name, value in os.environ.items():
|
||||||
uname = name.upper()
|
uname = name.upper()
|
||||||
if (uname in ENV_VARS
|
if (uname in ENV_VARS
|
||||||
# Copy PYTHON* and LC_* variables
|
# Copy PYTHON* variables like PYTHONPATH
|
||||||
|
# Copy LC_* variables like LC_ALL
|
||||||
or uname.startswith(("PYTHON", "LC_"))
|
or uname.startswith(("PYTHON", "LC_"))
|
||||||
# Visual Studio: VS140COMNTOOLS
|
# Visual Studio: VS140COMNTOOLS
|
||||||
or (uname.startswith("VS") and uname.endswith("COMNTOOLS"))):
|
or (uname.startswith("VS") and uname.endswith("COMNTOOLS"))):
|
||||||
|
@ -500,6 +503,7 @@ def collect_sysconfig(info_add):
|
||||||
'CFLAGS',
|
'CFLAGS',
|
||||||
'CFLAGSFORSHARED',
|
'CFLAGSFORSHARED',
|
||||||
'CONFIG_ARGS',
|
'CONFIG_ARGS',
|
||||||
|
'HOSTRUNNER',
|
||||||
'HOST_GNU_TYPE',
|
'HOST_GNU_TYPE',
|
||||||
'MACHDEP',
|
'MACHDEP',
|
||||||
'MULTIARCH',
|
'MULTIARCH',
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue