Skip to content

Commit d63114c

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. (cherry picked from commit a9640d7) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
1 parent 519bcc6 commit d63114c

File tree

3 files changed

+104
-53
lines changed

3 files changed

+104
-53
lines changed

Lib/unittest/case.py

Lines changed: 50 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -555,73 +555,71 @@ def _callCleanup(self, function, /, *args, **kwargs):
555555
function(*args, **kwargs)
556556

557557
def run(self, result=None):
558-
orig_result = result
559558
if result is None:
560559
result = self.defaultTestResult()
561560
startTestRun = getattr(result, 'startTestRun', None)
561+
stopTestRun = getattr(result, 'stopTestRun', None)
562562
if startTestRun is not None:
563563
startTestRun()
564+
else:
565+
stopTestRun = None
564566

565567
result.startTest(self)
566-
567-
testMethod = getattr(self, self._testMethodName)
568-
if (getattr(self.__class__, "__unittest_skip__", False) or
569-
getattr(testMethod, "__unittest_skip__", False)):
570-
# If the class or method was skipped.
571-
try:
568+
try:
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.
572573
skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
573574
or getattr(testMethod, '__unittest_skip_why__', ''))
574575
self._addSkip(result, self, skip_why)
575-
finally:
576-
result.stopTest(self)
577-
return
578-
expecting_failure_method = getattr(testMethod,
579-
"__unittest_expecting_failure__", False)
580-
expecting_failure_class = getattr(self,
581-
"__unittest_expecting_failure__", False)
582-
expecting_failure = expecting_failure_class or expecting_failure_method
583-
outcome = _Outcome(result)
584-
try:
585-
self._outcome = outcome
576+
return
577+
578+
expecting_failure = (
579+
getattr(self, "__unittest_expecting_failure__", False) or
580+
getattr(testMethod, "__unittest_expecting_failure__", False)
581+
)
582+
outcome = _Outcome(result)
583+
try:
584+
self._outcome = outcome
586585

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

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

Lib/unittest/test/test_skipping.py

Lines changed: 49 additions & 1 deletion
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:
Lines changed: 5 additions & 0 deletions
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)