summaryrefslogtreecommitdiffstats
path: root/Lib/unittest
diff options
context:
space:
mode:
authorVictor Stinner <victor.stinner@gmail.com>2017-03-27 22:56:28 (GMT)
committerGitHub <noreply@github.com>2017-03-27 22:56:28 (GMT)
commitbbd3cf8f1ef1e91a8d6dac6411e18b4b9084abf5 (patch)
tree38bdb93a32d6ad08bba502f3aedda22b60b6c845 /Lib/unittest
parent6003db7db5fec545c01923c198a5fdfca5a91538 (diff)
downloadcpython-bbd3cf8f1ef1e91a8d6dac6411e18b4b9084abf5.zip
cpython-bbd3cf8f1ef1e91a8d6dac6411e18b4b9084abf5.tar.gz
cpython-bbd3cf8f1ef1e91a8d6dac6411e18b4b9084abf5.tar.bz2
Fix ref cycles in TestCase.assertRaises() (#193)
bpo-23890: unittest.TestCase.assertRaises() now manually breaks a reference cycle to not keep objects alive longer than expected.
Diffstat (limited to 'Lib/unittest')
-rw-r--r--Lib/unittest/case.py52
-rw-r--r--Lib/unittest/test/test_case.py13
2 files changed, 43 insertions, 22 deletions
diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py
index b523f73..f4dbc52 100644
--- a/Lib/unittest/case.py
+++ b/Lib/unittest/case.py
@@ -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
diff --git a/Lib/unittest/test/test_case.py b/Lib/unittest/test/test_case.py
index 8f752b8..b849591 100644
--- a/Lib/unittest/test/test_case.py
+++ b/Lib/unittest/test/test_case.py
@@ -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