Repository: pytest-dev/pytest
File: src/_pytest/faulthandler.py (Python)

Code before:
        faulthandler.enable(config.stash[fault_handler_original_stderr_fd_key])
        del config.stash[fault_handler_original_stderr_fd_key]


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, ValueError):
        # 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.
        assert sys.__stderr__ is not None
        return sys.__stderr__.fileno()


def get_timeout_config_value(config: Config) -> float:
    return float(config.getini("faulthandler_timeout") or 0.0)


def get_exit_on_timeout_config_value(config: Config) -> bool:
    return bool(config.getini("faulthandler_exit_on_timeout"))


@pytest.hookimpl(wrapper=True, trylast=True)
def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
    timeout = get_timeout_config_value(item.config)
    exit_on_timeout = get_exit_on_timeout_config_value(item.config)
    if timeout > 0:
        import faulthandler

        stderr = item.config.stash[fault_handler_stderr_fd_key]
        faulthandler.dump_traceback_later(timeout, file=stderr, exit=exit_on_timeout)
        try:
            return (yield)
        finally:
            faulthandler.cancel_dump_traceback_later()
    else:
        return (yield)


@pytest.hookimpl(tryfirst=True)
def pytest_enter_pdb() -> None:
    """Cancel any traceback dumping due to timeout before entering pdb."""
    import faulthandler

    faulthandler.cancel_dump_traceback_later()

Review comment (suggestion):
Note to reviewers: the `bool()` cast is needed to please mypy but `getini` itself ensures that it is a boolean value at runtime because of the `type="bool"` kwarg passed to `addini` but the type checkers are probably blind to this.

Alternatively, I could have written:

```suggestion
    return config.getini("faulthandler_exit_on_timeout") is True
```

This is safer to avoid a false positive cast to `True` in case `getini` does not provide a `bool` value, but I found it uglier. Note that I added a test for `faulthandler_exit_on_timeout = false` so in case there is a regression in `getini`, the false positive case should not go unnoticed.

Let me know what you prefer.


Code after:
        faulthandler.enable(config.stash[fault_handler_original_stderr_fd_key])
        del config.stash[fault_handler_original_stderr_fd_key]


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, ValueError):
        # 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.
        assert sys.__stderr__ is not None
        return sys.__stderr__.fileno()


def get_timeout_config_value(config: Config) -> float:
    return float(config.getini("faulthandler_timeout") or 0.0)


def get_exit_on_timeout_config_value(config: Config) -> bool:
    exit_on_timeout = config.getini("faulthandler_exit_on_timeout")
    assert isinstance(exit_on_timeout, bool)
    return exit_on_timeout


@pytest.hookimpl(wrapper=True, trylast=True)
def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
    timeout = get_timeout_config_value(item.config)
    exit_on_timeout = get_exit_on_timeout_config_value(item.config)
    if timeout > 0:
        import faulthandler

        stderr = item.config.stash[fault_handler_stderr_fd_key]
        faulthandler.dump_traceback_later(timeout, file=stderr, exit=exit_on_timeout)
        try:
            return (yield)
        finally:
            faulthandler.cancel_dump_traceback_later()
    else:
        return (yield)


@pytest.hookimpl(tryfirst=True)
def pytest_enter_pdb() -> None:
    """Cancel any traceback dumping due to timeout before entering pdb."""
    import faulthandler