|
|
|
|
@@ -13,6 +13,7 @@ from typing import Any
|
|
|
|
|
from typing import Callable
|
|
|
|
|
from typing import cast
|
|
|
|
|
from typing import Dict
|
|
|
|
|
from typing import Final
|
|
|
|
|
from typing import final
|
|
|
|
|
from typing import Generator
|
|
|
|
|
from typing import Generic
|
|
|
|
|
@@ -73,6 +74,7 @@ if TYPE_CHECKING:
|
|
|
|
|
from _pytest.scope import _ScopeName
|
|
|
|
|
from _pytest.main import Session
|
|
|
|
|
from _pytest.python import CallSpec2
|
|
|
|
|
from _pytest.python import Function
|
|
|
|
|
from _pytest.python import Metafunc
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -352,17 +354,24 @@ def get_direct_param_fixture_func(request: "FixtureRequest") -> Any:
|
|
|
|
|
return request.param
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclasses.dataclass
|
|
|
|
|
@dataclasses.dataclass(frozen=True)
|
|
|
|
|
class FuncFixtureInfo:
|
|
|
|
|
__slots__ = ("argnames", "initialnames", "names_closure", "name2fixturedefs")
|
|
|
|
|
|
|
|
|
|
# Original function argument names.
|
|
|
|
|
# Original function argument names, i.e. fixture names that the function
|
|
|
|
|
# requests directly.
|
|
|
|
|
argnames: Tuple[str, ...]
|
|
|
|
|
# Argnames that function immediately requires. These include argnames +
|
|
|
|
|
# fixture names specified via usefixtures and via autouse=True in fixture
|
|
|
|
|
# definitions.
|
|
|
|
|
# Fixture names that the function immediately requires. These include
|
|
|
|
|
# argnames + fixture names specified via usefixtures and via autouse=True in
|
|
|
|
|
# fixture definitions.
|
|
|
|
|
initialnames: Tuple[str, ...]
|
|
|
|
|
# The transitive closure of the fixture names that the function requires.
|
|
|
|
|
# Note: can't include dynamic dependencies (`request.getfixturevalue` calls).
|
|
|
|
|
names_closure: List[str]
|
|
|
|
|
# A map from a fixture name in the transitive closure to the FixtureDefs
|
|
|
|
|
# matching the name which are applicable to this function.
|
|
|
|
|
# There may be multiple overriding fixtures with the same name. The
|
|
|
|
|
# sequence is ordered from furthest to closes to the function.
|
|
|
|
|
name2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]]
|
|
|
|
|
|
|
|
|
|
def prune_dependency_tree(self) -> None:
|
|
|
|
|
@@ -401,17 +410,31 @@ class FixtureRequest:
|
|
|
|
|
indirectly.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, pyfuncitem, *, _ispytest: bool = False) -> None:
|
|
|
|
|
def __init__(self, pyfuncitem: "Function", *, _ispytest: bool = False) -> None:
|
|
|
|
|
check_ispytest(_ispytest)
|
|
|
|
|
self._pyfuncitem = pyfuncitem
|
|
|
|
|
#: Fixture for which this request is being performed.
|
|
|
|
|
self.fixturename: Optional[str] = None
|
|
|
|
|
self._pyfuncitem = pyfuncitem
|
|
|
|
|
self._fixturemanager = pyfuncitem.session._fixturemanager
|
|
|
|
|
self._scope = Scope.Function
|
|
|
|
|
self._fixture_defs: Dict[str, FixtureDef[Any]] = {}
|
|
|
|
|
fixtureinfo: FuncFixtureInfo = pyfuncitem._fixtureinfo
|
|
|
|
|
self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
|
|
|
|
|
# The FixtureDefs for each fixture name requested by this item.
|
|
|
|
|
# Starts from the statically-known fixturedefs resolved during
|
|
|
|
|
# collection. Dynamically requested fixtures (using
|
|
|
|
|
# `request.getfixturevalue("foo")`) are added dynamically.
|
|
|
|
|
self._arg2fixturedefs = pyfuncitem._fixtureinfo.name2fixturedefs.copy()
|
|
|
|
|
# A fixture may override another fixture with the same name, e.g. a fixture
|
|
|
|
|
# in a module can override a fixture in a conftest, a fixture in a class can
|
|
|
|
|
# override a fixture in the module, and so on.
|
|
|
|
|
# An overriding fixture can request its own name; in this case it gets
|
|
|
|
|
# the value of the fixture it overrides, one level up.
|
|
|
|
|
# The _arg2index state keeps the current depth in the overriding chain.
|
|
|
|
|
# The fixturedefs list in _arg2fixturedefs for a given name is ordered from
|
|
|
|
|
# furthest to closest, so we use negative indexing -1, -2, ... to go from
|
|
|
|
|
# last to first.
|
|
|
|
|
self._arg2index: Dict[str, int] = {}
|
|
|
|
|
self._fixturemanager: FixtureManager = pyfuncitem.session._fixturemanager
|
|
|
|
|
# The evaluated argnames so far, mapping to the FixtureDef they resolved
|
|
|
|
|
# to.
|
|
|
|
|
self._fixture_defs: Dict[str, FixtureDef[Any]] = {}
|
|
|
|
|
# Notes on the type of `param`:
|
|
|
|
|
# -`request.param` is only defined in parametrized fixtures, and will raise
|
|
|
|
|
# AttributeError otherwise. Python typing has no notion of "undefined", so
|
|
|
|
|
@@ -466,10 +489,14 @@ class FixtureRequest:
|
|
|
|
|
fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid)
|
|
|
|
|
if fixturedefs is not None:
|
|
|
|
|
self._arg2fixturedefs[argname] = fixturedefs
|
|
|
|
|
# No fixtures defined with this name.
|
|
|
|
|
if fixturedefs is None:
|
|
|
|
|
raise FixtureLookupError(argname, self)
|
|
|
|
|
# fixturedefs list is immutable so we maintain a decreasing index.
|
|
|
|
|
# The are no fixtures with this name applicable for the function.
|
|
|
|
|
if not fixturedefs:
|
|
|
|
|
raise FixtureLookupError(argname, self)
|
|
|
|
|
index = self._arg2index.get(argname, 0) - 1
|
|
|
|
|
# The fixture requested its own name, but no remaining to override.
|
|
|
|
|
if -index > len(fixturedefs):
|
|
|
|
|
raise FixtureLookupError(argname, self)
|
|
|
|
|
self._arg2index[argname] = index
|
|
|
|
|
@@ -503,7 +530,7 @@ class FixtureRequest:
|
|
|
|
|
"""Instance (can be None) on which test function was collected."""
|
|
|
|
|
# unittest support hack, see _pytest.unittest.TestCaseFunction.
|
|
|
|
|
try:
|
|
|
|
|
return self._pyfuncitem._testcase
|
|
|
|
|
return self._pyfuncitem._testcase # type: ignore[attr-defined]
|
|
|
|
|
except AttributeError:
|
|
|
|
|
function = getattr(self, "function", None)
|
|
|
|
|
return getattr(function, "__self__", None)
|
|
|
|
|
@@ -513,7 +540,9 @@ class FixtureRequest:
|
|
|
|
|
"""Python module object where the test function was collected."""
|
|
|
|
|
if self.scope not in ("function", "class", "module"):
|
|
|
|
|
raise AttributeError(f"module not available in {self.scope}-scoped context")
|
|
|
|
|
return self._pyfuncitem.getparent(_pytest.python.Module).obj
|
|
|
|
|
mod = self._pyfuncitem.getparent(_pytest.python.Module)
|
|
|
|
|
assert mod is not None
|
|
|
|
|
return mod.obj
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def path(self) -> Path:
|
|
|
|
|
@@ -829,7 +858,9 @@ class FixtureLookupError(LookupError):
|
|
|
|
|
if msg is None:
|
|
|
|
|
fm = self.request._fixturemanager
|
|
|
|
|
available = set()
|
|
|
|
|
parentid = self.request._pyfuncitem.parent.nodeid
|
|
|
|
|
parent = self.request._pyfuncitem.parent
|
|
|
|
|
assert parent is not None
|
|
|
|
|
parentid = parent.nodeid
|
|
|
|
|
for name, fixturedefs in fm._arg2fixturedefs.items():
|
|
|
|
|
faclist = list(fm._matchfactories(fixturedefs, parentid))
|
|
|
|
|
if faclist:
|
|
|
|
|
@@ -976,15 +1007,15 @@ class FixtureDef(Generic[FixtureValue]):
|
|
|
|
|
# directory path relative to the rootdir.
|
|
|
|
|
#
|
|
|
|
|
# For other plugins, the baseid is the empty string (always matches).
|
|
|
|
|
self.baseid = baseid or ""
|
|
|
|
|
self.baseid: Final = baseid or ""
|
|
|
|
|
# Whether the fixture was found from a node or a conftest in the
|
|
|
|
|
# collection tree. Will be false for fixtures defined in non-conftest
|
|
|
|
|
# plugins.
|
|
|
|
|
self.has_location = baseid is not None
|
|
|
|
|
self.has_location: Final = baseid is not None
|
|
|
|
|
# The fixture factory function.
|
|
|
|
|
self.func = func
|
|
|
|
|
self.func: Final = func
|
|
|
|
|
# The name by which the fixture may be requested.
|
|
|
|
|
self.argname = argname
|
|
|
|
|
self.argname: Final = argname
|
|
|
|
|
if scope is None:
|
|
|
|
|
scope = Scope.Function
|
|
|
|
|
elif callable(scope):
|
|
|
|
|
@@ -993,23 +1024,23 @@ class FixtureDef(Generic[FixtureValue]):
|
|
|
|
|
scope = Scope.from_user(
|
|
|
|
|
scope, descr=f"Fixture '{func.__name__}'", where=baseid
|
|
|
|
|
)
|
|
|
|
|
self._scope = scope
|
|
|
|
|
self._scope: Final = scope
|
|
|
|
|
# If the fixture is directly parametrized, the parameter values.
|
|
|
|
|
self.params: Optional[Sequence[object]] = params
|
|
|
|
|
self.params: Final = params
|
|
|
|
|
# If the fixture is directly parametrized, a tuple of explicit IDs to
|
|
|
|
|
# assign to the parameter values, or a callable to generate an ID given
|
|
|
|
|
# a parameter value.
|
|
|
|
|
self.ids = ids
|
|
|
|
|
self.ids: Final = ids
|
|
|
|
|
# The names requested by the fixtures.
|
|
|
|
|
self.argnames = getfuncargnames(func, name=argname, is_method=unittest)
|
|
|
|
|
self.argnames: Final = getfuncargnames(func, name=argname, is_method=unittest)
|
|
|
|
|
# Whether the fixture was collected from a unittest TestCase class.
|
|
|
|
|
# Note that it really only makes sense to define autouse fixtures in
|
|
|
|
|
# unittest TestCases.
|
|
|
|
|
self.unittest = unittest
|
|
|
|
|
self.unittest: Final = unittest
|
|
|
|
|
# If the fixture was executed, the current value of the fixture.
|
|
|
|
|
# Can change if the fixture is executed with different parameters.
|
|
|
|
|
self.cached_result: Optional[_FixtureCachedResult[FixtureValue]] = None
|
|
|
|
|
self._finalizers: List[Callable[[], object]] = []
|
|
|
|
|
self._finalizers: Final[List[Callable[[], object]]] = []
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def scope(self) -> "_ScopeName":
|
|
|
|
|
@@ -1040,7 +1071,7 @@ class FixtureDef(Generic[FixtureValue]):
|
|
|
|
|
# value and remove all finalizers because they may be bound methods
|
|
|
|
|
# which will keep instances alive.
|
|
|
|
|
self.cached_result = None
|
|
|
|
|
self._finalizers = []
|
|
|
|
|
self._finalizers.clear()
|
|
|
|
|
|
|
|
|
|
def execute(self, request: SubRequest) -> FixtureValue:
|
|
|
|
|
# Get required arguments and register our own finish()
|
|
|
|
|
@@ -1417,10 +1448,14 @@ class FixtureManager:
|
|
|
|
|
def __init__(self, session: "Session") -> None:
|
|
|
|
|
self.session = session
|
|
|
|
|
self.config: Config = session.config
|
|
|
|
|
self._arg2fixturedefs: Dict[str, List[FixtureDef[Any]]] = {}
|
|
|
|
|
self._holderobjseen: Set[object] = set()
|
|
|
|
|
# Maps a fixture name (argname) to all of the FixtureDefs in the test
|
|
|
|
|
# suite/plugins defined with this name. Populated by parsefactories().
|
|
|
|
|
# TODO: The order of the FixtureDefs list of each arg is significant,
|
|
|
|
|
# explain.
|
|
|
|
|
self._arg2fixturedefs: Final[Dict[str, List[FixtureDef[Any]]]] = {}
|
|
|
|
|
self._holderobjseen: Final[Set[object]] = set()
|
|
|
|
|
# A mapping from a nodeid to a list of autouse fixtures it defines.
|
|
|
|
|
self._nodeid_autousenames: Dict[str, List[str]] = {
|
|
|
|
|
self._nodeid_autousenames: Final[Dict[str, List[str]]] = {
|
|
|
|
|
"": self.config.getini("usefixtures"),
|
|
|
|
|
}
|
|
|
|
|
session.config.pluginmanager.register(self, "funcmanage")
|
|
|
|
|
@@ -1699,11 +1734,16 @@ class FixtureManager:
|
|
|
|
|
def getfixturedefs(
|
|
|
|
|
self, argname: str, nodeid: str
|
|
|
|
|
) -> Optional[Sequence[FixtureDef[Any]]]:
|
|
|
|
|
"""Get a list of fixtures which are applicable to the given node id.
|
|
|
|
|
"""Get FixtureDefs for a fixture name which are applicable
|
|
|
|
|
to a given node.
|
|
|
|
|
|
|
|
|
|
:param str argname: Name of the fixture to search for.
|
|
|
|
|
:param str nodeid: Full node id of the requesting test.
|
|
|
|
|
:rtype: Sequence[FixtureDef]
|
|
|
|
|
Returns None if there are no fixtures at all defined with the given
|
|
|
|
|
name. (This is different from the case in which there are fixtures
|
|
|
|
|
with the given name, but none applicable to the node. In this case,
|
|
|
|
|
an empty result is returned).
|
|
|
|
|
|
|
|
|
|
:param argname: Name of the fixture to search for.
|
|
|
|
|
:param nodeid: Full node id of the requesting test.
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
fixturedefs = self._arg2fixturedefs[argname]
|
|
|
|
|
|