174 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			174 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Python
		
	
	
	
# mypy: allow-untyped-defs
 | 
						|
import io
 | 
						|
import sys
 | 
						|
 | 
						|
import pytest
 | 
						|
from _pytest.pytester import Pytester
 | 
						|
 | 
						|
 | 
						|
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.parametrize(
 | 
						|
    "enabled",
 | 
						|
    [
 | 
						|
        pytest.param(
 | 
						|
            True, marks=pytest.mark.skip(reason="sometimes crashes on CI (#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(["*%s*" % tb_output])
 | 
						|
    else:
 | 
						|
        assert tb_output not in result.stderr.str()
 | 
						|
    result.stdout.fnmatch_lines(["*1 passed*"])
 | 
						|
    assert result.ret == 0
 | 
						|
 | 
						|
 | 
						|
@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
 |