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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy