unittest.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. # mypy: allow-untyped-defs
  2. """Discover and run std-library "unittest" style tests."""
  3. from __future__ import annotations
  4. from collections.abc import Callable
  5. from collections.abc import Generator
  6. from collections.abc import Iterable
  7. from collections.abc import Iterator
  8. from enum import auto
  9. from enum import Enum
  10. import inspect
  11. import sys
  12. import traceback
  13. import types
  14. from typing import Any
  15. from typing import TYPE_CHECKING
  16. from unittest import TestCase
  17. import _pytest._code
  18. from _pytest._code import ExceptionInfo
  19. from _pytest.compat import assert_never
  20. from _pytest.compat import is_async_function
  21. from _pytest.config import hookimpl
  22. from _pytest.fixtures import FixtureRequest
  23. from _pytest.monkeypatch import MonkeyPatch
  24. from _pytest.nodes import Collector
  25. from _pytest.nodes import Item
  26. from _pytest.outcomes import exit
  27. from _pytest.outcomes import fail
  28. from _pytest.outcomes import skip
  29. from _pytest.outcomes import xfail
  30. from _pytest.python import Class
  31. from _pytest.python import Function
  32. from _pytest.python import Module
  33. from _pytest.runner import CallInfo
  34. from _pytest.runner import check_interactive_exception
  35. from _pytest.subtests import SubtestContext
  36. from _pytest.subtests import SubtestReport
  37. if sys.version_info[:2] < (3, 11):
  38. from exceptiongroup import ExceptionGroup
  39. if TYPE_CHECKING:
  40. from types import TracebackType
  41. import unittest
  42. import twisted.trial.unittest
  43. _SysExcInfoType = (
  44. tuple[type[BaseException], BaseException, types.TracebackType]
  45. | tuple[None, None, None]
  46. )
  47. def pytest_pycollect_makeitem(
  48. collector: Module | Class, name: str, obj: object
  49. ) -> UnitTestCase | None:
  50. try:
  51. # Has unittest been imported?
  52. ut = sys.modules["unittest"]
  53. # Is obj a subclass of unittest.TestCase?
  54. # Type ignored because `ut` is an opaque module.
  55. if not issubclass(obj, ut.TestCase): # type: ignore
  56. return None
  57. except Exception:
  58. return None
  59. # Is obj a concrete class?
  60. # Abstract classes can't be instantiated so no point collecting them.
  61. if inspect.isabstract(obj):
  62. return None
  63. # Yes, so let's collect it.
  64. return UnitTestCase.from_parent(collector, name=name, obj=obj)
  65. class UnitTestCase(Class):
  66. # Marker for fixturemanger.getfixtureinfo()
  67. # to declare that our children do not support funcargs.
  68. nofuncargs = True
  69. def newinstance(self):
  70. # TestCase __init__ takes the method (test) name. The TestCase
  71. # constructor treats the name "runTest" as a special no-op, so it can be
  72. # used when a dummy instance is needed. While unittest.TestCase has a
  73. # default, some subclasses omit the default (#9610), so always supply
  74. # it.
  75. return self.obj("runTest")
  76. def collect(self) -> Iterable[Item | Collector]:
  77. from unittest import TestLoader
  78. cls = self.obj
  79. if not getattr(cls, "__test__", True):
  80. return
  81. skipped = _is_skipped(cls)
  82. if not skipped:
  83. self._register_unittest_setup_method_fixture(cls)
  84. self._register_unittest_setup_class_fixture(cls)
  85. self._register_setup_class_fixture()
  86. self.session._fixturemanager.parsefactories(self.newinstance(), self.nodeid)
  87. loader = TestLoader()
  88. foundsomething = False
  89. for name in loader.getTestCaseNames(self.obj):
  90. x = getattr(self.obj, name)
  91. if not getattr(x, "__test__", True):
  92. continue
  93. yield TestCaseFunction.from_parent(self, name=name)
  94. foundsomething = True
  95. if not foundsomething:
  96. runtest = getattr(self.obj, "runTest", None)
  97. if runtest is not None:
  98. ut = sys.modules.get("twisted.trial.unittest", None)
  99. if ut is None or runtest != ut.TestCase.runTest:
  100. yield TestCaseFunction.from_parent(self, name="runTest")
  101. def _register_unittest_setup_class_fixture(self, cls: type) -> None:
  102. """Register an auto-use fixture to invoke setUpClass and
  103. tearDownClass (#517)."""
  104. setup = getattr(cls, "setUpClass", None)
  105. teardown = getattr(cls, "tearDownClass", None)
  106. if setup is None and teardown is None:
  107. return None
  108. cleanup = getattr(cls, "doClassCleanups", lambda: None)
  109. def process_teardown_exceptions() -> None:
  110. # tearDown_exceptions is a list set in the class containing exc_infos for errors during
  111. # teardown for the class.
  112. exc_infos = getattr(cls, "tearDown_exceptions", None)
  113. if not exc_infos:
  114. return
  115. exceptions = [exc for (_, exc, _) in exc_infos]
  116. # If a single exception, raise it directly as this provides a more readable
  117. # error (hopefully this will improve in #12255).
  118. if len(exceptions) == 1:
  119. raise exceptions[0]
  120. else:
  121. raise ExceptionGroup("Unittest class cleanup errors", exceptions)
  122. def unittest_setup_class_fixture(
  123. request: FixtureRequest,
  124. ) -> Generator[None]:
  125. cls = request.cls
  126. if _is_skipped(cls):
  127. reason = cls.__unittest_skip_why__
  128. raise skip.Exception(reason, _use_item_location=True)
  129. if setup is not None:
  130. try:
  131. setup()
  132. # unittest does not call the cleanup function for every BaseException, so we
  133. # follow this here.
  134. except Exception:
  135. cleanup()
  136. process_teardown_exceptions()
  137. raise
  138. yield
  139. try:
  140. if teardown is not None:
  141. teardown()
  142. finally:
  143. cleanup()
  144. process_teardown_exceptions()
  145. self.session._fixturemanager._register_fixture(
  146. # Use a unique name to speed up lookup.
  147. name=f"_unittest_setUpClass_fixture_{cls.__qualname__}",
  148. func=unittest_setup_class_fixture,
  149. nodeid=self.nodeid,
  150. scope="class",
  151. autouse=True,
  152. )
  153. def _register_unittest_setup_method_fixture(self, cls: type) -> None:
  154. """Register an auto-use fixture to invoke setup_method and
  155. teardown_method (#517)."""
  156. setup = getattr(cls, "setup_method", None)
  157. teardown = getattr(cls, "teardown_method", None)
  158. if setup is None and teardown is None:
  159. return None
  160. def unittest_setup_method_fixture(
  161. request: FixtureRequest,
  162. ) -> Generator[None]:
  163. self = request.instance
  164. if _is_skipped(self):
  165. reason = self.__unittest_skip_why__
  166. raise skip.Exception(reason, _use_item_location=True)
  167. if setup is not None:
  168. setup(self, request.function)
  169. yield
  170. if teardown is not None:
  171. teardown(self, request.function)
  172. self.session._fixturemanager._register_fixture(
  173. # Use a unique name to speed up lookup.
  174. name=f"_unittest_setup_method_fixture_{cls.__qualname__}",
  175. func=unittest_setup_method_fixture,
  176. nodeid=self.nodeid,
  177. scope="function",
  178. autouse=True,
  179. )
  180. class TestCaseFunction(Function):
  181. nofuncargs = True
  182. failfast = False
  183. _excinfo: list[_pytest._code.ExceptionInfo[BaseException]] | None = None
  184. def _getinstance(self):
  185. assert isinstance(self.parent, UnitTestCase)
  186. return self.parent.obj(self.name)
  187. # Backward compat for pytest-django; can be removed after pytest-django
  188. # updates + some slack.
  189. @property
  190. def _testcase(self):
  191. return self.instance
  192. def setup(self) -> None:
  193. # A bound method to be called during teardown() if set (see 'runtest()').
  194. self._explicit_tearDown: Callable[[], None] | None = None
  195. super().setup()
  196. if sys.version_info < (3, 11):
  197. # A cache of the subTest errors and non-subtest skips in self._outcome.
  198. # Compute and cache these lists once, instead of computing them again and again for each subtest (#13965).
  199. self._cached_errors_and_skips: tuple[list[Any], list[Any]] | None = None
  200. def teardown(self) -> None:
  201. if self._explicit_tearDown is not None:
  202. self._explicit_tearDown()
  203. self._explicit_tearDown = None
  204. self._obj = None
  205. del self._instance
  206. super().teardown()
  207. def startTest(self, testcase: unittest.TestCase) -> None:
  208. pass
  209. def _addexcinfo(self, rawexcinfo: _SysExcInfoType) -> None:
  210. rawexcinfo = _handle_twisted_exc_info(rawexcinfo)
  211. try:
  212. excinfo = _pytest._code.ExceptionInfo[BaseException].from_exc_info(
  213. rawexcinfo # type: ignore[arg-type]
  214. )
  215. # Invoke the attributes to trigger storing the traceback
  216. # trial causes some issue there.
  217. _ = excinfo.value
  218. _ = excinfo.traceback
  219. except TypeError:
  220. try:
  221. try:
  222. values = traceback.format_exception(*rawexcinfo)
  223. values.insert(
  224. 0,
  225. "NOTE: Incompatible Exception Representation, "
  226. "displaying natively:\n\n",
  227. )
  228. fail("".join(values), pytrace=False)
  229. except (fail.Exception, KeyboardInterrupt):
  230. raise
  231. except BaseException:
  232. fail(
  233. "ERROR: Unknown Incompatible Exception "
  234. f"representation:\n{rawexcinfo!r}",
  235. pytrace=False,
  236. )
  237. except KeyboardInterrupt:
  238. raise
  239. except fail.Exception:
  240. excinfo = _pytest._code.ExceptionInfo.from_current()
  241. self.__dict__.setdefault("_excinfo", []).append(excinfo)
  242. def addError(
  243. self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType
  244. ) -> None:
  245. try:
  246. if isinstance(rawexcinfo[1], exit.Exception):
  247. exit(rawexcinfo[1].msg)
  248. except TypeError:
  249. pass
  250. self._addexcinfo(rawexcinfo)
  251. def addFailure(
  252. self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType
  253. ) -> None:
  254. self._addexcinfo(rawexcinfo)
  255. def addSkip(
  256. self, testcase: unittest.TestCase, reason: str, *, handle_subtests: bool = True
  257. ) -> None:
  258. from unittest.case import _SubTest # type: ignore[attr-defined]
  259. def add_skip() -> None:
  260. try:
  261. raise skip.Exception(reason, _use_item_location=True)
  262. except skip.Exception:
  263. self._addexcinfo(sys.exc_info())
  264. if not handle_subtests:
  265. add_skip()
  266. return
  267. if isinstance(testcase, _SubTest):
  268. add_skip()
  269. if self._excinfo is not None:
  270. exc_info = self._excinfo[-1]
  271. self.addSubTest(testcase.test_case, testcase, exc_info)
  272. else:
  273. # For python < 3.11: the non-subtest skips have to be added by `add_skip` only after all subtest
  274. # failures are processed by `_addSubTest`: `self.instance._outcome` has no attribute
  275. # `skipped/errors` anymore.
  276. # We also need to check if `self.instance._outcome` is `None` (this happens if the test
  277. # class/method is decorated with `unittest.skip`, see pytest-dev/pytest-subtests#173).
  278. if sys.version_info < (3, 11) and self.instance._outcome is not None:
  279. subtest_errors, _ = self._obtain_errors_and_skips()
  280. if len(subtest_errors) == 0:
  281. add_skip()
  282. else:
  283. add_skip()
  284. def addExpectedFailure(
  285. self,
  286. testcase: unittest.TestCase,
  287. rawexcinfo: _SysExcInfoType,
  288. reason: str = "",
  289. ) -> None:
  290. try:
  291. xfail(str(reason))
  292. except xfail.Exception:
  293. self._addexcinfo(sys.exc_info())
  294. def addUnexpectedSuccess(
  295. self,
  296. testcase: unittest.TestCase,
  297. reason: twisted.trial.unittest.Todo | None = None,
  298. ) -> None:
  299. msg = "Unexpected success"
  300. if reason:
  301. msg += f": {reason.reason}"
  302. # Preserve unittest behaviour - fail the test. Explicitly not an XPASS.
  303. try:
  304. fail(msg, pytrace=False)
  305. except fail.Exception:
  306. self._addexcinfo(sys.exc_info())
  307. def addSuccess(self, testcase: unittest.TestCase) -> None:
  308. pass
  309. def stopTest(self, testcase: unittest.TestCase) -> None:
  310. pass
  311. def addDuration(self, testcase: unittest.TestCase, elapsed: float) -> None:
  312. pass
  313. def runtest(self) -> None:
  314. from _pytest.debugging import maybe_wrap_pytest_function_for_tracing
  315. testcase = self.instance
  316. assert testcase is not None
  317. maybe_wrap_pytest_function_for_tracing(self)
  318. # Let the unittest framework handle async functions.
  319. if is_async_function(self.obj):
  320. testcase(result=self)
  321. else:
  322. # When --pdb is given, we want to postpone calling tearDown() otherwise
  323. # when entering the pdb prompt, tearDown() would have probably cleaned up
  324. # instance variables, which makes it difficult to debug.
  325. # Arguably we could always postpone tearDown(), but this changes the moment where the
  326. # TestCase instance interacts with the results object, so better to only do it
  327. # when absolutely needed.
  328. # We need to consider if the test itself is skipped, or the whole class.
  329. assert isinstance(self.parent, UnitTestCase)
  330. skipped = _is_skipped(self.obj) or _is_skipped(self.parent.obj)
  331. if self.config.getoption("usepdb") and not skipped:
  332. self._explicit_tearDown = testcase.tearDown
  333. setattr(testcase, "tearDown", lambda *args: None)
  334. # We need to update the actual bound method with self.obj, because
  335. # wrap_pytest_function_for_tracing replaces self.obj by a wrapper.
  336. setattr(testcase, self.name, self.obj)
  337. try:
  338. testcase(result=self)
  339. finally:
  340. delattr(testcase, self.name)
  341. def _traceback_filter(
  342. self, excinfo: _pytest._code.ExceptionInfo[BaseException]
  343. ) -> _pytest._code.Traceback:
  344. traceback = super()._traceback_filter(excinfo)
  345. ntraceback = traceback.filter(
  346. lambda x: not x.frame.f_globals.get("__unittest"),
  347. )
  348. if not ntraceback:
  349. ntraceback = traceback
  350. return ntraceback
  351. def addSubTest(
  352. self,
  353. test_case: Any,
  354. test: TestCase,
  355. exc_info: ExceptionInfo[BaseException]
  356. | tuple[type[BaseException], BaseException, TracebackType]
  357. | None,
  358. ) -> None:
  359. exception_info: ExceptionInfo[BaseException] | None
  360. match exc_info:
  361. case tuple():
  362. exception_info = ExceptionInfo(exc_info, _ispytest=True)
  363. case ExceptionInfo() | None:
  364. exception_info = exc_info
  365. case unreachable:
  366. assert_never(unreachable)
  367. call_info = CallInfo[None](
  368. None,
  369. exception_info,
  370. start=0,
  371. stop=0,
  372. duration=0,
  373. when="call",
  374. _ispytest=True,
  375. )
  376. msg = test._message if isinstance(test._message, str) else None # type: ignore[attr-defined]
  377. report = self.ihook.pytest_runtest_makereport(item=self, call=call_info)
  378. sub_report = SubtestReport._new(
  379. report,
  380. SubtestContext(msg=msg, kwargs=dict(test.params)), # type: ignore[attr-defined]
  381. captured_output=None,
  382. captured_logs=None,
  383. )
  384. self.ihook.pytest_runtest_logreport(report=sub_report)
  385. if check_interactive_exception(call_info, sub_report):
  386. self.ihook.pytest_exception_interact(
  387. node=self, call=call_info, report=sub_report
  388. )
  389. # For python < 3.11: add non-subtest skips once all subtest failures are processed by # `_addSubTest`.
  390. if sys.version_info < (3, 11):
  391. subtest_errors, non_subtest_skip = self._obtain_errors_and_skips()
  392. # Check if we have non-subtest skips: if there are also sub failures, non-subtest skips are not treated in
  393. # `_addSubTest` and have to be added using `add_skip` after all subtest failures are processed.
  394. if len(non_subtest_skip) > 0 and len(subtest_errors) > 0:
  395. # Make sure we have processed the last subtest failure
  396. last_subset_error = subtest_errors[-1]
  397. if exc_info is last_subset_error[-1]:
  398. # Add non-subtest skips (as they could not be treated in `_addSkip`)
  399. for testcase, reason in non_subtest_skip:
  400. self.addSkip(testcase, reason, handle_subtests=False)
  401. def _obtain_errors_and_skips(self) -> tuple[list[Any], list[Any]]:
  402. """Compute or obtain the cached values for subtest errors and non-subtest skips."""
  403. from unittest.case import _SubTest # type: ignore[attr-defined]
  404. assert sys.version_info < (3, 11), (
  405. "This workaround only should be used in Python 3.10"
  406. )
  407. if self._cached_errors_and_skips is not None:
  408. return self._cached_errors_and_skips
  409. subtest_errors = [
  410. (x, y)
  411. for x, y in self.instance._outcome.errors
  412. if isinstance(x, _SubTest) and y is not None
  413. ]
  414. non_subtest_skips = [
  415. (x, y)
  416. for x, y in self.instance._outcome.skipped
  417. if not isinstance(x, _SubTest)
  418. ]
  419. self._cached_errors_and_skips = (subtest_errors, non_subtest_skips)
  420. return subtest_errors, non_subtest_skips
  421. @hookimpl(tryfirst=True)
  422. def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> None:
  423. if isinstance(item, TestCaseFunction):
  424. if item._excinfo:
  425. call.excinfo = item._excinfo.pop(0)
  426. try:
  427. del call.result
  428. except AttributeError:
  429. pass
  430. # Convert unittest.SkipTest to pytest.skip.
  431. # This covers explicit `raise unittest.SkipTest`.
  432. unittest = sys.modules.get("unittest")
  433. if unittest and call.excinfo and isinstance(call.excinfo.value, unittest.SkipTest):
  434. excinfo = call.excinfo
  435. call2 = CallInfo[None].from_call(lambda: skip(str(excinfo.value)), call.when)
  436. call.excinfo = call2.excinfo
  437. def _is_skipped(obj) -> bool:
  438. """Return True if the given object has been marked with @unittest.skip."""
  439. return bool(getattr(obj, "__unittest_skip__", False))
  440. def pytest_configure() -> None:
  441. """Register the TestCaseFunction class as an IReporter if twisted.trial is available."""
  442. if _get_twisted_version() is not TwistedVersion.NotInstalled:
  443. from twisted.trial.itrial import IReporter
  444. from zope.interface import classImplements
  445. classImplements(TestCaseFunction, IReporter)
  446. class TwistedVersion(Enum):
  447. """
  448. The Twisted version installed in the environment.
  449. We have different workarounds in place for different versions of Twisted.
  450. """
  451. # Twisted version 24 or prior.
  452. Version24 = auto()
  453. # Twisted version 25 or later.
  454. Version25 = auto()
  455. # Twisted version is not available.
  456. NotInstalled = auto()
  457. def _get_twisted_version() -> TwistedVersion:
  458. # We need to check if "twisted.trial.unittest" is specifically present in sys.modules.
  459. # This is because we intend to integrate with Trial only when it's actively running
  460. # the test suite, but not needed when only other Twisted components are in use.
  461. if "twisted.trial.unittest" not in sys.modules:
  462. return TwistedVersion.NotInstalled
  463. import importlib.metadata
  464. import packaging.version
  465. version_str = importlib.metadata.version("twisted")
  466. version = packaging.version.parse(version_str)
  467. if version.major <= 24:
  468. return TwistedVersion.Version24
  469. else:
  470. return TwistedVersion.Version25
  471. # Name of the attribute in `twisted.python.Failure` instances that stores
  472. # the `sys.exc_info()` tuple.
  473. # See twisted.trial support in `pytest_runtest_protocol`.
  474. TWISTED_RAW_EXCINFO_ATTR = "_twisted_raw_excinfo"
  475. @hookimpl(wrapper=True)
  476. def pytest_runtest_protocol(item: Item) -> Iterator[None]:
  477. if _get_twisted_version() is TwistedVersion.Version24:
  478. import twisted.python.failure as ut
  479. # Monkeypatch `Failure.__init__` to store the raw exception info.
  480. original__init__ = ut.Failure.__init__
  481. def store_raw_exception_info(
  482. self, exc_value=None, exc_type=None, exc_tb=None, captureVars=None
  483. ): # pragma: no cover
  484. if exc_value is None:
  485. raw_exc_info = sys.exc_info()
  486. else:
  487. if exc_type is None:
  488. exc_type = type(exc_value)
  489. if exc_tb is None:
  490. exc_tb = sys.exc_info()[2]
  491. raw_exc_info = (exc_type, exc_value, exc_tb)
  492. setattr(self, TWISTED_RAW_EXCINFO_ATTR, tuple(raw_exc_info))
  493. try:
  494. original__init__(
  495. self, exc_value, exc_type, exc_tb, captureVars=captureVars
  496. )
  497. except TypeError: # pragma: no cover
  498. original__init__(self, exc_value, exc_type, exc_tb)
  499. with MonkeyPatch.context() as patcher:
  500. patcher.setattr(ut.Failure, "__init__", store_raw_exception_info)
  501. return (yield)
  502. else:
  503. return (yield)
  504. def _handle_twisted_exc_info(
  505. rawexcinfo: _SysExcInfoType | BaseException,
  506. ) -> _SysExcInfoType:
  507. """
  508. Twisted passes a custom Failure instance to `addError()` instead of using `sys.exc_info()`.
  509. Therefore, if `rawexcinfo` is a `Failure` instance, convert it into the equivalent `sys.exc_info()` tuple
  510. as expected by pytest.
  511. """
  512. twisted_version = _get_twisted_version()
  513. if twisted_version is TwistedVersion.NotInstalled:
  514. # Unfortunately, because we cannot import `twisted.python.failure` at the top of the file
  515. # and use it in the signature, we need to use `type:ignore` here because we cannot narrow
  516. # the type properly in the `if` statement above.
  517. return rawexcinfo # type:ignore[return-value]
  518. elif twisted_version is TwistedVersion.Version24:
  519. # Twisted calls addError() passing its own classes (like `twisted.python.Failure`), which violates
  520. # the `addError()` signature, so we extract the original `sys.exc_info()` tuple which is stored
  521. # in the object.
  522. if hasattr(rawexcinfo, TWISTED_RAW_EXCINFO_ATTR):
  523. saved_exc_info = getattr(rawexcinfo, TWISTED_RAW_EXCINFO_ATTR)
  524. # Delete the attribute from the original object to avoid leaks.
  525. delattr(rawexcinfo, TWISTED_RAW_EXCINFO_ATTR)
  526. return saved_exc_info # type:ignore[no-any-return]
  527. return rawexcinfo # type:ignore[return-value]
  528. elif twisted_version is TwistedVersion.Version25:
  529. if isinstance(rawexcinfo, BaseException):
  530. import twisted.python.failure
  531. if isinstance(rawexcinfo, twisted.python.failure.Failure):
  532. tb = rawexcinfo.__traceback__
  533. if tb is None:
  534. tb = sys.exc_info()[2]
  535. return type(rawexcinfo.value), rawexcinfo.value, tb
  536. return rawexcinfo # type:ignore[return-value]
  537. else:
  538. # Ideally we would use assert_never() here, but it is not available in all Python versions
  539. # we support, plus we do not require `type_extensions` currently.
  540. assert False, f"Unexpected Twisted version: {twisted_version}"