Fixed #17011 - Made override_settings modify a decorated class in-place rather than creating a dynamic subclass, so as to avoid infinite recursion when used with super(). Thanks jsdalton for the report and patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16942 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Carl Meyer 2011-10-08 08:16:17 +00:00
parent 0f5d69155e
commit b6ad1afa68
3 changed files with 48 additions and 13 deletions

View File

@ -196,23 +196,17 @@ class override_settings(object):
def __call__(self, test_func): def __call__(self, test_func):
from django.test import TransactionTestCase from django.test import TransactionTestCase
if isinstance(test_func, type) and issubclass(test_func, TransactionTestCase): if isinstance(test_func, type) and issubclass(test_func, TransactionTestCase):
# When decorating a class, we need to construct a new class original_pre_setup = test_func._pre_setup
# with the same name so that the test discovery tools can original_post_teardown = test_func._post_teardown
# get a useful name.
def _pre_setup(innerself): def _pre_setup(innerself):
self.enable() self.enable()
test_func._pre_setup(innerself) original_pre_setup(innerself)
def _post_teardown(innerself): def _post_teardown(innerself):
test_func._post_teardown(innerself) original_post_teardown(innerself)
self.disable() self.disable()
inner = type( test_func._pre_setup = _pre_setup
test_func.__name__, test_func._post_teardown = _post_teardown
(test_func,), return test_func
{
'_pre_setup': _pre_setup,
'_post_teardown': _post_teardown,
'__module__': test_func.__module__,
})
else: else:
@wraps(test_func) @wraps(test_func)
def inner(*args, **kwargs): def inner(*args, **kwargs):

View File

@ -1450,6 +1450,15 @@ The decorator can also be applied to test case classes::
LoginTestCase = override_settings(LOGIN_URL='/other/login/')(LoginTestCase) LoginTestCase = override_settings(LOGIN_URL='/other/login/')(LoginTestCase)
.. note::
When given a class, the decorator modifies the class directly and
returns it; it doesn't create and return a modified copy of it. So if
you try to tweak the above example to assign the return value to a
different name than ``LoginTestCase``, you may be surprised to find that
the original ``LoginTestCase`` is still equally affected by the
decorator.
On Python 2.6 and higher you can also use the well known decorator syntax to On Python 2.6 and higher you can also use the well known decorator syntax to
decorate the class:: decorate the class::

View File

@ -35,6 +35,38 @@ class FullyDecoratedTestCase(TestCase):
FullyDecoratedTestCase = override_settings(TEST='override')(FullyDecoratedTestCase) FullyDecoratedTestCase = override_settings(TEST='override')(FullyDecoratedTestCase)
class ClassDecoratedTestCaseSuper(TestCase):
"""
Dummy class for testing max recursion error in child class call to
super(). Refs #17011.
"""
def test_max_recursion_error(self):
pass
@override_settings(TEST='override')
class ClassDecoratedTestCase(ClassDecoratedTestCaseSuper):
def test_override(self):
self.assertEqual(settings.TEST, 'override')
@override_settings(TEST='override2')
def test_method_override(self):
self.assertEqual(settings.TEST, 'override2')
def test_max_recursion_error(self):
"""
Overriding a method on a super class and then calling that method on
the super class should not trigger infinite recursion. See #17011.
"""
try:
super(ClassDecoratedTestCase, self).test_max_recursion_error()
except RuntimeError, e:
self.fail()
class SettingGetter(object): class SettingGetter(object):
def __init__(self): def __init__(self):
self.test = getattr(settings, 'TEST', 'undefined') self.test = getattr(settings, 'TEST', 'undefined')