@@ -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,15 @@ 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
246+ del saved_exc_info
233247 try :
234248 excinfo = _pytest ._code .ExceptionInfo [BaseException ].from_exc_info (
235249 rawexcinfo # type: ignore[arg-type]
@@ -394,31 +408,36 @@ def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
394408 if isinstance (item , TestCaseFunction ) and "twisted.trial.unittest" in sys .modules :
395409 ut : Any = sys .modules ["twisted.python.failure" ]
396410 global classImplements_has_run
397- Failure__init__ = ut .Failure .__init__
398411 if not classImplements_has_run :
399412 from twisted .trial .itrial import IReporter
400413 from zope .interface import classImplements
401414
402415 classImplements (TestCaseFunction , IReporter )
403416 classImplements_has_run = True
404417
405- def excstore (
418+ # Monkeypatch `Failure.__init__` to store the raw exception info.
419+ Failure__init__ = ut .Failure .__init__
420+
421+ def store_raw_exception_info (
406422 self , exc_value = None , exc_type = None , exc_tb = None , captureVars = None
407423 ):
408424 if exc_value is None :
409- self . _rawexcinfo = sys .exc_info ()
425+ raw_exc_info = sys .exc_info ()
410426 else :
411427 if exc_type is None :
412428 exc_type = type (exc_value )
413- self ._rawexcinfo = (exc_type , exc_value , exc_tb )
429+ if exc_tb is None :
430+ exc_tb = sys .exc_info ()[2 ]
431+ raw_exc_info = (exc_type , exc_value , exc_tb )
432+ setattr (self , TWISTED_RAW_EXCINFO_ATTR , tuple (raw_exc_info ))
414433 try :
415434 Failure__init__ (
416435 self , exc_value , exc_type , exc_tb , captureVars = captureVars
417436 )
418437 except TypeError :
419438 Failure__init__ (self , exc_value , exc_type , exc_tb )
420439
421- ut .Failure .__init__ = excstore
440+ ut .Failure .__init__ = store_raw_exception_info
422441 try :
423442 res = yield
424443 finally :
0 commit comments