mirror of https://github.com/python/cpython.git
gh-94026: Buffer regrtest worker stdout in temporary file (GH-94253)
Co-authored-by: Victor Stinner <vstinner@python.org>
This commit is contained in:
parent
5150cbcd68
commit
199ba23324
|
@ -9,7 +9,7 @@
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from typing import NamedTuple, NoReturn, Literal, Any
|
from typing import NamedTuple, NoReturn, Literal, Any, TextIO
|
||||||
|
|
||||||
from test import support
|
from test import support
|
||||||
from test.support import os_helper
|
from test.support import os_helper
|
||||||
|
@ -53,7 +53,7 @@ def parse_worker_args(worker_args) -> tuple[Namespace, str]:
|
||||||
return (ns, test_name)
|
return (ns, test_name)
|
||||||
|
|
||||||
|
|
||||||
def run_test_in_subprocess(testname: str, ns: Namespace, tmp_dir: str) -> subprocess.Popen:
|
def run_test_in_subprocess(testname: str, ns: Namespace, tmp_dir: str, stdout_fh: TextIO) -> subprocess.Popen:
|
||||||
ns_dict = vars(ns)
|
ns_dict = vars(ns)
|
||||||
worker_args = (ns_dict, testname)
|
worker_args = (ns_dict, testname)
|
||||||
worker_args = json.dumps(worker_args)
|
worker_args = json.dumps(worker_args)
|
||||||
|
@ -75,18 +75,18 @@ def run_test_in_subprocess(testname: str, ns: Namespace, tmp_dir: str) -> subpro
|
||||||
# Running the child from the same working directory as regrtest's original
|
# Running the child from the same working directory as regrtest's original
|
||||||
# invocation ensures that TEMPDIR for the child is the same when
|
# invocation ensures that TEMPDIR for the child is the same when
|
||||||
# sysconfig.is_python_build() is true. See issue 15300.
|
# sysconfig.is_python_build() is true. See issue 15300.
|
||||||
kw = {'env': env}
|
kw = dict(
|
||||||
if USE_PROCESS_GROUP:
|
env=env,
|
||||||
kw['start_new_session'] = True
|
stdout=stdout_fh,
|
||||||
return subprocess.Popen(cmd,
|
# bpo-45410: Write stderr into stdout to keep messages order
|
||||||
stdout=subprocess.PIPE,
|
stderr=stdout_fh,
|
||||||
# bpo-45410: Write stderr into stdout to keep
|
text=True,
|
||||||
# messages order
|
|
||||||
stderr=subprocess.STDOUT,
|
|
||||||
universal_newlines=True,
|
|
||||||
close_fds=(os.name != 'nt'),
|
close_fds=(os.name != 'nt'),
|
||||||
cwd=os_helper.SAVEDCWD,
|
cwd=os_helper.SAVEDCWD,
|
||||||
**kw)
|
)
|
||||||
|
if USE_PROCESS_GROUP:
|
||||||
|
kw['start_new_session'] = True
|
||||||
|
return subprocess.Popen(cmd, **kw)
|
||||||
|
|
||||||
|
|
||||||
def run_tests_worker(ns: Namespace, test_name: str) -> NoReturn:
|
def run_tests_worker(ns: Namespace, test_name: str) -> NoReturn:
|
||||||
|
@ -212,12 +212,12 @@ def mp_result_error(
|
||||||
test_result.duration_sec = time.monotonic() - self.start_time
|
test_result.duration_sec = time.monotonic() - self.start_time
|
||||||
return MultiprocessResult(test_result, stdout, err_msg)
|
return MultiprocessResult(test_result, stdout, err_msg)
|
||||||
|
|
||||||
def _run_process(self, test_name: str, tmp_dir: str) -> tuple[int, str, str]:
|
def _run_process(self, test_name: str, tmp_dir: str, stdout_fh: TextIO) -> int:
|
||||||
self.start_time = time.monotonic()
|
self.start_time = time.monotonic()
|
||||||
|
|
||||||
self.current_test_name = test_name
|
self.current_test_name = test_name
|
||||||
try:
|
try:
|
||||||
popen = run_test_in_subprocess(test_name, self.ns, tmp_dir)
|
popen = run_test_in_subprocess(test_name, self.ns, tmp_dir, stdout_fh)
|
||||||
|
|
||||||
self._killed = False
|
self._killed = False
|
||||||
self._popen = popen
|
self._popen = popen
|
||||||
|
@ -234,10 +234,10 @@ def _run_process(self, test_name: str, tmp_dir: str) -> tuple[int, str, str]:
|
||||||
raise ExitThread
|
raise ExitThread
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# bpo-45410: stderr is written into stdout
|
# gh-94026: stdout+stderr are written to tempfile
|
||||||
stdout, _ = popen.communicate(timeout=self.timeout)
|
retcode = popen.wait(timeout=self.timeout)
|
||||||
retcode = popen.returncode
|
|
||||||
assert retcode is not None
|
assert retcode is not None
|
||||||
|
return retcode
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
if self._stopped:
|
if self._stopped:
|
||||||
# kill() has been called: communicate() fails on reading
|
# kill() has been called: communicate() fails on reading
|
||||||
|
@ -252,17 +252,12 @@ def _run_process(self, test_name: str, tmp_dir: str) -> tuple[int, str, str]:
|
||||||
# bpo-38207: Don't attempt to call communicate() again: on it
|
# bpo-38207: Don't attempt to call communicate() again: on it
|
||||||
# can hang until all child processes using stdout
|
# can hang until all child processes using stdout
|
||||||
# pipes completes.
|
# pipes completes.
|
||||||
stdout = ''
|
|
||||||
except OSError:
|
except OSError:
|
||||||
if self._stopped:
|
if self._stopped:
|
||||||
# kill() has been called: communicate() fails
|
# kill() has been called: communicate() fails
|
||||||
# on reading closed stdout
|
# on reading closed stdout
|
||||||
raise ExitThread
|
raise ExitThread
|
||||||
raise
|
raise
|
||||||
else:
|
|
||||||
stdout = stdout.strip()
|
|
||||||
|
|
||||||
return (retcode, stdout)
|
|
||||||
except:
|
except:
|
||||||
self._kill()
|
self._kill()
|
||||||
raise
|
raise
|
||||||
|
@ -272,23 +267,30 @@ def _run_process(self, test_name: str, tmp_dir: str) -> tuple[int, str, str]:
|
||||||
self.current_test_name = None
|
self.current_test_name = None
|
||||||
|
|
||||||
def _runtest(self, test_name: str) -> MultiprocessResult:
|
def _runtest(self, test_name: str) -> MultiprocessResult:
|
||||||
# Don't check for leaked temporary files and directories if Python is
|
# gh-94026: Write stdout+stderr to a tempfile as workaround for
|
||||||
# run on WASI. WASI don't pass environment variables like TMPDIR to
|
# non-blocking pipes on Emscripten with NodeJS.
|
||||||
# worker processes.
|
with tempfile.TemporaryFile(
|
||||||
if not support.is_wasi:
|
'w+', encoding=sys.stdout.encoding
|
||||||
|
) as stdout_fh:
|
||||||
# gh-93353: Check for leaked temporary files in the parent process,
|
# gh-93353: Check for leaked temporary files in the parent process,
|
||||||
# since the deletion of temporary files can happen late during
|
# since the deletion of temporary files can happen late during
|
||||||
# Python finalization: too late for libregrtest.
|
# Python finalization: too late for libregrtest.
|
||||||
|
if not support.is_wasi:
|
||||||
|
# Don't check for leaked temporary files and directories if Python is
|
||||||
|
# run on WASI. WASI don't pass environment variables like TMPDIR to
|
||||||
|
# worker processes.
|
||||||
tmp_dir = tempfile.mkdtemp(prefix="test_python_")
|
tmp_dir = tempfile.mkdtemp(prefix="test_python_")
|
||||||
tmp_dir = os.path.abspath(tmp_dir)
|
tmp_dir = os.path.abspath(tmp_dir)
|
||||||
try:
|
try:
|
||||||
retcode, stdout = self._run_process(test_name, tmp_dir)
|
retcode = self._run_process(test_name, tmp_dir, stdout_fh)
|
||||||
finally:
|
finally:
|
||||||
tmp_files = os.listdir(tmp_dir)
|
tmp_files = os.listdir(tmp_dir)
|
||||||
os_helper.rmtree(tmp_dir)
|
os_helper.rmtree(tmp_dir)
|
||||||
else:
|
else:
|
||||||
retcode, stdout = self._run_process(test_name, None)
|
retcode = self._run_process(test_name, None, stdout_fh)
|
||||||
tmp_files = ()
|
tmp_files = ()
|
||||||
|
stdout_fh.seek(0)
|
||||||
|
stdout = stdout_fh.read().strip()
|
||||||
|
|
||||||
if retcode is None:
|
if retcode is None:
|
||||||
return self.mp_result_error(Timeout(test_name), stdout)
|
return self.mp_result_error(Timeout(test_name), stdout)
|
||||||
|
@ -343,9 +345,6 @@ def run(self) -> None:
|
||||||
def _wait_completed(self) -> None:
|
def _wait_completed(self) -> None:
|
||||||
popen = self._popen
|
popen = self._popen
|
||||||
|
|
||||||
# stdout must be closed to ensure that communicate() does not hang
|
|
||||||
popen.stdout.close()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
popen.wait(JOIN_TIMEOUT)
|
popen.wait(JOIN_TIMEOUT)
|
||||||
except (subprocess.TimeoutExpired, OSError) as exc:
|
except (subprocess.TimeoutExpired, OSError) as exc:
|
||||||
|
|
Loading…
Reference in New Issue