diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 35a54de4b97..e88eb5b09e2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,7 +12,10 @@ * When loading plugins, import errors which contain non-ascii messages are now properly handled in Python 2 (`#1998`_). 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. * Fixed false-positives warnings from assertion rewrite hook for modules that were rewritten but diff --git a/_pytest/python.py b/_pytest/python.py index a42e7185e6e..18432c1e7f8 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1237,7 +1237,11 @@ def __exit__(self, *tp): exc_type, value, traceback = tp tp = exc_type, exc_type(value), traceback 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 diff --git a/testing/python/raises.py b/testing/python/raises.py index 80c3df2abe3..8f141cfa1a9 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -1,4 +1,6 @@ import pytest +import sys + class TestRaises: def test_raises(self): @@ -98,10 +100,13 @@ def test_custom_raise_message(self): assert False, "Expected pytest.raises.Exception" @pytest.mark.parametrize('method', ['function', 'with']) - def test_raises_memoryleak(self, method): - import weakref + def test_raises_cyclic_reference(self, method): + """ + Ensure pytest.raises does not leave a reference cycle (#1965). + """ + import gc - class T: + class T(object): def __call__(self): raise ValueError @@ -112,5 +117,12 @@ def __call__(self): with pytest.raises(ValueError): t() - t = weakref.ref(t) - assert t() is None + # ensure both forms of pytest.raises don't leave exceptions in sys.exc_info() + 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 +