Merge pull request #3384 from nicoddemus/leak-frame
Reset reference to failed test frame before each test executes
This commit is contained in:
commit
015626ce69
|
@ -105,6 +105,7 @@ def pytest_runtest_setup(item):
|
||||||
|
|
||||||
def pytest_runtest_call(item):
|
def pytest_runtest_call(item):
|
||||||
_update_current_test_var(item, 'call')
|
_update_current_test_var(item, 'call')
|
||||||
|
sys.last_type, sys.last_value, sys.last_traceback = (None, None, None)
|
||||||
try:
|
try:
|
||||||
item.runtest()
|
item.runtest()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -114,7 +115,7 @@ def pytest_runtest_call(item):
|
||||||
sys.last_type = type
|
sys.last_type = type
|
||||||
sys.last_value = value
|
sys.last_value = value
|
||||||
sys.last_traceback = tb
|
sys.last_traceback = tb
|
||||||
del tb # Get rid of it in this namespace
|
del type, value, tb # Get rid of these in this frame
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Reset ``sys.last_type``, ``sys.last_value`` and ``sys.last_traceback`` before each test executes. Those attributes
|
||||||
|
are added by pytest during the test run to aid debugging, but were never reset so they would create a leaking
|
||||||
|
reference to the last failing test's frame which in turn could never be reclaimed by the garbage collector.
|
|
@ -988,3 +988,33 @@ def test_fixture_order_respects_scope(testdir):
|
||||||
''')
|
''')
|
||||||
result = testdir.runpytest()
|
result = testdir.runpytest()
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_frame_leak_on_failing_test(testdir):
|
||||||
|
"""pytest would leak garbage referencing the frames of tests that failed that could never be reclaimed (#2798)
|
||||||
|
|
||||||
|
Unfortunately it was not possible to remove the actual circles because most of them
|
||||||
|
are made of traceback objects which cannot be weakly referenced. Those objects at least
|
||||||
|
can be eventually claimed by the garbage collector.
|
||||||
|
"""
|
||||||
|
testdir.makepyfile('''
|
||||||
|
import gc
|
||||||
|
import weakref
|
||||||
|
|
||||||
|
class Obj:
|
||||||
|
pass
|
||||||
|
|
||||||
|
ref = None
|
||||||
|
|
||||||
|
def test1():
|
||||||
|
obj = Obj()
|
||||||
|
global ref
|
||||||
|
ref = weakref.ref(obj)
|
||||||
|
assert 0
|
||||||
|
|
||||||
|
def test2():
|
||||||
|
gc.collect()
|
||||||
|
assert ref() is None
|
||||||
|
''')
|
||||||
|
result = testdir.runpytest_subprocess()
|
||||||
|
result.stdout.fnmatch_lines(['*1 failed, 1 passed in*'])
|
||||||
|
|
|
@ -719,18 +719,20 @@ def test_makereport_getsource_dynamic_code(testdir, monkeypatch):
|
||||||
result.stdout.fnmatch_lines(["*test_fix*", "*fixture*'missing'*not found*"])
|
result.stdout.fnmatch_lines(["*test_fix*", "*fixture*'missing'*not found*"])
|
||||||
|
|
||||||
|
|
||||||
def test_store_except_info_on_eror():
|
def test_store_except_info_on_error():
|
||||||
""" Test that upon test failure, the exception info is stored on
|
""" Test that upon test failure, the exception info is stored on
|
||||||
sys.last_traceback and friends.
|
sys.last_traceback and friends.
|
||||||
"""
|
"""
|
||||||
# Simulate item that raises a specific exception
|
# Simulate item that might raise a specific exception, depending on `raise_error` class var
|
||||||
class ItemThatRaises(object):
|
class ItemMightRaise(object):
|
||||||
nodeid = 'item_that_raises'
|
nodeid = 'item_that_raises'
|
||||||
|
raise_error = True
|
||||||
|
|
||||||
def runtest(self):
|
def runtest(self):
|
||||||
raise IndexError('TEST')
|
if self.raise_error:
|
||||||
|
raise IndexError('TEST')
|
||||||
try:
|
try:
|
||||||
runner.pytest_runtest_call(ItemThatRaises())
|
runner.pytest_runtest_call(ItemMightRaise())
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
# Check that exception info is stored on sys
|
# Check that exception info is stored on sys
|
||||||
|
@ -738,6 +740,13 @@ def test_store_except_info_on_eror():
|
||||||
assert sys.last_value.args[0] == 'TEST'
|
assert sys.last_value.args[0] == 'TEST'
|
||||||
assert sys.last_traceback
|
assert sys.last_traceback
|
||||||
|
|
||||||
|
# The next run should clear the exception info stored by the previous run
|
||||||
|
ItemMightRaise.raise_error = False
|
||||||
|
runner.pytest_runtest_call(ItemMightRaise())
|
||||||
|
assert sys.last_type is None
|
||||||
|
assert sys.last_value is None
|
||||||
|
assert sys.last_traceback is None
|
||||||
|
|
||||||
|
|
||||||
def test_current_test_env_var(testdir, monkeypatch):
|
def test_current_test_env_var(testdir, monkeypatch):
|
||||||
pytest_current_test_vars = []
|
pytest_current_test_vars = []
|
||||||
|
|
Loading…
Reference in New Issue