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.
This commit is contained in:
Serhiy Storchaka 2021-08-22 10:33:52 +03:00 committed by GitHub
parent 64f9e7b19d
commit a9640d7553
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 103 additions and 52 deletions

View File

@ -557,31 +557,30 @@ class TestCase(object):
function(*args, **kwargs) function(*args, **kwargs)
def run(self, result=None): def run(self, result=None):
orig_result = result
if result is None: if result is None:
result = self.defaultTestResult() result = self.defaultTestResult()
startTestRun = getattr(result, 'startTestRun', None) startTestRun = getattr(result, 'startTestRun', None)
stopTestRun = getattr(result, 'stopTestRun', None)
if startTestRun is not None: if startTestRun is not None:
startTestRun() startTestRun()
else:
stopTestRun = None
result.startTest(self) result.startTest(self)
try:
testMethod = getattr(self, self._testMethodName) testMethod = getattr(self, self._testMethodName)
if (getattr(self.__class__, "__unittest_skip__", False) or if (getattr(self.__class__, "__unittest_skip__", False) or
getattr(testMethod, "__unittest_skip__", False)): getattr(testMethod, "__unittest_skip__", False)):
# If the class or method was skipped. # If the class or method was skipped.
try:
skip_why = (getattr(self.__class__, '__unittest_skip_why__', '') skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
or getattr(testMethod, '__unittest_skip_why__', '')) or getattr(testMethod, '__unittest_skip_why__', ''))
self._addSkip(result, self, skip_why) self._addSkip(result, self, skip_why)
finally:
result.stopTest(self)
return return
expecting_failure_method = getattr(testMethod,
"__unittest_expecting_failure__", False) expecting_failure = (
expecting_failure_class = getattr(self, getattr(self, "__unittest_expecting_failure__", False) or
"__unittest_expecting_failure__", False) getattr(testMethod, "__unittest_expecting_failure__", False)
expecting_failure = expecting_failure_class or expecting_failure_method )
outcome = _Outcome(result) outcome = _Outcome(result)
try: try:
self._outcome = outcome self._outcome = outcome
@ -610,12 +609,6 @@ class TestCase(object):
result.addSuccess(self) result.addSuccess(self)
return result return result
finally: finally:
result.stopTest(self)
if orig_result is None:
stopTestRun = getattr(result, 'stopTestRun', None)
if stopTestRun is not None:
stopTestRun()
# explicitly break reference cycles: # explicitly break reference cycles:
# outcome.errors -> frame -> outcome -> outcome.errors # outcome.errors -> frame -> outcome -> outcome.errors
# outcome.expectedFailure -> frame -> outcome -> outcome.expectedFailure # outcome.expectedFailure -> frame -> outcome -> outcome.expectedFailure
@ -625,6 +618,11 @@ class TestCase(object):
# clear the outcome, no more needed # clear the outcome, no more needed
self._outcome = None self._outcome = None
finally:
result.stopTest(self)
if stopTestRun is not None:
stopTestRun()
def doCleanups(self): def doCleanups(self):
"""Execute all cleanup functions. Normally called for you after """Execute all cleanup functions. Normally called for you after
tearDown.""" tearDown."""

View File

