Skip to content

Commit dbe213d

Browse files
authored
bpo-45410: Enhance libregrtest -W/--verbose3 option (GH-28908)
libregrtest -W/--verbose3 now also replace sys.__stdout__, sys.__stderr__, and stdout and stderr file descriptors (fd 1 and fd 2). support.print_warning() messages are now logged in the expected order. The "./python -m test test_eintr -W" command no longer logs into stdout if the test pass.
1 parent 2d21612 commit dbe213d

File tree

3 files changed

+72
-15
lines changed

3 files changed

+72
-15
lines changed

Lib/test/libregrtest/runtest.py

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import contextlib
12
import faulthandler
23
import functools
34
import gc
45
import importlib
56
import io
67
import os
78
import sys
9+
import tempfile
810
import time
911
import traceback
1012
import unittest
@@ -173,6 +175,63 @@ def get_abs_module(ns: Namespace, test_name: str) -> str:
173175
return 'test.' + test_name
174176

175177

178+
@contextlib.contextmanager
179+
def override_fd(fd, fd2):
180+
fd2_copy = os.dup(fd2)
181+
try:
182+
os.dup2(fd, fd2)
183+
yield
184+
finally:
185+
os.dup2(fd2_copy, fd2)
186+
os.close(fd2_copy)
187+
188+
189+
def get_stream_fd(stream):
190+
if stream is None:
191+
return None
192+
try:
193+
return stream.fileno()
194+
except io.UnsupportedOperation:
195+
return None
196+
197+
198+
@contextlib.contextmanager
199+
def capture_std_streams():
200+
"""
201+
Redirect all standard streams to a temporary file:
202+
203+
* stdout and stderr file descriptors (fd 1 and fd 2)
204+
* sys.stdout, sys.__stdout__
205+
* sys.stderr, sys.__stderr__
206+
"""
207+
try:
208+
stderr_fd = sys.stderr.fileno()
209+
except io.UnsupportedOperation:
210+
stderr_fd = None
211+
212+
# Use a temporary file to support fileno() operation
213+
tmp_file = tempfile.TemporaryFile(mode='w+',
214+
# line buffering
215+
buffering=1,
216+
encoding=sys.stderr.encoding,
217+
errors=sys.stderr.errors)
218+
with contextlib.ExitStack() as stack:
219+
stack.enter_context(tmp_file)
220+
221+
# Override stdout and stderr file descriptors
222+
tmp_fd = tmp_file.fileno()
223+
for stream in (sys.stdout, sys.stderr):
224+
fd = get_stream_fd(stream)
225+
if fd is not None:
226+
stack.enter_context(override_fd(tmp_fd, fd))
227+
228+
# Override sys attributes
229+
for name in ('stdout', 'stderr', '__stdout__', '__stderr__'):
230+
stack.enter_context(support.swap_attr(sys, name, tmp_file))
231+
232+
yield tmp_file
233+
234+
176235
def _runtest(ns: Namespace, test_name: str) -> TestResult:
177236
# Handle faulthandler timeout, capture stdout+stderr, XML serialization
178237
# and measure time.
@@ -193,21 +252,17 @@ def _runtest(ns: Namespace, test_name: str) -> TestResult:
193252
if output_on_failure:
194253
support.verbose = True
195254

196-
stream = io.StringIO()
197-
orig_stdout = sys.stdout
198-
orig_stderr = sys.stderr
199-
try:
200-
sys.stdout = stream
201-
sys.stderr = stream
255+
output = None
256+
with capture_std_streams() as stream:
202257
result = _runtest_inner(ns, test_name,
203258
display_failure=False)
204259
if not isinstance(result, Passed):
205-
output = stream.getvalue()
206-
orig_stderr.write(output)
207-
orig_stderr.flush()
208-
finally:
209-
sys.stdout = orig_stdout
210-
sys.stderr = orig_stderr
260+
stream.seek(0)
261+
output = stream.read()
262+
263+
if output is not None:
264+
sys.stderr.write(output)
265+
sys.stderr.flush()
211266
else:
212267
# Tell tests to be moderately quiet
213268
support.verbose = ns.verbose

Lib/test/libregrtest/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def print_warning(msg):
7171
def regrtest_unraisable_hook(unraisable):
7272
global orig_unraisablehook
7373
support.environment_altered = True
74-
print_warning("Unraisable exception")
74+
support.print_warning("Unraisable exception")
7575
old_stderr = sys.stderr
7676
try:
7777
support.flush_std_streams()
@@ -94,7 +94,7 @@ def setup_unraisable_hook():
9494
def regrtest_threading_excepthook(args):
9595
global orig_threading_excepthook
9696
support.environment_altered = True
97-
print_warning(f"Uncaught thread exception: {args.exc_type.__name__}")
97+
support.print_warning(f"Uncaught thread exception: {args.exc_type.__name__}")
9898
old_stderr = sys.stderr
9999
try:
100100
support.flush_std_streams()

Lib/test/support/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1179,8 +1179,10 @@ def print_warning(msg):
11791179
flush_std_streams()
11801180
# bpo-39983: Print into sys.__stderr__ to display the warning even
11811181
# when sys.stderr is captured temporarily by a test
1182+
stream = sys.__stderr__
11821183
for line in msg.splitlines():
1183-
print(f"Warning -- {line}", file=sys.__stderr__, flush=True)
1184+
print(f"Warning -- {line}", file=stream)
1185+
stream.flush()
11841186

11851187

11861188
# Flag used by saved_test_environment of test.libregrtest.save_env,

0 commit comments

Comments
 (0)