Coverage for /opt/homebrew/lib/python3.11/site-packages/_pytest/threadexception.py: 88%
42 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 threading
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/threading_helper.py, with modifications.
15class catch_threading_exception:
16 """Context manager catching threading.Thread exception using
17 threading.excepthook.
19 Storing exc_value using a custom hook can create a reference cycle. The
20 reference cycle is broken explicitly when the context manager exits.
22 Storing thread using a custom hook can resurrect it if it is set to an
23 object which is being finalized. Exiting the context manager clears the
24 stored object.
26 Usage:
27 with threading_helper.catch_threading_exception() as cm:
28 # code spawning a thread which raises an exception
29 ...
30 # check the thread exception: use cm.args
31 ...
32 # cm.args attribute no longer exists at this point
33 # (to break a reference cycle)
34 """
36 def __init__(self) -> None:
37 self.args: Optional["threading.ExceptHookArgs"] = None
38 self._old_hook: Optional[Callable[["threading.ExceptHookArgs"], Any]] = None
40 def _hook(self, args: "threading.ExceptHookArgs") -> None:
41 self.args = args
43 def __enter__(self) -> "catch_threading_exception":
44 self._old_hook = threading.excepthook
45 threading.excepthook = self._hook
46 return self
48 def __exit__(
49 self,
50 exc_type: Optional[Type[BaseException]],
51 exc_val: Optional[BaseException],
52 exc_tb: Optional[TracebackType],
53 ) -> None:
54 assert self._old_hook is not None
55 threading.excepthook = self._old_hook
56 self._old_hook = None
57 del self.args
60def thread_exception_runtest_hook() -> Generator[None, None, None]:
61 with catch_threading_exception() as cm:
62 yield
63 if cm.args:
64 thread_name = "<unknown>" if cm.args.thread is None else cm.args.thread.name
65 msg = f"Exception in thread {thread_name}\n\n"
66 msg += "".join(
67 traceback.format_exception(
68 cm.args.exc_type,
69 cm.args.exc_value,
70 cm.args.exc_traceback,
71 )
72 )
73 warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg))
76@pytest.hookimpl(hookwrapper=True, trylast=True)
77def pytest_runtest_setup() -> Generator[None, None, None]:
78 yield from thread_exception_runtest_hook()
81@pytest.hookimpl(hookwrapper=True, tryfirst=True)
82def pytest_runtest_call() -> Generator[None, None, None]:
83 yield from thread_exception_runtest_hook()
86@pytest.hookimpl(hookwrapper=True, tryfirst=True)
87def pytest_runtest_teardown() -> Generator[None, None, None]:
88 yield from thread_exception_runtest_hook()