diff --git a/AUTHORS b/AUTHORS index 20798f3093d..b35bebf7af3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -21,6 +21,7 @@ Anders Hovmöller Andras Mitzki Andras Tim Andrea Cimatoribus +Andreas Motl Andreas Zeidler Andrey Paramonov Andrzej Klajnert diff --git a/changelog/8249.bugfix.rst b/changelog/8249.bugfix.rst new file mode 100644 index 00000000000..aa084c75738 --- /dev/null +++ b/changelog/8249.bugfix.rst @@ -0,0 +1 @@ +Fix the ``faulthandler`` plugin for occasions when running with ``twisted.logger`` and using ``pytest --capture=no``. diff --git a/src/_pytest/faulthandler.py b/src/_pytest/faulthandler.py index d0cc0430c49..ff673b5b164 100644 --- a/src/_pytest/faulthandler.py +++ b/src/_pytest/faulthandler.py @@ -69,7 +69,12 @@ def pytest_unconfigure(self, config: Config) -> None: @staticmethod def _get_stderr_fileno(): try: - return sys.stderr.fileno() + fileno = sys.stderr.fileno() + # The Twisted Logger will return an invalid file descriptor since it is not backed + # by an FD. So, let's also forward this to the same code path as with pytest-xdist. + if fileno == -1: + raise AttributeError() + return fileno except (AttributeError, io.UnsupportedOperation): # pytest-xdist monkeypatches sys.stderr with an object that is not an actual file. # https://docs.python.org/3/library/faulthandler.html#issue-with-file-descriptors diff --git a/testing/test_faulthandler.py b/testing/test_faulthandler.py index caf39813cf4..370084c125f 100644 --- a/testing/test_faulthandler.py +++ b/testing/test_faulthandler.py @@ -1,3 +1,4 @@ +import io import sys import pytest @@ -135,3 +136,27 @@ def test(): result.stdout.no_fnmatch_line(warning_line) result.stdout.fnmatch_lines("*1 passed*") assert result.ret == 0 + + +def test_get_stderr_fileno_invalid_fd() -> None: + """Test for faulthandler being able to handle invalid file descriptors for stderr (#8249).""" + from _pytest.faulthandler import FaultHandlerHooks + + class StdErrWrapper(io.StringIO): + """ + Mimic ``twisted.logger.LoggingFile`` to simulate returning an invalid file descriptor. + + https://github.com/twisted/twisted/blob/twisted-20.3.0/src/twisted/logger/_io.py#L132-L139 + """ + + def fileno(self): + return -1 + + wrapper = StdErrWrapper() + + with pytest.MonkeyPatch.context() as mp: + mp.setattr("sys.stderr", wrapper) + + # Even when the stderr wrapper signals an invalid file descriptor, + # ``_get_stderr_fileno()`` should return the real one. + assert FaultHandlerHooks._get_stderr_fileno() == 2