diff --git a/AUTHORS b/AUTHORS index ab646f406..3a564606c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -197,3 +197,4 @@ Xuan Luong Xuecong Liao Zoltán Máté Roland Puntaier +Allan Feldman diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index a8445767c..f9d0ef572 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -24,6 +24,12 @@ from _pytest.compat import ( from _pytest.outcomes import fail, TEST_OUTCOME +@attr.s(frozen=True) +class PseudoFixtureDef(object): + cached_result = attr.ib() + scope = attr.ib() + + def pytest_sessionstart(session): import _pytest.python import _pytest.nodes @@ -440,10 +446,9 @@ class FixtureRequest(FuncargnamesCompatAttr): fixturedef = self._getnextfixturedef(argname) except FixtureLookupError: if argname == "request": - class PseudoFixtureDef(object): - cached_result = (self, [0], None) - scope = "function" - return PseudoFixtureDef + cached_result = (self, [0], None) + scope = "function" + return PseudoFixtureDef(cached_result, scope) raise # remove indent to prevent the python3 exception # from leaking into the call diff --git a/changelog/3249.bugfix.rst b/changelog/3249.bugfix.rst new file mode 100644 index 000000000..38fa9179f --- /dev/null +++ b/changelog/3249.bugfix.rst @@ -0,0 +1 @@ +Fix reference cycle generated when using the ``request`` fixture. diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 6bcb1ab00..8638e361a 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -519,6 +519,41 @@ class TestRequestBasic(object): assert len(arg2fixturedefs) == 1 assert arg2fixturedefs['something'][0].argname == "something" + def test_request_garbage(self, testdir): + testdir.makepyfile(""" + import sys + import pytest + import gc + + @pytest.fixture(autouse=True) + def something(request): + # this method of test doesn't work on pypy + if hasattr(sys, "pypy_version_info"): + yield + else: + original = gc.get_debug() + gc.set_debug(gc.DEBUG_SAVEALL) + gc.collect() + + yield + + gc.collect() + leaked_types = sum(1 for _ in gc.garbage + if 'PseudoFixtureDef' in str(_)) + + gc.garbage[:] = [] + + try: + assert leaked_types == 0 + finally: + gc.set_debug(original) + + def test_func(): + pass + """) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=1) + def test_getfixturevalue_recursive(self, testdir): testdir.makeconftest(""" import pytest