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,34 +242,41 @@ 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
if scope is Scope.Session: is_parametrized = (
key: _Key = (argname, param_index) hasattr(item, "callspec") and argname in item.callspec._arg2scope
elif scope is Scope.Package: )
key = (argname, param_index, item.path) fixturedef = item._fixtureinfo.name2fixturedefs[argname][-1]
elif scope is Scope.Module: # In the case item is parametrized on the `argname` with
key = (argname, param_index, item.path) # a scope, it overrides that of the fixture.
elif scope is Scope.Class: if (
item_cls = item.cls # type: ignore[attr-defined] is_parametrized
key = (argname, param_index, item.path, item_cls) and cast(Function, item).callspec._arg2scope[argname] == scope
else: ) or (not is_parametrized and fixturedef._scope == scope):
assert_never(scope) param_index = None
yield key 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. # 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) 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]])