-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use session.config.hook instead of ihook. Fixes #2124 #2127
Conversation
_pytest/fixtures.py
Outdated
@@ -783,8 +783,8 @@ def execute(self, request): | |||
self.finish() | |||
assert not hasattr(self, "cached_result") | |||
|
|||
ihook = self._fixturemanager.session.ihook | |||
return ihook.pytest_fixture_setup(fixturedef=self, request=request) | |||
hook = self._fixturemanager.session.config.hook |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as far as i understand, at this place, config.hook is an incorrect choice, as it does not apply fs path filtering
@hpk42 please cross-check my concern
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't then session.gethookproxy(fspath)
be used here instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nicoddemus i think so
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nicoddemus @RonnyPfannschmidt I disagree - session.gethookproxy(fspath)
is specifically what I'm changing in this PR, because session.ihook
does precisely session.gethookproxy(self.fspath)
(https://github.com/pytest-dev/pytest/blob/master/_pytest/main.py#L267-L270) and it doesn't work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Session.fspath is indeeed broken, you need the item fspath, which has a different value
@hpk42 please, review my PR. It blocks me. |
@RonnyPfannschmidt @nicoddemus ok, I've changed |
@malinoff the scope of this one is unfortunately growing a pr like this where one detail after another creeps up tends to be very frustrating for the contributor and i am very thankful that you patiently stick to it i believe the |
@malinoff @RonnyPfannschmidt gentle ping. 😁 |
@nicoddemus i reviewed correct passing of the data - and it seems currently impossible, the complete fixture definition work-flow is completely broken as far as i can tell and it only works because it does the wrong thing which is mostly not wrong enough to be a problem @hpk42 im deferring the complete issue to you |
…tems with empty "parent.nodeid" if run from a different root directory
…info remove unused _pytest.runner.NodeInfo class
@RonnyPfannschmidt should we close this? The PR is now stale anyway... |
This PR is not stale. I'm waiting for the guidance of how should I proceed. |
Oh OK thanks for the quick response. Sorry if my comment implied that you were somehow to blame; it as more of an observation that we have not seen activity on it over some months and I was under the impression that it would not get anymore attention. |
Took the opportunity to take a further look into this, and I managed to pass diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py
index f71f357..5ac210c 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
@@ -742,7 +743,7 @@ class FixtureDef:
def addfinalizer(self, finalizer):
self._finalizer.append(finalizer)
- def finish(self):
+ def finish(self, request):
exceptions = []
try:
while self._finalizer:
@@ -757,7 +758,7 @@ class FixtureDef:
py.builtin._reraise(*e)
finally:
- ihook = self._fixturemanager.session.ihook
+ ihook = self._fixturemanager.session.gethookproxy(request.node.fspath)
ihook.pytest_fixture_post_finalizer(fixturedef=self)
# even if finalization fails, we invalidate
# the cached fixture value
@@ -770,7 +771,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))
my_cache_key = request.param_index
cached_result = getattr(self, "cached_result", None)
@@ -783,10 +784,10 @@ class FixtureDef:
return result
# we have a previous but differently parametrized fixture instance
# so we need to tear it down before creating a new one
- self.finish()
+ self.finish(request)
assert not hasattr(self, "cached_result")
- ihook = self._fixturemanager.session.ihook
+ ihook = self._fixturemanager.session.gethookproxy(request.node.fspath)
return ihook.pytest_fixture_setup(fixturedef=self, request=request)
def __repr__(self): This passes all tests for me on Python 3.6 and produces correct results on my short test below: # conftest.py
import pytest
def pytest_fixture_post_finalizer():
print('ROOT conftest: pytest_fixture_post_finalizer')
def pytest_fixture_setup():
print('ROOT conftest: pytest_fixture_setup')
@pytest.fixture(scope='module')
def my_fixture():
yield 'my_fixture value'
# tests/conftest.py
def pytest_fixture_post_finalizer():
print('SUB conftest: pytest_fixture_post_finalizer')
def pytest_fixture_setup():
print('SUB conftest: pytest_fixture_setup')
# tests/test_usage.py
def test_usage(my_fixture):
print('TEST USAGE')
def test_foobar(my_fixture):
print('TEST FOOBAR') The hooks are called appropriately in the correct order:
Before the patch:
Finally, if we are fixing this, we should take the opportunity to pass This was not easy, took me a couple of hours of debugging to understand all the moving parts involved. The parts I think were hard for me to make sense while debugging was the relationship between Also surprising why I previously thought the |
This implicit definition really tripped me while debugging pytest-dev#2127, unfortunately hidden as it was in the middle of all the variable declarations. I think the explicit definition is much easier for the eyes and IDEs to find.
Make SubRequest.addfinalizer an explicit method
@nicoddemus looks good, the failure is unrelated, can you also resolve the conflicts by a merge ?` |
…d-with-pyargs Fix pytest-dev#2775 - running pytest with "--pyargs" will result in Items with …
if hasattr(self, "cached_result"): | ||
del self.cached_result | ||
self._finalizers = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RonnyPfannschmidt I realized we should also do this at this point to avoid keeping instances of bound methods alive in case they are registered as finalizers and an exception occurred during teardown, do you agree?
@@ -499,8 +499,16 @@ def __init__(self, fspath, parent=None, config=None, session=None): | |||
super(FSCollector, self).__init__(name, parent, config, session) | |||
self.fspath = fspath | |||
|
|||
def _check_initialpaths_for_relpath(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately I first merged with upstream/master
while working here so this PR is bringing #2776 into features
as well. Because it is a small patch I don't think this will get into the way of reviewing things so if you guys are OK with it I won't bother trying to fix history in this case.
Updated the CHANGELOG and changed the target branch to |
Thanks @malinoff for all the work and patience on this. 👍 |
The leak was caused by the (unused) `FixtureRequest._fixture_values` cache. This was introduced because the `partial` object (created to call FixtureDef.finish() bound with the Request) is kept alive through the entire session when a function-scoped fixture depends on a session-scoped (or higher) fixture because of the nested `addfinalizer` calls. FixtureDef.finish() started receiving a request object in order to obtain the proper hook proxy object (pytest-dev#2127), but this does not seem useful at all in practice because `pytest_fixture_post_finalizer` will be called with the `request` object of the moment the fixture value was *created*, not the request object active when the fixture value is being destroyed. We should probably deprecate/remove the request parameter from `pytest_fixture_post_finalizer`. Fix pytest-dev#2981
AUTHORS
;CHANGELOG.rst
CHANGELOG
, so please add a thank note to yourself ("Thanks @user for the PR") and a link to your GitHub profile. It may sound weird thanking yourself, but otherwise a maintainer would have to do it manually before or after merging instead of just using GitHub's merge button. This makes it easier on the maintainers to merge PRs.