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
« 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
11import pytest
14# Copied from cpython/Lib/test/support/__init__.py, with modifications.
15class catch_unraisable_exception:
16 """Context manager catching unraisable exception using sys.unraisablehook.
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.
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.
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 """
36 def __init__(self) -> None:
37 self.unraisable: Optional["sys.UnraisableHookArgs"] = None
38 self._old_hook: Optional[Callable[["sys.UnraisableHookArgs"], Any]] = None
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
45 def __enter__(self) -> "catch_unraisable_exception":
46 self._old_hook = sys.unraisablehook
47 sys.unraisablehook = self._hook
48 return self
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
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))
81@pytest.hookimpl(hookwrapper=True, tryfirst=True)
82def pytest_runtest_setup() -> Generator[None, None, None]:
83 yield from unraisable_exception_runtest_hook()
86@pytest.hookimpl(hookwrapper=True, tryfirst=True)
87def pytest_runtest_call() -> Generator[None, None, None]:
88 yield from unraisable_exception_runtest_hook()
91@pytest.hookimpl(hookwrapper=True, tryfirst=True)
92def pytest_runtest_teardown() -> Generator[None, None, None]:
93 yield from unraisable_exception_runtest_hook()