@@ -197,6 +197,12 @@ def unittest_setup_method_fixture(
197
197
)
198
198
199
199
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
+
200
206
class TestCaseFunction (Function ):
201
207
nofuncargs = True
202
208
_excinfo : list [_pytest ._code .ExceptionInfo [BaseException ]] | None = None
@@ -229,7 +235,14 @@ def startTest(self, testcase: unittest.TestCase) -> None:
229
235
230
236
def _addexcinfo (self , rawexcinfo : _SysExcInfoType ) -> None :
231
237
# 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
233
246
try :
234
247
excinfo = _pytest ._code .ExceptionInfo [BaseException ].from_exc_info (
235
248
rawexcinfo # type: ignore[arg-type]
@@ -394,31 +407,36 @@ def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
394
407
if isinstance (item , TestCaseFunction ) and "twisted.trial.unittest" in sys .modules :
395
408
ut : Any = sys .modules ["twisted.python.failure" ]
396
409
global classImplements_has_run
397
- Failure__init__ = ut .Failure .__init__
398
410
if not classImplements_has_run :
399
411
from twisted .trial .itrial import IReporter
400
412
from zope .interface import classImplements
401
413
402
414
classImplements (TestCaseFunction , IReporter )
403
415
classImplements_has_run = True
404
416
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 (
406
421
self , exc_value = None , exc_type = None , exc_tb = None , captureVars = None
407
422
):
408
423
if exc_value is None :
409
- self . _rawexcinfo = sys .exc_info ()
424
+ raw_exc_info = sys .exc_info ()
410
425
else :
411
426
if exc_type is None :
412
427
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 ))
414
432
try :
415
433
Failure__init__ (
416
434
self , exc_value , exc_type , exc_tb , captureVars = captureVars
417
435
)
418
436
except TypeError :
419
437
Failure__init__ (self , exc_value , exc_type , exc_tb )
420
438
421
- ut .Failure .__init__ = excstore
439
+ ut .Failure .__init__ = store_raw_exception_info
422
440
try :
423
441
res = yield
424
442
finally :
0 commit comments