20
20
from .runtests import RunTests
21
21
from .single import PROGRESS_MIN_TIME
22
22
from .utils import (
23
- StrPath , TestName ,
23
+ StrPath , StrJSON , TestName ,
24
24
format_duration , print_warning )
25
25
from .worker import create_worker_process , USE_PROCESS_GROUP
26
26
@@ -153,10 +153,11 @@ def mp_result_error(
153
153
) -> MultiprocessResult :
154
154
return MultiprocessResult (test_result , stdout , err_msg )
155
155
156
- def _run_process (self , runtests : RunTests , output_file : TextIO ,
156
+ def _run_process (self , runtests : RunTests , output_fd : int , json_fd : int ,
157
157
tmp_dir : StrPath | None = None ) -> int :
158
158
try :
159
- popen = create_worker_process (runtests , output_file , tmp_dir )
159
+ popen = create_worker_process (runtests , output_fd , json_fd ,
160
+ tmp_dir )
160
161
161
162
self ._killed = False
162
163
self ._popen = popen
@@ -221,14 +222,23 @@ def _runtest(self, test_name: TestName) -> MultiprocessResult:
221
222
match_tests = self .runtests .get_match_tests (test_name )
222
223
else :
223
224
match_tests = None
224
- kwargs = {}
225
- if match_tests :
226
- kwargs ['match_tests' ] = match_tests
227
- worker_runtests = self .runtests .copy (tests = tests , ** kwargs )
225
+ err_msg = None
228
226
229
227
# gh-94026: Write stdout+stderr to a tempfile as workaround for
230
228
# non-blocking pipes on Emscripten with NodeJS.
231
- with tempfile .TemporaryFile ('w+' , encoding = encoding ) as stdout_file :
229
+ with (tempfile .TemporaryFile ('w+' , encoding = encoding ) as stdout_file ,
230
+ tempfile .TemporaryFile ('w+' , encoding = 'utf8' ) as json_file ):
231
+ stdout_fd = stdout_file .fileno ()
232
+ json_fd = json_file .fileno ()
233
+
234
+ kwargs = {}
235
+ if match_tests :
236
+ kwargs ['match_tests' ] = match_tests
237
+ worker_runtests = self .runtests .copy (
238
+ tests = tests ,
239
+ json_fd = json_fd ,
240
+ ** kwargs )
241
+
232
242
# gh-93353: Check for leaked temporary files in the parent process,
233
243
# since the deletion of temporary files can happen late during
234
244
# Python finalization: too late for libregrtest.
@@ -239,12 +249,14 @@ def _runtest(self, test_name: TestName) -> MultiprocessResult:
239
249
tmp_dir = tempfile .mkdtemp (prefix = "test_python_" )
240
250
tmp_dir = os .path .abspath (tmp_dir )
241
251
try :
242
- retcode = self ._run_process (worker_runtests , stdout_file , tmp_dir )
252
+ retcode = self ._run_process (worker_runtests ,
253
+ stdout_fd , json_fd , tmp_dir )
243
254
finally :
244
255
tmp_files = os .listdir (tmp_dir )
245
256
os_helper .rmtree (tmp_dir )
246
257
else :
247
- retcode = self ._run_process (worker_runtests , stdout_file )
258
+ retcode = self ._run_process (worker_runtests ,
259
+ stdout_fd , json_fd )
248
260
tmp_files = ()
249
261
stdout_file .seek (0 )
250
262
@@ -257,23 +269,27 @@ def _runtest(self, test_name: TestName) -> MultiprocessResult:
257
269
result = TestResult (test_name , state = State .MULTIPROCESSING_ERROR )
258
270
return self .mp_result_error (result , err_msg = err_msg )
259
271
272
+ try :
273
+ # deserialize run_tests_worker() output
274
+ json_file .seek (0 )
275
+ worker_json : StrJSON = json_file .read ()
276
+ if worker_json :
277
+ result = TestResult .from_json (worker_json )
278
+ else :
279
+ err_msg = f"empty JSON"
280
+ except Exception as exc :
281
+ # gh-101634: Catch UnicodeDecodeError if stdout cannot be
282
+ # decoded from encoding
283
+ err_msg = f"Fail to read or parser worker process JSON: { exc } "
284
+ result = TestResult (test_name , state = State .MULTIPROCESSING_ERROR )
285
+ return self .mp_result_error (result , stdout , err_msg = err_msg )
286
+
260
287
if retcode is None :
261
288
result = TestResult (test_name , state = State .TIMEOUT )
262
289
return self .mp_result_error (result , stdout )
263
290
264
- err_msg = None
265
291
if retcode != 0 :
266
292
err_msg = "Exit code %s" % retcode
267
- else :
268
- stdout , _ , worker_json = stdout .rpartition ("\n " )
269
- stdout = stdout .rstrip ()
270
- if not worker_json :
271
- err_msg = "Failed to parse worker stdout"
272
- else :
273
- try :
274
- result = TestResult .from_json (worker_json )
275
- except Exception as exc :
276
- err_msg = "Failed to parse worker JSON: %s" % exc
277
293
278
294
if err_msg :
279
295
result = TestResult (test_name , state = State .MULTIPROCESSING_ERROR )
0 commit comments