-
-
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
Improved fixture reuse by new param keys that can be derived from API ids #9420
base: main
Are you sure you want to change the base?
Conversation
98ce476
to
099e4d1
Compare
@haxtibal Thanks a lot for creating this separate PR with nice commits and all! I'm looking forward to reviewing this PR, when I have the time available. Right now we are mostly focused on getting the 7.0 release out the door, just wanted to write this so you don't get discouraged :) |
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 I'm going to leave a separate review for each commit. This is for "Refactor idmaker functions into class IdMaker'.
The refactoring LGTM, with some comments.
src/_pytest/python.py
Outdated
@@ -911,6 +911,124 @@ def hasnew(obj: object) -> bool: | |||
return False | |||
|
|||
|
|||
@final | |||
@attr.s(frozen=True, auto_attribs=True) |
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.
Can add slots=True
here as well, doesn't hurt.
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.
doesn't hurt
If I understand attrs docs correctly, @attr.s(frozen=True, slots=True, ...)
actually could hurt in some cases:
You should avoid instantiating lots of frozen slotted classes (i.e. @Frozen) in performance-critical code
I can't reason offhand about whether we'd be affected, can you?
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.
Hmm interesting. We use this all over the place so we should probably check if it has any effect separately. But until then let's keep it in this PR.
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.
Some more comments on the first commit. Sorry for all of this, I just hate to pass on an opportunity to make some pytest code clearer :)
If it's too much I can cherry pick the commit to a separate PR along with my suggestions.
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.
Review for "Add IdMaker.make_parameter_keys".
The commit message note writes pytest.param("apple", ids=["fruit"])
but meant to write pytest.param("apple", id="fruit")
.
Regarding the the problem with the duplicates, when there are conflicts in make_unique_test_ids
pytest disambiguates them with a suffix. This should probably also be done for parameter keys then, since duplicates probably don't make sense within the same group of parameter sets.
src/_pytest/python.py
Outdated
def make_parameter_keys(self) -> Iterable[Dict[str, Hashable]]: | ||
"""Make hashable keys for parameters in a parametrized test. | ||
|
||
This key will be considered to determine if parameters |
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 key will be considered (along with the parameter name) to determine ...
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.
(along with the parameter name)
What is the "parameter name"? reorder_items
uses e.g. key = (argname, param_key, item.path, item_cls)
, and FixtureDef.cache_key
uses param_key
. Do you mean argument name?
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.
Review of "Extend CallSpec2 with param_keys".
LGTM, though note #9531 causes some conflicts, sorry about that.
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.
"Extend SubRequest with param_key" LGTM.
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 reorder_items use our new parameter key" LGTM with comment
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.
Review for " Let FixtureDef.cache_key use our new parameter key "
I reviewed everything, though I ran out of time toward the end. The commit separation and commit messages were very helpful. |
For |
ff42377
to
a700b05
Compare
@bluetech Thanks for taking your time! That was a really helpful review. Most of the remarks are hopefully done, and the commit series is rebased on latest main. I left conversations open where response from you would be good. |
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.
Second review of the " Refactor idmaker functions into class IdMaker " commit.
I left a few comments, but it looks good to me now. It's nice regardless, so if you agree, I can cherry-pick it to main already, to reduce the size of the remaining PR.
I will try to review the rest again soon.
a700b05
to
3274429
Compare
Just applied your recent comments. Sure, cherry-pick it, or in case you find some more bits you'd like to change, it's fine for me if you amend the commit to spare another review iteration on "Refactor idmaker functions into class IdMaker". |
These parameter keys will later become the unified way how reorder_items and FixtureDefs decide if parameters are equal and can be reused. Order for what to use as key is as follows: 1. If users gave explicitly parameter ids, use them as key. 2. If not explictely given, and the parameter value is hashable, use the parameter value as key. 3. Else, fallback to the parameters identity. NB: Rule 1 gives users ultimate (equallity-telling) power, and with great power comes great responsiblity. One could now do something wired like @pytest.mark.parametrize(fruit, [ pytest.param("apple", id="fruit"), pytest.param("orange", id="fruit"), ] def test_fruits(fruit): pass The user just made "apple" equal to "orange". If that's what they intend is unknown, but probably not.
This way we make the previously calculated parameter key accessible to reorder_items and FixtureDef.
Pick up the value from curent CallSpec2 and assign it to the SubRequest. It's required to make the parameter key accessible in FixtureDef.execute.
Fixes test reordering for indirect parameterization (see pytest-dev#8913). Prior to this commit, reorder_items considered the parameter index to tell if a parameter is "the same" and therefore can be shared. Looking at the index causes trouble if there are multiple parametrizations for the same fixture, basically because one index means different things in different parameter lists. This is fixed here by using the recently introduced parameter key as grouping criterion. Caution: The parameter key ends up inside the key of another dict, and therefore must be hashable. CallSpec2.param_keys is crafted sufficiently, it guarantees to contain comparable and hashable values.
The FixtureDef cache must agree with reorder_items about what parmeters are the same. The new param key must (and can) be compared by value, so we change from "is" to "==" in FixtureDef.execute.
Add tests to assert pytest-dev#8914 is fixed. Tests assures that both reordering and caching work as intended, and demonstrates how one can use parameter ids to decide about equality. Adapting issue_519.checked_order is not cheating. See our discussion at pytest-dev#9350 (review)
3274429
to
26ae398
Compare
(I rebased on top of the cherry-picked commit) |
Gave this another (quick) look. First, the example in the first commit message is not very fitting, because import pytest
@pytest.fixture(params=["apple", "orange"], ids=["fruit", "fruit"], scope="module")
def a_fruit(request):
return request.param
def test_fruits(a_fruit):
print(a_fruit) This makes me think, if we shouldn't add a "deduplication" step to And that made me further think, if we shouldn't just completely do away with falling back on comparing values (with the entire This stuff is pretty entangled and complicated so I may be confused or missing something, but WDYT? |
@bluetech Sorry for delay in response.
Agreed, my example not only missed
Agreed. That's better suited.
Agreed. I'd suggest to print a warning or error message then, and maybe even bail out early, instead of deduplicating silently?
Agreed. Right now I'm slightly puzzled why direct parametrization (as in my commit example) is subject to fixture lookup at all - as opposed to unconditionally take values from
We can't simply rely on import pytest
@pytest.fixture(scope="module")
def fruitfix(request):
print(f"setup fruitfix with {request.param}")
return request.param
@pytest.mark.parametrize("fruitfix", [(1, 2), (3, 4)], indirect=True)
def test_fruits_1(fruitfix):
print(f"test1 using {fruitfix}")
@pytest.mark.parametrize("fruitfix", [(3, 4), (5, 6)], indirect=True)
def test_fruits_2(fruitfix):
print(f"test2 using {fruitfix}") Output with
Output with using
This is because What we could do was to use |
Sorry for the edits in yesterdays answer. They make decision even simpler: We can trade the current My personal opinion is to keep But I can understand if you want to leave the compare-by-value fallback out: It's simpler to document and understand, and it's probably simpler to maintain. @bluetech It's your turn to decide, I will for sure be fine with either decision. |
@bluetech would you want to have another look at this. Or otherwise this might better be closed? |
This is a less trivial but (arguably) more powerful alternative to #9350. It fixes #8914. For a review, please follow the commit series and messages.
Situation: Both
reorder_items
andFixtureDef.execute
must decide and agree about whether fixture instances can be reused. The obvious is to decide by comparing parameter values. Sadly none ofprm1 == prm2
,prm1 is prm2
orhash(prm1) == hash(prm2)
is generally suited to decide about "are parameters the same?":dict
s are not hashableobject
s compare and hash by identity, but a user may expect reuse only if it's equal by valueUsing the parameter index instead of value doesn't work either, because, well, see #8914.
Idea: Give users the option to explicitly control equality of parameters by leveraging parameter ids. Under the hood we introduce a
param_key
to support fixture reordering and caching. The key is built according to the following rulesExample 1: Use
id=
to control equality of the unhashabledict
parameters (rule 1 applies).Example 2: No need to use
id=
orids=
if the parameter is hashable by value (rule 2 applies).Also related: #244, #5693, #6497, #6541.