Skip to content

Commit e8a8a5f

Browse files
python: fix scope assignment for indirect parameter sets (#11277)
Previously, when assigning a scope for a fully-indirect parameter set, when there are multiple fixturedefs for a param (i.e. same-name fixture chain), the highest scope was used, but it should be the lowest scope, since that's the effective scope of the fixture.
1 parent 1c04a92 commit e8a8a5f

File tree

4 files changed

+67
-3
lines changed

4 files changed

+67
-3
lines changed

changelog/11277.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fixed a bug that when there are multiple fixtures for an indirect parameter,
2+
the scope of the highest-scope fixture is picked for the parameter set, instead of that of the one with the narrowest scope.

src/_pytest/fixtures.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ def node(self):
492492
node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem
493493
elif scope is Scope.Package:
494494
# FIXME: _fixturedef is not defined on FixtureRequest (this class),
495-
# but on FixtureRequest (a subclass).
495+
# but on SubRequest (a subclass).
496496
node = get_scope_package(self._pyfuncitem, self._fixturedef) # type: ignore[attr-defined]
497497
else:
498498
node = get_scope_node(self._pyfuncitem, scope)

src/_pytest/python.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1516,7 +1516,7 @@ def _find_parametrized_scope(
15161516
if all_arguments_are_fixtures:
15171517
fixturedefs = arg2fixturedefs or {}
15181518
used_scopes = [
1519-
fixturedef[0]._scope
1519+
fixturedef[-1]._scope
15201520
for name, fixturedef in fixturedefs.items()
15211521
if name in argnames
15221522
]
@@ -1682,7 +1682,7 @@ class Function(PyobjMixin, nodes.Item):
16821682
:param config:
16831683
The pytest Config object.
16841684
:param callspec:
1685-
If given, this is function has been parametrized and the callspec contains
1685+
If given, this function has been parametrized and the callspec contains
16861686
meta information about the parametrization.
16871687
:param callobj:
16881688
If given, the object which will be called when the Function is invoked,

testing/python/metafunc.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ class DummyFixtureDef:
151151
module_fix=[DummyFixtureDef(Scope.Module)],
152152
class_fix=[DummyFixtureDef(Scope.Class)],
153153
func_fix=[DummyFixtureDef(Scope.Function)],
154+
mixed_fix=[DummyFixtureDef(Scope.Module), DummyFixtureDef(Scope.Class)],
154155
),
155156
)
156157

@@ -187,6 +188,7 @@ def find_scope(argnames, indirect):
187188
)
188189
== Scope.Module
189190
)
191+
assert find_scope(["mixed_fix"], indirect=True) == Scope.Class
190192

191193
def test_parametrize_and_id(self) -> None:
192194
def func(x, y):
@@ -1503,6 +1505,66 @@ def test_it(x): pass
15031505
result = pytester.runpytest()
15041506
assert result.ret == 0
15051507

1508+
def test_reordering_with_scopeless_and_just_indirect_parametrization(
1509+
self, pytester: Pytester
1510+
) -> None:
1511+
pytester.makeconftest(
1512+
"""
1513+
import pytest
1514+
1515+
@pytest.fixture(scope="package")
1516+
def fixture1():
1517+
pass
1518+
"""
1519+
)
1520+
pytester.makepyfile(
1521+
"""
1522+
import pytest
1523+
1524+
@pytest.fixture(scope="module")
1525+
def fixture0():
1526+
pass
1527+
1528+
@pytest.fixture(scope="module")
1529+
def fixture1(fixture0):
1530+
pass
1531+
1532+
@pytest.mark.parametrize("fixture1", [0], indirect=True)
1533+
def test_0(fixture1):
1534+
pass
1535+
1536+
@pytest.fixture(scope="module")
1537+
def fixture():
1538+
pass
1539+
1540+
@pytest.mark.parametrize("fixture", [0], indirect=True)
1541+
def test_1(fixture):
1542+
pass
1543+
1544+
def test_2():
1545+
pass
1546+
1547+
class Test:
1548+
@pytest.fixture(scope="class")
1549+
def fixture(self, fixture):
1550+
pass
1551+
1552+
@pytest.mark.parametrize("fixture", [0], indirect=True)
1553+
def test_3(self, fixture):
1554+
pass
1555+
"""
1556+
)
1557+
result = pytester.runpytest("-v")
1558+
assert result.ret == 0
1559+
result.stdout.fnmatch_lines(
1560+
[
1561+
"*test_0*",
1562+
"*test_1*",
1563+
"*test_2*",
1564+
"*test_3*",
1565+
]
1566+
)
1567+
15061568

15071569
class TestMetafuncFunctionalAuto:
15081570
"""Tests related to automatically find out the correct scope for

0 commit comments

Comments
 (0)