Skip to content

Commit 1130b9f

Browse files
committed
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
1 parent 552c7d4 commit 1130b9f

File tree

3 files changed

+26
-7
lines changed

3 files changed

+26
-7
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
* When loading plugins, import errors which contain non-ascii messages are now properly handled in Python 2 (`#1998`_).
1313
Thanks `@nicoddemus`_ for the PR.
1414

15-
* Fixed memory leak in the RaisesContext (pytest.raises) (`#1965`_).
15+
* Fixed cyclic reference when ``pytest.raises`` is used in context-manager form (`#1965`_). Also as a
16+
result of this fix, ``sys.exc_info()`` is left empty in both context-manager and function call usages.
17+
Previously, ``sys.exc_info`` would contain the exception caught by the context manager,
18+
even when the expected exception occurred.
1619
Thanks `@MSeifert04`_ for the report and the PR.
1720

1821
* Fixed false-positives warnings from assertion rewrite hook for modules that were rewritten but

_pytest/python.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1237,7 +1237,11 @@ def __exit__(self, *tp):
12371237
exc_type, value, traceback = tp
12381238
tp = exc_type, exc_type(value), traceback
12391239
self.excinfo.__init__(tp)
1240-
return issubclass(self.excinfo.type, self.expected_exception)
1240+
suppress_exception = issubclass(self.excinfo.type, self.expected_exception)
1241+
if sys.version_info[0] == 2 and suppress_exception:
1242+
sys.exc_clear()
1243+
return suppress_exception
1244+
12411245

12421246
# builtin pytest.approx helper
12431247

testing/python/raises.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import pytest
2+
import sys
3+
24

35
class TestRaises:
46
def test_raises(self):
@@ -98,10 +100,13 @@ def test_custom_raise_message(self):
98100
assert False, "Expected pytest.raises.Exception"
99101

100102
@pytest.mark.parametrize('method', ['function', 'with'])
101-
def test_raises_memoryleak(self, method):
102-
import weakref
103+
def test_raises_cyclic_reference(self, method):
104+
"""
105+
Ensure pytest.raises does not leave a reference cycle (#1965).
106+
"""
107+
import gc
103108

104-
class T:
109+
class T(object):
105110
def __call__(self):
106111
raise ValueError
107112

@@ -112,5 +117,12 @@ def __call__(self):
112117
with pytest.raises(ValueError):
113118
t()
114119

115-
t = weakref.ref(t)
116-
assert t() is None
120+
# ensure both forms of pytest.raises don't leave exceptions in sys.exc_info()
121+
assert sys.exc_info() == (None, None, None)
122+
123+
del t
124+
125+
# ensure the t instance is not stuck in a cyclic reference
126+
for o in gc.get_objects():
127+
assert type(o) is not T
128+

0 commit comments

Comments
 (0)