8
8
from collections import deque
9
9
from contextlib import suppress
10
10
from pathlib import Path
11
+ from typing import AbstractSet
11
12
from typing import Any
12
13
from typing import Callable
13
14
from typing import cast
@@ -1382,7 +1383,7 @@ def pytest_addoption(parser: Parser) -> None:
1382
1383
)
1383
1384
1384
1385
1385
- def _get_direct_parametrize_args (node : nodes .Node ) -> List [str ]:
1386
+ def _get_direct_parametrize_args (node : nodes .Node ) -> Set [str ]:
1386
1387
"""Return all direct parametrization arguments of a node, so we don't
1387
1388
mistake them for fixtures.
1388
1389
@@ -1391,17 +1392,22 @@ def _get_direct_parametrize_args(node: nodes.Node) -> List[str]:
1391
1392
These things are done later as well when dealing with parametrization
1392
1393
so this could be improved.
1393
1394
"""
1394
- parametrize_argnames : List [str ] = []
1395
+ parametrize_argnames : Set [str ] = set ()
1395
1396
for marker in node .iter_markers (name = "parametrize" ):
1396
1397
if not marker .kwargs .get ("indirect" , False ):
1397
1398
p_argnames , _ = ParameterSet ._parse_parametrize_args (
1398
1399
* marker .args , ** marker .kwargs
1399
1400
)
1400
- parametrize_argnames .extend (p_argnames )
1401
-
1401
+ parametrize_argnames .update (p_argnames )
1402
1402
return parametrize_argnames
1403
1403
1404
1404
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
+
1405
1411
class FixtureManager :
1406
1412
"""pytest fixture definitions and information is stored and managed
1407
1413
from this class.
@@ -1454,13 +1460,12 @@ def __init__(self, session: "Session") -> None:
1454
1460
def getfixtureinfo (
1455
1461
self ,
1456
1462
node : nodes .Item ,
1457
- func : Callable [..., object ],
1463
+ func : Optional [ Callable [..., object ] ],
1458
1464
cls : Optional [type ],
1459
- funcargs : bool = True ,
1460
1465
) -> FuncFixtureInfo :
1461
1466
"""Calculate the :class:`FuncFixtureInfo` for an item.
1462
1467
1463
- If ``funcargs `` is false , or if the item sets an attribute
1468
+ If ``func `` is None , or if the item sets an attribute
1464
1469
``nofuncargs = True``, then ``func`` is not examined at all.
1465
1470
1466
1471
:param node:
@@ -1469,21 +1474,23 @@ def getfixtureinfo(
1469
1474
The item's function.
1470
1475
:param cls:
1471
1476
If the function is a method, the method's class.
1472
- :param funcargs:
1473
- Whether to look into func's parameters as fixture requests.
1474
1477
"""
1475
- if funcargs and not getattr (node , "nofuncargs" , False ):
1478
+ if func is not None and not getattr (node , "nofuncargs" , False ):
1476
1479
argnames = getfuncargnames (func , name = node .name , cls = cls )
1477
1480
else :
1478
1481
argnames = ()
1482
+ usefixturesnames = self ._getusefixturesnames (node )
1483
+ autousenames = self ._getautousenames (node .nodeid )
1484
+ initialnames = deduplicate_names (autousenames , usefixturesnames , argnames )
1479
1485
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 ,
1486
1492
)
1493
+
1487
1494
return FuncFixtureInfo (argnames , initialnames , names_closure , arg2fixturedefs )
1488
1495
1489
1496
def pytest_plugin_registered (self , plugin : _PluggyPlugin ) -> None :
@@ -1515,12 +1522,17 @@ def _getautousenames(self, nodeid: str) -> Iterator[str]:
1515
1522
if basenames :
1516
1523
yield from basenames
1517
1524
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
+
1518
1530
def getfixtureclosure (
1519
1531
self ,
1520
- fixturenames : Tuple [str , ...],
1521
1532
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 ]]]]:
1524
1536
# Collect the closure of all fixtures, starting with the given
1525
1537
# fixturenames as the initial set. As we have to visit all
1526
1538
# factory definitions anyway, we also return an arg2fixturedefs
@@ -1529,19 +1541,7 @@ def getfixtureclosure(
1529
1541
# (discovering matching fixtures for a given name/node is expensive).
1530
1542
1531
1543
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 )
1545
1545
1546
1546
arg2fixturedefs : Dict [str , Sequence [FixtureDef [Any ]]] = {}
1547
1547
lastlen = - 1
@@ -1555,7 +1555,9 @@ def merge(otherlist: Iterable[str]) -> None:
1555
1555
fixturedefs = self .getfixturedefs (argname , parentid )
1556
1556
if fixturedefs :
1557
1557
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 )
1559
1561
1560
1562
def sort_by_scope (arg_name : str ) -> Scope :
1561
1563
try :
@@ -1566,7 +1568,7 @@ def sort_by_scope(arg_name: str) -> Scope:
1566
1568
return fixturedefs [- 1 ]._scope
1567
1569
1568
1570
fixturenames_closure .sort (key = sort_by_scope , reverse = True )
1569
- return initialnames , fixturenames_closure , arg2fixturedefs
1571
+ return fixturenames_closure , arg2fixturedefs
1570
1572
1571
1573
def pytest_generate_tests (self , metafunc : "Metafunc" ) -> None :
1572
1574
"""Generate new tests based on parametrized fixtures used by the given metafunc"""
0 commit comments