@ -7,6 +7,8 @@ class Test_TestSkipping(unittest.TestCase):
def test_skipping(self): def test_skipping(self):
class Foo(unittest.TestCase): class Foo(unittest.TestCase):
def defaultTestResult(self):
return LoggingResult(events)
def test_skip_me(self): def test_skip_me(self):
self.skipTest("skip") self.skipTest("skip")
events = [] events = []
@ -16,8 +18,15 @@ class Test_TestSkipping(unittest.TestCase):
self.assertEqual(events, ['startTest', 'addSkip', 'stopTest']) self.assertEqual(events, ['startTest', 'addSkip', 'stopTest'])
self.assertEqual(result.skipped, [(test, "skip")]) self.assertEqual(result.skipped, [(test, "skip")])
events = []
test.run()
self.assertEqual(events, ['startTestRun', 'startTest', 'addSkip',
'stopTest', 'stopTestRun'])
# Try letting setUp skip the test now. # Try letting setUp skip the test now.
class Foo(unittest.TestCase): class Foo(unittest.TestCase):
def defaultTestResult(self):
return LoggingResult(events)
def setUp(self): def setUp(self):
self.skipTest("testing") self.skipTest("testing")
def test_nothing(self): pass def test_nothing(self): pass
@ -29,8 +38,15 @@ class Test_TestSkipping(unittest.TestCase):
self.assertEqual(result.skipped, [(test, "testing")]) self.assertEqual(result.skipped, [(test, "testing")])
self.assertEqual(result.testsRun, 1) self.assertEqual(result.testsRun, 1)
events = []
test.run()
self.assertEqual(events, ['startTestRun', 'startTest', 'addSkip',
'stopTest', 'stopTestRun'])
def test_skipping_subtests(self): def test_skipping_subtests(self):
class Foo(unittest.TestCase): class Foo(unittest.TestCase):
def defaultTestResult(self):
return LoggingResult(events)
def test_skip_me(self): def test_skip_me(self):
with self.subTest(a=1): with self.subTest(a=1):
with self.subTest(b=2): with self.subTest(b=2):
@ -54,11 +70,20 @@ class Test_TestSkipping(unittest.TestCase):
self.assertIsNot(subtest, test) self.assertIsNot(subtest, test)
self.assertEqual(result.skipped[2], (test, "skip 3")) self.assertEqual(result.skipped[2], (test, "skip 3"))
events = []
test.run()
self.assertEqual(events,
['startTestRun', 'startTest', 'addSkip', 'addSkip',
'addSkip', 'stopTest', 'stopTestRun'])
def test_skipping_decorators(self): def test_skipping_decorators(self):
op_table = ((unittest.skipUnless, False, True), op_table = ((unittest.skipUnless, False, True),
(unittest.skipIf, True, False)) (unittest.skipIf, True, False))
for deco, do_skip, dont_skip in op_table: for deco, do_skip, dont_skip in op_table:
class Foo(unittest.TestCase): class Foo(unittest.TestCase):
def defaultTestResult(self):
return LoggingResult(events)
@deco(do_skip, "testing") @deco(do_skip, "testing")
def test_skip(self): pass def test_skip(self): pass
@ -66,6 +91,7 @@ class Test_TestSkipping(unittest.TestCase):
def test_dont_skip(self): pass def test_dont_skip(self): pass
test_do_skip = Foo("test_skip") test_do_skip = Foo("test_skip")
test_dont_skip = Foo("test_dont_skip") test_dont_skip = Foo("test_dont_skip")
suite = unittest.TestSuite([test_do_skip, test_dont_skip]) suite = unittest.TestSuite([test_do_skip, test_dont_skip])
events = [] events = []
result = LoggingResult(events) result = LoggingResult(events)
@ -78,19 +104,41 @@ class Test_TestSkipping(unittest.TestCase):
self.assertEqual(result.skipped, [(test_do_skip, "testing")]) self.assertEqual(result.skipped, [(test_do_skip, "testing")])
self.assertTrue(result.wasSuccessful()) self.assertTrue(result.wasSuccessful())
events = []
test_do_skip.run()
self.assertEqual(len(result.skipped), 1)
self.assertEqual(events, ['startTestRun', 'startTest', 'addSkip',
'stopTest', 'stopTestRun'])
events = []
test_dont_skip.run()
self.assertEqual(len(result.skipped), 1)
self.assertEqual(events, ['startTestRun', 'startTest', 'addSuccess',
'stopTest', 'stopTestRun'])
def test_skip_class(self): def test_skip_class(self):
@unittest.skip("testing") @unittest.skip("testing")
class Foo(unittest.TestCase): class Foo(unittest.TestCase):
def defaultTestResult(self):
return LoggingResult(events)
def test_1(self): def test_1(self):
record.append(1) record.append(1)
events = []
record = [] record = []
result = unittest.TestResult() result = LoggingResult(events)
test = Foo("test_1") test = Foo("test_1")
suite = unittest.TestSuite([test]) suite = unittest.TestSuite([test])
suite.run(result) suite.run(result)
self.assertEqual(events, ['startTest', 'addSkip', 'stopTest'])
self.assertEqual(result.skipped, [(test, "testing")]) self.assertEqual(result.skipped, [(test, "testing")])
self.assertEqual(record, []) self.assertEqual(record, [])
events = []
test.run()
self.assertEqual(events, ['startTestRun', 'startTest', 'addSkip',
'stopTest', 'stopTestRun'])
self.assertEqual(record, [])
def test_skip_non_unittest_class(self): def test_skip_non_unittest_class(self):
@unittest.skip("testing") @unittest.skip("testing")
class Mixin: class Mixin:

View File

@ -0,0 +1,5 @@
Method :meth:`~unittest.TestResult.stopTestRun` is now always called in pair
with method :meth:`~unittest.TestResult.startTestRun` for
:class:`~unittest.TestResult` objects implicitly created in
:meth:`~unittest.TestCase.run`. Previously it was not called for test
methods and classes decorated with a skipping decorator.