Skip to content

Commit 120be26

Browse files
Improve solution
Now we precisely prune only when there's dynamic direct parametrization.
1 parent 57ad1e8 commit 120be26

File tree

2 files changed

+30
-28
lines changed

2 files changed

+30
-28
lines changed

src/_pytest/python.py

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from pathlib import Path
1515
from typing import Any
1616
from typing import Callable
17+
from typing import cast
1718
from typing import Dict
1819
from typing import final
1920
from typing import Generator
@@ -381,11 +382,6 @@ class _EmptyClass: pass # noqa: E701
381382
# fmt: on
382383

383384

384-
def check_if_test_is_dynamically_parametrized(metafunc):
385-
if metafunc._calls:
386-
setattr(metafunc, "has_dynamic_parametrize", True)
387-
388-
389385
class PyCollector(PyobjMixin, nodes.Collector):
390386
def funcnamefilter(self, name: str) -> bool:
391387
return self._matches_prefix_or_glob_option("python_functions", name)
@@ -490,7 +486,16 @@ def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]:
490486
module=module,
491487
_ispytest=True,
492488
)
493-
methods = [check_if_test_is_dynamically_parametrized]
489+
490+
def prune_dependency_tree_if_test_is_directly_parametrized(metafunc: Metafunc):
491+
# Direct (those with `indirect=False`) parametrizations taking place in
492+
# module/class-specific `pytest_generate_tests` hooks, a.k.a dynamic direct
493+
# parametrizations, may have shadowed some fixtures so make sure we update what
494+
# the function really needs.
495+
if metafunc.has_direct_parametrization:
496+
metafunc.update_dependency_tree()
497+
498+
methods = [prune_dependency_tree_if_test_is_directly_parametrized]
494499
if hasattr(module, "pytest_generate_tests"):
495500
methods.append(module.pytest_generate_tests)
496501
if cls is not None and hasattr(cls, "pytest_generate_tests"):
@@ -503,23 +508,6 @@ def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]:
503508
if not metafunc._calls:
504509
yield Function.from_parent(self, name=name, fixtureinfo=fixtureinfo)
505510
else:
506-
if hasattr(metafunc, "has_dynamic_parametrize"):
507-
# Parametrizations takeing place in module/class-specific `pytest_generate_tests`
508-
# hooks, a.k.a dynamic parametrizations, may have shadowed some fixtures
509-
# so make sure we update what the function really needs.
510-
#
511-
# Note that we didn't need to do this if only indirect dynamic parametrization had
512-
# taken place i.e. with `indirect=True`, but anyway we did it as differentiating
513-
# between direct and indirect requires a dirty hack.
514-
fm = self.session._fixturemanager
515-
fixture_closure, _ = fm.getfixtureclosure(
516-
definition,
517-
fixtureinfo.initialnames,
518-
fixtureinfo.name2fixturedefs,
519-
ignore_args=_get_direct_parametrize_args(definition),
520-
)
521-
fixtureinfo.names_closure[:] = fixture_closure
522-
523511
for callspec in metafunc._calls:
524512
subname = f"{name}[{callspec.id}]"
525513
yield Function.from_parent(
@@ -1232,6 +1220,9 @@ def __init__(
12321220
# Result of parametrize().
12331221
self._calls: List[CallSpec2] = []
12341222

1223+
# Whether it's ever been directly parametrized, i.e. with `indirect=False`.
1224+
self.has_direct_parametrization = False
1225+
12351226
def parametrize(
12361227
self,
12371228
argnames: Union[str, Sequence[str]],
@@ -1380,6 +1371,7 @@ def parametrize(
13801371
for argname in argnames:
13811372
if arg_directness[argname] == "indirect":
13821373
continue
1374+
self.has_direct_parametrization = True
13831375
if name2pseudofixturedef is not None and argname in name2pseudofixturedef:
13841376
fixturedef = name2pseudofixturedef[argname]
13851377
else:
@@ -1549,6 +1541,21 @@ def _validate_if_using_arg_names(
15491541
pytrace=False,
15501542
)
15511543

1544+
def update_dependency_tree(self) -> None:
1545+
definition = self.definition
1546+
(
1547+
fixture_closure,
1548+
_,
1549+
) = cast(
1550+
nodes.Node, definition.parent
1551+
).session._fixturemanager.getfixtureclosure(
1552+
definition,
1553+
definition._fixtureinfo.initialnames,
1554+
definition._fixtureinfo.name2fixturedefs,
1555+
ignore_args=_get_direct_parametrize_args(definition),
1556+
)
1557+
definition._fixtureinfo.names_closure[:] = fixture_closure
1558+
15521559

15531560
def _find_parametrized_scope(
15541561
argnames: Sequence[str],

testing/python/fixtures.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4534,11 +4534,6 @@ def test_fixt(custom):
45344534
assert result.ret == ExitCode.TESTS_FAILED
45354535

45364536

4537-
@pytest.mark.xfail(
4538-
reason="fixtureclosure should get updated before fixtures.py::pytest_generate_tests"
4539-
" and after modifying arg2fixturedefs when there's direct"
4540-
" dynamic parametrize. This gets solved by PR#11220"
4541-
)
45424537
def test_fixture_info_after_dynamic_parametrize(pytester: Pytester) -> None:
45434538
pytester.makeconftest(
45444539
"""

0 commit comments

Comments
 (0)