summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@python.org>2021-10-13 01:52:22 +0200
committerGitHub <noreply@github.com>2021-10-13 01:52:22 +0200
commitdbe213de7ef28712bbfdb9d94a33abb9c33ef0c2 (patch)
tree3c94ff3ceddfb9d0ef52c18cdd18b735c31fac6c
parent2d21612f0dd84bf6d0ce35bcfcc9f0e1a41c202d (diff)
downloadcpython-git-dbe213de7ef28712bbfdb9d94a33abb9c33ef0c2.tar.gz
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.
-rw-r--r--Lib/test/libregrtest/runtest.py79
-rw-r--r--Lib/test/libregrtest/utils.py4
-rw-r--r--Lib/test/support/__init__.py4
3 files changed, 72 insertions, 15 deletions
diff --git a/Lib/test/libregrtest/runtest.py b/Lib/test/libregrtest/runtest.py
index fe4693bad9..31474a222e 100644
--- a/Lib/test/libregrtest/runtest.py
+++ b/Lib/test/libregrtest/runtest.py
@@ -1,3 +1,4 @@
+import contextlib
import faulthandler
import functools
import gc
@@ -5,6 +6,7 @@ import importlib
import io
import os
import sys
+import tempfile
import time
import traceback
import unittest
@@ -173,6 +175,63 @@ def get_abs_module(ns: Namespace, test_name: str) -> str:
return 'test.' + test_name
+@contextlib.contextmanager
+def override_fd(fd, fd2):
+ fd2_copy = os.dup(fd2)
+ try:
+ os.dup2(fd, fd2)
+ yield
+ finally:
+ os.dup2(fd2_copy, fd2)
+ os.close(fd2_copy)
+
+
+def get_stream_fd(stream):
+ if stream is None:
+ return None
+ try:
+ return stream.fileno()
+ except io.UnsupportedOperation:
+ return None
+
+
+@contextlib.contextmanager
+def capture_std_streams():
+ """
+ Redirect all standard streams to a temporary file:
+
+ * stdout and stderr file descriptors (fd 1 and fd 2)
+ * sys.stdout, sys.__stdout__
+ * sys.stderr, sys.__stderr__
+ """
+ try:
+ stderr_fd = sys.stderr.fileno()
+ except io.UnsupportedOperation:
+ stderr_fd = None
+
+ # Use a temporary file to support fileno() operation
+ tmp_file = tempfile.TemporaryFile(mode='w+',
+ # line buffering
+ buffering=1,
+ encoding=sys.stderr.encoding,
+ errors=sys.stderr.errors)
+ with contextlib.ExitStack() as stack:
+ stack.enter_context(tmp_file)
+
+ # Override stdout and stderr file descriptors
+ tmp_fd = tmp_file.fileno()
+ for stream in (sys.stdout, sys.stderr):
+ fd = get_stream_fd(stream)
+ if fd is not None:
+ stack.enter_context(override_fd(tmp_fd, fd))
+
+ # Override sys attributes
+ for name in ('stdout', 'stderr', '__stdout__', '__stderr__'):
+ stack.enter_context(support.swap_attr(sys, name, tmp_file))
+
+ yield tmp_file
+
+
def _runtest(ns: Namespace, test_name: str) -> TestResult:
# Handle faulthandler timeout, capture stdout+stderr, XML serialization
# and measure time.
@@ -193,21 +252,17 @@ def _runtest(ns: Namespace, test_name: str) -> TestResult:
if output_on_failure:
support.verbose = True
- stream = io.StringIO()
- orig_stdout = sys.stdout
- orig_stderr = sys.stderr
- try:
- sys.stdout = stream
- sys.stderr = stream
+ output = None
+ with capture_std_streams() as stream:
result = _runtest_inner(ns, test_name,
display_failure=False)
if not isinstance(result, Passed):
- output = stream.getvalue()
- orig_stderr.write(output)
- orig_stderr.flush()
- finally:
- sys.stdout = orig_stdout
- sys.stderr = orig_stderr
+ stream.seek(0)
+ output = stream.read()
+
+ if output is not None:
+ sys.stderr.write(output)
+ sys.stderr.flush()
else:
# Tell tests to be moderately quiet
support.verbose = ns.verbose
diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py
index c71467a519..256f9a4cb6 100644
--- a/Lib/test/libregrtest/utils.py
+++ b/Lib/test/libregrtest/utils.py
@@ -71,7 +71,7 @@ orig_unraisablehook = None
def regrtest_unraisable_hook(unraisable):
global orig_unraisablehook
support.environment_altered = True
- print_warning("Unraisable exception")
+ support.print_warning("Unraisable exception")
old_stderr = sys.stderr
try:
support.flush_std_streams()
@@ -94,7 +94,7 @@ orig_threading_excepthook = None
def regrtest_threading_excepthook(args):
global orig_threading_excepthook
support.environment_altered = True
- print_warning(f"Uncaught thread exception: {args.exc_type.__name__}")
+ support.print_warning(f"Uncaught thread exception: {args.exc_type.__name__}")
old_stderr = sys.stderr
try:
support.flush_std_streams()
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index 0bbe813775..45801dc317 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -1179,8 +1179,10 @@ def print_warning(msg):
flush_std_streams()
# bpo-39983: Print into sys.__stderr__ to display the warning even
# when sys.stderr is captured temporarily by a test
+ stream = sys.__stderr__
for line in msg.splitlines():
- print(f"Warning -- {line}", file=sys.__stderr__, flush=True)
+ print(f"Warning -- {line}", file=stream)
+ stream.flush()
# Flag used by saved_test_environment of test.libregrtest.save_env,