@@ -197,6 +197,12 @@ def unittest_setup_method_fixture(
197197 )
198198
199199
200+ # Name of the attribute in `twisted.python.Failure` instances that stores
201+ # the `sys.exc_info()` tuple.
202+ # See twisted.trial support in `pytest_runtest_protocol`.
203+ TWISTED_RAW_EXCINFO_ATTR = "_twisted_raw_excinfo"
204+
205+
200206class TestCaseFunction (Function ):
201207 nofuncargs = True
202208 _excinfo : list [_pytest ._code .ExceptionInfo [BaseException ]] | None = None
@@ -229,7 +235,14 @@ def startTest(self, testcase: unittest.TestCase) -> None:
229235
230236 def _addexcinfo (self , rawexcinfo : _SysExcInfoType ) -> None :
231237 # Unwrap potential exception info (see twisted trial support below).
232- rawexcinfo = getattr (rawexcinfo , "_rawexcinfo" , rawexcinfo )
238+ # Twisted calls addError() passing its own classes (like `twisted.python.Failure`), which violates
239+ # the `addError()` signature, so we extract the original `sys.exc_info()` tuple which is stored
240+ # in the object.
241+ if hasattr (rawexcinfo , TWISTED_RAW_EXCINFO_ATTR ):
242+ saved_exc_info = getattr (rawexcinfo , TWISTED_RAW_EXCINFO_ATTR )
243+ # Delete the attribute from the original object to avoid leaks.
244+ delattr (rawexcinfo , TWISTED_RAW_EXCINFO_ATTR )
245+ rawexcinfo = saved_exc_info
233246 try :
234247 excinfo = _pytest ._code .ExceptionInfo [BaseException ].from_exc_info (
235248 rawexcinfo # type: ignore[arg-type]
@@ -394,31 +407,36 @@ def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
394407 if isinstance (item , TestCaseFunction ) and "twisted.trial.unittest" in sys .modules :
395408 ut : Any = sys .modules ["twisted.python.failure" ]
396409 global classImplements_has_run
397- Failure__init__ = ut .Failure .__init__
398410 if not classImplements_has_run :
399411 from twisted .trial .itrial import IReporter
400412 from zope .interface import classImplements
401413
402414 classImplements (TestCaseFunction , IReporter )
403415 classImplements_has_run = True
404416
405- def excstore (
417+ # Monkeypatch `Failure.__init__` to store the raw exception info.
418+ Failure__init__ = ut .Failure .__init__
419+
420+ def store_raw_exception_info (
406421 self , exc_value = None , exc_type = None , exc_tb = None , captureVars = None
407422 ):
408423 if exc_value is None :
409- self . _rawexcinfo = sys .exc_info ()
424+ raw_exc_info = sys .exc_info ()
410425 else :
411426 if exc_type is None :
412427 exc_type = type (exc_value )
413- self ._rawexcinfo = (exc_type , exc_value , exc_tb )
428+ if exc_tb is None :
429+ exc_tb = sys .exc_info ()[2 ]
430+ raw_exc_info = (exc_type , exc_value , exc_tb )
431+ setattr (self , TWISTED_RAW_EXCINFO_ATTR , tuple (raw_exc_info ))
414432 try :
415433 Failure__init__ (
416434 self , exc_value , exc_type , exc_tb , captureVars = captureVars
417435 )
418436 except TypeError :
419437 Failure__init__ (self , exc_value , exc_type , exc_tb )
420438
421- ut .Failure .__init__ = excstore
439+ ut .Failure .__init__ = store_raw_exception_info
422440 try :
423441 res = yield
424442 finally :
0 commit comments