Fix the stubborn test about cyclic references left by pytest.raises
In Python 2, a context manager's __exit__() leaves sys.exc_info with the exception values even when it was supposed to suppress the exception, so we explicitly call sys.exc_clear() which removes the traceback and allow the object to be released. Also updated the test to not depend on the immediate destruction of the object but instead to ensure it is not being tracked as a cyclic reference. Fix #1965
This commit is contained in:
parent
552c7d4286
commit
1130b9f742
|
@ -12,7 +12,10 @@
|
||||||
* When loading plugins, import errors which contain non-ascii messages are now properly handled in Python 2 (`#1998`_).
|
* When loading plugins, import errors which contain non-ascii messages are now properly handled in Python 2 (`#1998`_).
|
||||||
Thanks `@nicoddemus`_ for the PR.
|
Thanks `@nicoddemus`_ for the PR.
|
||||||
|
|
||||||
* Fixed memory leak in the RaisesContext (pytest.raises) (`#1965`_).
|
* Fixed cyclic reference when ``pytest.raises`` is used in context-manager form (`#1965`_). Also as a
|
||||||
|
result of this fix, ``sys.exc_info()`` is left empty in both context-manager and function call usages.
|
||||||
|
Previously, ``sys.exc_info`` would contain the exception caught by the context manager,
|
||||||
|
even when the expected exception occurred.
|
||||||
Thanks `@MSeifert04`_ for the report and the PR.
|
Thanks `@MSeifert04`_ for the report and the PR.
|
||||||
|
|
||||||
* Fixed false-positives warnings from assertion rewrite hook for modules that were rewritten but
|
* Fixed false-positives warnings from assertion rewrite hook for modules that were rewritten but
|
||||||
|
|
|
@ -1237,7 +1237,11 @@ class RaisesContext(object):
|
||||||
exc_type, value, traceback = tp
|
exc_type, value, traceback = tp
|
||||||
tp = exc_type, exc_type(value), traceback
|
tp = exc_type, exc_type(value), traceback
|
||||||
self.excinfo.__init__(tp)
|
self.excinfo.__init__(tp)
|
||||||
return issubclass(self.excinfo.type, self.expected_exception)
|
suppress_exception = issubclass(self.excinfo.type, self.expected_exception)
|
||||||
|
if sys.version_info[0] == 2 and suppress_exception:
|
||||||
|
sys.exc_clear()
|
||||||
|
return suppress_exception
|
||||||
|
|
||||||
|
|
||||||
# builtin pytest.approx helper
|
# builtin pytest.approx helper
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
class TestRaises:
|
class TestRaises:
|
||||||
def test_raises(self):
|
def test_raises(self):
|
||||||
|
@ -98,10 +100,13 @@ class TestRaises:
|
||||||
assert False, "Expected pytest.raises.Exception"
|
assert False, "Expected pytest.raises.Exception"
|
||||||
|
|
||||||
@pytest.mark.parametrize('method', ['function', 'with'])
|
@pytest.mark.parametrize('method', ['function', 'with'])
|
||||||
def test_raises_memoryleak(self, method):
|
def test_raises_cyclic_reference(self, method):
|
||||||
import weakref
|
"""
|
||||||
|
Ensure pytest.raises does not leave a reference cycle (#1965).
|
||||||
|
"""
|
||||||
|
import gc
|
||||||
|
|
||||||
class T:
|
class T(object):
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
|
@ -112,5 +117,12 @@ class TestRaises:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
t()
|
t()
|
||||||
|
|
||||||
t = weakref.ref(t)
|
# ensure both forms of pytest.raises don't leave exceptions in sys.exc_info()
|
||||||
assert t() is None
|
assert sys.exc_info() == (None, None, None)
|
||||||
|
|
||||||
|
del t
|
||||||
|
|
||||||
|
# ensure the t instance is not stuck in a cyclic reference
|
||||||
|
for o in gc.get_objects():
|
||||||
|
assert type(o) is not T
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue