Coverage for /opt/homebrew/lib/python3.11/site-packages/_pytest/unraisableexception.py: 84%

44 statements  

« prev     ^ index     » next       coverage.py v7.2.3, created at 2023-05-04 13:14 +0700

1import sys 

2import traceback 

3import warnings 

4from types import TracebackType 

5from typing import Any 

6from typing import Callable 

7from typing import Generator 

8from typing import Optional 

9from typing import Type 

10 

11import pytest 

12 

13 

14# Copied from cpython/Lib/test/support/__init__.py, with modifications. 

15class catch_unraisable_exception: 

16 """Context manager catching unraisable exception using sys.unraisablehook. 

17 

18 Storing the exception value (cm.unraisable.exc_value) creates a reference 

19 cycle. The reference cycle is broken explicitly when the context manager 

20 exits. 

21 

22 Storing the object (cm.unraisable.object) can resurrect it if it is set to 

23 an object which is being finalized. Exiting the context manager clears the 

24 stored object. 

25 

26 Usage: 

27 with catch_unraisable_exception() as cm: 

28 # code creating an "unraisable exception" 

29 ... 

30 # check the unraisable exception: use cm.unraisable 

31 ... 

32 # cm.unraisable attribute no longer exists at this point 

33 # (to break a reference cycle) 

34 """ 

35 

36 def __init__(self) -> None: 

37 self.unraisable: Optional["sys.UnraisableHookArgs"] = None 

38 self._old_hook: Optional[Callable[["sys.UnraisableHookArgs"], Any]] = None 

39 

40 def _hook(self, unraisable: "sys.UnraisableHookArgs") -> None: 

41 # Storing unraisable.object can resurrect an object which is being 

42 # finalized. Storing unraisable.exc_value creates a reference cycle. 

43 self.unraisable = unraisable 

44 

45 def __enter__(self) -> "catch_unraisable_exception": 

46 self._old_hook = sys.unraisablehook 

47 sys.unraisablehook = self._hook 

48 return self 

49 

50 def __exit__( 

51 self, 

52 exc_type: Optional[Type[BaseException]], 

53 exc_val: Optional[BaseException], 

54 exc_tb: Optional[TracebackType], 

55 ) -> None: 

56 assert self._old_hook is not None 

57 sys.unraisablehook = self._old_hook 

58 self._old_hook = None 

59 del self.unraisable 

60 

61 

62def unraisable_exception_runtest_hook() -> Generator[None, None, None]: 

63 with catch_unraisable_exception() as cm: 

64 yield 

65 if cm.unraisable: 

66 if cm.unraisable.err_msg is not None: 

67 err_msg = cm.unraisable.err_msg 

68 else: 

69 err_msg = "Exception ignored in" 

70 msg = f"{err_msg}: {cm.unraisable.object!r}\n\n" 

71 msg += "".join( 

72 traceback.format_exception( 

73 cm.unraisable.exc_type, 

74 cm.unraisable.exc_value, 

75 cm.unraisable.exc_traceback, 

76 ) 

77 ) 

78 warnings.warn(pytest.PytestUnraisableExceptionWarning(msg)) 

79 

80 

81@pytest.hookimpl(hookwrapper=True, tryfirst=True) 

82def pytest_runtest_setup() -> Generator[None, None, None]: 

83 yield from unraisable_exception_runtest_hook() 

84 

85 

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

87def pytest_runtest_call() -> Generator[None, None, None]: 

88 yield from unraisable_exception_runtest_hook() 

89 

90 

91@pytest.hookimpl(hookwrapper=True, tryfirst=True) 

92def pytest_runtest_teardown() -> Generator[None, None, None]: 

93 yield from unraisable_exception_runtest_hook()