diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 7b50b8574..5e3474ac3 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -4,6 +4,7 @@ import inspect import sys import warnings +import functools import py from py._code.code import FormattedExcinfo @@ -521,7 +522,7 @@ class FixtureRequest(FuncargnamesCompatAttr): val = fixturedef.execute(request=subrequest) finally: # if fixture function failed it might have registered finalizers - self.session._setupstate.addfinalizer(fixturedef.finish, + self.session._setupstate.addfinalizer(functools.partial(fixturedef.finish, request=subrequest), subrequest.node) return val @@ -744,7 +745,7 @@ class FixtureDef: def addfinalizer(self, finalizer): self._finalizer.append(finalizer) - def finish(self): + def finish(self, request): exceptions = [] try: while self._finalizer: @@ -759,10 +760,12 @@ class FixtureDef: py.builtin._reraise(*e) finally: - hook = self._fixturemanager.session.config.hook - hook.pytest_fixture_post_finalizer(fixturedef=self) + hook = self._fixturemanager.session.gethookproxy(request.node.fspath) + hook.pytest_fixture_post_finalizer(fixturedef=self, request=request) # even if finalization fails, we invalidate - # the cached fixture value + # the cached fixture value and remove + # all finalizers because they may be bound methods which will + # keep instances alive if hasattr(self, "cached_result"): del self.cached_result @@ -772,7 +775,7 @@ class FixtureDef: for argname in self.argnames: fixturedef = request._get_active_fixturedef(argname) if argname != "request": - fixturedef.addfinalizer(self.finish) + fixturedef.addfinalizer(functools.partial(self.finish, request=request)) my_cache_key = request.param_index cached_result = getattr(self, "cached_result", None) @@ -788,9 +791,7 @@ class FixtureDef: self.finish() assert not hasattr(self, "cached_result") - hook = self._fixturemanager.session.gethookproxy( - request._pyfuncitem.fspath - ) + hook = self._fixturemanager.session.gethookproxy(request.node.fspath) return hook.pytest_fixture_setup(fixturedef=self, request=request) def __repr__(self): diff --git a/_pytest/hookspec.py b/_pytest/hookspec.py index e5c966e58..ceb87a3c2 100644 --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -296,7 +296,7 @@ def pytest_fixture_setup(fixturedef, request): Stops at first non-None result, see :ref:`firstresult` """ -def pytest_fixture_post_finalizer(fixturedef): +def pytest_fixture_post_finalizer(fixturedef, request): """ called after fixture teardown, but before the cache is cleared so the fixture result cache ``fixturedef.cached_result`` can still be accessed.""" diff --git a/testing/python/fixture.py b/testing/python/fixture.py index b351eeeca..f843e1948 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -3127,49 +3127,41 @@ class TestParameterizedSubRequest(object): """.format(fixfile.strpath, testfile.basename)) -def test_pytest_fixture_setup_hook(testdir): +def test_pytest_fixture_setup_and_post_finalizer_hook(testdir): testdir.makeconftest(""" - import pytest - - def pytest_fixture_setup(): - print('pytest_fixture_setup hook called') + from __future__ import print_function + def pytest_fixture_setup(fixturedef, request): + print('ROOT setup hook called for {0} from {1}'.format(fixturedef.argname, request.node.name)) + def pytest_fixture_post_finalizer(fixturedef, request): + print('ROOT finalizer hook called for {0} from {1}'.format(fixturedef.argname, request.node.name)) """) - testdir.makepyfile(""" - import pytest + testdir.makepyfile(**{ + 'tests/conftest.py': """ + from __future__ import print_function + def pytest_fixture_setup(fixturedef, request): + print('TESTS setup hook called for {0} from {1}'.format(fixturedef.argname, request.node.name)) + def pytest_fixture_post_finalizer(fixturedef, request): + print('TESTS finalizer hook called for {0} from {1}'.format(fixturedef.argname, request.node.name)) + """, + 'tests/test_hooks.py': """ + from __future__ import print_function + import pytest - @pytest.fixture() - def some(): - return 'some' + @pytest.fixture() + def my_fixture(): + return 'some' - def test_func(some): - assert some == 'some' - """) + def test_func(my_fixture): + print('TEST test_func') + assert my_fixture == 'some' + """ + }) result = testdir.runpytest("-s") assert result.ret == 0 result.stdout.fnmatch_lines([ - "*pytest_fixture_setup hook called*", - ]) - - -def test_pytest_fixture_post_finalizer_hook(testdir): - testdir.makeconftest(""" - import pytest - - def pytest_fixture_post_finalizer(): - print('pytest_fixture_post_finalizer hook called') - """) - testdir.makepyfile(""" - import pytest - - @pytest.fixture() - def some(): - return 'some' - - def test_func(some): - assert some == 'some' - """) - result = testdir.runpytest("-s") - assert result.ret == 0 - result.stdout.fnmatch_lines([ - "*pytest_fixture_post_finalizer hook called*", + "*TESTS setup hook called for my_fixture from test_func*", + "*ROOT setup hook called for my_fixture from test_func*", + "*TEST test_func*", + "*TESTS finalizer hook called for my_fixture from test_func*", + "*ROOT finalizer hook called for my_fixture from test_func*", ])