88from collections import deque
99from contextlib import suppress
1010from pathlib import Path
11+ from typing import AbstractSet
1112from typing import Any
1213from typing import Callable
1314from typing import cast
@@ -1382,7 +1383,7 @@ def pytest_addoption(parser: Parser) -> None:
13821383 )
13831384
13841385
1385- def _get_direct_parametrize_args (node : nodes .Node ) -> List [str ]:
1386+ def _get_direct_parametrize_args (node : nodes .Node ) -> Set [str ]:
13861387 """Return all direct parametrization arguments of a node, so we don't
13871388 mistake them for fixtures.
13881389
@@ -1391,17 +1392,22 @@ def _get_direct_parametrize_args(node: nodes.Node) -> List[str]:
13911392 These things are done later as well when dealing with parametrization
13921393 so this could be improved.
13931394 """
1394- parametrize_argnames : List [str ] = []
1395+ parametrize_argnames : Set [str ] = set ()
13951396 for marker in node .iter_markers (name = "parametrize" ):
13961397 if not marker .kwargs .get ("indirect" , False ):
13971398 p_argnames , _ = ParameterSet ._parse_parametrize_args (
13981399 * marker .args , ** marker .kwargs
13991400 )
1400- parametrize_argnames .extend (p_argnames )
1401-
1401+ parametrize_argnames .update (p_argnames )
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+
14051411class FixtureManager :
14061412 """pytest fixture definitions and information is stored and managed
14071413 from this class.
@@ -1454,13 +1460,12 @@ def __init__(self, session: "Session") -> None:
14541460 def getfixtureinfo (
14551461 self ,
14561462 node : nodes .Item ,
1457- func : Callable [..., object ],
1463+ func : Optional [ Callable [..., object ] ],
14581464 cls : Optional [type ],
1459- funcargs : bool = True ,
14601465 ) -> FuncFixtureInfo :
14611466 """Calculate the :class:`FuncFixtureInfo` for an item.
14621467
1463- If ``funcargs `` is false , or if the item sets an attribute
1468+ If ``func `` is None , or if the item sets an attribute
14641469 ``nofuncargs = True``, then ``func`` is not examined at all.
14651470
14661471 :param node:
@@ -1469,21 +1474,23 @@ def getfixtureinfo(
14691474 The item's function.
14701475 :param cls:
14711476 If the function is a method, the method's class.
1472- :param funcargs:
1473- Whether to look into func's parameters as fixture requests.
14741477 """
1475- if funcargs and not getattr (node , "nofuncargs" , False ):
1478+ if func is not None and not getattr (node , "nofuncargs" , False ):
14761479 argnames = getfuncargnames (func , name = node .name , cls = cls )
14771480 else :
14781481 argnames = ()
1482+ usefixturesnames = self ._getusefixturesnames (node )
1483+ autousenames = self ._getautousenames (node .nodeid )
1484+ initialnames = deduplicate_names (autousenames , usefixturesnames , argnames )
14791485
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 )
1486+ direct_parametrize_args = _get_direct_parametrize_args ( node )
1487+
1488+ names_closure , arg2fixturedefs = self . getfixtureclosure (
1489+ parentnode = node ,
1490+ initialnames = initialnames ,
1491+ ignore_args = direct_parametrize_args ,
14861492 )
1493+
14871494 return FuncFixtureInfo (argnames , initialnames , names_closure , arg2fixturedefs )
14881495
14891496 def pytest_plugin_registered (self , plugin : _PluggyPlugin ) -> None :
@@ -1515,12 +1522,17 @@ def _getautousenames(self, nodeid: str) -> Iterator[str]:
15151522 if basenames :
15161523 yield from basenames
15171524
1525+ def _getusefixturesnames (self , node : nodes .Item ) -> Iterator [str ]:
1526+ """Return the names of usefixtures fixtures applicable to node."""
1527+ for mark in node .iter_markers (name = "usefixtures" ):
1528+ yield from mark .args
1529+
15181530 def getfixtureclosure (
15191531 self ,
1520- fixturenames : Tuple [str , ...],
15211532 parentnode : nodes .Node ,
1522- ignore_args : Sequence [str ] = (),
1523- ) -> Tuple [Tuple [str , ...], List [str ], Dict [str , Sequence [FixtureDef [Any ]]]]:
1533+ initialnames : Tuple [str , ...],
1534+ ignore_args : AbstractSet [str ],
1535+ ) -> Tuple [List [str ], Dict [str , Sequence [FixtureDef [Any ]]]]:
15241536 # Collect the closure of all fixtures, starting with the given
15251537 # fixturenames as the initial set. As we have to visit all
15261538 # factory definitions anyway, we also return an arg2fixturedefs
@@ -1529,19 +1541,7 @@ def getfixtureclosure(
15291541 # (discovering matching fixtures for a given name/node is expensive).
15301542
15311543 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 )
1544+ fixturenames_closure = list (initialnames )
15451545
15461546 arg2fixturedefs : Dict [str , Sequence [FixtureDef [Any ]]] = {}
15471547 lastlen = - 1
@@ -1555,7 +1555,9 @@ def merge(otherlist: Iterable[str]) -> None:
15551555 fixturedefs = self .getfixturedefs (argname , parentid )
15561556 if fixturedefs :
15571557 arg2fixturedefs [argname ] = fixturedefs
1558- merge (fixturedefs [- 1 ].argnames )
1558+ for arg in fixturedefs [- 1 ].argnames :
1559+ if arg not in fixturenames_closure :
1560+ fixturenames_closure .append (arg )
15591561
15601562 def sort_by_scope (arg_name : str ) -> Scope :
15611563 try :
@@ -1566,7 +1568,7 @@ def sort_by_scope(arg_name: str) -> Scope:
15661568 return fixturedefs [- 1 ]._scope
15671569
15681570 fixturenames_closure .sort (key = sort_by_scope , reverse = True )
1569- return initialnames , fixturenames_closure , arg2fixturedefs
1571+ return fixturenames_closure , arg2fixturedefs
15701572
15711573 def pytest_generate_tests (self , metafunc : "Metafunc" ) -> None :
15721574 """Generate new tests based on parametrized fixtures used by the given metafunc"""
0 commit comments