0

I have a unit test that I want to show failing as part of a bug report. It's important not only that the test fail with an error, but that the error message very clearly evidences the underlying bug. I do NOT want the test to pass.

Here is a minimal example:

import unittest

import attrs


@attrs.frozen
class FrozenException(Exception):
    pass


class BugException(Exception):
    pass


class Test(unittest.TestCase):

    def testFromNone(self):
        raise BugException

    def testFromFrozen(self):
        try:
            raise FrozenException
        except FrozenException:
            raise BugException


if __name__ == '__main__':
    unittest.main()

When I execute this file, I expect (and desire) for both test methods to fail and show BugException as the reason for the failure. testFromNone behaves as expected:

$ python3 -m unittest scratch.Test.testFromNone
E
======================================================================
ERROR: testFromNone (scratch.Test.testFromNone)
----------------------------------------------------------------------
Traceback (most recent call last):
...
scratch.BugException

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (errors=1)

However, running testFromFrozen causes the unit test code itself to crash1, hiding the BugException deep in the stack trace2:

$ python3 -m unittest scratch.Test.testFromFrozen
Traceback (most recent call last):
...
<very long traceback>
...
attr.exceptions.FrozenInstanceError

It appears that the unittest framework does some sort of post-processing on exceptions that aren't caught by the test, and that in the course of this processing, it tries to mutate the FrozenException instance stored in the BugException's traceback, triggering the FrozenInstanceError.

How can I work around this so that the test shows the correct error message? I'd prefer to avoid removing the @attrs.frozen decorator if at all possible3.

Python version 3.11.8, attrs version 22.2.0.


1This behavior is only observable if the exception is not caught within the body of the test method. If my example is changed to use assertRaises or an explicit try/catch, the FrozenInstanceError is not raised.


2In the real world, beyond the scope of this example, the symptom is even worse because the unexpected error causes the entire test suite to hang, causing no further tests to be executed and the process to just sit there until the test job times out.


3In the real code, the frozen exception class actually has attributes.

1 Answer 1

0

You get the expected result in attrs 23.1.0 and later:

EE
======================================================================
ERROR: testFromFrozen (__main__.Test.testFromFrozen)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/hynek/FOSS/attrs/t2.py", line 22, in testFromFrozen
    raise FrozenException
FrozenException

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/hynek/FOSS/attrs/t2.py", line 24, in testFromFrozen
    raise BugException
BugException

======================================================================
ERROR: testFromNone (__main__.Test.testFromNone)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/hynek/FOSS/attrs/t2.py", line 18, in testFromNone
    raise BugException
BugException

----------------------------------------------------------------------
Ran 2 tests in 0.000s

FAILED (errors=2)

The relevant changes are in https://github.com/python-attrs/attrs/pull/1081 and allow the modification of __traceback__.

1
  • Ah, I was hoping an attrs upgrade would fix this but I couldn't find the relevant patch notes. Not sure why I didn't think to just try it :/ Thanks! Commented Feb 28 at 10:00

Not the answer you're looking for? Browse other questions tagged or ask your own question.