Fixed #31944 -- Used addCleanup() to register TestContextDecorator cleanups.

Cleanups from addCleanup() are scheduled to happen in reverse order to
the order they are added (LIFO). Ensures each cleanup is executed from
the innermost to the outermost.
This commit is contained in:
François Freitag 2020-09-06 14:36:20 +02:00 committed by Mariusz Felisiak
parent 11ebc6479f
commit 57dadfac3c
3 changed files with 30 additions and 11 deletions

View File

@ -351,24 +351,15 @@ class TestContextDecorator:
def decorate_class(self, cls):
if issubclass(cls, TestCase):
decorated_setUp = cls.setUp
decorated_tearDown = cls.tearDown
def setUp(inner_self):
context = self.enable()
inner_self.addCleanup(self.disable)
if self.attr_name:
setattr(inner_self, self.attr_name, context)
try:
decorated_setUp(inner_self)
except Exception:
self.disable()
raise
def tearDown(inner_self):
decorated_tearDown(inner_self)
self.disable()
decorated_setUp(inner_self)
cls.setUp = setUp
cls.tearDown = tearDown
return cls
raise TypeError('Can only decorate subclasses of unittest.TestCase')

View File

@ -484,6 +484,11 @@ Miscellaneous
* The undocumented ``django.utils.http.limited_parse_qsl()`` function is
removed. Please use :func:`urllib.parse.parse_qsl` instead.
* ``django.test.utils.TestContextDecorator`` now uses
:py:meth:`~unittest.TestCase.addCleanup` so that cleanups registered in the
:py:meth:`~unittest.TestCase.setUp` method are called before
``TestContextDecorator.disable()``.
.. _deprecated-features-3.2:
Features deprecated in 3.2

View File

@ -1477,4 +1477,27 @@ class TestContextDecoratorTests(SimpleTestCase):
self.assertFalse(mock_disable.called)
with self.assertRaisesMessage(NotImplementedError, 'reraised'):
decorated_test_class.setUp()
decorated_test_class.doCleanups()
self.assertTrue(mock_disable.called)
def test_cleanups_run_after_tearDown(self):
calls = []
class SaveCallsDecorator(TestContextDecorator):
def enable(self):
calls.append('enable')
def disable(self):
calls.append('disable')
class AddCleanupInSetUp(unittest.TestCase):
def setUp(self):
calls.append('setUp')
self.addCleanup(lambda: calls.append('cleanup'))
decorator = SaveCallsDecorator()
decorated_test_class = decorator.__call__(AddCleanupInSetUp)()
decorated_test_class.setUp()
decorated_test_class.tearDown()
decorated_test_class.doCleanups()
self.assertEqual(calls, ['enable', 'setUp', 'cleanup', 'disable'])