diff options
author | Victor Stinner <vstinner@python.org> | 2021-10-13 01:52:22 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-13 01:52:22 +0200 |
commit | dbe213de7ef28712bbfdb9d94a33abb9c33ef0c2 (patch) | |
tree | 3c94ff3ceddfb9d0ef52c18cdd18b735c31fac6c | |
parent | 2d21612f0dd84bf6d0ce35bcfcc9f0e1a41c202d (diff) | |
download | cpython-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.py | 79 | ||||
-rw-r--r-- | Lib/test/libregrtest/utils.py | 4 | ||||
-rw-r--r-- | Lib/test/support/__init__.py | 4 |
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, |