fixtures: clean up getfixtureclosure()

Some code cleanups - no functional changes.
This commit is contained in:
Sadra Barikbin 2023-09-04 23:44:49 +03:00 committed by Ran Benita
parent 9c11275553
commit 48b0395648
2 changed files with 36 additions and 23 deletions

View File

@ -1402,6 +1402,12 @@ def _get_direct_parametrize_args(node: nodes.Node) -> Set[str]:
return parametrize_argnames return parametrize_argnames
def deduplicate_names(*seqs: Iterable[str]) -> Tuple[str, ...]:
"""De-duplicate the sequence of names while keeping the original order."""
# Ideally we would use a set, but it does not preserve insertion order.
return tuple(dict.fromkeys(name for seq in seqs for name in seq))
class FixtureManager: class FixtureManager:
"""pytest fixture definitions and information is stored and managed """pytest fixture definitions and information is stored and managed
from this class. from this class.
@ -1476,14 +1482,18 @@ class FixtureManager:
argnames = getfuncargnames(func, name=node.name, cls=cls) argnames = getfuncargnames(func, name=node.name, cls=cls)
else: else:
argnames = () argnames = ()
usefixturesnames = self._getusefixturesnames(node)
autousenames = self._getautousenames(node.nodeid)
initialnames = deduplicate_names(autousenames, usefixturesnames, argnames)
usefixtures = tuple( direct_parametrize_args = _get_direct_parametrize_args(node)
arg for mark in node.iter_markers(name="usefixtures") for arg in mark.args
) names_closure, arg2fixturedefs = self.getfixtureclosure(
initialnames = usefixtures + argnames parentnode=node,
initialnames, names_closure, arg2fixturedefs = self.getfixtureclosure( initialnames=initialnames,
initialnames, node, ignore_args=_get_direct_parametrize_args(node) ignore_args=direct_parametrize_args,
) )
return FuncFixtureInfo(argnames, initialnames, names_closure, arg2fixturedefs) return FuncFixtureInfo(argnames, initialnames, names_closure, arg2fixturedefs)
def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None: def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None:
@ -1515,12 +1525,17 @@ class FixtureManager:
if basenames: if basenames:
yield from basenames yield from basenames
def _getusefixturesnames(self, node: nodes.Item) -> Iterator[str]:
"""Return the names of usefixtures fixtures applicable to node."""
for mark in node.iter_markers(name="usefixtures"):
yield from mark.args
def getfixtureclosure( def getfixtureclosure(
self, self,
fixturenames: Tuple[str, ...],
parentnode: nodes.Node, parentnode: nodes.Node,
initialnames: Tuple[str, ...],
ignore_args: AbstractSet[str], ignore_args: AbstractSet[str],
) -> Tuple[Tuple[str, ...], List[str], Dict[str, Sequence[FixtureDef[Any]]]]: ) -> Tuple[List[str], Dict[str, Sequence[FixtureDef[Any]]]]:
# Collect the closure of all fixtures, starting with the given # Collect the closure of all fixtures, starting with the given
# fixturenames as the initial set. As we have to visit all # fixturenames as the initial set. As we have to visit all
# factory definitions anyway, we also return an arg2fixturedefs # factory definitions anyway, we also return an arg2fixturedefs
@ -1529,19 +1544,7 @@ class FixtureManager:
# (discovering matching fixtures for a given name/node is expensive). # (discovering matching fixtures for a given name/node is expensive).
parentid = parentnode.nodeid parentid = parentnode.nodeid
fixturenames_closure = list(self._getautousenames(parentid)) fixturenames_closure = list(initialnames)
def merge(otherlist: Iterable[str]) -> None:
for arg in otherlist:
if arg not in fixturenames_closure:
fixturenames_closure.append(arg)
merge(fixturenames)
# At this point, fixturenames_closure contains what we call "initialnames",
# which is a set of fixturenames the function immediately requests. We
# need to return it as well, so save this.
initialnames = tuple(fixturenames_closure)
arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {} arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {}
lastlen = -1 lastlen = -1
@ -1555,7 +1558,9 @@ class FixtureManager:
fixturedefs = self.getfixturedefs(argname, parentid) fixturedefs = self.getfixturedefs(argname, parentid)
if fixturedefs: if fixturedefs:
arg2fixturedefs[argname] = fixturedefs arg2fixturedefs[argname] = fixturedefs
merge(fixturedefs[-1].argnames) for arg in fixturedefs[-1].argnames:
if arg not in fixturenames_closure:
fixturenames_closure.append(arg)
def sort_by_scope(arg_name: str) -> Scope: def sort_by_scope(arg_name: str) -> Scope:
try: try:
@ -1566,7 +1571,7 @@ class FixtureManager:
return fixturedefs[-1]._scope return fixturedefs[-1]._scope
fixturenames_closure.sort(key=sort_by_scope, reverse=True) fixturenames_closure.sort(key=sort_by_scope, reverse=True)
return initialnames, fixturenames_closure, arg2fixturedefs return fixturenames_closure, arg2fixturedefs
def pytest_generate_tests(self, metafunc: "Metafunc") -> None: def pytest_generate_tests(self, metafunc: "Metafunc") -> None:
"""Generate new tests based on parametrized fixtures used by the given metafunc""" """Generate new tests based on parametrized fixtures used by the given metafunc"""

View File

@ -6,6 +6,7 @@ from pathlib import Path
import pytest import pytest
from _pytest.compat import getfuncargnames from _pytest.compat import getfuncargnames
from _pytest.config import ExitCode from _pytest.config import ExitCode
from _pytest.fixtures import deduplicate_names
from _pytest.fixtures import TopRequest from _pytest.fixtures import TopRequest
from _pytest.monkeypatch import MonkeyPatch from _pytest.monkeypatch import MonkeyPatch
from _pytest.pytester import get_public_names from _pytest.pytester import get_public_names
@ -4531,3 +4532,10 @@ 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_deduplicate_names() -> None:
items = deduplicate_names("abacd")
assert items == ("a", "b", "c", "d")
items = deduplicate_names(items + ("g", "f", "g", "e", "b"))
assert items == ("a", "b", "c", "d", "g", "f", "e")