diff --git a/django/test/testcases.py b/django/test/testcases.py index 8fd84b1423d..be5720a80bb 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -1083,11 +1083,23 @@ class TestCase(TransactionTestCase): class CheckCondition(object): """Descriptor class for deferred condition checking""" - def __init__(self, cond_func): - self.cond_func = cond_func + def __init__(self, *conditions): + self.conditions = conditions + + def add_condition(self, condition, reason): + return self.__class__(*self.conditions + ((condition, reason),)) def __get__(self, instance, cls=None): - return self.cond_func() + # Trigger access for all bases. + if any(getattr(base, '__unittest_skip__', False) for base in cls.__bases__): + return True + for condition, reason in self.conditions: + if condition(): + # Override this descriptor's value and set the skip reason. + cls.__unittest_skip__ = True + cls.__unittest_skip_why__ = reason + return True + return False def _deferredSkip(condition, reason): @@ -1103,8 +1115,13 @@ def _deferredSkip(condition, reason): else: # Assume a class is decorated test_item = test_func - test_item.__unittest_skip__ = CheckCondition(condition) - test_item.__unittest_skip_why__ = reason + # Retrieve the possibly existing value from the class's dict to + # avoid triggering the descriptor. + skip = test_func.__dict__.get('__unittest_skip__') + if isinstance(skip, CheckCondition): + test_item.__unittest_skip__ = skip.add_condition(condition, reason) + elif skip is not True: + test_item.__unittest_skip__ = CheckCondition((condition, reason)) return test_item return decorator diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py index cd9b723a4e2..1fb70e2aa30 100644 --- a/tests/test_utils/tests.py +++ b/tests/test_utils/tests.py @@ -106,20 +106,28 @@ class SkippingClassTestCase(SimpleTestCase): def test_dummy(self): return + @skipUnlessDBFeature("missing") @skipIfDBFeature("__class__") class SkippedTests(unittest.TestCase): def test_will_be_skipped(self): self.fail("We should never arrive here.") + @skipIfDBFeature("__dict__") + class SkippedTestsSubclass(SkippedTests): + pass + test_suite = unittest.TestSuite() test_suite.addTest(NotSkippedTests('test_dummy')) try: test_suite.addTest(SkippedTests('test_will_be_skipped')) + test_suite.addTest(SkippedTestsSubclass('test_will_be_skipped')) except unittest.SkipTest: self.fail("SkipTest should not be raised at this stage") result = unittest.TextTestRunner(stream=six.StringIO()).run(test_suite) - self.assertEqual(result.testsRun, 2) - self.assertEqual(len(result.skipped), 1) + self.assertEqual(result.testsRun, 3) + self.assertEqual(len(result.skipped), 2) + self.assertEqual(result.skipped[0][1], 'Database has feature(s) __class__') + self.assertEqual(result.skipped[1][1], 'Database has feature(s) __class__') @override_settings(ROOT_URLCONF='test_utils.urls')