]> granicus.if.org Git - python/commitdiff
Issue #11798: TestSuite now drops references to own tests after execution.
authorAndrew Svetlov <andrew.svetlov@gmail.com>
Wed, 28 Aug 2013 18:28:38 +0000 (21:28 +0300)
committerAndrew Svetlov <andrew.svetlov@gmail.com>
Wed, 28 Aug 2013 18:28:38 +0000 (21:28 +0300)
Doc/library/unittest.rst
Lib/unittest/suite.py
Lib/unittest/test/test_setups.py
Lib/unittest/test/test_suite.py
Misc/ACKS
Misc/NEWS

index 66e427a97b819ad07dc4e7e4aa90f80a3885e614..412bee78840042300fc467fd744b13c6b70a14a9 100644 (file)
@@ -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.
 
index cde5d385ed1f290f0c3cf040d12f3bd974898e18..176af57fb0bdf396d6c35768a383cf214c77c010 100644 (file)
@@ -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)
index b8d5aa41e947f19136853d783a4d853b640a40df..bcd69a8585c8f09d71d5e83b75be0f13fbd610c2 100644 (file)
@@ -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()
 
index 2db978ddb8a9a21a1f6a9c2f4a3e46e3a10755c9..1ad9b56cd68bb080152e1a67520760fffeeb3410 100644 (file)
@@ -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()
index e7edc25c2d7afb6c5cc71681840bead314ce5a24..1eaa897a359b52780385709f398112e24b616c3e 100644 (file)
--- 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
index 8c59c091fd9a95537c2dbeaff21d90ed7cf7535a..3625a38804782555dbeb68a1fd9e568d88a82ad7 100644 (file)
--- 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.