Skip to content

Commit b108db6

Browse files
authored
bpo-45410: libregrtest -jN writes stderr into stdout (GH-28819)
When libregrtest spawns a worker process, stderr is now written into stdout to keep messages order. Use a single pipe for stdout and stderr, rather than two pipes. Previously, messages were out of order which made analysis of buildbot logs harder
1 parent 9f7a94f commit b108db6

File tree

2 files changed

+22
-22
lines changed

2 files changed

+22
-22
lines changed

Lib/test/libregrtest/runtest_mp.py

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ def run_test_in_subprocess(testname: str, ns: Namespace) -> subprocess.Popen:
7070
kw['start_new_session'] = True
7171
return subprocess.Popen(cmd,
7272
stdout=subprocess.PIPE,
73-
stderr=subprocess.PIPE,
73+
# bpo-45410: Write stderr into stdout to keep
74+
# messages order
75+
stderr=subprocess.STDOUT,
7476
universal_newlines=True,
7577
close_fds=(os.name != 'nt'),
7678
cwd=os_helper.SAVEDCWD,
@@ -114,8 +116,8 @@ def stop(self):
114116

115117
class MultiprocessResult(NamedTuple):
116118
result: TestResult
119+
# bpo-45410: stderr is written into stdout to keep messages order
117120
stdout: str
118-
stderr: str
119121
error_msg: str
120122

121123

@@ -195,11 +197,10 @@ def mp_result_error(
195197
self,
196198
test_result: TestResult,
197199
stdout: str = '',
198-
stderr: str = '',
199200
err_msg=None
200201
) -> MultiprocessResult:
201202
test_result.duration_sec = time.monotonic() - self.start_time
202-
return MultiprocessResult(test_result, stdout, stderr, err_msg)
203+
return MultiprocessResult(test_result, stdout, err_msg)
203204

204205
def _run_process(self, test_name: str) -> tuple[int, str, str]:
205206
self.start_time = time.monotonic()
@@ -223,13 +224,14 @@ def _run_process(self, test_name: str) -> tuple[int, str, str]:
223224
raise ExitThread
224225

225226
try:
226-
stdout, stderr = popen.communicate(timeout=self.timeout)
227+
# bpo-45410: stderr is written into stdout
228+
stdout, _ = popen.communicate(timeout=self.timeout)
227229
retcode = popen.returncode
228230
assert retcode is not None
229231
except subprocess.TimeoutExpired:
230232
if self._stopped:
231-
# kill() has been called: communicate() fails
232-
# on reading closed stdout/stderr
233+
# kill() has been called: communicate() fails on reading
234+
# closed stdout
233235
raise ExitThread
234236

235237
# On timeout, kill the process
@@ -238,20 +240,19 @@ def _run_process(self, test_name: str) -> tuple[int, str, str]:
238240
# None means TIMEOUT for the caller
239241
retcode = None
240242
# bpo-38207: Don't attempt to call communicate() again: on it
241-
# can hang until all child processes using stdout and stderr
243+
# can hang until all child processes using stdout
242244
# pipes completes.
243-
stdout = stderr = ''
245+
stdout = ''
244246
except OSError:
245247
if self._stopped:
246248
# kill() has been called: communicate() fails
247-
# on reading closed stdout/stderr
249+
# on reading closed stdout
248250
raise ExitThread
249251
raise
250252
else:
251253
stdout = stdout.strip()
252-
stderr = stderr.rstrip()
253254

254-
return (retcode, stdout, stderr)
255+
return (retcode, stdout)
255256
except:
256257
self._kill()
257258
raise
@@ -261,10 +262,10 @@ def _run_process(self, test_name: str) -> tuple[int, str, str]:
261262
self.current_test_name = None
262263

263264
def _runtest(self, test_name: str) -> MultiprocessResult:
264-
retcode, stdout, stderr = self._run_process(test_name)
265+
retcode, stdout = self._run_process(test_name)
265266

266267
if retcode is None:
267-
return self.mp_result_error(Timeout(test_name), stdout, stderr)
268+
return self.mp_result_error(Timeout(test_name), stdout)
268269

269270
err_msg = None
270271
if retcode != 0:
@@ -282,10 +283,9 @@ def _runtest(self, test_name: str) -> MultiprocessResult:
282283
err_msg = "Failed to parse worker JSON: %s" % exc
283284

284285
if err_msg is not None:
285-
return self.mp_result_error(ChildError(test_name),
286-
stdout, stderr, err_msg)
286+
return self.mp_result_error(ChildError(test_name), stdout, err_msg)
287287

288-
return MultiprocessResult(result, stdout, stderr, err_msg)
288+
return MultiprocessResult(result, stdout, err_msg)
289289

290290
def run(self) -> None:
291291
while not self._stopped:
@@ -309,10 +309,8 @@ def run(self) -> None:
309309
def _wait_completed(self) -> None:
310310
popen = self._popen
311311

312-
# stdout and stderr must be closed to ensure that communicate()
313-
# does not hang
312+
# stdout must be closed to ensure that communicate() does not hang
314313
popen.stdout.close()
315-
popen.stderr.close()
316314

317315
try:
318316
popen.wait(JOIN_TIMEOUT)
@@ -449,8 +447,6 @@ def _process_result(self, item: QueueOutput) -> bool:
449447

450448
if mp_result.stdout:
451449
print(mp_result.stdout, flush=True)
452-
if mp_result.stderr and not self.ns.pgo:
453-
print(mp_result.stderr, file=sys.stderr, flush=True)
454450

455451
if must_stop(mp_result.result, self.ns):
456452
return True
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
When libregrtest spawns a worker process, stderr is now written into stdout
2+
to keep messages order. Use a single pipe for stdout and stderr, rather than
3+
two pipes. Previously, messages were out of order which made analysis of
4+
buildbot logs harder Patch by Victor Stinner.

0 commit comments

Comments
 (0)