Implement the feature

This commit is contained in:
Sadra Barikbin 2023-07-21 04:36:47 +03:30
parent c9163402e0
commit 768f47f376
2 changed files with 102 additions and 31 deletions

View File

@ -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:

View File

@ -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]])