Skip to content

Commit a9640d7

Browse files
bpo-44955: Always call stopTestRun() for implicitly created TestResult objects (GH-27831)
Method stopTestRun() is now always called in pair with method startTestRun() for TestResult objects implicitly created in TestCase.run(). Previously it was not called for test methods and classes decorated with a skipping decorator.
1 parent 64f9e7b commit a9640d7

File tree

3 files changed

+104
-53
lines changed

3 files changed

+104
-53
lines changed

Lib/unittest/case.py

+50-52
Original file line numberDiff line numberDiff line change
@@ -557,73 +557,71 @@ def _callCleanup(self, function, /, *args, **kwargs):
557557
function(*args, **kwargs)
558558

559559
def run(self, result=None):
560-
orig_result = result
561560
if result is None:
562561
result = self.defaultTestResult()
563562
startTestRun = getattr(result, 'startTestRun', None)
563+
stopTestRun = getattr(result, 'stopTestRun', None)
564564
if startTestRun is not None:
565565
startTestRun()
566+
else:
567+
stopTestRun = None
566568

567569
result.startTest(self)
568-
569-
testMethod = getattr(self, self._testMethodName)
570-
if (getattr(self.__class__, "__unittest_skip__", False) or
571-
getattr(testMethod, "__unittest_skip__", False)):
572-
# If the class or method was skipped.
573-
try:
570+
try:
571+
testMethod = getattr(self, self._testMethodName)
572+
if (getattr(self.__class__, "__unittest_skip__", False) or
573+
getattr(testMethod, "__unittest_skip__", False)):
574+
# If the class or method was skipped.
574575
skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
575576
or getattr(testMethod, '__unittest_skip_why__', ''))
576577
self._addSkip(result, self, skip_why)
577-
finally:
578-
result.stopTest(self)
579-
return
580-
expecting_failure_method = getattr(testMethod,
581-
"__unittest_expecting_failure__", False)
582-
expecting_failure_class = getattr(self,
583-
"__unittest_expecting_failure__", False)
584-
expecting_failure = expecting_failure_class or expecting_failure_method
585-
outcome = _Outcome(result)
586-
try:
587-
self._outcome = outcome
578+
return
579+
580+
expecting_failure = (
581+
getattr(self, "__unittest_expecting_failure__", False) or
582+
getattr(testMethod, "__unittest_expecting_failure__", False)
583+
)
584+
outcome = _Outcome(result)
585+
try:
586+
self._outcome = outcome
588587

589-
with outcome.testPartExecutor(self):
590-
self._callSetUp()
591-
if outcome.success:
592-
outcome.expecting_failure = expecting_failure
593-
with outcome.testPartExecutor(self, isTest=True):
594-
self._callTestMethod(testMethod)
595-
outcome.expecting_failure = False
596588
with outcome.testPartExecutor(self):
597-
self._callTearDown()
598-
599-
self.doCleanups()
600-
for test, reason in outcome.skipped:
601-
self._addSkip(result, test, reason)
602-
self._feedErrorsToResult(result, outcome.errors)
603-
if outcome.success:
604-
if expecting_failure:
605-
if outcome.expectedFailure:
606-
self._addExpectedFailure(result, outcome.expectedFailure)
589+
self._callSetUp()
590+
if outcome.success:
591+
outcome.expecting_failure = expecting_failure
592+
with outcome.testPartExecutor(self, isTest=True):
593+
self._callTestMethod(testMethod)
594+
outcome.expecting_failure = False
595+
with outcome.testPartExecutor(self):
596+
self._callTearDown()
597+
598+
self.doCleanups()
599+
for test, reason in outcome.skipped:
600+
self._addSkip(result, test, reason)
601+
self._feedErrorsToResult(result, outcome.errors)
602+
if outcome.success:
603+
if expecting_failure:
604+
if outcome.expectedFailure:
605+
self._addExpectedFailure(result, outcome.expectedFailure)
606+
else:
607+
self._addUnexpectedSuccess(result)
607608
else:
608-
self._addUnexpectedSuccess(result)
609-
else:
610-
result.addSuccess(self)
611-
return result
609+
result.addSuccess(self)
610+
return result
611+
finally:
612+
# explicitly break reference cycles:
613+
# outcome.errors -> frame -> outcome -> outcome.errors
614+
# outcome.expectedFailure -> frame -> outcome -> outcome.expectedFailure
615+
outcome.errors.clear()
616+
outcome.expectedFailure = None
617+
618+
# clear the outcome, no more needed
619+
self._outcome = None
620+
612621
finally:
613622
result.stopTest(self)
614-
if orig_result is None:
615-
stopTestRun = getattr(result, 'stopTestRun', None)
616-
if stopTestRun is not None:
617-
stopTestRun()
618-
619-
# explicitly break reference cycles:
620-
# outcome.errors -> frame -> outcome -> outcome.errors
621-
# outcome.expectedFailure -> frame -> outcome -> outcome.expectedFailure
622-
outcome.errors.clear()
623-
outcome.expectedFailure = None
624-
625-
# clear the outcome, no more needed
626-
self._outcome = None
623+
if stopTestRun is not None:
624+
stopTestRun()
627625

