Implement the feature
This commit is contained in:
parent
c9163402e0
commit
768f47f376
|
@ -70,7 +70,6 @@ if TYPE_CHECKING:
|
|||
from typing import Deque
|
||||
|
||||
from _pytest.main import Session
|
||||
from _pytest.python import CallSpec2
|
||||
from _pytest.python import Function
|
||||
from _pytest.python import Metafunc
|
||||
|
||||
|
@ -243,34 +242,41 @@ def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]:
|
|||
_Key = Tuple[object, ...]
|
||||
|
||||
|
||||
def get_parametrized_fixture_keys(item: nodes.Item, scope: Scope) -> Iterator[_Key]:
|
||||
"""Return list of keys for all parametrized arguments which match
|
||||
def get_fixture_keys(item: nodes.Item, scope: Scope) -> Iterator[_Key]:
|
||||
"""Return list of keys for all arguments which match
|
||||
the specified scope."""
|
||||
assert scope is not Scope.Function
|
||||
try:
|
||||
callspec = item.callspec # type: ignore[attr-defined]
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
cs: CallSpec2 = callspec
|
||||
# cs.indices.items() is random order of argnames. Need to
|
||||
# sort this so that different calls to
|
||||
# get_parametrized_fixture_keys will be deterministic.
|
||||
for argname, param_index in sorted(cs.indices.items()):
|
||||
if cs._arg2scope[argname] != scope:
|
||||
if hasattr(item, "_fixtureinfo"):
|
||||
for argname in item._fixtureinfo.names_closure:
|
||||
if argname not in item._fixtureinfo.name2fixturedefs:
|
||||
# We can also raise FixtureLookupError
|
||||
continue
|
||||
if scope is Scope.Session:
|
||||
key: _Key = (argname, param_index)
|
||||
elif scope is Scope.Package:
|
||||
key = (argname, param_index, item.path)
|
||||
elif scope is Scope.Module:
|
||||
key = (argname, param_index, item.path)
|
||||
elif scope is Scope.Class:
|
||||
item_cls = item.cls # type: ignore[attr-defined]
|
||||
key = (argname, param_index, item.path, item_cls)
|
||||
else:
|
||||
assert_never(scope)
|
||||
yield key
|
||||
is_parametrized = (
|
||||
hasattr(item, "callspec") and argname in item.callspec._arg2scope
|
||||
)
|
||||
fixturedef = item._fixtureinfo.name2fixturedefs[argname][-1]
|
||||
# In the case item is parametrized on the `argname` with
|
||||
# a scope, it overrides that of the fixture.
|
||||
if (
|
||||
is_parametrized
|
||||
and cast(Function, item).callspec._arg2scope[argname] == scope
|
||||
) or (not is_parametrized and fixturedef._scope == scope):
|
||||
param_index = None
|
||||
if is_parametrized:
|
||||
param_index = cast(Function, item).callspec.indices[argname]
|
||||
|
||||
if scope is Scope.Session:
|
||||
key: _Key = (argname, param_index)
|
||||
elif scope is Scope.Package:
|
||||
key = (argname, param_index, item.path.parent)
|
||||
elif scope is Scope.Module:
|
||||
key = (argname, param_index, item.path)
|
||||
elif scope is Scope.Class:
|
||||
item_cls = item.cls # type: ignore[attr-defined]
|
||||
key = (argname, param_index, item.path, item_cls)
|
||||
else:
|
||||
assert_never(scope)
|
||||
yield key
|
||||
|
||||
|
||||
# Algorithm for sorting on a per-parametrized resource setup basis.
|
||||
|
@ -288,7 +294,7 @@ def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]:
|
|||
item_d: Dict[_Key, Deque[nodes.Item]] = defaultdict(deque)
|
||||
items_by_argkey[scope] = item_d
|
||||
for item in items:
|
||||
keys = dict.fromkeys(get_parametrized_fixture_keys(item, scope), None)
|
||||
keys = dict.fromkeys(get_fixture_keys(item, scope), None)
|
||||
if keys:
|
||||
d[item] = keys
|
||||
for key in keys:
|
||||
|
|
|
@ -2731,16 +2731,19 @@ class TestFixtureMarker:
|
|||
"""
|
||||
)
|
||||
result = pytester.runpytest("-v")
|
||||
# Order changed because fixture keys were sorted by their names in fixtures::get_fixture_keys
|
||||
# beforehand so encap key came before flavor. This isn't problematic here as both fixtures
|
||||
# are session-scoped but when this isn't the case, it might be problematic.
|
||||
result.stdout.fnmatch_lines(
|
||||
"""
|
||||
test_dynamic_parametrized_ordering.py::test[flavor1-vxlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test2[flavor1-vxlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test[flavor2-vxlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test2[flavor2-vxlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test[flavor2-vlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test2[flavor2-vlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test[flavor1-vlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test2[flavor1-vlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test[flavor2-vlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test2[flavor2-vlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test[flavor2-vxlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test2[flavor2-vxlan] PASSED
|
||||
"""
|
||||
)
|
||||
|
||||
|
@ -4536,3 +4539,65 @@ def test_yield_fixture_with_no_value(pytester: Pytester) -> None:
|
|||
result.assert_outcomes(errors=1)
|
||||
result.stdout.fnmatch_lines([expected])
|
||||
assert result.ret == ExitCode.TESTS_FAILED
|
||||
|
||||
|
||||
def test_reorder_with_nonparametrized_fixtures(pytester: Pytester):
|
||||
path = pytester.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def a():
|
||||
return "a"
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def b():
|
||||
return "b"
|
||||
|
||||
def test_0(a):
|
||||
pass
|
||||
|
||||
def test_1(b):
|
||||
pass
|
||||
|
||||
def test_2(a):
|
||||
pass
|
||||
|
||||
def test_3(b):
|
||||
pass
|
||||
|
||||
def test_4(b):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest(path, "-q", "--collect-only")
|
||||
result.stdout.fnmatch_lines([f"*test_{i}*" for i in [0, 2, 1, 3, 4]])
|
||||
|
||||
|
||||
def test_reorder_with_both_parametrized_and_nonparametrized_fixtures(
|
||||
pytester: Pytester,
|
||||
):
|
||||
path = pytester.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(scope='module',params=[None])
|
||||
def parametrized():
|
||||
yield
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def nonparametrized():
|
||||
yield
|
||||
|
||||
def test_0(parametrized, nonparametrized):
|
||||
pass
|
||||
|
||||
def test_1():
|
||||
pass
|
||||
|
||||
def test_2(nonparametrized):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest(path, "-q", "--collect-only")
|
||||
result.stdout.fnmatch_lines([f"*test_{i}*" for i in [0, 2, 1]])
|
||||
|
|
Loading…
Reference in New Issue