summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/unittest.rst15
-rw-r--r--Lib/unittest/suite.py15
-rw-r--r--Lib/unittest/test/test_setups.py6
-rw-r--r--Lib/unittest/test/test_suite.py42
-rw-r--r--Misc/ACKS2
-rw-r--r--Misc/NEWS2
6 files changed, 72 insertions, 10 deletions
diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst
index 66e427a..412bee7 100644
--- a/Doc/library/unittest.rst
+++ b/Doc/library/unittest.rst
@@ -1470,15 +1470,24 @@ Grouping tests
Tests grouped by a :class:`TestSuite` are always accessed by iteration.
Subclasses can lazily provide tests by overriding :meth:`__iter__`. Note
- that this method maybe called several times on a single suite
- (for example when counting tests or comparing for equality)
- so the tests returned must be the same for repeated iterations.
+ that this method may be called several times on a single suite (for
+ example when counting tests or comparing for equality) so the tests
+ returned by repeated iterations before :meth:`TestSuite.run` must be the
+ same for each call iteration. After :meth:`TestSuite.run`, callers should
+ not rely on the tests returned by this method unless the caller uses a
+ subclass that overrides :meth:`TestSuite._removeTestAtIndex` to preserve
+ test references.
.. versionchanged:: 3.2
In earlier versions the :class:`TestSuite` accessed tests directly rather
than through iteration, so overriding :meth:`__iter__` wasn't sufficient
for providing tests.
+ .. versionchanged:: 3.4
+ In earlier versions the :class:`TestSuite` held references to each
+ :class:`TestCase` after :meth:`TestSuite.run`. Subclasses can restore
+ that behavior by overriding :meth:`TestSuite._removeTestAtIndex`.
+
In the typical usage of a :class:`TestSuite` object, the :meth:`run` method
is invoked by a :class:`TestRunner` rather than by the end-user test harness.
diff --git a/Lib/unittest/suite.py b/Lib/unittest/suite.py
index cde5d38..176af57 100644
--- a/Lib/unittest/suite.py
+++ b/Lib/unittest/suite.py
@@ -57,12 +57,21 @@ class BaseTestSuite(object):
self.addTest(test)
def run(self, result):
- for test in self:
+ for index, test in enumerate(self):
if result.shouldStop:
break
test(result)
+ self._removeTestAtIndex(index)
return result
+ def _removeTestAtIndex(self, index):
+ """Stop holding a reference to the TestCase at index."""
+ try:
+ self._tests[index] = None
+ except TypeError:
+ # support for suite implementations that have overriden self._test
+ pass
+
def __call__(self, *args, **kwds):
return self.run(*args, **kwds)
@@ -87,7 +96,7 @@ class TestSuite(BaseTestSuite):
if getattr(result, '_testRunEntered', False) is False:
result._testRunEntered = topLevel = True
- for test in self:
+ for index, test in enumerate(self):
if result.shouldStop:
break
@@ -106,6 +115,8 @@ class TestSuite(BaseTestSuite):
else:
test.debug()
+ self._removeTestAtIndex(index)
+
if topLevel:
self._tearDownPreviousClass(None, result)
self._handleModuleTearDown(result)
diff --git a/Lib/unittest/test/test_setups.py b/Lib/unittest/test/test_setups.py
index b8d5aa4..bcd69a8 100644
--- a/Lib/unittest/test/test_setups.py
+++ b/Lib/unittest/test/test_setups.py
@@ -494,12 +494,10 @@ class TestSetups(unittest.TestCase):
Test.__module__ = 'Module'
sys.modules['Module'] = Module
- _suite = unittest.defaultTestLoader.loadTestsFromTestCase(Test)
- suite = unittest.TestSuite()
- suite.addTest(_suite)
-
messages = ('setUpModule', 'tearDownModule', 'setUpClass', 'tearDownClass', 'test_something')
for phase, msg in enumerate(messages):
+ _suite = unittest.defaultTestLoader.loadTestsFromTestCase(Test)
+ suite = unittest.TestSuite([_suite])
with self.assertRaisesRegex(Exception, msg):
suite.debug()
diff --git a/Lib/unittest/test/test_suite.py b/Lib/unittest/test/test_suite.py
index 2db978d..1ad9b56 100644
--- a/Lib/unittest/test/test_suite.py
+++ b/Lib/unittest/test/test_suite.py
@@ -1,6 +1,8 @@
import unittest
+import gc
import sys
+import weakref
from .support import LoggingResult, TestEquality
@@ -300,7 +302,46 @@ class Test_TestSuite(unittest.TestCase, TestEquality):
# when the bug is fixed this line will not crash
suite.run(unittest.TestResult())
+ def test_remove_test_at_index(self):
+ suite = unittest.TestSuite()
+
+ suite._tests = [1, 2, 3]
+ suite._removeTestAtIndex(1)
+
+ self.assertEqual([1, None, 3], suite._tests)
+
+ def test_remove_test_at_index_not_indexable(self):
+ suite = unittest.TestSuite()
+ suite._tests = None
+
+ # if _removeAtIndex raises for noniterables this next line will break
+ suite._removeTestAtIndex(2)
+
+ def assert_garbage_collect_test_after_run(self, TestSuiteClass):
+
+ class Foo(unittest.TestCase):
+ def test_nothing(self):
+ pass
+
+ test = Foo('test_nothing')
+ wref = weakref.ref(test)
+
+ suite = TestSuiteClass([wref()])
+ suite.run(unittest.TestResult())
+
+ del test
+ # for the benefit of non-reference counting implementations
+ gc.collect()
+
+ self.assertEqual(suite._tests, [None])
+ self.assertIsNone(wref())
+
+ def test_garbage_collect_test_after_run_BaseTestSuite(self):
+ self.assert_garbage_collect_test_after_run(unittest.BaseTestSuite)
+
+ def test_garbage_collect_test_after_run_TestSuite(self):
+ self.assert_garbage_collect_test_after_run(unittest.TestSuite)
def test_basetestsuite(self):
class Test(unittest.TestCase):
@@ -363,6 +404,5 @@ class Test_TestSuite(unittest.TestCase, TestEquality):
self.assertFalse(result._testRunEntered)
-
if __name__ == '__main__':
unittest.main()
diff --git a/Misc/ACKS b/Misc/ACKS
index e7edc25..1eaa897 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -814,6 +814,7 @@ Daniel May
Madison May
Lucas Maystre
Arnaud Mazin
+Matt McClure
Rebecca McCreary
Kirk McDonald
Chris McDonough
@@ -1336,6 +1337,7 @@ Kevin Walzer
Rodrigo Steinmuller Wanderley
Ke Wang
Greg Ward
+Tom Wardill
Zachary Ware
Jonas Wagner
Barry Warsaw
diff --git a/Misc/NEWS b/Misc/NEWS
index 8c59c09..3625a38 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -51,6 +51,8 @@ Core and Builtins
Library
-------
+- Issue #11798: TestSuite now drops references to own tests after execution.
+
- Issue #16611: http.cookie now correctly parses the 'secure' and 'httponly'
cookie flags.