Skip to content

Commit 20ea215

Browse files
Fix a bug in tests;Add docstring to them;Apply review comments
1 parent 7c56b16 commit 20ea215

File tree

3 files changed

+40
-28
lines changed

3 files changed

+40
-28
lines changed

src/_pytest/fixtures.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,7 +1074,7 @@ def __repr__(self) -> str:
10741074
)
10751075

10761076

1077-
class IdentityFixture(FixtureDef[FixtureValue]):
1077+
class IdentityFixtureDef(FixtureDef[FixtureValue]):
10781078
def __init__(
10791079
self,
10801080
fixturemanager: "FixtureManager",
@@ -1476,11 +1476,11 @@ def getfixtureinfo(
14761476
initialnames = deduplicate_names(autousenames, usefixturesnames, argnames)
14771477

14781478
direct_parametrize_args = _get_direct_parametrize_args(node)
1479-
1480-
names_closure, arg2fixturedefs = self.getfixtureclosure(
1479+
arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {}
1480+
names_closure = self.getfixtureclosure(
14811481
parentnode=node,
14821482
initialnames=initialnames,
1483-
arg2fixturedefs=None,
1483+
arg2fixturedefs=arg2fixturedefs,
14841484
ignore_args=direct_parametrize_args,
14851485
)
14861486

@@ -1524,9 +1524,9 @@ def getfixtureclosure(
15241524
self,
15251525
parentnode: nodes.Node,
15261526
initialnames: Tuple[str, ...],
1527-
arg2fixturedefs: Union[Dict[str, Sequence[FixtureDef[Any]]], None],
1527+
arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]],
15281528
ignore_args: AbstractSet[str],
1529-
) -> Tuple[List[str], Dict[str, Sequence[FixtureDef[Any]]]]:
1529+
) -> List[str]:
15301530
# Collect the closure of all fixtures, starting with the given
15311531
# initialnames containing function arguments, `usefixture` markers
15321532
# and `autouse` fixtures as the initial set. As we have to visit all
@@ -1535,8 +1535,6 @@ def getfixtureclosure(
15351535
# not have to re-discover fixturedefs again for each fixturename
15361536
# (discovering matching fixtures for a given name/node is expensive).
15371537

1538-
if arg2fixturedefs is None:
1539-
arg2fixturedefs = {}
15401538
parentid = parentnode.nodeid
15411539
fixturenames_closure = list(initialnames)
15421540

@@ -1552,7 +1550,7 @@ def getfixtureclosure(
15521550
arg2fixturedefs[argname] = fixturedefs
15531551
else:
15541552
fixturedefs = arg2fixturedefs[argname]
1555-
if fixturedefs and not isinstance(fixturedefs[-1], IdentityFixture):
1553+
if fixturedefs and not isinstance(fixturedefs[-1], IdentityFixtureDef):
15561554
for arg in fixturedefs[-1].argnames:
15571555
if arg not in fixturenames_closure:
15581556
fixturenames_closure.append(arg)
@@ -1566,7 +1564,7 @@ def sort_by_scope(arg_name: str) -> Scope:
15661564
return fixturedefs[-1]._scope
15671565

15681566
fixturenames_closure.sort(key=sort_by_scope, reverse=True)
1569-
return fixturenames_closure, arg2fixturedefs
1567+
return fixturenames_closure
15701568

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

src/_pytest/python.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
from pathlib import Path
1515
from typing import Any
1616
from typing import Callable
17-
from typing import cast
1817
from typing import Dict
1918
from typing import final
2019
from typing import Generator
@@ -63,7 +62,7 @@
6362
from _pytest.fixtures import FixtureDef
6463
from _pytest.fixtures import FuncFixtureInfo
6564
from _pytest.fixtures import get_scope_node
66-
from _pytest.fixtures import IdentityFixture
65+
from _pytest.fixtures import IdentityFixtureDef
6766
from _pytest.main import Session
6867
from _pytest.mark import MARK_GEN
6968
from _pytest.mark import ParameterSet
@@ -492,10 +491,14 @@ def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]:
492491
def prune_dependency_tree_if_test_is_directly_parametrized(metafunc: Metafunc):
493492
# Direct (those with `indirect=False`) parametrizations taking place in
494493
# module/class-specific `pytest_generate_tests` hooks, a.k.a dynamic direct
495-
# parametrizations, may have shadowed some fixtures so make sure we update what
496-
# the function really needs.
497-
if metafunc.has_direct_parametrization:
498-
metafunc.update_dependency_tree()
494+
# parametrizations using `metafunc.parametrize`, may have shadowed some
495+
# fixtures, making some fixtures no longer reachable. Update the dependency
496+
# tree to reflect what the item really needs.
497+
# Note that direct parametrizations using `@pytest.mark.parametrize` have
498+
# already been considered into making the closure using the `ignore_args`
499+
# arg to `getfixtureclosure`.
500+
if metafunc._has_direct_parametrization:
501+
metafunc._update_dependency_tree()
499502

500503
methods = [prune_dependency_tree_if_test_is_directly_parametrized]
501504
if hasattr(module, "pytest_generate_tests"):
@@ -1223,7 +1226,7 @@ def __init__(
12231226
self._calls: List[CallSpec2] = []
12241227

12251228
# Whether it's ever been directly parametrized, i.e. with `indirect=False`.
1226-
self.has_direct_parametrization = False
1229+
self._has_direct_parametrization = False
12271230

12281231
def parametrize(
12291232
self,
@@ -1373,11 +1376,11 @@ def parametrize(
13731376
for argname in argnames:
13741377
if arg_directness[argname] == "indirect":
13751378
continue
1376-
self.has_direct_parametrization = True
1379+
self._has_direct_parametrization = True
13771380
if name2pseudofixturedef is not None and argname in name2pseudofixturedef:
13781381
fixturedef = name2pseudofixturedef[argname]
13791382
else:
1380-
fixturedef = IdentityFixture(
1383+
fixturedef = IdentityFixtureDef(
13811384
self.definition.session._fixturemanager,
13821385
argname,
13831386
scope_,
@@ -1546,10 +1549,11 @@ def _validate_if_using_arg_names(
15461549
pytrace=False,
15471550
)
15481551

1549-
def update_dependency_tree(self) -> None:
1552+
def _update_dependency_tree(self) -> None:
15501553
definition = self.definition
1551-
fm = cast(nodes.Node, definition.parent).session._fixturemanager
1552-
fixture_closure, _ = fm.getfixtureclosure(
1554+
assert definition.parent is not None
1555+
fm = definition.parent.session._fixturemanager
1556+
fixture_closure = fm.getfixtureclosure(
15531557
parentnode=definition,
15541558
initialnames=definition._fixtureinfo.initialnames,
15551559
arg2fixturedefs=definition._fixtureinfo.name2fixturedefs,

testing/python/fixtures.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4535,6 +4535,11 @@ def test_fixt(custom):
45354535

45364536

45374537
def test_fixture_info_after_dynamic_parametrize(pytester: Pytester) -> None:
4538+
"""
4539+
Item dependency tree should get prunned before `FixtureManager::pytest_generate_tests`
4540+
hook implementation because it attempts to parametrize on the fixtures in the
4541+
fixture closure.
4542+
"""
45384543
pytester.makeconftest(
45394544
"""
45404545
import pytest
@@ -4559,11 +4564,7 @@ def pytest_generate_tests(metafunc):
45594564
metafunc.parametrize("fixture2", [4, 5], scope='session')
45604565
45614566
@pytest.fixture(scope='session')
4562-
def fixture4():
4563-
pass
4564-
4565-
@pytest.fixture(scope='session')
4566-
def fixture2(fixture3, fixture4):
4567+
def fixture2(fixture3):
45674568
pass
45684569
45694570
def test(fixture2):
@@ -4575,6 +4576,8 @@ def test(fixture2):
45754576

45764577

45774578
def test_reordering_after_dynamic_parametrize(pytester: Pytester):
4579+
"""Make sure that prunning dependency tree takes place correctly, regarding from
4580+
reordering's viewpoint."""
45784581
pytester.makepyfile(
45794582
"""
45804583
import pytest
@@ -4583,7 +4586,7 @@ def pytest_generate_tests(metafunc):
45834586
if metafunc.definition.name == "test_0":
45844587
metafunc.parametrize("fixture2", [0])
45854588
4586-
@pytest.fixture(scope='module')
4589+
@pytest.fixture(scope='module', params=[0])
45874590
def fixture1():
45884591
pass
45894592
@@ -4615,6 +4618,9 @@ def test_2(fixture1):
46154618
def test_request_shouldnt_be_in_closure_after_pruning_dep_tree_when_its_not_in_initial_closure(
46164619
pytester: Pytester,
46174620
):
4621+
"""Make sure that fixture `request` doesn't show up in the closure after prunning dependency
4622+
tree when it has not been there beforehand.
4623+
"""
46184624
pytester.makepyfile(
46194625
"""
46204626
import pytest
@@ -4641,6 +4647,10 @@ def test(fixture, arg):
46414647
def test_dont_recompute_dependency_tree_if_no_direct_dynamic_parametrize(
46424648
pytester: Pytester,
46434649
):
4650+
"""We should not update item's dependency tree when there's no direct dynamic
4651+
parametrization, i.e. `metafunc.parametrize(indirect=False)`s in module/class specific
4652+
`pytest_generate_tests` hooks, for the sake of efficiency.
4653+
"""
46444654
pytester.makeconftest(
46454655
"""
46464656
import pytest

0 commit comments

Comments
 (0)