diff --git a/django/test/utils.py b/django/test/utils.py index 0ca7a1a25d0..87f23118971 100644 --- a/django/test/utils.py +++ b/django/test/utils.py @@ -196,23 +196,17 @@ class override_settings(object): def __call__(self, test_func): from django.test import TransactionTestCase if isinstance(test_func, type) and issubclass(test_func, TransactionTestCase): - # When decorating a class, we need to construct a new class - # with the same name so that the test discovery tools can - # get a useful name. + original_pre_setup = test_func._pre_setup + original_post_teardown = test_func._post_teardown def _pre_setup(innerself): self.enable() - test_func._pre_setup(innerself) + original_pre_setup(innerself) def _post_teardown(innerself): - test_func._post_teardown(innerself) + original_post_teardown(innerself) self.disable() - inner = type( - test_func.__name__, - (test_func,), - { - '_pre_setup': _pre_setup, - '_post_teardown': _post_teardown, - '__module__': test_func.__module__, - }) + test_func._pre_setup = _pre_setup + test_func._post_teardown = _post_teardown + return test_func else: @wraps(test_func) def inner(*args, **kwargs): diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index f4c06e5a53c..1a4515673a8 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -1450,6 +1450,15 @@ The decorator can also be applied to test case classes:: 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 decorate the class:: diff --git a/tests/regressiontests/settings_tests/tests.py b/tests/regressiontests/settings_tests/tests.py index 3f76368cea8..ff2334a95fe 100644 --- a/tests/regressiontests/settings_tests/tests.py +++ b/tests/regressiontests/settings_tests/tests.py @@ -35,6 +35,38 @@ class FullyDecoratedTestCase(TestCase): 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): def __init__(self): self.test = getattr(settings, 'TEST', 'undefined')