Close #19880: Fix a reference leak in unittest.TestCase. Explicitly break
reference cycles between frames and the _Outcome instance.
diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py
index 7ed932f..87fb02b 100644
--- a/Lib/unittest/case.py
+++ b/Lib/unittest/case.py
@@ -69,6 +69,9 @@
else:
self.success = False
self.errors.append((test_case, exc_info))
+ # explicitly break a reference cycle:
+ # exc_info -> frame -> exc_info
+ exc_info = None
else:
if self.result_supports_subtests and self.success:
self.errors.append((test_case, None))
@@ -559,8 +562,8 @@
return
expecting_failure = getattr(testMethod,
"__unittest_expecting_failure__", False)
+ outcome = _Outcome(result)
try:
- outcome = _Outcome(result)
self._outcome = outcome
with outcome.testPartExecutor(self):
@@ -593,6 +596,15 @@
if stopTestRun is not None:
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):
"""Execute all cleanup functions. Normally called for you after
tearDown."""
diff --git a/Lib/unittest/test/test_case.py b/Lib/unittest/test/test_case.py
index 4b93179..658d23d 100644
--- a/Lib/unittest/test/test_case.py
+++ b/Lib/unittest/test/test_case.py
@@ -1533,6 +1533,32 @@
del case
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__":
unittest.main()
diff --git a/Misc/NEWS b/Misc/NEWS
index 351106f..2cefcb6 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -21,6 +21,9 @@
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
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