Skip to content

Commit c61cc9a

Browse files
Fix a bug
1 parent d508df7 commit c61cc9a

File tree

3 files changed

+31
-23
lines changed

3 files changed

+31
-23
lines changed

src/_pytest/fixtures.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,11 +1492,10 @@ def getfixtureinfo(
14921492
tuple(self._getautousenames(node.nodeid)) + usefixtures + argnames
14931493
)
14941494

1495-
arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {}
1496-
names_closure = self.getfixtureclosure(
1495+
names_closure, arg2fixturedefs = self.getfixtureclosure(
14971496
node,
14981497
initialnames,
1499-
arg2fixturedefs,
1498+
None,
15001499
ignore_args=_get_direct_parametrize_args(node),
15011500
)
15021501
return FuncFixtureInfo(
@@ -1539,9 +1538,9 @@ def getfixtureclosure(
15391538
self,
15401539
parentnode: nodes.Node,
15411540
initialnames: Tuple[str, ...],
1542-
arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]],
1541+
arg2fixturedefs: Union[Dict[str, Sequence[FixtureDef[Any]]], None],
15431542
ignore_args: Sequence[str] = (),
1544-
) -> List[str]:
1543+
) -> Tuple[List[str], Dict[str, Sequence[FixtureDef[Any]]]]:
15451544
# Collect the closure of all fixtures, starting with the given
15461545
# initialnames containing function arguments, `usefixture` markers
15471546
# and `autouse` fixtures as the initial set. As we have to visit all
@@ -1552,6 +1551,8 @@ def getfixtureclosure(
15521551

15531552
fixturenames_closure = initialnames
15541553

1554+
if arg2fixturedefs is None:
1555+
arg2fixturedefs = {}
15551556
lastlen = -1
15561557
parentid = parentnode.nodeid
15571558
while lastlen != len(fixturenames_closure):
@@ -1576,7 +1577,10 @@ def sort_by_scope(arg_name: str) -> Scope:
15761577
else:
15771578
return fixturedefs[-1]._scope
15781579

1579-
return sorted(fixturenames_closure, key=sort_by_scope, reverse=True)
1580+
return (
1581+
sorted(fixturenames_closure, key=sort_by_scope, reverse=True),
1582+
arg2fixturedefs,
1583+
)
15801584

15811585
def pytest_generate_tests(self, metafunc: "Metafunc") -> None:
15821586
"""Generate new tests based on parametrized fixtures used by the given metafunc"""

src/_pytest/python.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -378,21 +378,9 @@ class _EmptyClass: pass # noqa: E701
378378
# fmt: on
379379

380380

381-
def prune_dependency_tree_if_test_is_dynamically_parametrized(metafunc):
381+
def check_if_test_is_dynamically_parametrized(metafunc):
382382
if metafunc._calls:
383-
# Dynamic direct parametrization may have shadowed some fixtures
384-
# so make sure we update what the function really needs. Note that
385-
# we didn't need to do this if only indirect dynamic parametrization
386-
# had taken place, but anyway we did it as differentiating between direct
387-
# and indirect requires a dirty hack.
388-
definition = metafunc.definition
389-
fixture_closure = definition.parent.session._fixturemanager.getfixtureclosure(
390-
definition,
391-
definition._fixtureinfo.initialnames,
392-
definition._fixtureinfo.name2fixturedefs,
393-
ignore_args=_get_direct_parametrize_args(definition) + ["request"],
394-
)
395-
definition._fixtureinfo.names_closure[:] = fixture_closure
383+
setattr(metafunc, "has_dynamic_parametrize", True)
396384

397385

398386
class PyCollector(PyobjMixin, nodes.Collector):
@@ -499,7 +487,7 @@ def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]:
499487
module=module,
500488
_ispytest=True,
501489
)
502-
methods = [prune_dependency_tree_if_test_is_dynamically_parametrized]
490+
methods = [check_if_test_is_dynamically_parametrized]
503491
if hasattr(module, "pytest_generate_tests"):
504492
methods.append(module.pytest_generate_tests)
505493
if cls is not None and hasattr(cls, "pytest_generate_tests"):
@@ -516,6 +504,20 @@ def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]:
516504
fm = self.session._fixturemanager
517505
fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm)
518506

507+
if hasattr(metafunc, "has_dynamic_parametrize"):
508+
# add_funcarg_pseudo_fixture_def may have shadowed some fixtures
509+
# due to dynamic direct parametrization so make sure we update
510+
# what the function really needs. Note that we didn't need to do this if
511+
# only indirect dynamic parametrization had taken place, but anyway we did
512+
# it as differentiating between direct and indirect requires a dirty hack.
513+
fixture_closure, _ = fm.getfixtureclosure(
514+
definition,
515+
fixtureinfo.initialnames,
516+
fixtureinfo.name2fixturedefs,
517+
ignore_args=_get_direct_parametrize_args(definition),
518+
)
519+
fixtureinfo.names_closure[:] = fixture_closure
520+
519521
for callspec in metafunc._calls:
520522
subname = f"{name}[{callspec.id}]"
521523
yield Function.from_parent(

testing/python/fixtures.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4539,7 +4539,9 @@ def test_fixt(custom):
45394539

45404540

45414541
@pytest.mark.xfail(
4542-
reason="arg2fixturedefs should get updated on dynamic parametrize. This gets solved by PR#11220"
4542+
reason="fixtureclosure should get updated before fixtures.py::pytest_generate_tests"
4543+
" and after modifying arg2fixturedefs when there's direct"
4544+
" dynamic parametrize. This gets solved by PR#11220"
45434545
)
45444546
def test_fixture_info_after_dynamic_parametrize(pytester: Pytester) -> None:
45454547
pytester.makeconftest(
@@ -4577,7 +4579,7 @@ def test(fixture2):
45774579
assert fixture2 in (4, 5)
45784580
"""
45794581
)
4580-
res = pytester.inline_run("-s")
4582+
res = pytester.inline_run()
45814583
res.assertoutcome(passed=2)
45824584

45834585

0 commit comments

Comments
 (0)