diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index 8341b885af7..b609cafe793 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -380,6 +380,9 @@ class TestCase(object): self.setUp() getattr(self, self._testMethodName)() self.tearDown() + while self._cleanups: + function, args, kwargs = self._cleanups.pop(-1) + function(*args, **kwargs) def skipTest(self, reason): """Skip this test.""" diff --git a/Lib/unittest/suite.py b/Lib/unittest/suite.py index bed9044da4c..04d35b883b3 100644 --- a/Lib/unittest/suite.py +++ b/Lib/unittest/suite.py @@ -87,9 +87,16 @@ class TestSuite(BaseTestSuite): self._handleModuleTearDown(result) return result + def debug(self): + """Run the tests without collecting errors in a TestResult""" + debug = _DebugResult() + self._wrapped_run(debug, True) + self._tearDownPreviousClass(None, debug) + self._handleModuleTearDown(debug) + ################################ # private methods - def _wrapped_run(self, result): + def _wrapped_run(self, result, debug=False): for test in self: if result.shouldStop: break @@ -106,8 +113,10 @@ class TestSuite(BaseTestSuite): if hasattr(test, '_wrapped_run'): test._wrapped_run(result) - else: + elif not debug: test(result) + else: + test.debug() def _handleClassSetUp(self, test, result): previousClass = getattr(result, '_previousTestClass', None) @@ -131,6 +140,8 @@ class TestSuite(BaseTestSuite): try: setUpClass() except Exception as e: + if isinstance(result, _DebugResult): + raise currentClass._classSetupFailed = True className = util.strclass(currentClass) errorName = 'setUpClass (%s)' % className @@ -163,6 +174,8 @@ class TestSuite(BaseTestSuite): try: setUpModule() except Exception, e: + if isinstance(result, _DebugResult): + raise result._moduleSetUpFailed = True errorName = 'setUpModule (%s)' % currentModule self._addClassOrModuleLevelException(result, e, errorName) @@ -192,6 +205,8 @@ class TestSuite(BaseTestSuite): try: tearDownModule() except Exception as e: + if isinstance(result, _DebugResult): + raise errorName = 'tearDownModule (%s)' % previousModule self._addClassOrModuleLevelException(result, e, errorName) @@ -212,6 +227,8 @@ class TestSuite(BaseTestSuite): try: tearDownClass() except Exception, e: + if isinstance(result, _DebugResult): + raise className = util.strclass(previousClass) errorName = 'tearDownClass (%s)' % className self._addClassOrModuleLevelException(result, e, errorName) @@ -262,3 +279,10 @@ def _isnotsuite(test): except TypeError: return True return False + + +class _DebugResult(object): + "Used by the TestSuite to hold previous class when running in debug." + _previousTestClass = None + _moduleSetUpFailed = False + shouldStop = False diff --git a/Lib/unittest/test/test_runner.py b/Lib/unittest/test/test_runner.py index 7e606cad467..6df6a9ca4e3 100644 --- a/Lib/unittest/test/test_runner.py +++ b/Lib/unittest/test/test_runner.py @@ -111,6 +111,31 @@ class TestCleanUp(unittest.TestCase): test.run(result) self.assertEqual(ordering, ['setUp', 'cleanup1']) + def testTestCaseDebugExecutesCleanups(self): + ordering = [] + + class TestableTest(unittest.TestCase): + def setUp(self): + ordering.append('setUp') + self.addCleanup(cleanup1) + + def testNothing(self): + ordering.append('test') + + def tearDown(self): + ordering.append('tearDown') + + test = TestableTest('testNothing') + + def cleanup1(): + ordering.append('cleanup1') + test.addCleanup(cleanup2) + def cleanup2(): + ordering.append('cleanup2') + + test.debug() + self.assertEqual(ordering, ['setUp', 'test', 'tearDown', 'cleanup1', 'cleanup2']) + class Test_TextTestRunner(unittest.TestCase): """Tests for TextTestRunner.""" diff --git a/Lib/unittest/test/test_setups.py b/Lib/unittest/test/test_setups.py index aff7ab28956..a9e1ae98b4e 100644 --- a/Lib/unittest/test/test_setups.py +++ b/Lib/unittest/test/test_setups.py @@ -439,6 +439,68 @@ class TestSetups(unittest.TestCase): skipped = result.skipped[0][0] self.assertEqual(str(skipped), 'setUpModule (Module)') + def test_suite_debug_executes_setups_and_teardowns(self): + ordering = [] + + class Module(object): + @staticmethod + def setUpModule(): + ordering.append('setUpModule') + @staticmethod + def tearDownModule(): + ordering.append('tearDownModule') + + class Test(unittest.TestCase): + @classmethod + def setUpClass(cls): + ordering.append('setUpClass') + @classmethod + def tearDownClass(cls): + ordering.append('tearDownClass') + def test_something(self): + ordering.append('test_something') + + Test.__module__ = 'Module' + sys.modules['Module'] = Module + + suite = unittest.defaultTestLoader.loadTestsFromTestCase(Test) + suite.debug() + expectedOrder = ['setUpModule', 'setUpClass', 'test_something', 'tearDownClass', 'tearDownModule'] + self.assertEqual(ordering, expectedOrder) + + def test_suite_debug_propagates_exceptions(self): + class Module(object): + @staticmethod + def setUpModule(): + if phase == 0: + raise Exception('setUpModule') + @staticmethod + def tearDownModule(): + if phase == 1: + raise Exception('tearDownModule') + + class Test(unittest.TestCase): + @classmethod + def setUpClass(cls): + if phase == 2: + raise Exception('setUpClass') + @classmethod + def tearDownClass(cls): + if phase == 3: + raise Exception('tearDownClass') + def test_something(self): + if phase == 4: + raise Exception('test_something') + + Test.__module__ = 'Module' + sys.modules['Module'] = Module + + suite = unittest.defaultTestLoader.loadTestsFromTestCase(Test) + + messages = ('setUpModule', 'tearDownModule', 'setUpClass', 'tearDownClass', 'test_something') + for phase, msg in enumerate(messages): + with self.assertRaisesRegexp(Exception, msg): + suite.debug() if __name__ == '__main__': unittest.main()