pytest: Pytest holds on to fixtures too long causing a memory leak

Pytest seems to hold on to the object yielded, even after the test is done, sometimes, even until all the tests are done.

The following code:

import pytest
import gc
import weakref


class SomethingInteresting(object):

	def check_app(self):
		assert True
		print('app checked')


@pytest.fixture
def my_app():
	app = SomethingInteresting()
	yield app

	app = weakref.ref(app)
	gc.collect()
	print('app is', app())
	if app() is not None:
		assert False


def test_my_app(my_app):
	my_app.check_app()

Results in this:

pytest /g/Python/libs/Playground/src/playground15.py
============================================================================================================= test session starts ============================================================================================================= platform win32 -- Python 3.7.3, pytest-4.6.3, py-1.8.0, pluggy-0.12.0
rootdir: G:\Python\libs\Playground
collected 1 item

..\Playground\src\playground15.py .E                                                                                                                                                                                                     [100%]

=================================================================================================================== ERRORS ==================================================================================================================== ______________________________________________________________________________________________________ ERROR at teardown of test_my_app _______________________________________________________________________________________________________

    @pytest.fixture
    def my_app():
        app = SomethingInteresting()
        yield app

        app = weakref.ref(app)
        gc.collect()
        print('app is', app())
        if app() is not None:
>               assert False
E     assert False

..\Playground\src\playground15.py:22: AssertionError
------------------------------------------------------------------------------------------------------------ Captured stdout call ------------------------------------------------------------------------------------------------------------- app checked
---------------------------------------------------------------------------------------------------------- Captured stdout teardown ----------------------------------------------------------------------------------------------------------- app is <src.playground15.SomethingInteresting object at 0x0000021F5F295908>
====================================================================================================== 1 passed, 1 error in 0.09 seconds ======================================================================================================

I tried to investigate using objgraph, and various pytest modules (e.g. warning) were blamed depending on the exact code. When I tested the app after the fixture has fully returned at the next test, then the app seemed to have been released.

However, in a more complex app that I have, pytest seemed to hold on to the app fixture instances forever, untill all the test has finished. I’m attaching what objgraph shows for this case that Terminal reporter is holding on to it. sample-graph

Tested on Windows10, py3.7.

pytest                        4.6.3
pytest-cov                    2.7.1
pytest-trio                   0.5.2

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 31 (16 by maintainers)

Most upvoted comments

will -p no:warnings solve this?

that sounds like a completely different question – I’d make a new issue / discussion – this one has gotten pretty off topic already

my point is more that you’d use a context manager to do the allocation and deallocation explicitly – rather than relying on the garbage collector

the easiest is to just:

@pytest.fixture
def whatever():
    with whatever_context() as obj:
        yield obj

@ax3l not sure what you’re asking – those fixtures will be collected and destroyed per-test (since you have scope='function')