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.
This commit is contained in:
Victor Stinner 2017-03-28 00:56:28 +02:00 committed by GitHub
parent 6003db7db5
commit bbd3cf8f1e
3 changed files with 46 additions and 22 deletions

View File

@ -153,28 +153,32 @@ class _AssertRaisesBaseContext(_BaseTestCaseContext):
If args is not empty, call a callable passing positional and keyword If args is not empty, call a callable passing positional and keyword
arguments. 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: try:
self.obj_name = callable_obj.__name__ if not _is_subtype(self.expected, self._base_type):
except AttributeError: raise TypeError('%s() arg 1 must be %s' %
self.obj_name = str(callable_obj) (name, self._base_type_str))
with self: if args and args[0] is None:
callable_obj(*args, **kwargs) 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): class _AssertRaisesContext(_AssertRaisesBaseContext):
@ -725,7 +729,11 @@ class TestCase(object):
self.assertEqual(the_exception.error_code, 3) self.assertEqual(the_exception.error_code, 3)
""" """
context = _AssertRaisesContext(expected_exception, self) 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): def assertWarns(self, expected_warning, *args, **kwargs):
"""Fail unless a warning of class warnClass is triggered """Fail unless a warning of class warnClass is triggered

View File

@ -1273,6 +1273,19 @@ test case
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
self.assertRaises((ValueError, object)) 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): def testAssertRaisesRegex(self):
class ExceptionMock(Exception): class ExceptionMock(Exception):
pass pass

View File

@ -291,6 +291,9 @@ Extension Modules
Library Library
------- -------
- bpo-23890: unittest.TestCase.assertRaises() now manually breaks a reference
cycle to not keep objects alive longer than expected.
- bpo-29901: The zipapp module now supports general path-like objects, not - bpo-29901: The zipapp module now supports general path-like objects, not
just pathlib.Path. just pathlib.Path.