]> granicus.if.org Git - python/commitdiff
Fix ref cycles in TestCase.assertRaises() (#193) (#2228)
authorVictor Stinner <victor.stinner@gmail.com>
Thu, 15 Jun 2017 22:51:24 +0000 (00:51 +0200)
committerGitHub <noreply@github.com>
Thu, 15 Jun 2017 22:51:24 +0000 (00:51 +0200)
bpo-23890: unittest.TestCase.assertRaises() now manually breaks a
reference cycle to not keep objects alive longer than expected.
(cherry picked from commit bbd3cf8f1ef1e91a8d6dac6411e18b4b9084abf5)

Lib/unittest/case.py
Lib/unittest/test/test_case.py
Misc/NEWS

index b523f73ff2278436b1cdcfe8a494a6e105687148..f4dbc52852dc1356f7b69254ad36deb8ea72f2b0 100644 (file)
@@ -153,28 +153,32 @@ class _AssertRaisesBaseContext(_BaseTestCaseContext):
         If args is not empty, call a callable passing positional and keyword
         arguments.
         """
-        if not _is_subtype(self.expected, self._base_type):
-            raise TypeError('%s() arg 1 must be %s' %
-                            (name, self._base_type_str))
-        if args and args[0] is None:
-            warnings.warn("callable is None",
-                          DeprecationWarning, 3)
-            args = ()
-        if not args:
-            self.msg = kwargs.pop('msg', None)
-            if kwargs:
-                warnings.warn('%r is an invalid keyword argument for '
-                              'this function' % next(iter(kwargs)),
-                              DeprecationWarning, 3)
-            return self
-
-        callable_obj, *args = args
         try:
-            self.obj_name = callable_obj.__name__
-        except AttributeError:
-            self.obj_name = str(callable_obj)
-        with self:
-            callable_obj(*args, **kwargs)
+            if not _is_subtype(self.expected, self._base_type):
+                raise TypeError('%s() arg 1 must be %s' %
+                                (name, self._base_type_str))
+            if args and args[0] is None:
+                warnings.warn("callable is None",
+                              DeprecationWarning, 3)
+                args = ()
+            if not args:
+                self.msg = kwargs.pop('msg', None)
+                if kwargs:
+                    warnings.warn('%r is an invalid keyword argument for '
+                                  'this function' % next(iter(kwargs)),
+                                  DeprecationWarning, 3)
+                return self
+
+            callable_obj, *args = args
+            try:
+                self.obj_name = callable_obj.__name__
+            except AttributeError:
+                self.obj_name = str(callable_obj)
+            with self:
+                callable_obj(*args, **kwargs)
+        finally:
+            # bpo-23890: manually break a reference cycle
+            self = None
 
 
 class _AssertRaisesContext(_AssertRaisesBaseContext):
@@ -725,7 +729,11 @@ class TestCase(object):
                self.assertEqual(the_exception.error_code, 3)
         """
         context = _AssertRaisesContext(expected_exception, self)
-        return context.handle('assertRaises', args, kwargs)
+        try:
+            return context.handle('assertRaises', args, kwargs)
+        finally:
+            # bpo-23890: manually break a reference cycle
+            context = None
 
     def assertWarns(self, expected_warning, *args, **kwargs):
         """Fail unless a warning of class warnClass is triggered
index 8f752b8ae0d0e238c9fb217dd0adfc69dc4e45be..b84959150542bdfe95529dee196d1be5aae48397 100644 (file)
@@ -1273,6 +1273,19 @@ test case
         with self.assertRaises(TypeError):
             self.assertRaises((ValueError, object))
 
+    def testAssertRaisesRefcount(self):
+        # bpo-23890: assertRaises() must not keep objects alive longer
+        # than expected
+        def func() :
+            try:
+                raise ValueError
+            except ValueError:
+                raise ValueError
+
+        refcount = sys.getrefcount(func)
+        self.assertRaises(ValueError, func)
+        self.assertEqual(refcount, sys.getrefcount(func))
+
     def testAssertRaisesRegex(self):
         class ExceptionMock(Exception):
             pass
index 8b62d792367abad535eff9a8957d39077f27393a..b6bab9be07d5ec44de201d128436266e21aae6ee 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -56,6 +56,9 @@ Extension Modules
 Library
 -------
 
+- bpo-23890: unittest.TestCase.assertRaises() now manually breaks a reference
+  cycle to not keep objects alive longer than expected.
+
 - bpo-30149: inspect.signature() now supports callables with
   variable-argument parameters wrapped with partialmethod.
   Patch by Dong-hee Na.