#29024 closed Bug (fixed)

`TestContextDecorator` never exits if `setUp` fails in tests

Reported by: Anthony King Owned by: Kamil
Component: Testing framework Version: master
Severity: Normal Keywords:
Cc: Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: yes Patch needs improvement: yes
Easy pickings: yes UI/UX: no

Description (last modified by Anthony King)

when using TestContextDecorator as a class decorator, enable is called just before Test.setUp, and disable is called after Test.tearDown.

unfortunately, tearDown is not called in the event of a failure inside setUp. This can result in unexpected behaviour.

Even though this class is not documented, there are decorators that use it that are.


class example_decorator(TestContextDecorator):
    some_var = False

    def enable(self):
        self.__class__.some_var = True

    def disable(self):
        self.__class__.some_var = False

class Example1TestCase(TestCase):
    def test_var_is_false(self):
        # some_var will be False

class Example2TestCase(TestCase):
    def setUp(self):
        raise Exception

    def test_var_is_true(self):
        # won't be hit due to exception in setUp

class Example3TestCase(TestCase):
    def test_var_is_false(self):
        # some_var will be True now due to no cleanup in Example2TestCase


ERROR: test_var_is_true (example.tests.Example2TestCase)
... Traceback

FAIL: test_var_is_false (example.tests.Example3TestCase)
AssertionError: True is not false

Ran 3 tests in 0.007s

FAILED (failures=1, errors=1)

There are 2 potential fixes here:

  • decorate setUpClass and tearDownClass
  • use addCleanup inside the setUp decorator

addCleanup will always run, and will only ever be registered if the context manager was successfully enabled.

It would also be useful to have this class documented, however that's out of scope of this ticket.

Change History (10)

comment:1 Changed 17 months ago by Simon Charette

Triage Stage: UnreviewedAccepted
Version: 1.11master

Using addCleanup instead of hooking up on tearDown makes sense here.

comment:2 Changed 17 months ago by Shahbaj Sayyad

Owner: changed from nobody to Shahbaj Sayyad
Status: newassigned

comment:3 Changed 17 months ago by Anthony King

Description: modified (diff)

comment:4 Changed 16 months ago by Anthony King

Hi Shahbaj,

I saw your post in the mailing list.

So how we've done it internally is like this:

class TestContextDecorator(DjangoTestContextDecorator):
    def decorate_class(self, cls):
        # https://code.djangoproject.com/ticket/29024

        if issubclass(cls, TestCase):
            decorated_setUp = cls.setUp

            def setUp(inner_self):
                context = self.enable()
                if self.attr_name:
                    setattr(inner_self, self.attr_name, context)

            cls.setUp = setUp
            return cls
        raise TypeError('Can only decorate subclasses of unittest.TestCase')

comment:5 Changed 16 months ago by Shahbaj Sayyad

Has patch: set

PR (no test)

Last edited 16 months ago by Tim Graham (previous) (diff)

comment:6 Changed 16 months ago by Tim Graham

Needs tests: set

comment:7 Changed 11 months ago by Shahbaj Sayyad

Owner: Shahbaj Sayyad deleted
Status: assignednew

comment:8 Changed 11 months ago by Kamil

Owner: set to Kamil
Status: newassigned

comment:9 Changed 10 months ago by Jeff

Patch needs improvement: set

Recommendations present on the new PR.

Last edited 10 months ago by Tim Graham (previous) (diff)

comment:10 Changed 10 months ago by Tim Graham <timograham@…>

Resolution: fixed
Status: assignedclosed

In 3d4080f:

Fixed #29024 -- Made TestContextDecorator call disable() if setUp() raises an exception.

Note: See TracTickets for help on using tickets.
Back to Top