Skip to content

Commit

Permalink
Fix a bug
Browse files Browse the repository at this point in the history
  • Loading branch information
sadra-barikbin committed Jul 30, 2023
1 parent d508df7 commit c61cc9a
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 23 deletions.
16 changes: 10 additions & 6 deletions src/_pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -1492,11 +1492,10 @@ def getfixtureinfo(
tuple(self._getautousenames(node.nodeid)) + usefixtures + argnames
)

arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {}
names_closure = self.getfixtureclosure(
names_closure, arg2fixturedefs = self.getfixtureclosure(
node,
initialnames,
arg2fixturedefs,
None,
ignore_args=_get_direct_parametrize_args(node),
)
return FuncFixtureInfo(
Expand Down Expand Up @@ -1539,9 +1538,9 @@ def getfixtureclosure(
self,
parentnode: nodes.Node,
initialnames: Tuple[str, ...],
arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]],
arg2fixturedefs: Union[Dict[str, Sequence[FixtureDef[Any]]], None],
ignore_args: Sequence[str] = (),
) -> List[str]:
) -> Tuple[List[str], Dict[str, Sequence[FixtureDef[Any]]]]:
# Collect the closure of all fixtures, starting with the given
# initialnames containing function arguments, `usefixture` markers
# and `autouse` fixtures as the initial set. As we have to visit all
Expand All @@ -1552,6 +1551,8 @@ def getfixtureclosure(

fixturenames_closure = initialnames

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

return sorted(fixturenames_closure, key=sort_by_scope, reverse=True)
return (
sorted(fixturenames_closure, key=sort_by_scope, reverse=True),
arg2fixturedefs,
)

def pytest_generate_tests(self, metafunc: "Metafunc") -> None:
"""Generate new tests based on parametrized fixtures used by the given metafunc"""
Expand Down
32 changes: 17 additions & 15 deletions src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,21 +378,9 @@ class _EmptyClass: pass # noqa: E701
# fmt: on


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


class PyCollector(PyobjMixin, nodes.Collector):
Expand Down Expand Up @@ -499,7 +487,7 @@ def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]:
module=module,
_ispytest=True,
)
methods = [prune_dependency_tree_if_test_is_dynamically_parametrized]
methods = [check_if_test_is_dynamically_parametrized]
if hasattr(module, "pytest_generate_tests"):
methods.append(module.pytest_generate_tests)
if cls is not None and hasattr(cls, "pytest_generate_tests"):
Expand All @@ -516,6 +504,20 @@ def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]:
fm = self.session._fixturemanager
fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm)

if hasattr(metafunc, "has_dynamic_parametrize"):
# add_funcarg_pseudo_fixture_def may have shadowed some fixtures
# due to dynamic direct parametrization so make sure we update
# what the function really needs. Note that we didn't need to do this if
# only indirect dynamic parametrization had taken place, but anyway we did
# it as differentiating between direct and indirect requires a dirty hack.
fixture_closure, _ = fm.getfixtureclosure(
definition,
fixtureinfo.initialnames,
fixtureinfo.name2fixturedefs,
ignore_args=_get_direct_parametrize_args(definition),
)
fixtureinfo.names_closure[:] = fixture_closure

for callspec in metafunc._calls:
subname = f"{name}[{callspec.id}]"
yield Function.from_parent(
Expand Down
6 changes: 4 additions & 2 deletions testing/python/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -4539,7 +4539,9 @@ def test_fixt(custom):


@pytest.mark.xfail(
reason="arg2fixturedefs should get updated on dynamic parametrize. This gets solved by PR#11220"
reason="fixtureclosure should get updated before fixtures.py::pytest_generate_tests"
" and after modifying arg2fixturedefs when there's direct"
" dynamic parametrize. This gets solved by PR#11220"
)
def test_fixture_info_after_dynamic_parametrize(pytester: Pytester) -> None:
pytester.makeconftest(
Expand Down Expand Up @@ -4577,7 +4579,7 @@ def test(fixture2):
assert fixture2 in (4, 5)
"""
)
res = pytester.inline_run("-s")
res = pytester.inline_run()
res.assertoutcome(passed=2)


Expand Down

0 comments on commit c61cc9a

Please sign in to comment.