5
5
import signal
6
6
import subprocess
7
7
import sys
8
- import tempfile
9
8
import threading
10
9
import time
11
10
import traceback
12
- from typing import NamedTuple , NoReturn , Literal , Any , TextIO
11
+ from typing import NamedTuple , NoReturn , Literal , Any
13
12
14
13
from test import support
15
14
from test .support import os_helper
@@ -52,7 +51,7 @@ def parse_worker_args(worker_args) -> tuple[Namespace, str]:
52
51
return (ns , test_name )
53
52
54
53
55
- def run_test_in_subprocess (testname : str , ns : Namespace , stdout_fh : TextIO ) -> subprocess .Popen :
54
+ def run_test_in_subprocess (testname : str , ns : Namespace ) -> subprocess .Popen :
56
55
ns_dict = vars (ns )
57
56
worker_args = (ns_dict , testname )
58
57
worker_args = json .dumps (worker_args )
@@ -68,17 +67,18 @@ def run_test_in_subprocess(testname: str, ns: Namespace, stdout_fh: TextIO) -> s
68
67
# Running the child from the same working directory as regrtest's original
69
68
# invocation ensures that TEMPDIR for the child is the same when
70
69
# sysconfig.is_python_build() is true. See issue 15300.
71
- kw = dict (
72
- stdout = stdout_fh ,
73
- # bpo-45410: Write stderr into stdout to keep messages order
74
- stderr = stdout_fh ,
75
- text = True ,
76
- close_fds = (os .name != 'nt' ),
77
- cwd = os_helper .SAVEDCWD ,
78
- )
70
+ kw = {}
79
71
if USE_PROCESS_GROUP :
80
72
kw ['start_new_session' ] = True
81
- return subprocess .Popen (cmd , ** kw )
73
+ return subprocess .Popen (cmd ,
74
+ stdout = subprocess .PIPE ,
75
+ # bpo-45410: Write stderr into stdout to keep
76
+ # messages order
77
+ stderr = subprocess .STDOUT ,
78
+ universal_newlines = True ,
79
+ close_fds = (os .name != 'nt' ),
80
+ cwd = os_helper .SAVEDCWD ,
81
+ ** kw )
82
82
83
83
84
84
def run_tests_worker (ns : Namespace , test_name : str ) -> NoReturn :
@@ -204,12 +204,12 @@ def mp_result_error(
204
204
test_result .duration_sec = time .monotonic () - self .start_time
205
205
return MultiprocessResult (test_result , stdout , err_msg )
206
206
207
- def _run_process (self , test_name : str , stdout_fh : TextIO ) -> int :
207
+ def _run_process (self , test_name : str ) -> tuple [ int , str , str ] :
208
208
self .start_time = time .monotonic ()
209
209
210
210
self .current_test_name = test_name
211
211
try :
212
- popen = run_test_in_subprocess (test_name , self .ns , stdout_fh )
212
+ popen = run_test_in_subprocess (test_name , self .ns )
213
213
214
214
self ._killed = False
215
215
self ._popen = popen
@@ -226,10 +226,10 @@ def _run_process(self, test_name: str, stdout_fh: TextIO) -> int:
226
226
raise ExitThread
227
227
228
228
try :
229
- # gh-94026: stdout+stderr are written to tempfile
230
- retcode = popen .wait (timeout = self .timeout )
229
+ # bpo-45410: stderr is written into stdout
230
+ stdout , _ = popen .communicate (timeout = self .timeout )
231
+ retcode = popen .returncode
231
232
assert retcode is not None
232
- return retcode
233
233
except subprocess .TimeoutExpired :
234
234
if self ._stopped :
235
235
# kill() has been called: communicate() fails on reading
@@ -244,12 +244,17 @@ def _run_process(self, test_name: str, stdout_fh: TextIO) -> int:
244
244
# bpo-38207: Don't attempt to call communicate() again: on it
245
245
# can hang until all child processes using stdout
246
246
# pipes completes.
247
+ stdout = ''
247
248
except OSError :
248
249
if self ._stopped :
249
250
# kill() has been called: communicate() fails
250
251
# on reading closed stdout
251
252
raise ExitThread
252
253
raise
254
+ else :
255
+ stdout = stdout .strip ()
256
+
257
+ return (retcode , stdout )
253
258
except :
254
259
self ._kill ()
255
260
raise
@@ -259,17 +264,7 @@ def _run_process(self, test_name: str, stdout_fh: TextIO) -> int:
259
264
self .current_test_name = None
260
265
261
266
def _runtest (self , test_name : str ) -> MultiprocessResult :
262
- # gh-94026: Write stdout+stderr to a tempfile as workaround for
263
- # non-blocking pipes on Emscripten with NodeJS.
264
- with tempfile .TemporaryFile (
265
- 'w+' , encoding = sys .stdout .encoding
266
- ) as stdout_fh :
267
- # gh-93353: Check for leaked temporary files in the parent process,
268
- # since the deletion of temporary files can happen late during
269
- # Python finalization: too late for libregrtest.
270
- retcode = self ._run_process (test_name , stdout_fh )
271
- stdout_fh .seek (0 )
272
- stdout = stdout_fh .read ().strip ()
267
+ retcode , stdout = self ._run_process (test_name )
273
268
274
269
if retcode is None :
275
270
return self .mp_result_error (Timeout (test_name ), stdout )
@@ -316,6 +311,9 @@ def run(self) -> None:
316
311
def _wait_completed (self ) -> None :
317
312
popen = self ._popen
318
313
314
+ # stdout must be closed to ensure that communicate() does not hang
315
+ popen .stdout .close ()
316
+
319
317
try :
320
318
popen .wait (JOIN_TIMEOUT )
321
319
except (subprocess .TimeoutExpired , OSError ) as exc :
0 commit comments