Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1import io 

2import os 

3import sys 

4from typing import Generator 

5from typing import TextIO 

6 

7import pytest 

8from _pytest.config import Config 

9from _pytest.config.argparsing import Parser 

10from _pytest.nodes import Item 

11from _pytest.store import StoreKey 

12 

13 

14fault_handler_stderr_key = StoreKey[TextIO]() 

15 

16 

17def pytest_addoption(parser: Parser) -> None: 

18 help = ( 

19 "Dump the traceback of all threads if a test takes " 

20 "more than TIMEOUT seconds to finish." 

21 ) 

22 parser.addini("faulthandler_timeout", help, default=0.0) 

23 

24 

25def pytest_configure(config: Config) -> None: 

26 import faulthandler 

27 

28 if not faulthandler.is_enabled(): 

29 # faulthhandler is not enabled, so install plugin that does the actual work 

30 # of enabling faulthandler before each test executes. 

31 config.pluginmanager.register(FaultHandlerHooks(), "faulthandler-hooks") 

32 else: 

33 from _pytest.warnings import _issue_warning_captured 

34 

35 # Do not handle dumping to stderr if faulthandler is already enabled, so warn 

36 # users that the option is being ignored. 

37 timeout = FaultHandlerHooks.get_timeout_config_value(config) 

38 if timeout > 0: 

39 _issue_warning_captured( 

40 pytest.PytestConfigWarning( 

41 "faulthandler module enabled before pytest configuration step, " 

42 "'faulthandler_timeout' option ignored" 

43 ), 

44 config.hook, 

45 stacklevel=2, 

46 ) 

47 

48 

49class FaultHandlerHooks: 

50 """Implements hooks that will actually install fault handler before tests execute, 

51 as well as correctly handle pdb and internal errors.""" 

52 

53 def pytest_configure(self, config: Config) -> None: 

54 import faulthandler 

55 

56 stderr_fd_copy = os.dup(self._get_stderr_fileno()) 

57 config._store[fault_handler_stderr_key] = open(stderr_fd_copy, "w") 

58 faulthandler.enable(file=config._store[fault_handler_stderr_key]) 

59 

60 def pytest_unconfigure(self, config: Config) -> None: 

61 import faulthandler 

62 

63 faulthandler.disable() 

64 # close our dup file installed during pytest_configure 

65 # re-enable the faulthandler, attaching it to the default sys.stderr 

66 # so we can see crashes after pytest has finished, usually during 

67 # garbage collection during interpreter shutdown 

68 config._store[fault_handler_stderr_key].close() 

69 del config._store[fault_handler_stderr_key] 

70 faulthandler.enable(file=self._get_stderr_fileno()) 

71 

72 @staticmethod 

73 def _get_stderr_fileno(): 

74 try: 

75 return sys.stderr.fileno() 

76 except (AttributeError, io.UnsupportedOperation): 

77 # pytest-xdist monkeypatches sys.stderr with an object that is not an actual file. 

78 # https://docs.python.org/3/library/faulthandler.html#issue-with-file-descriptors 

79 # This is potentially dangerous, but the best we can do. 

80 return sys.__stderr__.fileno() 

81 

82 @staticmethod 

83 def get_timeout_config_value(config): 

84 return float(config.getini("faulthandler_timeout") or 0.0) 

85 

86 @pytest.hookimpl(hookwrapper=True, trylast=True) 

87 def pytest_runtest_protocol(self, item: Item) -> Generator[None, None, None]: 

88 timeout = self.get_timeout_config_value(item.config) 

89 stderr = item.config._store[fault_handler_stderr_key] 

90 if timeout > 0 and stderr is not None: 

91 import faulthandler 

92 

93 faulthandler.dump_traceback_later(timeout, file=stderr) 

94 try: 

95 yield 

96 finally: 

97 faulthandler.cancel_dump_traceback_later() 

98 else: 

99 yield 

100 

101 @pytest.hookimpl(tryfirst=True) 

102 def pytest_enter_pdb(self) -> None: 

103 """Cancel any traceback dumping due to timeout before entering pdb. 

104 """ 

105 import faulthandler 

106 

107 faulthandler.cancel_dump_traceback_later() 

108 

109 @pytest.hookimpl(tryfirst=True) 

110 def pytest_exception_interact(self) -> None: 

111 """Cancel any traceback dumping due to an interactive exception being 

112 raised. 

113 """ 

114 import faulthandler 

115 

116 faulthandler.cancel_dump_traceback_later()