Merge pull request #11209 from bluetech/fixtures-doc-comments
fixtures: some tweaks & improvements
This commit is contained in:
commit
32f480814c
|
@ -0,0 +1,2 @@
|
||||||
|
The (internal) ``FixtureDef.cached_result`` type has changed.
|
||||||
|
Now the third item ``cached_result[2]``, when set, is an exception instance instead of an exception triplet.
|
|
@ -0,0 +1 @@
|
||||||
|
:class:`~pytest.FixtureDef` is now exported as ``pytest.FixtureDef`` for typing purposes.
|
|
@ -910,7 +910,7 @@ ExitCode
|
||||||
FixtureDef
|
FixtureDef
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
.. autoclass:: _pytest.fixtures.FixtureDef()
|
.. autoclass:: pytest.FixtureDef()
|
||||||
:members:
|
:members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ def is_async_function(func: object) -> bool:
|
||||||
return iscoroutinefunction(func) or inspect.isasyncgenfunction(func)
|
return iscoroutinefunction(func) or inspect.isasyncgenfunction(func)
|
||||||
|
|
||||||
|
|
||||||
def getlocation(function, curdir: str | None = None) -> str:
|
def getlocation(function, curdir: str | os.PathLike[str] | None = None) -> str:
|
||||||
function = get_real_func(function)
|
function = get_real_func(function)
|
||||||
fn = Path(inspect.getfile(function))
|
fn = Path(inspect.getfile(function))
|
||||||
lineno = function.__code__.co_firstlineno
|
lineno = function.__code__.co_firstlineno
|
||||||
|
|
|
@ -582,7 +582,7 @@ def _setup_fixtures(doctest_item: DoctestItem) -> FixtureRequest:
|
||||||
doctest_item._fixtureinfo = fm.getfixtureinfo( # type: ignore[attr-defined]
|
doctest_item._fixtureinfo = fm.getfixtureinfo( # type: ignore[attr-defined]
|
||||||
node=doctest_item, func=func, cls=None, funcargs=False
|
node=doctest_item, func=func, cls=None, funcargs=False
|
||||||
)
|
)
|
||||||
fixture_request = FixtureRequest(doctest_item, _ispytest=True)
|
fixture_request = FixtureRequest(doctest_item, _ispytest=True) # type: ignore[arg-type]
|
||||||
fixture_request._fillfixtures()
|
fixture_request._fillfixtures()
|
||||||
return fixture_request
|
return fixture_request
|
||||||
|
|
||||||
|
|
|
@ -2,17 +2,16 @@ import dataclasses
|
||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import warnings
|
import warnings
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import TracebackType
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
from typing import cast
|
from typing import cast
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
from typing import Final
|
||||||
from typing import final
|
from typing import final
|
||||||
from typing import Generator
|
from typing import Generator
|
||||||
from typing import Generic
|
from typing import Generic
|
||||||
|
@ -26,7 +25,6 @@ from typing import overload
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
from typing import Set
|
from typing import Set
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
from typing import Type
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
@ -73,6 +71,7 @@ if TYPE_CHECKING:
|
||||||
from _pytest.scope import _ScopeName
|
from _pytest.scope import _ScopeName
|
||||||
from _pytest.main import Session
|
from _pytest.main import Session
|
||||||
from _pytest.python import CallSpec2
|
from _pytest.python import CallSpec2
|
||||||
|
from _pytest.python import Function
|
||||||
from _pytest.python import Metafunc
|
from _pytest.python import Metafunc
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,8 +96,8 @@ _FixtureCachedResult = Union[
|
||||||
None,
|
None,
|
||||||
# Cache key.
|
# Cache key.
|
||||||
object,
|
object,
|
||||||
# Exc info if raised.
|
# Exception if raised.
|
||||||
Tuple[Type[BaseException], BaseException, TracebackType],
|
BaseException,
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -217,6 +216,7 @@ def add_funcarg_pseudo_fixture_def(
|
||||||
params=valuelist,
|
params=valuelist,
|
||||||
unittest=False,
|
unittest=False,
|
||||||
ids=None,
|
ids=None,
|
||||||
|
_ispytest=True,
|
||||||
)
|
)
|
||||||
arg2fixturedefs[argname] = [fixturedef]
|
arg2fixturedefs[argname] = [fixturedef]
|
||||||
if name2pseudofixturedef is not None:
|
if name2pseudofixturedef is not None:
|
||||||
|
@ -352,17 +352,24 @@ def get_direct_param_fixture_func(request: "FixtureRequest") -> Any:
|
||||||
return request.param
|
return request.param
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass(frozen=True)
|
||||||
class FuncFixtureInfo:
|
class FuncFixtureInfo:
|
||||||
__slots__ = ("argnames", "initialnames", "names_closure", "name2fixturedefs")
|
__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: Tuple[str, ...]
|
||||||
# Argnames that function immediately requires. These include argnames +
|
# Fixture names that the function immediately requires. These include
|
||||||
# fixture names specified via usefixtures and via autouse=True in fixture
|
# argnames + fixture names specified via usefixtures and via autouse=True in
|
||||||
# definitions.
|
# fixture definitions.
|
||||||
initialnames: Tuple[str, ...]
|
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]
|
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]"]]
|
name2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]]
|
||||||
|
|
||||||
def prune_dependency_tree(self) -> None:
|
def prune_dependency_tree(self) -> None:
|
||||||
|
@ -401,17 +408,31 @@ class FixtureRequest:
|
||||||
indirectly.
|
indirectly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, pyfuncitem, *, _ispytest: bool = False) -> None:
|
def __init__(self, pyfuncitem: "Function", *, _ispytest: bool = False) -> None:
|
||||||
check_ispytest(_ispytest)
|
check_ispytest(_ispytest)
|
||||||
self._pyfuncitem = pyfuncitem
|
|
||||||
#: Fixture for which this request is being performed.
|
#: Fixture for which this request is being performed.
|
||||||
self.fixturename: Optional[str] = None
|
self.fixturename: Optional[str] = None
|
||||||
|
self._pyfuncitem = pyfuncitem
|
||||||
|
self._fixturemanager = pyfuncitem.session._fixturemanager
|
||||||
self._scope = Scope.Function
|
self._scope = Scope.Function
|
||||||
self._fixture_defs: Dict[str, FixtureDef[Any]] = {}
|
# The FixtureDefs for each fixture name requested by this item.
|
||||||
fixtureinfo: FuncFixtureInfo = pyfuncitem._fixtureinfo
|
# Starts from the statically-known fixturedefs resolved during
|
||||||
self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
|
# 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._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`:
|
# Notes on the type of `param`:
|
||||||
# -`request.param` is only defined in parametrized fixtures, and will raise
|
# -`request.param` is only defined in parametrized fixtures, and will raise
|
||||||
# AttributeError otherwise. Python typing has no notion of "undefined", so
|
# AttributeError otherwise. Python typing has no notion of "undefined", so
|
||||||
|
@ -464,12 +485,17 @@ class FixtureRequest:
|
||||||
assert self._pyfuncitem.parent is not None
|
assert self._pyfuncitem.parent is not None
|
||||||
parentid = self._pyfuncitem.parent.nodeid
|
parentid = self._pyfuncitem.parent.nodeid
|
||||||
fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid)
|
fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid)
|
||||||
# TODO: Fix this type ignore. Either add assert or adjust types.
|
if fixturedefs is not None:
|
||||||
# Can this be None here?
|
self._arg2fixturedefs[argname] = fixturedefs
|
||||||
self._arg2fixturedefs[argname] = fixturedefs # type: ignore[assignment]
|
# No fixtures defined with this name.
|
||||||
# fixturedefs list is immutable so we maintain a decreasing index.
|
if fixturedefs is None:
|
||||||
|
raise FixtureLookupError(argname, self)
|
||||||
|
# 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
|
index = self._arg2index.get(argname, 0) - 1
|
||||||
if fixturedefs is None or (-index > len(fixturedefs)):
|
# The fixture requested its own name, but no remaining to override.
|
||||||
|
if -index > len(fixturedefs):
|
||||||
raise FixtureLookupError(argname, self)
|
raise FixtureLookupError(argname, self)
|
||||||
self._arg2index[argname] = index
|
self._arg2index[argname] = index
|
||||||
return fixturedefs[index]
|
return fixturedefs[index]
|
||||||
|
@ -502,7 +528,7 @@ class FixtureRequest:
|
||||||
"""Instance (can be None) on which test function was collected."""
|
"""Instance (can be None) on which test function was collected."""
|
||||||
# unittest support hack, see _pytest.unittest.TestCaseFunction.
|
# unittest support hack, see _pytest.unittest.TestCaseFunction.
|
||||||
try:
|
try:
|
||||||
return self._pyfuncitem._testcase
|
return self._pyfuncitem._testcase # type: ignore[attr-defined]
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
function = getattr(self, "function", None)
|
function = getattr(self, "function", None)
|
||||||
return getattr(function, "__self__", None)
|
return getattr(function, "__self__", None)
|
||||||
|
@ -512,7 +538,9 @@ class FixtureRequest:
|
||||||
"""Python module object where the test function was collected."""
|
"""Python module object where the test function was collected."""
|
||||||
if self.scope not in ("function", "class", "module"):
|
if self.scope not in ("function", "class", "module"):
|
||||||
raise AttributeError(f"module not available in {self.scope}-scoped context")
|
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
|
@property
|
||||||
def path(self) -> Path:
|
def path(self) -> Path:
|
||||||
|
@ -698,7 +726,8 @@ class FixtureRequest:
|
||||||
self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest"
|
self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest"
|
||||||
) -> None:
|
) -> None:
|
||||||
# If fixture function failed it might have registered finalizers.
|
# If fixture function failed it might have registered finalizers.
|
||||||
subrequest.node.addfinalizer(lambda: fixturedef.finish(request=subrequest))
|
finalizer = functools.partial(fixturedef.finish, request=subrequest)
|
||||||
|
subrequest.node.addfinalizer(finalizer)
|
||||||
|
|
||||||
def _check_scope(
|
def _check_scope(
|
||||||
self,
|
self,
|
||||||
|
@ -827,7 +856,9 @@ class FixtureLookupError(LookupError):
|
||||||
if msg is None:
|
if msg is None:
|
||||||
fm = self.request._fixturemanager
|
fm = self.request._fixturemanager
|
||||||
available = set()
|
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():
|
for name, fixturedefs in fm._arg2fixturedefs.items():
|
||||||
faclist = list(fm._matchfactories(fixturedefs, parentid))
|
faclist = list(fm._matchfactories(fixturedefs, parentid))
|
||||||
if faclist:
|
if faclist:
|
||||||
|
@ -944,7 +975,11 @@ def _eval_scope_callable(
|
||||||
|
|
||||||
@final
|
@final
|
||||||
class FixtureDef(Generic[FixtureValue]):
|
class FixtureDef(Generic[FixtureValue]):
|
||||||
"""A container for a fixture definition."""
|
"""A container for a fixture definition.
|
||||||
|
|
||||||
|
Note: At this time, only explicitly documented fields and methods are
|
||||||
|
considered public stable API.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -958,7 +993,10 @@ class FixtureDef(Generic[FixtureValue]):
|
||||||
ids: Optional[
|
ids: Optional[
|
||||||
Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]
|
Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]
|
||||||
] = None,
|
] = None,
|
||||||
|
*,
|
||||||
|
_ispytest: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
check_ispytest(_ispytest)
|
||||||
self._fixturemanager = fixturemanager
|
self._fixturemanager = fixturemanager
|
||||||
# The "base" node ID for the fixture.
|
# The "base" node ID for the fixture.
|
||||||
#
|
#
|
||||||
|
@ -974,15 +1012,15 @@ class FixtureDef(Generic[FixtureValue]):
|
||||||
# directory path relative to the rootdir.
|
# directory path relative to the rootdir.
|
||||||
#
|
#
|
||||||
# For other plugins, the baseid is the empty string (always matches).
|
# 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
|
# Whether the fixture was found from a node or a conftest in the
|
||||||
# collection tree. Will be false for fixtures defined in non-conftest
|
# collection tree. Will be false for fixtures defined in non-conftest
|
||||||
# plugins.
|
# plugins.
|
||||||
self.has_location = baseid is not None
|
self.has_location: Final = baseid is not None
|
||||||
# The fixture factory function.
|
# The fixture factory function.
|
||||||
self.func = func
|
self.func: Final = func
|
||||||
# The name by which the fixture may be requested.
|
# The name by which the fixture may be requested.
|
||||||
self.argname = argname
|
self.argname: Final = argname
|
||||||
if scope is None:
|
if scope is None:
|
||||||
scope = Scope.Function
|
scope = Scope.Function
|
||||||
elif callable(scope):
|
elif callable(scope):
|
||||||
|
@ -991,23 +1029,23 @@ class FixtureDef(Generic[FixtureValue]):
|
||||||
scope = Scope.from_user(
|
scope = Scope.from_user(
|
||||||
scope, descr=f"Fixture '{func.__name__}'", where=baseid
|
scope, descr=f"Fixture '{func.__name__}'", where=baseid
|
||||||
)
|
)
|
||||||
self._scope = scope
|
self._scope: Final = scope
|
||||||
# If the fixture is directly parametrized, the parameter values.
|
# 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
|
# 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
|
# assign to the parameter values, or a callable to generate an ID given
|
||||||
# a parameter value.
|
# a parameter value.
|
||||||
self.ids = ids
|
self.ids: Final = ids
|
||||||
# The names requested by the fixtures.
|
# 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.
|
# Whether the fixture was collected from a unittest TestCase class.
|
||||||
# Note that it really only makes sense to define autouse fixtures in
|
# Note that it really only makes sense to define autouse fixtures in
|
||||||
# unittest TestCases.
|
# unittest TestCases.
|
||||||
self.unittest = unittest
|
self.unittest: Final = unittest
|
||||||
# If the fixture was executed, the current value of the fixture.
|
# If the fixture was executed, the current value of the fixture.
|
||||||
# Can change if the fixture is executed with different parameters.
|
# Can change if the fixture is executed with different parameters.
|
||||||
self.cached_result: Optional[_FixtureCachedResult[FixtureValue]] = None
|
self.cached_result: Optional[_FixtureCachedResult[FixtureValue]] = None
|
||||||
self._finalizers: List[Callable[[], object]] = []
|
self._finalizers: Final[List[Callable[[], object]]] = []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def scope(self) -> "_ScopeName":
|
def scope(self) -> "_ScopeName":
|
||||||
|
@ -1038,7 +1076,7 @@ class FixtureDef(Generic[FixtureValue]):
|
||||||
# value and remove all finalizers because they may be bound methods
|
# value and remove all finalizers because they may be bound methods
|
||||||
# which will keep instances alive.
|
# which will keep instances alive.
|
||||||
self.cached_result = None
|
self.cached_result = None
|
||||||
self._finalizers = []
|
self._finalizers.clear()
|
||||||
|
|
||||||
def execute(self, request: SubRequest) -> FixtureValue:
|
def execute(self, request: SubRequest) -> FixtureValue:
|
||||||
# Get required arguments and register our own finish()
|
# Get required arguments and register our own finish()
|
||||||
|
@ -1052,13 +1090,13 @@ class FixtureDef(Generic[FixtureValue]):
|
||||||
|
|
||||||
my_cache_key = self.cache_key(request)
|
my_cache_key = self.cache_key(request)
|
||||||
if self.cached_result is not None:
|
if self.cached_result is not None:
|
||||||
|
cache_key = self.cached_result[1]
|
||||||
# note: comparison with `==` can fail (or be expensive) for e.g.
|
# note: comparison with `==` can fail (or be expensive) for e.g.
|
||||||
# numpy arrays (#6497).
|
# numpy arrays (#6497).
|
||||||
cache_key = self.cached_result[1]
|
|
||||||
if my_cache_key is cache_key:
|
if my_cache_key is cache_key:
|
||||||
if self.cached_result[2] is not None:
|
if self.cached_result[2] is not None:
|
||||||
_, val, tb = self.cached_result[2]
|
exc = self.cached_result[2]
|
||||||
raise val.with_traceback(tb)
|
raise exc
|
||||||
else:
|
else:
|
||||||
result = self.cached_result[0]
|
result = self.cached_result[0]
|
||||||
return result
|
return result
|
||||||
|
@ -1123,35 +1161,17 @@ def pytest_fixture_setup(
|
||||||
my_cache_key = fixturedef.cache_key(request)
|
my_cache_key = fixturedef.cache_key(request)
|
||||||
try:
|
try:
|
||||||
result = call_fixture_func(fixturefunc, request, kwargs)
|
result = call_fixture_func(fixturefunc, request, kwargs)
|
||||||
except TEST_OUTCOME:
|
except TEST_OUTCOME as e:
|
||||||
exc_info = sys.exc_info()
|
if isinstance(e, skip.Exception) and not fixturefunc.__name__.startswith(
|
||||||
assert exc_info[0] is not None
|
"xunit_setup"
|
||||||
if isinstance(
|
):
|
||||||
exc_info[1], skip.Exception
|
e._use_item_location = True
|
||||||
) and not fixturefunc.__name__.startswith("xunit_setup"):
|
fixturedef.cached_result = (None, my_cache_key, e)
|
||||||
exc_info[1]._use_item_location = True # type: ignore[attr-defined]
|
|
||||||
fixturedef.cached_result = (None, my_cache_key, exc_info)
|
|
||||||
raise
|
raise
|
||||||
fixturedef.cached_result = (result, my_cache_key, None)
|
fixturedef.cached_result = (result, my_cache_key, None)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def _ensure_immutable_ids(
|
|
||||||
ids: Optional[Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]]
|
|
||||||
) -> Optional[Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]]:
|
|
||||||
if ids is None:
|
|
||||||
return None
|
|
||||||
if callable(ids):
|
|
||||||
return ids
|
|
||||||
return tuple(ids)
|
|
||||||
|
|
||||||
|
|
||||||
def _params_converter(
|
|
||||||
params: Optional[Iterable[object]],
|
|
||||||
) -> Optional[Tuple[object, ...]]:
|
|
||||||
return tuple(params) if params is not None else None
|
|
||||||
|
|
||||||
|
|
||||||
def wrap_function_to_error_out_if_called_directly(
|
def wrap_function_to_error_out_if_called_directly(
|
||||||
function: FixtureFunction,
|
function: FixtureFunction,
|
||||||
fixture_marker: "FixtureFunctionMarker",
|
fixture_marker: "FixtureFunctionMarker",
|
||||||
|
@ -1415,10 +1435,14 @@ class FixtureManager:
|
||||||
def __init__(self, session: "Session") -> None:
|
def __init__(self, session: "Session") -> None:
|
||||||
self.session = session
|
self.session = session
|
||||||
self.config: Config = session.config
|
self.config: Config = session.config
|
||||||
self._arg2fixturedefs: Dict[str, List[FixtureDef[Any]]] = {}
|
# Maps a fixture name (argname) to all of the FixtureDefs in the test
|
||||||
self._holderobjseen: Set[object] = set()
|
# 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.
|
# 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"),
|
"": self.config.getini("usefixtures"),
|
||||||
}
|
}
|
||||||
session.config.pluginmanager.register(self, "funcmanage")
|
session.config.pluginmanager.register(self, "funcmanage")
|
||||||
|
@ -1470,7 +1494,7 @@ class FixtureManager:
|
||||||
# Construct the base nodeid which is later used to check
|
# Construct the base nodeid which is later used to check
|
||||||
# what fixtures are visible for particular tests (as denoted
|
# what fixtures are visible for particular tests (as denoted
|
||||||
# by their test id).
|
# by their test id).
|
||||||
if p.name.startswith("conftest.py"):
|
if p.name == "conftest.py":
|
||||||
try:
|
try:
|
||||||
nodeid = str(p.parent.relative_to(self.config.rootpath))
|
nodeid = str(p.parent.relative_to(self.config.rootpath))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -1676,6 +1700,7 @@ class FixtureManager:
|
||||||
params=marker.params,
|
params=marker.params,
|
||||||
unittest=unittest,
|
unittest=unittest,
|
||||||
ids=marker.ids,
|
ids=marker.ids,
|
||||||
|
_ispytest=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
faclist = self._arg2fixturedefs.setdefault(name, [])
|
faclist = self._arg2fixturedefs.setdefault(name, [])
|
||||||
|
@ -1697,11 +1722,16 @@ class FixtureManager:
|
||||||
def getfixturedefs(
|
def getfixturedefs(
|
||||||
self, argname: str, nodeid: str
|
self, argname: str, nodeid: str
|
||||||
) -> Optional[Sequence[FixtureDef[Any]]]:
|
) -> 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.
|
Returns None if there are no fixtures at all defined with the given
|
||||||
:param str nodeid: Full node id of the requesting test.
|
name. (This is different from the case in which there are fixtures
|
||||||
:rtype: Sequence[FixtureDef]
|
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:
|
try:
|
||||||
fixturedefs = self._arg2fixturedefs[argname]
|
fixturedefs = self._arg2fixturedefs[argname]
|
||||||
|
|
|
@ -20,6 +20,7 @@ from _pytest.config.argparsing import Parser
|
||||||
from _pytest.debugging import pytestPDB as __pytestPDB
|
from _pytest.debugging import pytestPDB as __pytestPDB
|
||||||
from _pytest.doctest import DoctestItem
|
from _pytest.doctest import DoctestItem
|
||||||
from _pytest.fixtures import fixture
|
from _pytest.fixtures import fixture
|
||||||
|
from _pytest.fixtures import FixtureDef
|
||||||
from _pytest.fixtures import FixtureLookupError
|
from _pytest.fixtures import FixtureLookupError
|
||||||
from _pytest.fixtures import FixtureRequest
|
from _pytest.fixtures import FixtureRequest
|
||||||
from _pytest.fixtures import yield_fixture
|
from _pytest.fixtures import yield_fixture
|
||||||
|
@ -102,6 +103,7 @@ __all__ = [
|
||||||
"fail",
|
"fail",
|
||||||
"File",
|
"File",
|
||||||
"fixture",
|
"fixture",
|
||||||
|
"FixtureDef",
|
||||||
"FixtureLookupError",
|
"FixtureLookupError",
|
||||||
"FixtureRequest",
|
"FixtureRequest",
|
||||||
"freeze_includes",
|
"freeze_includes",
|
||||||
|
|
|
@ -699,6 +699,7 @@ class TestRequestBasic:
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
(item1,) = pytester.genitems([modcol])
|
(item1,) = pytester.genitems([modcol])
|
||||||
|
assert isinstance(item1, Function)
|
||||||
assert item1.name == "test_method"
|
assert item1.name == "test_method"
|
||||||
arg2fixturedefs = fixtures.FixtureRequest(
|
arg2fixturedefs = fixtures.FixtureRequest(
|
||||||
item1, _ispytest=True
|
item1, _ispytest=True
|
||||||
|
@ -967,6 +968,7 @@ class TestRequestBasic:
|
||||||
def test_request_getmodulepath(self, pytester: Pytester) -> None:
|
def test_request_getmodulepath(self, pytester: Pytester) -> None:
|
||||||
modcol = pytester.getmodulecol("def test_somefunc(): pass")
|
modcol = pytester.getmodulecol("def test_somefunc(): pass")
|
||||||
(item,) = pytester.genitems([modcol])
|
(item,) = pytester.genitems([modcol])
|
||||||
|
assert isinstance(item, Function)
|
||||||
req = fixtures.FixtureRequest(item, _ispytest=True)
|
req = fixtures.FixtureRequest(item, _ispytest=True)
|
||||||
assert req.path == modcol.path
|
assert req.path == modcol.path
|
||||||
|
|
||||||
|
@ -1125,6 +1127,7 @@ class TestRequestMarking:
|
||||||
pass
|
pass
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
assert isinstance(item1, Function)
|
||||||
req1 = fixtures.FixtureRequest(item1, _ispytest=True)
|
req1 = fixtures.FixtureRequest(item1, _ispytest=True)
|
||||||
assert "xfail" not in item1.keywords
|
assert "xfail" not in item1.keywords
|
||||||
req1.applymarker(pytest.mark.xfail)
|
req1.applymarker(pytest.mark.xfail)
|
||||||
|
@ -4009,6 +4012,7 @@ class TestScopeOrdering:
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
items, _ = pytester.inline_genitems()
|
items, _ = pytester.inline_genitems()
|
||||||
|
assert isinstance(items[0], Function)
|
||||||
request = FixtureRequest(items[0], _ispytest=True)
|
request = FixtureRequest(items[0], _ispytest=True)
|
||||||
assert request.fixturenames == "m1 f1".split()
|
assert request.fixturenames == "m1 f1".split()
|
||||||
|
|
||||||
|
@ -4057,6 +4061,7 @@ class TestScopeOrdering:
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
items, _ = pytester.inline_genitems()
|
items, _ = pytester.inline_genitems()
|
||||||
|
assert isinstance(items[0], Function)
|
||||||
request = FixtureRequest(items[0], _ispytest=True)
|
request = FixtureRequest(items[0], _ispytest=True)
|
||||||
# order of fixtures based on their scope and position in the parameter list
|
# order of fixtures based on their scope and position in the parameter list
|
||||||
assert (
|
assert (
|
||||||
|
@ -4084,6 +4089,7 @@ class TestScopeOrdering:
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
items, _ = pytester.inline_genitems()
|
items, _ = pytester.inline_genitems()
|
||||||
|
assert isinstance(items[0], Function)
|
||||||
request = FixtureRequest(items[0], _ispytest=True)
|
request = FixtureRequest(items[0], _ispytest=True)
|
||||||
assert request.fixturenames == "m1 f1".split()
|
assert request.fixturenames == "m1 f1".split()
|
||||||
|
|
||||||
|
@ -4117,6 +4123,7 @@ class TestScopeOrdering:
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
items, _ = pytester.inline_genitems()
|
items, _ = pytester.inline_genitems()
|
||||||
|
assert isinstance(items[0], Function)
|
||||||
request = FixtureRequest(items[0], _ispytest=True)
|
request = FixtureRequest(items[0], _ispytest=True)
|
||||||
assert request.fixturenames == "s1 m1 c1 f2 f1".split()
|
assert request.fixturenames == "s1 m1 c1 f2 f1".split()
|
||||||
|
|
||||||
|
@ -4159,6 +4166,7 @@ class TestScopeOrdering:
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
items, _ = pytester.inline_genitems()
|
items, _ = pytester.inline_genitems()
|
||||||
|
assert isinstance(items[0], Function)
|
||||||
request = FixtureRequest(items[0], _ispytest=True)
|
request = FixtureRequest(items[0], _ispytest=True)
|
||||||
assert request.fixturenames == "p_sub m_conf m_sub m_test f1".split()
|
assert request.fixturenames == "p_sub m_conf m_sub m_test f1".split()
|
||||||
|
|
||||||
|
@ -4203,6 +4211,7 @@ class TestScopeOrdering:
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
items, _ = pytester.inline_genitems()
|
items, _ = pytester.inline_genitems()
|
||||||
|
assert isinstance(items[0], Function)
|
||||||
request = FixtureRequest(items[0], _ispytest=True)
|
request = FixtureRequest(items[0], _ispytest=True)
|
||||||
assert request.fixturenames == "s1 p1 m1 m2 c1 f2 f1".split()
|
assert request.fixturenames == "s1 p1 m1 m2 c1 f2 f1".split()
|
||||||
|
|
||||||
|
|
|
@ -90,6 +90,7 @@ def test_cache_makedir(cache: pytest.Cache) -> None:
|
||||||
def test_fixturerequest_getmodulepath(pytester: pytest.Pytester) -> None:
|
def test_fixturerequest_getmodulepath(pytester: pytest.Pytester) -> None:
|
||||||
modcol = pytester.getmodulecol("def test_somefunc(): pass")
|
modcol = pytester.getmodulecol("def test_somefunc(): pass")
|
||||||
(item,) = pytester.genitems([modcol])
|
(item,) = pytester.genitems([modcol])
|
||||||
|
assert isinstance(item, pytest.Function)
|
||||||
req = pytest.FixtureRequest(item, _ispytest=True)
|
req = pytest.FixtureRequest(item, _ispytest=True)
|
||||||
assert req.path == modcol.path
|
assert req.path == modcol.path
|
||||||
assert req.fspath == modcol.fspath # type: ignore[attr-defined]
|
assert req.fspath == modcol.fspath # type: ignore[attr-defined]
|
||||||
|
|
Loading…
Reference in New Issue