Kaydet (Commit) 031bd532 authored tarafından Victor Stinner's avatar Victor Stinner

Close #19880: Fix a reference leak in unittest.TestCase. Explicitly break

reference cycles between frames and the _Outcome instance.
üst 28dd6dec
...@@ -69,6 +69,9 @@ class _Outcome(object): ...@@ -69,6 +69,9 @@ class _Outcome(object):
else: else:
self.success = False self.success = False
self.errors.append((test_case, exc_info)) self.errors.append((test_case, exc_info))
# explicitly break a reference cycle:
# exc_info -> frame -> exc_info
exc_info = None
else: else:
if self.result_supports_subtests and self.success: if self.result_supports_subtests and self.success:
self.errors.append((test_case, None)) self.errors.append((test_case, None))
...@@ -559,8 +562,8 @@ class TestCase(object): ...@@ -559,8 +562,8 @@ class TestCase(object):
return return
expecting_failure = getattr(testMethod, expecting_failure = getattr(testMethod,
"__unittest_expecting_failure__", False) "__unittest_expecting_failure__", False)
try:
outcome = _Outcome(result) outcome = _Outcome(result)
try:
self._outcome = outcome self._outcome = outcome
with outcome.testPartExecutor(self): with outcome.testPartExecutor(self):
...@@ -593,6 +596,15 @@ class TestCase(object): ...@@ -593,6 +596,15 @@ class TestCase(object):
if stopTestRun is not None: if stopTestRun is not None:
stopTestRun() stopTestRun()
# explicitly break reference cycles:
# outcome.errors -> frame -> outcome -> outcome.errors
# outcome.expectedFailure -> frame -> outcome -> outcome.expectedFailure
outcome.errors.clear()
outcome.expectedFailure = None
# clear the outcome, no more needed
self._outcome = None
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."""
......
...@@ -1533,6 +1533,32 @@ test case ...@@ -1533,6 +1533,32 @@ test case
del case del case
self.assertFalse(wr()) self.assertFalse(wr())
def test_no_exception_leak(self):
# Issue #19880: TestCase.run() should not keep a reference
# to the exception
class MyException(Exception):
ninstance = 0
def __init__(self):
MyException.ninstance += 1
Exception.__init__(self)
def __del__(self):
MyException.ninstance -= 1
class TestCase(unittest.TestCase):
def test1(self):
raise MyException()
@unittest.expectedFailure
def test2(self):
raise MyException()
for method_name in ('test1', 'test2'):
testcase = TestCase(method_name)
testcase.run()
self.assertEqual(MyException.ninstance, 0)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
...@@ -21,6 +21,9 @@ Core and Builtins ...@@ -21,6 +21,9 @@ Core and Builtins
Library Library
------- -------
- Issue #19880: Fix a reference leak in unittest.TestCase. Explicitly break
reference cycles between frames and the _Outcome instance.
- Issue #17429: platform.linux_distribution() now decodes files from the UTF-8 - Issue #17429: platform.linux_distribution() now decodes files from the UTF-8
encoding with the surrogateescape error handler, instead of decoding from the encoding with the surrogateescape error handler, instead of decoding from the
locale encoding in strict mode. It fixes the function on Fedora 19 which is locale encoding in strict mode. It fixes the function on Fedora 19 which is
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment