-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Add FixtureArgKey class to represent fixture deps in fixtures.py
#11231
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
Changes from 5 commits
81464b3
2c270b8
e653fa9
fdc1b10
cd4a26a
5b7ebee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -239,11 +239,17 @@ def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]: | |
| ) | ||
|
|
||
|
|
||
| # Parametrized fixture key, helper alias for code below. | ||
| _Key = Tuple[object, ...] | ||
| @dataclasses.dataclass(frozen=True) | ||
| class FixtureArgKey: | ||
| argname: str | ||
| param_index: int | ||
| scoped_item_path: Optional[Path] | ||
| item_cls: Optional[type] | ||
|
|
||
|
|
||
| def get_parametrized_fixture_keys(item: nodes.Item, scope: Scope) -> Iterator[_Key]: | ||
| def get_parametrized_fixture_keys( | ||
| item: nodes.Item, scope: Scope | ||
| ) -> Iterator[FixtureArgKey]: | ||
| """Return list of keys for all parametrized arguments which match | ||
| the specified scope.""" | ||
| assert scope is not Scope.Function | ||
|
|
@@ -256,21 +262,25 @@ def get_parametrized_fixture_keys(item: nodes.Item, scope: Scope) -> Iterator[_K | |
| # cs.indices.items() is random order of argnames. Need to | ||
| # sort this so that different calls to | ||
| # get_parametrized_fixture_keys will be deterministic. | ||
| for argname, param_index in sorted(cs.indices.items()): | ||
| for argname in sorted(cs.indices): | ||
| if cs._arg2scope[argname] != scope: | ||
| continue | ||
|
|
||
| item_cls = None | ||
| if scope is Scope.Session: | ||
| key: _Key = (argname, param_index) | ||
| scoped_item_path = None | ||
| elif scope is Scope.Package: | ||
| key = (argname, param_index, item.path) | ||
| scoped_item_path = item.path.parent | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a semantic conflict here, the Anyway, let's keep it
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is elif scope is Scope.Package:
if isinstance(item.parent, Package):
scoped_item_path = item.parent.path
elif isinstance(item.parent.parent, Package):
scoped_item_path = item.parent.parent.path # If item is a class method.
else:
continue # It's not located in a package.Also shouldn't we change the elif scope is Scope.Class:
if not item.cls:
continue
scoped_item_path = item.path
item_cls = item.clsFor class-less test functions?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As you said, this test fails currently: def test_reordering_with_package_scoped_parametrization_of_test_in_subdirectory(pytester: Pytester) -> None:
pytester.makepyfile(
__init__= "",
test_a= textwrap.dedent(
"""\
import pytest
@pytest.mark.parametrize("arg", [0], scope="package")
def test_1(arg):
pass
def test_2():
pass
""",
),
)
subdir = pytester.mkdir("subdirectory")
subdir.joinpath("test_b.py").write_text(
textwrap.dedent(
"""\
import pytest
@pytest.mark.parametrize("arg", [0], scope="package")
def test_3(arg):
pass
""",
),
encoding="utf-8",
)
result = pytester.runpytest("--collect-only")
result.stdout.fnmatch_lines(
[
"*test_1*",
"*test_3*",
"*test_2*",
]
)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And this also fails for class branch: def test_reordering_with_class_scoped_parametrization_of_classless_test(pytester: Pytester) -> None:
pytester.makepyfile(
"""
import pytest
@pytest.mark.parametrize("arg", [0], scope="class")
def test_1(arg):
pass
def test_2():
pass
@pytest.mark.parametrize("arg", [0], scope="class")
def test_3(arg):
pass
"""
)
result = pytester.runpytest("--collect-only")
result.stdout.fnmatch_lines(
[
"*test_1*",
"*test_2*",
"*test_3*",
]
)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Two reasons:
You got the idea in your code, we need to find the actual Package and use its path. But we shouldn't assume it's the
Hmm I don't think it's OK to just skip is it?
Right, I think it should pass. We should add it as a test in the follow up PR.
When I run it on current main I get order 1-3-2, which seems good to me. Do you think the order should be 1-2-3 in this case?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we set
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we skip the key, it might seem more intuitive to the user. He/She says: I've set scope |
||
| elif scope is Scope.Module: | ||
| key = (argname, param_index, item.path) | ||
| scoped_item_path = item.path | ||
| elif scope is Scope.Class: | ||
| scoped_item_path = item.path | ||
| item_cls = item.cls # type: ignore[attr-defined] | ||
| key = (argname, param_index, item.path, item_cls) | ||
| else: | ||
| assert_never(scope) | ||
| yield key | ||
|
|
||
| param_index = cs.indices[argname] | ||
| yield FixtureArgKey(argname, param_index, scoped_item_path, item_cls) | ||
|
|
||
|
|
||
| # Algorithm for sorting on a per-parametrized resource setup basis. | ||
|
|
@@ -280,12 +290,12 @@ def get_parametrized_fixture_keys(item: nodes.Item, scope: Scope) -> Iterator[_K | |
|
|
||
|
|
||
| def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]: | ||
| argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]] = {} | ||
| items_by_argkey: Dict[Scope, Dict[_Key, Deque[nodes.Item]]] = {} | ||
| argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]] = {} | ||
| items_by_argkey: Dict[Scope, Dict[FixtureArgKey, Deque[nodes.Item]]] = {} | ||
| for scope in HIGH_SCOPES: | ||
| d: Dict[nodes.Item, Dict[_Key, None]] = {} | ||
| d: Dict[nodes.Item, Dict[FixtureArgKey, None]] = {} | ||
| argkeys_cache[scope] = d | ||
| item_d: Dict[_Key, Deque[nodes.Item]] = defaultdict(deque) | ||
| item_d: Dict[FixtureArgKey, Deque[nodes.Item]] = defaultdict(deque) | ||
| items_by_argkey[scope] = item_d | ||
| for item in items: | ||
| keys = dict.fromkeys(get_parametrized_fixture_keys(item, scope), None) | ||
|
|
@@ -301,8 +311,8 @@ def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]: | |
|
|
||
| def fix_cache_order( | ||
| item: nodes.Item, | ||
| argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]], | ||
| items_by_argkey: Dict[Scope, Dict[_Key, "Deque[nodes.Item]"]], | ||
| argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]], | ||
| items_by_argkey: Dict[Scope, Dict[FixtureArgKey, "Deque[nodes.Item]"]], | ||
| ) -> None: | ||
| for scope in HIGH_SCOPES: | ||
| for key in argkeys_cache[scope].get(item, []): | ||
|
|
@@ -311,13 +321,13 @@ def fix_cache_order( | |
|
|
||
| def reorder_items_atscope( | ||
| items: Dict[nodes.Item, None], | ||
| argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]], | ||
| items_by_argkey: Dict[Scope, Dict[_Key, "Deque[nodes.Item]"]], | ||
| argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]], | ||
|
sadra-barikbin marked this conversation as resolved.
|
||
| items_by_argkey: Dict[Scope, Dict[FixtureArgKey, "Deque[nodes.Item]"]], | ||
| scope: Scope, | ||
| ) -> Dict[nodes.Item, None]: | ||
| if scope is Scope.Function or len(items) < 3: | ||
| return items | ||
| ignore: Set[Optional[_Key]] = set() | ||
| ignore: Set[Optional[FixtureArgKey]] = set() | ||
| items_deque = deque(items) | ||
| items_done: Dict[nodes.Item, None] = {} | ||
| scoped_items_by_argkey = items_by_argkey[scope] | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.