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 typing import Deque
from _pytest.main import Session from _pytest.main import Session
from _pytest.python import CallSpec2
from _pytest.python import Function from _pytest.python import Function
from _pytest.python import Metafunc from _pytest.python import Metafunc
@ -243,26 +242,33 @@ def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]:
_Key = Tuple[object, ...] _Key = Tuple[object, ...]
def get_parametrized_fixture_keys(item: nodes.Item, scope: Scope) -> Iterator[_Key]: def get_fixture_keys(item: nodes.Item, scope: Scope) -> Iterator[_Key]:
"""Return list of keys for all parametrized arguments which match """Return list of keys for all arguments which match
the specified scope.""" the specified scope."""
assert scope is not Scope.Function assert scope is not Scope.Function
try: if hasattr(item, "_fixtureinfo"):
callspec = item.callspec # type: ignore[attr-defined] for argname in item._fixtureinfo.names_closure:
except AttributeError: if argname not in item._fixtureinfo.name2fixturedefs:
pass # We can also raise FixtureLookupError
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:
continue continue
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: if scope is Scope.Session:
key: _Key = (argname, param_index) key: _Key = (argname, param_index)
elif scope is Scope.Package: elif scope is Scope.Package:
key = (argname, param_index, item.path) key = (argname, param_index, item.path.parent)
elif scope is Scope.Module: elif scope is Scope.Module:
key = (argname, param_index, item.path) key = (argname, param_index, item.path)
elif scope is Scope.Class: elif scope is Scope.Class:
@ -288,7 +294,7 @@ def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]:
item_d: Dict[_Key, Deque[nodes.Item]] = defaultdict(deque) item_d: Dict[_Key, Deque[nodes.Item]] = defaultdict(deque)
items_by_argkey[scope] = item_d items_by_argkey[scope] = item_d
for item in items: 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: if keys:
d[item] = keys d[item] = keys
for key in keys: for key in keys:

View File

@ -2731,16 +2731,19 @@ class TestFixtureMarker:
""" """
) )
result = pytester.runpytest("-v") 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( result.stdout.fnmatch_lines(
""" """
test_dynamic_parametrized_ordering.py::test[flavor1-vxlan] PASSED test_dynamic_parametrized_ordering.py::test[flavor1-vxlan] PASSED
test_dynamic_parametrized_ordering.py::test2[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::test[flavor1-vlan] PASSED
test_dynamic_parametrized_ordering.py::test2[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.assert_outcomes(errors=1)
result.stdout.fnmatch_lines([expected]) result.stdout.fnmatch_lines([expected])
assert result.ret == ExitCode.TESTS_FAILED 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]])