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.
This commit is contained in:
Sadra Barikbin 2023-08-06 17:29:54 +03:30 committed by GitHub
parent 1c04a92503
commit e8a8a5f320
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 67 additions and 3 deletions

View File

@ -0,0 +1,2 @@
Fixed a bug that when there are multiple fixtures for an indirect parameter,
the scope of the highest-scope fixture is picked for the parameter set, instead of that of the one with the narrowest scope.

View File

@ -492,7 +492,7 @@ class FixtureRequest:
node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem
elif scope is Scope.Package: elif scope is Scope.Package:
# FIXME: _fixturedef is not defined on FixtureRequest (this class), # FIXME: _fixturedef is not defined on FixtureRequest (this class),
# but on FixtureRequest (a subclass). # but on SubRequest (a subclass).
node = get_scope_package(self._pyfuncitem, self._fixturedef) # type: ignore[attr-defined] node = get_scope_package(self._pyfuncitem, self._fixturedef) # type: ignore[attr-defined]
else: else:
node = get_scope_node(self._pyfuncitem, scope) node = get_scope_node(self._pyfuncitem, scope)

View File

@ -1516,7 +1516,7 @@ def _find_parametrized_scope(
if all_arguments_are_fixtures: if all_arguments_are_fixtures:
fixturedefs = arg2fixturedefs or {} fixturedefs = arg2fixturedefs or {}
used_scopes = [ used_scopes = [
fixturedef[0]._scope fixturedef[-1]._scope
for name, fixturedef in fixturedefs.items() for name, fixturedef in fixturedefs.items()
if name in argnames if name in argnames
] ]
@ -1682,7 +1682,7 @@ class Function(PyobjMixin, nodes.Item):
:param config: :param config:
The pytest Config object. The pytest Config object.
:param callspec: :param callspec:
If given, this is function has been parametrized and the callspec contains If given, this function has been parametrized and the callspec contains
meta information about the parametrization. meta information about the parametrization.
:param callobj: :param callobj:
If given, the object which will be called when the Function is invoked, If given, the object which will be called when the Function is invoked,

View File

@ -151,6 +151,7 @@ class TestMetafunc:
module_fix=[DummyFixtureDef(Scope.Module)], module_fix=[DummyFixtureDef(Scope.Module)],
class_fix=[DummyFixtureDef(Scope.Class)], class_fix=[DummyFixtureDef(Scope.Class)],
func_fix=[DummyFixtureDef(Scope.Function)], func_fix=[DummyFixtureDef(Scope.Function)],
mixed_fix=[DummyFixtureDef(Scope.Module), DummyFixtureDef(Scope.Class)],
), ),
) )
@ -187,6 +188,7 @@ class TestMetafunc:
) )
== Scope.Module == Scope.Module
) )
assert find_scope(["mixed_fix"], indirect=True) == Scope.Class
def test_parametrize_and_id(self) -> None: def test_parametrize_and_id(self) -> None:
def func(x, y): def func(x, y):
@ -1503,6 +1505,66 @@ class TestMetafuncFunctional:
result = pytester.runpytest() result = pytester.runpytest()
assert result.ret == 0 assert result.ret == 0
def test_reordering_with_scopeless_and_just_indirect_parametrization(
self, pytester: Pytester
) -> None:
pytester.makeconftest(
"""
import pytest
@pytest.fixture(scope="package")
def fixture1():
pass
"""
)
pytester.makepyfile(
"""
import pytest
@pytest.fixture(scope="module")
def fixture0():
pass
@pytest.fixture(scope="module")
def fixture1(fixture0):
pass
@pytest.mark.parametrize("fixture1", [0], indirect=True)
def test_0(fixture1):
pass
@pytest.fixture(scope="module")
def fixture():
pass
@pytest.mark.parametrize("fixture", [0], indirect=True)
def test_1(fixture):
pass
def test_2():
pass
class Test:
@pytest.fixture(scope="class")
def fixture(self, fixture):
pass
@pytest.mark.parametrize("fixture", [0], indirect=True)
def test_3(self, fixture):
pass
"""
)
result = pytester.runpytest("-v")
assert result.ret == 0
result.stdout.fnmatch_lines(
[
"*test_0*",
"*test_1*",
"*test_2*",
"*test_3*",
]
)
class TestMetafuncFunctionalAuto: class TestMetafuncFunctionalAuto:
"""Tests related to automatically find out the correct scope for """Tests related to automatically find out the correct scope for