summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBruno Oliveira <nicoddemus@gmail.com>2021-01-20 10:05:36 -0300
committerBruno Oliveira <nicoddemus@gmail.com>2021-01-20 10:29:05 -0300
commitadc0f29b8f8fa8a63e0592c38503855a5b615a29 (patch)
treeece7c19d12747d19eda314b295b80af96d1345b7 /src
parentbda9ce4e0ffcbeb786101f988bbb51e5f832fcb3 (diff)
downloadpytest-adc0f29b8f8fa8a63e0592c38503855a5b615a29.tar.gz
Always handle faulthandler stderr even if already enabled
It seems the code that would not install pytest's faulthandler support if it was already enabled is not really needed at all, and even detrimental when using `python -X dev -m pytest` to run Python in "dev" mode. Also simplified the plugin by removing the hook class, now the hooks will always be active so there's no need to delay the hook definitions anymore. Fix #8258
Diffstat (limited to 'src')
-rw-r--r--src/_pytest/faulthandler.py134
1 files changed, 57 insertions, 77 deletions
diff --git a/src/_pytest/faulthandler.py b/src/_pytest/faulthandler.py
index ff673b5b1..9592de82d 100644
--- a/src/_pytest/faulthandler.py
+++ b/src/_pytest/faulthandler.py
@@ -25,92 +25,72 @@ def pytest_addoption(parser: Parser) -> None:
def pytest_configure(config: Config) -> None:
import faulthandler
- if not faulthandler.is_enabled():
- # faulthhandler is not enabled, so install plugin that does the actual work
- # of enabling faulthandler before each test executes.
- config.pluginmanager.register(FaultHandlerHooks(), "faulthandler-hooks")
- else:
- # Do not handle dumping to stderr if faulthandler is already enabled, so warn
- # users that the option is being ignored.
- timeout = FaultHandlerHooks.get_timeout_config_value(config)
- if timeout > 0:
- config.issue_config_time_warning(
- pytest.PytestConfigWarning(
- "faulthandler module enabled before pytest configuration step, "
- "'faulthandler_timeout' option ignored"
- ),
- stacklevel=2,
- )
-
-
-class FaultHandlerHooks:
- """Implements hooks that will actually install fault handler before tests execute,
- as well as correctly handle pdb and internal errors."""
-
- def pytest_configure(self, config: Config) -> None:
- import faulthandler
+ stderr_fd_copy = os.dup(get_stderr_fileno())
+ config._store[fault_handler_stderr_key] = open(stderr_fd_copy, "w")
+ faulthandler.enable(file=config._store[fault_handler_stderr_key])
- stderr_fd_copy = os.dup(self._get_stderr_fileno())
- config._store[fault_handler_stderr_key] = open(stderr_fd_copy, "w")
- faulthandler.enable(file=config._store[fault_handler_stderr_key])
- def pytest_unconfigure(self, config: Config) -> None:
- import faulthandler
+def pytest_unconfigure(config: Config) -> None:
+ import faulthandler
- faulthandler.disable()
- # close our dup file installed during pytest_configure
- # re-enable the faulthandler, attaching it to the default sys.stderr
- # so we can see crashes after pytest has finished, usually during
- # garbage collection during interpreter shutdown
+ faulthandler.disable()
+ # Close the dup file installed during pytest_configure.
+ if fault_handler_stderr_key in config._store:
config._store[fault_handler_stderr_key].close()
del config._store[fault_handler_stderr_key]
- faulthandler.enable(file=self._get_stderr_fileno())
+ # Re-enable the faulthandler, attaching it to the default sys.stderr
+ # so we can see crashes after pytest has finished, usually during
+ # garbage collection during interpreter shutdown.
+ faulthandler.enable(file=get_stderr_fileno())
+
+
+def get_stderr_fileno() -> int:
+ try:
+ 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
+ # This is potentially dangerous, but the best we can do.
+ return sys.__stderr__.fileno()
+
+
+def get_timeout_config_value(config: Config) -> float:
+ return float(config.getini("faulthandler_timeout") or 0.0)
+
+
+@pytest.hookimpl(hookwrapper=True, trylast=True)
+def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]:
+ timeout = get_timeout_config_value(item.config)
+ stderr = item.config._store[fault_handler_stderr_key]
+ if timeout > 0 and stderr is not None:
+ import faulthandler
- @staticmethod
- def _get_stderr_fileno():
+ faulthandler.dump_traceback_later(timeout, file=stderr)
try:
- 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
- # This is potentially dangerous, but the best we can do.
- return sys.__stderr__.fileno()
-
- @staticmethod
- def get_timeout_config_value(config):
- return float(config.getini("faulthandler_timeout") or 0.0)
-
- @pytest.hookimpl(hookwrapper=True, trylast=True)
- def pytest_runtest_protocol(self, item: Item) -> Generator[None, None, None]:
- timeout = self.get_timeout_config_value(item.config)
- stderr = item.config._store[fault_handler_stderr_key]
- if timeout > 0 and stderr is not None:
- import faulthandler
-
- faulthandler.dump_traceback_later(timeout, file=stderr)
- try:
- yield
- finally:
- faulthandler.cancel_dump_traceback_later()
- else:
yield
+ finally:
+ faulthandler.cancel_dump_traceback_later()
+ else:
+ yield
- @pytest.hookimpl(tryfirst=True)
- def pytest_enter_pdb(self) -> None:
- """Cancel any traceback dumping due to timeout before entering pdb."""
- import faulthandler
- faulthandler.cancel_dump_traceback_later()
+@pytest.hookimpl(tryfirst=True)
+def pytest_enter_pdb() -> None:
+ """Cancel any traceback dumping due to timeout before entering pdb."""
+ import faulthandler
- @pytest.hookimpl(tryfirst=True)
- def pytest_exception_interact(self) -> None:
- """Cancel any traceback dumping due to an interactive exception being
- raised."""
- import faulthandler
+ faulthandler.cancel_dump_traceback_later()
+
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_exception_interact() -> None:
+ """Cancel any traceback dumping due to an interactive exception being
+ raised."""
+ import faulthandler
- faulthandler.cancel_dump_traceback_later()
+ faulthandler.cancel_dump_traceback_later()