Skip to content

Commit 48b0395

Browse files
sadra-barikbinbluetech
authored andcommitted
fixtures: clean up getfixtureclosure()
Some code cleanups - no functional changes.
1 parent 9c11275 commit 48b0395

File tree

2 files changed

+36
-23
lines changed

2 files changed

+36
-23
lines changed

src/_pytest/fixtures.py

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1402,6 +1402,12 @@ def _get_direct_parametrize_args(node: nodes.Node) -> Set[str]:
14021402
return parametrize_argnames
14031403

14041404

1405+
def deduplicate_names(*seqs: Iterable[str]) -> Tuple[str, ...]:
1406+
"""De-duplicate the sequence of names while keeping the original order."""
1407+
# Ideally we would use a set, but it does not preserve insertion order.
1408+
return tuple(dict.fromkeys(name for seq in seqs for name in seq))
1409+
1410+
14051411
class FixtureManager:
14061412
"""pytest fixture definitions and information is stored and managed
14071413
from this class.
@@ -1476,14 +1482,18 @@ def getfixtureinfo(
14761482
argnames = getfuncargnames(func, name=node.name, cls=cls)
14771483
else:
14781484
argnames = ()
1485+
usefixturesnames = self._getusefixturesnames(node)
1486+
autousenames = self._getautousenames(node.nodeid)
1487+
initialnames = deduplicate_names(autousenames, usefixturesnames, argnames)
14791488

1480-
usefixtures = tuple(
1481-
arg for mark in node.iter_markers(name="usefixtures") for arg in mark.args
1482-
)
1483-
initialnames = usefixtures + argnames
1484-
initialnames, names_closure, arg2fixturedefs = self.getfixtureclosure(
1485-
initialnames, node, ignore_args=_get_direct_parametrize_args(node)
1489+
direct_parametrize_args = _get_direct_parametrize_args(node)
1490+
1491+
names_closure, arg2fixturedefs = self.getfixtureclosure(
1492+
parentnode=node,
1493+
initialnames=initialnames,
1494+
ignore_args=direct_parametrize_args,
14861495
)
1496+
14871497
return FuncFixtureInfo(argnames, initialnames, names_closure, arg2fixturedefs)
14881498

14891499
def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None:
@@ -1515,12 +1525,17 @@ def _getautousenames(self, nodeid: str) -> Iterator[str]:
15151525
if basenames:
15161526
yield from basenames
15171527

1528+
def _getusefixturesnames(self, node: nodes.Item) -> Iterator[str]:
1529+
"""Return the names of usefixtures fixtures applicable to node."""
1530+
for mark in node.iter_markers(name="usefixtures"):
1531+
yield from mark.args
1532+
15181533
def getfixtureclosure(
15191534
self,
1520-
fixturenames: Tuple[str, ...],
15211535
parentnode: nodes.Node,
1536+
initialnames: Tuple[str, ...],
15221537
ignore_args: AbstractSet[str],
1523-
) -> Tuple[Tuple[str, ...], List[str], Dict[str, Sequence[FixtureDef[Any]]]]:
1538+
) -> Tuple[List[str], Dict[str, Sequence[FixtureDef[Any]]]]:
15241539
# Collect the closure of all fixtures, starting with the given
15251540
# fixturenames as the initial set. As we have to visit all
15261541
# factory definitions anyway, we also return an arg2fixturedefs
@@ -1529,19 +1544,7 @@ def getfixtureclosure(
15291544
# (discovering matching fixtures for a given name/node is expensive).
15301545

15311546
parentid = parentnode.nodeid
1532-
fixturenames_closure = list(self._getautousenames(parentid))
1533-
1534-
def merge(otherlist: Iterable[str]) -> None:
1535-
for arg in otherlist:
1536-
if arg not in fixturenames_closure:
1537-
fixturenames_closure.append(arg)
1538-
1539-
merge(fixturenames)
1540-
1541-
# At this point, fixturenames_closure contains what we call "initialnames",
1542-
# which is a set of fixturenames the function immediately requests. We
1543-
# need to return it as well, so save this.
1544-
initialnames = tuple(fixturenames_closure)
1547+
fixturenames_closure = list(initialnames)
15451548

15461549
arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {}
15471550
lastlen = -1
@@ -1555,7 +1558,9 @@ def merge(otherlist: Iterable[str]) -> None:
15551558
fixturedefs = self.getfixturedefs(argname, parentid)
15561559
if fixturedefs:
15571560
arg2fixturedefs[argname] = fixturedefs
1558-
merge(fixturedefs[-1].argnames)
1561+
for arg in fixturedefs[-1].argnames:
1562+
if arg not in fixturenames_closure:
1563+
fixturenames_closure.append(arg)
15591564

15601565
def sort_by_scope(arg_name: str) -> Scope:
15611566
try:
@@ -1566,7 +1571,7 @@ def sort_by_scope(arg_name: str) -> Scope:
15661571
return fixturedefs[-1]._scope
15671572

15681573
fixturenames_closure.sort(key=sort_by_scope, reverse=True)
1569-
return initialnames, fixturenames_closure, arg2fixturedefs
1574+
return fixturenames_closure, arg2fixturedefs
15701575

15711576
def pytest_generate_tests(self, metafunc: "Metafunc") -> None:
15721577
"""Generate new tests based on parametrized fixtures used by the given metafunc"""

testing/python/fixtures.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import pytest
77
from _pytest.compat import getfuncargnames
88
from _pytest.config import ExitCode
9+
from _pytest.fixtures import deduplicate_names
910
from _pytest.fixtures import TopRequest
1011
from _pytest.monkeypatch import MonkeyPatch
1112
from _pytest.pytester import get_public_names
@@ -4531,3 +4532,10 @@ def test_fixt(custom):
45314532
result.assert_outcomes(errors=1)
45324533
result.stdout.fnmatch_lines([expected])
45334534
assert result.ret == ExitCode.TESTS_FAILED
4535+
4536+
4537+
def test_deduplicate_names() -> None:
4538+
items = deduplicate_names("abacd")
4539+
assert items == ("a", "b", "c", "d")
4540+
items = deduplicate_names(items + ("g", "f", "g", "e", "b"))
4541+
assert items == ("a", "b", "c", "d", "g", "f", "e")

0 commit comments

Comments
 (0)