Commit eb973684 authored by Andrew Svetlov's avatar Andrew Svetlov

Issue #11798: TestSuite now drops references to own tests after execution.

parent 6a53af89
...@@ -1470,15 +1470,24 @@ Grouping tests ...@@ -1470,15 +1470,24 @@ Grouping tests
Tests grouped by a :class:`TestSuite` are always accessed by iteration. Tests grouped by a :class:`TestSuite` are always accessed by iteration.
Subclasses can lazily provide tests by overriding :meth:`__iter__`. Note Subclasses can lazily provide tests by overriding :meth:`__iter__`. Note
that this method maybe called several times on a single suite that this method may be called several times on a single suite (for
(for example when counting tests or comparing for equality) example when counting tests or comparing for equality) so the tests
so the tests returned must be the same for repeated iterations. 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 .. versionchanged:: 3.2
In earlier versions the :class:`TestSuite` accessed tests directly rather In earlier versions the :class:`TestSuite` accessed tests directly rather
than through iteration, so overriding :meth:`__iter__` wasn't sufficient than through iteration, so overriding :meth:`__iter__` wasn't sufficient
for providing tests. 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 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. is invoked by a :class:`TestRunner` rather than by the end-user test harness.
......
...@@ -57,12 +57,21 @@ class BaseTestSuite(object): ...@@ -57,12 +57,21 @@ class BaseTestSuite(object):
self.addTest(test) self.addTest(test)
def run(self, result): def run(self, result):
for test in self: for index, test in enumerate(self):
if result.shouldStop: if result.shouldStop:
break break
test(result) test(result)
self._removeTestAtIndex(index)
return result 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): def __call__(self, *args, **kwds):
return self.run(*args, **kwds) return self.run(*args, **kwds)
...@@ -87,7 +96,7 @@ class TestSuite(BaseTestSuite): ...@@ -87,7 +96,7 @@ class TestSuite(BaseTestSuite):
if getattr(result, '_testRunEntered', False) is False: if getattr(result, '_testRunEntered', False) is False:
result._testRunEntered = topLevel = True result._testRunEntered = topLevel = True
for test in self: for index, test in enumerate(self):
if result.shouldStop: if result.shouldStop:
break break
...@@ -106,6 +115,8 @@ class TestSuite(BaseTestSuite): ...@@ -106,6 +115,8 @@ class TestSuite(BaseTestSuite):
else: else:
test.debug() test.debug()
self._removeTestAtIndex(index)
if topLevel: if topLevel:
self._tearDownPreviousClass(None, result) self._tearDownPreviousClass(None, result)
self._handleModuleTearDown(result) self._handleModuleTearDown(result)
......
...@@ -494,12 +494,10 @@ class TestSetups(unittest.TestCase): ...@@ -494,12 +494,10 @@ class TestSetups(unittest.TestCase):
Test.__module__ = 'Module' Test.__module__ = 'Module'
sys.modules['Module'] = Module sys.modules['Module'] = Module
_suite = unittest.defaultTestLoader.loadTestsFromTestCase(Test)
suite = unittest.TestSuite()
suite.addTest(_suite)
messages = ('setUpModule', 'tearDownModule', 'setUpClass', 'tearDownClass', 'test_something') messages = ('setUpModule', 'tearDownModule', 'setUpClass', 'tearDownClass', 'test_something')
for phase, msg in enumerate(messages): for phase, msg in enumerate(messages):
_suite = unittest.defaultTestLoader.loadTestsFromTestCase(Test)
suite = unittest.TestSuite([_suite])
with self.assertRaisesRegex(Exception, msg): with self.assertRaisesRegex(Exception, msg):
suite.debug() suite.debug()
......
import unittest import unittest
import gc
import sys import sys
import weakref
from .support import LoggingResult, TestEquality from .support import LoggingResult, TestEquality
...@@ -300,7 +302,46 @@ class Test_TestSuite(unittest.TestCase, TestEquality): ...@@ -300,7 +302,46 @@ class Test_TestSuite(unittest.TestCase, TestEquality):
# when the bug is fixed this line will not crash # when the bug is fixed this line will not crash
suite.run(unittest.TestResult()) 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): def test_basetestsuite(self):
class Test(unittest.TestCase): class Test(unittest.TestCase):
...@@ -363,6 +404,5 @@ class Test_TestSuite(unittest.TestCase, TestEquality): ...@@ -363,6 +404,5 @@ class Test_TestSuite(unittest.TestCase, TestEquality):
self.assertFalse(result._testRunEntered) self.assertFalse(result._testRunEntered)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
...@@ -814,6 +814,7 @@ Daniel May ...@@ -814,6 +814,7 @@ Daniel May
Madison May Madison May
Lucas Maystre Lucas Maystre
Arnaud Mazin Arnaud Mazin
Matt McClure
Rebecca McCreary Rebecca McCreary
Kirk McDonald Kirk McDonald
Chris McDonough Chris McDonough
...@@ -1336,6 +1337,7 @@ Kevin Walzer ...@@ -1336,6 +1337,7 @@ Kevin Walzer
Rodrigo Steinmuller Wanderley Rodrigo Steinmuller Wanderley
Ke Wang Ke Wang
Greg Ward Greg Ward
Tom Wardill
Zachary Ware Zachary Ware
Jonas Wagner Jonas Wagner
Barry Warsaw Barry Warsaw
......
...@@ -51,6 +51,8 @@ Core and Builtins ...@@ -51,6 +51,8 @@ Core and Builtins
Library Library
------- -------
- Issue #11798: TestSuite now drops references to own tests after execution.
- Issue #16611: http.cookie now correctly parses the 'secure' and 'httponly' - Issue #16611: http.cookie now correctly parses the 'secure' and 'httponly'
cookie flags. cookie flags.
......
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