628626
def doCleanups(self):
629627
"""Execute all cleanup functions. Normally called for you after

Lib/unittest/test/test_skipping.py

+49-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ class Test_TestSkipping(unittest.TestCase):
77

88
def test_skipping(self):
99
class Foo(unittest.TestCase):
10+
def defaultTestResult(self):
11+
return LoggingResult(events)
1012
def test_skip_me(self):
1113
self.skipTest("skip")
1214
events = []
@@ -16,8 +18,15 @@ def test_skip_me(self):
1618
self.assertEqual(events, ['startTest', 'addSkip', 'stopTest'])
1719
self.assertEqual(result.skipped, [(test, "skip")])
1820

21+
events = []
22+
test.run()
23+
self.assertEqual(events, ['startTestRun', 'startTest', 'addSkip',
24+
'stopTest', 'stopTestRun'])
25+
1926
# Try letting setUp skip the test now.
2027
class Foo(unittest.TestCase):
28+
def defaultTestResult(self):
29+
return LoggingResult(events)
2130
def setUp(self):
2231
self.skipTest("testing")
2332
def test_nothing(self): pass
@@ -29,8 +38,15 @@ def test_nothing(self): pass
2938
self.assertEqual(result.skipped, [(test, "testing")])
3039
self.assertEqual(result.testsRun, 1)
3140

41+
events = []
42+
test.run()
43+
self.assertEqual(events, ['startTestRun', 'startTest', 'addSkip',
44+
'stopTest', 'stopTestRun'])
45+
3246
def test_skipping_subtests(self):
3347
class Foo(unittest.TestCase):
48+
def defaultTestResult(self):
49+
return LoggingResult(events)
3450
def test_skip_me(self):
3551
with self.subTest(a=1):
3652
with self.subTest(b=2):
@@ -54,18 +70,28 @@ def test_skip_me(self):
5470
self.assertIsNot(subtest, test)
5571
self.assertEqual(result.skipped[2], (test, "skip 3"))
5672

73+
events = []
74+
test.run()
75+
self.assertEqual(events,
76+
['startTestRun', 'startTest', 'addSkip', 'addSkip',
77+
'addSkip', 'stopTest', 'stopTestRun'])
78+
5779
def test_skipping_decorators(self):
5880
op_table = ((unittest.skipUnless, False, True),
5981
(unittest.skipIf, True, False))
6082
for deco, do_skip, dont_skip in op_table:
6183
class Foo(unittest.TestCase):
84+
def defaultTestResult(self):
85+
return LoggingResult(events)
86+
6287
@deco(do_skip, "testing")
6388
def test_skip(self): pass
6489

6590
@deco(dont_skip, "testing")
6691
def test_dont_skip(self): pass
6792
test_do_skip = Foo("test_skip")
6893
test_dont_skip = Foo("test_dont_skip")
94+
6995
suite = unittest.TestSuite([test_do_skip, test_dont_skip])
7096
events = []
7197
result = LoggingResult(events)
@@ -78,19 +104,41 @@ def test_dont_skip(self): pass
78104
self.assertEqual(result.skipped, [(test_do_skip, "testing")])
79105
self.assertTrue(result.wasSuccessful())
80106

107+
events = []
108+
test_do_skip.run()
109+
self.assertEqual(len(result.skipped), 1)
110+
self.assertEqual(events, ['startTestRun', 'startTest', 'addSkip',
111+
'stopTest', 'stopTestRun'])
112+
113+
events = []
114+
test_dont_skip.run()
115+
self.assertEqual(len(result.skipped), 1)
116+
self.assertEqual(events, ['startTestRun', 'startTest', 'addSuccess',
117+
'stopTest', 'stopTestRun'])
118+
81119
def test_skip_class(self):
82120
@unittest.skip("testing")
83121
class Foo(unittest.TestCase):
122+
def defaultTestResult(self):
123+
return LoggingResult(events)
84124
def test_1(self):
85125
record.append(1)
126+
events = []
86127
record = []
87-
result = unittest.TestResult()
128+
result = LoggingResult(events)
88129
test = Foo("test_1")
89130
suite = unittest.TestSuite([test])
90131
suite.run(result)
132+
self.assertEqual(events, ['startTest', 'addSkip', 'stopTest'])
91133
self.assertEqual(result.skipped, [(test, "testing")])
92134
self.assertEqual(record, [])
93135

136+
events = []
137+
test.run()
138+
self.assertEqual(events, ['startTestRun', 'startTest', 'addSkip',
139+
'stopTest', 'stopTestRun'])
140+
self.assertEqual(record, [])
141+
94142
def test_skip_non_unittest_class(self):
95143
@unittest.skip("testing")
96144
class Mixin:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Method :meth:`~unittest.TestResult.stopTestRun` is now always called in pair
2+
with method :meth:`~unittest.TestResult.startTestRun` for
3+
:class:`~unittest.TestResult` objects implicitly created in
4+
:meth:`~unittest.TestCase.run`. Previously it was not called for test
5+
methods and classes decorated with a skipping decorator.

0 commit comments

Comments
 (0)