forked from pytest-dev/pytest
/
faulthandler.py
99 lines (79 loc) · 2.96 KB
/
faulthandler.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import io
import os
import sys
import pytest
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting")
group.addoption(
"--no-faulthandler",
action="store_false",
dest="fault_handler",
default=True,
help="Disable faulthandler module.",
)
group.addoption(
"--faulthandler-timeout",
type=int,
dest="fault_handler_timeout",
metavar="TIMEOUT",
default=0,
help="Dump the traceback of all threads if a test takes "
"more than TIMEOUT seconds to finish.\n"
"Not available on Windows.",
)
def pytest_configure(config):
if config.getoption("fault_handler"):
import faulthandler
# avoid trying to dup sys.stderr if faulthandler is already enabled
if faulthandler.is_enabled():
return
try:
stderr_fileno = sys.stderr.fileno()
except (AttributeError, io.UnsupportedOperation):
# python-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.
stderr_fileno = sys.__stderr__.fileno()
stderr_fd_copy = os.dup(stderr_fileno)
config.fault_handler_stderr = os.fdopen(stderr_fd_copy, "w")
faulthandler.enable(config.fault_handler_stderr)
def pytest_unconfigure(config):
if config.getoption("fault_handler"):
import faulthandler
faulthandler.disable()
# close our dup file installed during pytest_configure
f = getattr(config, "fault_handler_stderr", None)
if f is not None:
# 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
config.fault_handler_stderr.close()
del config.fault_handler_stderr
faulthandler.enable()
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_protocol(item):
enabled = item.config.getoption("fault_handler")
timeout = item.config.getoption("fault_handler_timeout")
if enabled and timeout > 0:
import faulthandler
stderr = item.config.fault_handler_stderr
faulthandler.dump_traceback_later(timeout, file=stderr)
try:
yield
finally:
faulthandler.cancel_dump_traceback_later()
else:
yield
@pytest.hookimpl(tryfirst=True)
def pytest_enter_pdb():
"""Cancel any traceback dumping due to timeout before entering pdb.
"""
import faulthandler
faulthandler.cancel_dump_traceback_later()
@pytest.hookimpl(tryfirst=True)
def pytest_exception_interact():
"""Cancel any traceback dumping due to an interactive exception being
raised.
"""
import faulthandler
faulthandler.cancel_dump_traceback_later()