-
-
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
Don't reregister subfixture finalizer in parent fixture if value is cached #12136
Don't reregister subfixture finalizer in parent fixture if value is cached #12136
Conversation
@jakkdl: I like how you add context by including the related issue in the source code of your regression test. I think I'll adopt that practice myself! Is it a common policy? Other than a point of praise, I don't really have anything to say. pytest is still quite opaque to me. But I'll read through the issues you cite (thank you for including them) when I have time and see if I can follow along with your approach. |
I have seen different versions of it in different repos, so I'd say it's relatively common. This repo also has a couple files dedicated to being regression tests for issues, e.g.
There's also stuff like https://docs.astral.sh/ruff/rules/missing-todo-link/
Oh yeah it took me a while to grok the fixture infrastructure, not for the faint of heart! |
Thanks for the thoughtful response. ruff looks cool too.
Ha! I'm at the point where if I say anything at all about the fixture infrastructure I invariably say nonsensical things and embarrass myself horribly. But am definitely trying to be a citizen of this place - one fainting spell at a time. In fact, wrote some things about it the other day in an Issue and likely face-planted myself all over the pavement. Such is the journey I suppose. Onwards and upwards. But also: Eeep! I won't yatter on and make it harder for someone to get up to speed on your PR. Thanks, @jakkdl! |
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.
Thanks @jakkdl! As mentioned in the issue, this change LGTM. I left some comments mostly on terminology.
changelog/12135.bugfix.rst
Outdated
@@ -0,0 +1 @@ | |||
Fix subfixtures adding their finalizer multiple times to parent fixtures, causing incorrect teardown ordering in some instances. |
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.
I feel the "parent fixture" terminology a bit confusing, since if fix1 requests fix2, from certain perspectives one could consider fix1 to be the parent of fix2, while you mean the reverse (which also makes sense in certain perspectives).
So I prefer to use "requests" terminology which is usually pretty clear.
I also wouldn't call it "incorrect teardown ordering", because I think we should reserve "incorrect" for cases where there is a violation of dependency order. We don't strictly guarantee stack order. So I suggest "non-intuitive".
Fix subfixtures adding their finalizer multiple times to parent fixtures, causing incorrect teardown ordering in some instances. | |
Fix fixtures adding their finalizer multiple times to fixture they request, causing non-intuitive teardown ordering in some instances. |
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.
"a fixture that this fixture requests" turned out to be very wordy and hard to read, so I swapped to using "dependent fixtures" - which seems to be somewhat established and shouldn't be possible to interpret as the reverse.
Switched to non-intuitive + unreliable (since teardown order will change depending on which fixture was last requested)
src/_pytest/fixtures.py
Outdated
# Ensure arguments (parent fixtures) are loaded. | ||
# If their cache has been invalidated it will finish itself and subfixtures, | ||
# which will set our self.cached_result = None. | ||
# If/when parent fixture parametrization is included in our cache key this | ||
# can be moved after checking our cache key and not require saving in a list. |
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.
- Let's avoid the term "subfixtures" since it's not a term we use and it's not very clear (to me at least), similar to "parent".
- I think the "if/when" comment is more of a wishlist item so better to keep it in the issue.
Suggested rewording:
# Ensure arguments (parent fixtures) are loaded. | |
# If their cache has been invalidated it will finish itself and subfixtures, | |
# which will set our self.cached_result = None. | |
# If/when parent fixture parametrization is included in our cache key this | |
# can be moved after checking our cache key and not require saving in a list. | |
# Ensure fixtures that this fixture requests are loaded (XXX: figure out | |
# why this is needed even in the cached case). | |
# | |
# Also make it such that whenever a fixture we request is torn down, | |
# this fixture is torn down first. This is generally handled by | |
# SetupState, but still currently needed when the requested fixture is | |
# not parameterized itself but depends on a parameterized fixture (see | |
# #4871 for more details). | |
# Only do this in the non-cached case. It's not needed in the cached | |
# case because the finalizers had already been registered. |
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.
XXX: figure out why this is needed even in the cached case
The reason this is needed in the cached case is also because of the requested-fixture-parametrization case (and possible other cases where the requested fixture's cache is invalidated?). This fixture's cache key does not change depending on the cache key of the requested fixture, and the following couple lines only check our cache key before deciding whether to return early.
Also make it such that whenever a fixture we request is torn down, this fixture is torn down first.
I slightly disagree on putting this up top instead of when this is actually ensured, where you wanted to remove the comment, but I'll reword it slightly different to not directly imply that this is a direct consequence of the lines directly following this.
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.
It's slightly confusing to have a huge block comment at the beginning of a function, implying it explains the whole function, but it in fact is just explaining the first 7 lines of it. So I'm adding a short docstring too.
src/_pytest/fixtures.py
Outdated
@@ -1060,6 +1065,11 @@ def execute(self, request: SubRequest) -> FixtureValue: | |||
self.finish(request) | |||
assert self.cached_result is None | |||
|
|||
finalizer = functools.partial(self.finish, request=request) | |||
# add finalizer to parent fixtures |
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.
I think the comment at the top is sufficient
# add finalizer to parent fixtures |
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.
I removed it.. and added it back. Feel free to remove it again if you still think it is detracting
src/_pytest/fixtures.py
Outdated
# Get required arguments and register our own finish() | ||
# with their finalization. | ||
"""Return the value of this fixture, executing it if not cached.""" | ||
# Ensure dependent fixtures that this fixture requests are loaded. |
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.
This comment has a grammar issue, can you rephrase it?
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.
I failed to see the problem on my own, but Claude fixed it :)
fix ambigious phrasing in comment
Thanks @jakkdl! BTW, I'm going to send you an invite to join pytest-dev. This doesn't require anything from you really, but will allow you to edit issues, merge your PRs, etc. if you'd like to contribute more. |
Sweet, might come in handy! |
Fixes #12135
Adds two regression tests: one that checks the ordering failure, and one that checks the number of finalizers.
The implementation would be slightly cleaner if parent fixture parametrization was included in the cache key, as discussed in #4871 (comment)