Skip to content

Commit 7733307

Browse files
authored
bpo-45410: regrtest -W leaves stdout/err FD unchanged (GH-28915)
support.print_warning() now stores the original value of sys.__stderr__ and uses it to log warnings. libregrtest uses the same stream to log unraisable exceptions and uncaught threading exceptions. Partially revert commit dbe213d: libregrtest no longer replaces sys.__stdout__, sys.__stderr__, and stdout and stderr file descriptors. Remove also a few unused imports in libregrtest.
1 parent 380c440 commit 7733307

File tree

7 files changed

+19
-82
lines changed

7 files changed

+19
-82
lines changed

Lib/test/libregrtest/cmdline.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import argparse
22
import os
33
import sys
4-
from test import support
54
from test.support import os_helper
65

76

Lib/test/libregrtest/refleak.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import os
2-
import re
32
import sys
43
import warnings
54
from inspect import isabstract

Lib/test/libregrtest/runtest.py

Lines changed: 10 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
import contextlib
21
import faulthandler
32
import functools
43
import gc
54
import importlib
65
import io
76
import os
87
import sys
9-
import tempfile
108
import time
119
import traceback
1210
import unittest
@@ -175,63 +173,6 @@ def get_abs_module(ns: Namespace, test_name: str) -> str:
175173
return 'test.' + test_name
176174

177175

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-
235176
def _runtest(ns: Namespace, test_name: str) -> TestResult:
236177
# Handle faulthandler timeout, capture stdout+stderr, XML serialization
237178
# and measure time.
@@ -252,13 +193,20 @@ def _runtest(ns: Namespace, test_name: str) -> TestResult:
252193
if output_on_failure:
253194
support.verbose = True
254195

196+
stream = io.StringIO()
197+
orig_stdout = sys.stdout
198+
orig_stderr = sys.stderr
255199
output = None
256-
with capture_std_streams() as stream:
200+
try:
201+
sys.stdout = stream
202+
sys.stderr = stream
257203
result = _runtest_inner(ns, test_name,
258204
display_failure=False)
259205
if not isinstance(result, Passed):
260-
stream.seek(0)
261-
output = stream.read()
206+
output = stream.getvalue()
207+
finally:
208+
sys.stdout = orig_stdout
209+
sys.stderr = orig_stderr
262210

263211
if output is not None:
264212
sys.stderr.write(output)

Lib/test/libregrtest/runtest_mp.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import collections
21
import faulthandler
32
import json
43
import os

Lib/test/libregrtest/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def regrtest_unraisable_hook(unraisable):
7575
old_stderr = sys.stderr
7676
try:
7777
support.flush_std_streams()
78-
sys.stderr = sys.__stderr__
78+
sys.stderr = support.print_warning.orig_stderr
7979
orig_unraisablehook(unraisable)
8080
sys.stderr.flush()
8181
finally:
@@ -98,7 +98,7 @@ def regrtest_threading_excepthook(args):
9898
old_stderr = sys.stderr
9999
try:
100100
support.flush_std_streams()
101-
sys.stderr = sys.__stderr__
101+
sys.stderr = support.print_warning.orig_stderr
102102
orig_threading_excepthook(args)
103103
sys.stderr.flush()
104104
finally:

Lib/test/support/__init__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,13 +1177,15 @@ def flush_std_streams():
11771177
def print_warning(msg):
11781178
# bpo-45410: Explicitly flush stdout to keep logs in order
11791179
flush_std_streams()
1180-
# bpo-39983: Print into sys.__stderr__ to display the warning even
1181-
# when sys.stderr is captured temporarily by a test
1182-
stream = sys.__stderr__
1180+
stream = print_warning.orig_stderr
11831181
for line in msg.splitlines():
11841182
print(f"Warning -- {line}", file=stream)
11851183
stream.flush()
11861184

1185+
# bpo-39983: Store the original sys.stderr at Python startup to be able to
1186+
# log warnings even if sys.stderr is captured temporarily by a test.
1187+
print_warning.orig_stderr = sys.stderr
1188+
11871189

11881190
# Flag used by saved_test_environment of test.libregrtest.save_env,
11891191
# to check if a test modified the environment. The flag should be set to False

Lib/test/test_support.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -469,12 +469,8 @@ def test_reap_children(self):
469469
if time.monotonic() > deadline:
470470
self.fail("timeout")
471471

472-
old_stderr = sys.__stderr__
473-
try:
474-
sys.__stderr__ = stderr
472+
with support.swap_attr(support.print_warning, 'orig_stderr', stderr):
475473
support.reap_children()
476-
finally:
477-
sys.__stderr__ = old_stderr
478474

479475
# Use environment_altered to check if reap_children() found
480476
# the child process
@@ -674,14 +670,8 @@ def test_fd_count(self):
674670

675671
def check_print_warning(self, msg, expected):
676672
stderr = io.StringIO()
677-
678-
old_stderr = sys.__stderr__
679-
try:
680-
sys.__stderr__ = stderr
673+
with support.swap_attr(support.print_warning, 'orig_stderr', stderr):
681674
support.print_warning(msg)
682-
finally:
683-
sys.__stderr__ = old_stderr
684-
685675
self.assertEqual(stderr.getvalue(), expected)
686676

687677
def test_print_warning(self):

0 commit comments

Comments
 (0)