/
niceSOFT
/
python3-pytest
Обзор
Документация
Войти
/
niceSOFT
/
python3-pytest
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
testing/test_faulthandler.py
221 строка
7 KB
Olivier Grisel
Make it possible for faulthandler to terminate the pytest process on timeout (#13679)
06 окт 2025, 16:36
Не верифицирован
06 окт 2025, 16:36
83da343
Код
Авторство
О чём код?
# mypy: allow-untyped-defs from __future__ import annotations import io import os import sys from _pytest.pytester import Pytester import pytest def test_enabled(pytester: Pytester) -> None: """Test single crashing test displays a traceback.""" pytester.makepyfile( """ import faulthandler def test_crash(): faulthandler._sigabrt() """ ) result = pytester.runpytest_subprocess() result.stderr.fnmatch_lines(["*Fatal Python error*"]) assert result.ret != 0 def setup_crashing_test(pytester: Pytester) -> None: pytester.makepyfile( """ import faulthandler import atexit def test_ok(): atexit.register(faulthandler._sigabrt) """ ) def test_crash_during_shutdown_captured(pytester: Pytester) -> None: """ Re-enable faulthandler if pytest encountered it enabled during configure. We should be able to then see crashes during interpreter shutdown. """ setup_crashing_test(pytester) args = (sys.executable, "-Xfaulthandler", "-mpytest") result = pytester.run(*args) result.stderr.fnmatch_lines(["*Fatal Python error*"]) assert result.ret != 0 def test_crash_during_shutdown_not_captured(pytester: Pytester) -> None: """ Check that pytest leaves faulthandler disabled if it was not enabled during configure. This prevents us from seeing crashes during interpreter shutdown (see #8260). """ setup_crashing_test(pytester) args = (sys.executable, "-mpytest") result = pytester.run(*args) result.stderr.no_fnmatch_line("*Fatal Python error*") assert result.ret != 0 def test_disabled(pytester: Pytester) -> None: """Test option to disable fault handler in the command line.""" pytester.makepyfile( """ import faulthandler def test_disabled(): assert not faulthandler.is_enabled() """ ) result = pytester.runpytest_subprocess("-p", "no:faulthandler") result.stdout.fnmatch_lines(["*1 passed*"]) assert result.ret == 0 @pytest.mark.keep_ci_var @pytest.mark.parametrize( "enabled", [ pytest.param( True, marks=pytest.mark.skipif( bool(os.environ.get("CI")) and sys.platform == "linux" and sys.version_info >= (3, 14), reason="sometimes crashes on CI because of truncated outputs (#7022)", ), ), False, ], ) def test_timeout(pytester: Pytester, enabled: bool) -> None: """Test option to dump tracebacks after a certain timeout. If faulthandler is disabled, no traceback will be dumped. """ pytester.makepyfile( """ import os, time def test_timeout(): time.sleep(1 if "CI" in os.environ else 0.1) """ ) pytester.makeini( """ [pytest] faulthandler_timeout = 0.01 """ ) args = ["-p", "no:faulthandler"] if not enabled else [] result = pytester.runpytest_subprocess(*args) tb_output = "most recent call first" if enabled: result.stderr.fnmatch_lines([f"*{tb_output}*"]) else: assert tb_output not in result.stderr.str() result.stdout.fnmatch_lines(["*1 passed*"]) assert result.ret == 0 @pytest.mark.keep_ci_var @pytest.mark.skipif( "CI" in os.environ and sys.platform == "linux" and sys.version_info >= (3, 14), reason="sometimes crashes on CI because of truncated outputs (#7022)", ) @pytest.mark.parametrize("exit_on_timeout", [True, False]) def test_timeout_and_exit(pytester: Pytester, exit_on_timeout: bool) -> None: """Test option to force exit pytest process after a certain timeout.""" pytester.makepyfile( """ import os, time def test_long_sleep_and_raise(): time.sleep(1 if "CI" in os.environ else 0.1) raise AssertionError( "This test should have been interrupted before reaching this point." ) """ ) pytester.makeini( f""" [pytest] faulthandler_timeout = 0.01 faulthandler_exit_on_timeout = {"true" if exit_on_timeout else "false"} """ ) result = pytester.runpytest_subprocess() tb_output = "most recent call first" result.stderr.fnmatch_lines([f"*{tb_output}*"]) if exit_on_timeout: result.stdout.no_fnmatch_line("*1 failed*") result.stdout.no_fnmatch_line("*AssertionError*") else: result.stdout.fnmatch_lines(["*1 failed*"]) result.stdout.fnmatch_lines(["*AssertionError*"]) assert result.ret == 1 @pytest.mark.parametrize("hook_name", ["pytest_enter_pdb", "pytest_exception_interact"]) def test_cancel_timeout_on_hook(monkeypatch, hook_name) -> None: """Make sure that we are cancelling any scheduled traceback dumping due to timeout before entering pdb (pytest-dev/pytest-faulthandler#12) or any other interactive exception (pytest-dev/pytest-faulthandler#14).""" import faulthandler from _pytest import faulthandler as faulthandler_plugin called = [] monkeypatch.setattr( faulthandler, "cancel_dump_traceback_later", lambda: called.append(1) ) # call our hook explicitly, we can trust that pytest will call the hook # for us at the appropriate moment hook_func = getattr(faulthandler_plugin, hook_name) hook_func() assert called == [1] def test_already_initialized_crash(pytester: Pytester) -> None: """Even if faulthandler is already initialized, we still dump tracebacks on crashes (#8258).""" pytester.makepyfile( """ def test(): import faulthandler faulthandler._sigabrt() """ ) result = pytester.run( sys.executable, "-X", "faulthandler", "-mpytest", pytester.path, ) result.stderr.fnmatch_lines(["*Fatal Python error*"]) 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 get_stderr_fileno 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 get_stderr_fileno() == 2