Coverage for /opt/homebrew/lib/python3.11/site-packages/_pytest/fixtures.py: 62%
820 statements
« prev ^ index » next coverage.py v7.2.3, created at 2023-05-04 13:14 +0700
« prev ^ index » next coverage.py v7.2.3, created at 2023-05-04 13:14 +0700
1import functools
2import inspect
3import os
4import sys
5import warnings
6from collections import defaultdict
7from collections import deque
8from contextlib import suppress
9from pathlib import Path
10from types import TracebackType
11from typing import Any
12from typing import Callable
13from typing import cast
14from typing import Dict
15from typing import Generator
16from typing import Generic
17from typing import Iterable
18from typing import Iterator
19from typing import List
20from typing import MutableMapping
21from typing import NoReturn
22from typing import Optional
23from typing import Sequence
24from typing import Set
25from typing import Tuple
26from typing import Type
27from typing import TYPE_CHECKING
28from typing import TypeVar
29from typing import Union
31import attr
33import _pytest
34from _pytest import nodes
35from _pytest._code import getfslineno
36from _pytest._code.code import FormattedExcinfo
37from _pytest._code.code import TerminalRepr
38from _pytest._io import TerminalWriter
39from _pytest.compat import _format_args
40from _pytest.compat import _PytestWrapper
41from _pytest.compat import assert_never
42from _pytest.compat import final
43from _pytest.compat import get_real_func
44from _pytest.compat import get_real_method
45from _pytest.compat import getfuncargnames
46from _pytest.compat import getimfunc
47from _pytest.compat import getlocation
48from _pytest.compat import is_generator
49from _pytest.compat import NOTSET
50from _pytest.compat import overload
51from _pytest.compat import safe_getattr
52from _pytest.config import _PluggyPlugin
53from _pytest.config import Config
54from _pytest.config.argparsing import Parser
55from _pytest.deprecated import check_ispytest
56from _pytest.deprecated import YIELD_FIXTURE
57from _pytest.mark import Mark
58from _pytest.mark import ParameterSet
59from _pytest.mark.structures import MarkDecorator
60from _pytest.outcomes import fail
61from _pytest.outcomes import skip
62from _pytest.outcomes import TEST_OUTCOME
63from _pytest.pathlib import absolutepath
64from _pytest.pathlib import bestrelpath
65from _pytest.scope import HIGH_SCOPES
66from _pytest.scope import Scope
67from _pytest.stash import StashKey
70if TYPE_CHECKING:
71 from typing import Deque
73 from _pytest.scope import _ScopeName
74 from _pytest.main import Session
75 from _pytest.python import CallSpec2
76 from _pytest.python import Metafunc
79# The value of the fixture -- return/yield of the fixture function (type variable).
80FixtureValue = TypeVar("FixtureValue")
81# The type of the fixture function (type variable).
82FixtureFunction = TypeVar("FixtureFunction", bound=Callable[..., object])
83# The type of a fixture function (type alias generic in fixture value).
84_FixtureFunc = Union[
85 Callable[..., FixtureValue], Callable[..., Generator[FixtureValue, None, None]]
86]
87# The type of FixtureDef.cached_result (type alias generic in fixture value).
88_FixtureCachedResult = Union[
89 Tuple[
90 # The result.
91 FixtureValue,
92 # Cache key.
93 object,
94 None,
95 ],
96 Tuple[
97 None,
98 # Cache key.
99 object,
100 # Exc info if raised.
101 Tuple[Type[BaseException], BaseException, TracebackType],
102 ],
103]
106@attr.s(frozen=True, auto_attribs=True)
107class PseudoFixtureDef(Generic[FixtureValue]):
108 cached_result: "_FixtureCachedResult[FixtureValue]"
109 _scope: Scope
112def pytest_sessionstart(session: "Session") -> None:
113 session._fixturemanager = FixtureManager(session)
116def get_scope_package(node, fixturedef: "FixtureDef[object]"):
117 import pytest
119 cls = pytest.Package
120 current = node
121 fixture_package_name = "{}/{}".format(fixturedef.baseid, "__init__.py")
122 while current and (
123 type(current) is not cls or fixture_package_name != current.nodeid
124 ):
125 current = current.parent
126 if current is None:
127 return node.session
128 return current
131def get_scope_node(
132 node: nodes.Node, scope: Scope
133) -> Optional[Union[nodes.Item, nodes.Collector]]:
134 import _pytest.python
136 if scope is Scope.Function:
137 return node.getparent(nodes.Item)
138 elif scope is Scope.Class:
139 return node.getparent(_pytest.python.Class)
140 elif scope is Scope.Module:
141 return node.getparent(_pytest.python.Module)
142 elif scope is Scope.Package:
143 return node.getparent(_pytest.python.Package)
144 elif scope is Scope.Session:
145 return node.getparent(_pytest.main.Session)
146 else:
147 assert_never(scope)
150# Used for storing artificial fixturedefs for direct parametrization.
151name2pseudofixturedef_key = StashKey[Dict[str, "FixtureDef[Any]"]]()
154def add_funcarg_pseudo_fixture_def(
155 collector: nodes.Collector, metafunc: "Metafunc", fixturemanager: "FixtureManager"
156) -> None:
157 # This function will transform all collected calls to functions
158 # if they use direct funcargs (i.e. direct parametrization)
159 # because we want later test execution to be able to rely on
160 # an existing FixtureDef structure for all arguments.
161 # XXX we can probably avoid this algorithm if we modify CallSpec2
162 # to directly care for creating the fixturedefs within its methods.
163 if not metafunc._calls[0].funcargs:
164 # This function call does not have direct parametrization.
165 return
166 # Collect funcargs of all callspecs into a list of values.
167 arg2params: Dict[str, List[object]] = {}
168 arg2scope: Dict[str, Scope] = {}
169 for callspec in metafunc._calls:
170 for argname, argvalue in callspec.funcargs.items():
171 assert argname not in callspec.params
172 callspec.params[argname] = argvalue
173 arg2params_list = arg2params.setdefault(argname, [])
174 callspec.indices[argname] = len(arg2params_list)
175 arg2params_list.append(argvalue)
176 if argname not in arg2scope:
177 scope = callspec._arg2scope.get(argname, Scope.Function)
178 arg2scope[argname] = scope
179 callspec.funcargs.clear()
181 # Register artificial FixtureDef's so that later at test execution
182 # time we can rely on a proper FixtureDef to exist for fixture setup.
183 arg2fixturedefs = metafunc._arg2fixturedefs
184 for argname, valuelist in arg2params.items():
185 # If we have a scope that is higher than function, we need
186 # to make sure we only ever create an according fixturedef on
187 # a per-scope basis. We thus store and cache the fixturedef on the
188 # node related to the scope.
189 scope = arg2scope[argname]
190 node = None
191 if scope is not Scope.Function:
192 node = get_scope_node(collector, scope)
193 if node is None:
194 assert scope is Scope.Class and isinstance(
195 collector, _pytest.python.Module
196 )
197 # Use module-level collector for class-scope (for now).
198 node = collector
199 if node is None:
200 name2pseudofixturedef = None
201 else:
202 default: Dict[str, FixtureDef[Any]] = {}
203 name2pseudofixturedef = node.stash.setdefault(
204 name2pseudofixturedef_key, default
205 )
206 if name2pseudofixturedef is not None and argname in name2pseudofixturedef:
207 arg2fixturedefs[argname] = [name2pseudofixturedef[argname]]
208 else:
209 fixturedef = FixtureDef(
210 fixturemanager=fixturemanager,
211 baseid="",
212 argname=argname,
213 func=get_direct_param_fixture_func,
214 scope=arg2scope[argname],
215 params=valuelist,
216 unittest=False,
217 ids=None,
218 )
219 arg2fixturedefs[argname] = [fixturedef]
220 if name2pseudofixturedef is not None:
221 name2pseudofixturedef[argname] = fixturedef
224def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]:
225 """Return fixturemarker or None if it doesn't exist or raised
226 exceptions."""
227 return cast(
228 Optional[FixtureFunctionMarker],
229 safe_getattr(obj, "_pytestfixturefunction", None),
230 )
233# Parametrized fixture key, helper alias for code below.
234_Key = Tuple[object, ...]
237def get_parametrized_fixture_keys(item: nodes.Item, scope: Scope) -> Iterator[_Key]:
238 """Return list of keys for all parametrized arguments which match
239 the specified scope."""
240 assert scope is not Scope.Function
241 try:
242 callspec = item.callspec # type: ignore[attr-defined]
243 except AttributeError:
244 pass
245 else:
246 cs: CallSpec2 = callspec
247 # cs.indices.items() is random order of argnames. Need to
248 # sort this so that different calls to
249 # get_parametrized_fixture_keys will be deterministic.
250 for argname, param_index in sorted(cs.indices.items()):
251 if cs._arg2scope[argname] != scope:
252 continue
253 if scope is Scope.Session:
254 key: _Key = (argname, param_index)
255 elif scope is Scope.Package:
256 key = (argname, param_index, item.path.parent)
257 elif scope is Scope.Module:
258 key = (argname, param_index, item.path)
259 elif scope is Scope.Class:
260 item_cls = item.cls # type: ignore[attr-defined]
261 key = (argname, param_index, item.path, item_cls)
262 else:
263 assert_never(scope)
264 yield key
267# Algorithm for sorting on a per-parametrized resource setup basis.
268# It is called for Session scope first and performs sorting
269# down to the lower scopes such as to minimize number of "high scope"
270# setups and teardowns.
273def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]:
274 argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]] = {}
275 items_by_argkey: Dict[Scope, Dict[_Key, Deque[nodes.Item]]] = {}
276 for scope in HIGH_SCOPES:
277 d: Dict[nodes.Item, Dict[_Key, None]] = {}
278 argkeys_cache[scope] = d
279 item_d: Dict[_Key, Deque[nodes.Item]] = defaultdict(deque)
280 items_by_argkey[scope] = item_d
281 for item in items:
282 keys = dict.fromkeys(get_parametrized_fixture_keys(item, scope), None)
283 if keys:
284 d[item] = keys
285 for key in keys:
286 item_d[key].append(item)
287 items_dict = dict.fromkeys(items, None)
288 return list(
289 reorder_items_atscope(items_dict, argkeys_cache, items_by_argkey, Scope.Session)
290 )
293def fix_cache_order(
294 item: nodes.Item,
295 argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]],
296 items_by_argkey: Dict[Scope, Dict[_Key, "Deque[nodes.Item]"]],
297) -> None:
298 for scope in HIGH_SCOPES:
299 for key in argkeys_cache[scope].get(item, []):
300 items_by_argkey[scope][key].appendleft(item)
303def reorder_items_atscope(
304 items: Dict[nodes.Item, None],
305 argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]],
306 items_by_argkey: Dict[Scope, Dict[_Key, "Deque[nodes.Item]"]],
307 scope: Scope,
308) -> Dict[nodes.Item, None]:
309 if scope is Scope.Function or len(items) < 3:
310 return items
311 ignore: Set[Optional[_Key]] = set()
312 items_deque = deque(items)
313 items_done: Dict[nodes.Item, None] = {}
314 scoped_items_by_argkey = items_by_argkey[scope]
315 scoped_argkeys_cache = argkeys_cache[scope]
316 while items_deque:
317 no_argkey_group: Dict[nodes.Item, None] = {}
318 slicing_argkey = None
319 while items_deque:
320 item = items_deque.popleft()
321 if item in items_done or item in no_argkey_group:
322 continue
323 argkeys = dict.fromkeys(
324 (k for k in scoped_argkeys_cache.get(item, []) if k not in ignore), None
325 )
326 if not argkeys:
327 no_argkey_group[item] = None
328 else:
329 slicing_argkey, _ = argkeys.popitem()
330 # We don't have to remove relevant items from later in the
331 # deque because they'll just be ignored.
332 matching_items = [
333 i for i in scoped_items_by_argkey[slicing_argkey] if i in items
334 ]
335 for i in reversed(matching_items):
336 fix_cache_order(i, argkeys_cache, items_by_argkey)
337 items_deque.appendleft(i)
338 break
339 if no_argkey_group:
340 no_argkey_group = reorder_items_atscope(
341 no_argkey_group, argkeys_cache, items_by_argkey, scope.next_lower()
342 )
343 for item in no_argkey_group:
344 items_done[item] = None
345 ignore.add(slicing_argkey)
346 return items_done
349def get_direct_param_fixture_func(request: "FixtureRequest") -> Any:
350 return request.param
353@attr.s(slots=True, auto_attribs=True)
354class FuncFixtureInfo:
355 # Original function argument names.
356 argnames: Tuple[str, ...]
357 # Argnames that function immediately requires. These include argnames +
358 # fixture names specified via usefixtures and via autouse=True in fixture
359 # definitions.
360 initialnames: Tuple[str, ...]
361 names_closure: List[str]
362 name2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]]
364 def prune_dependency_tree(self) -> None:
365 """Recompute names_closure from initialnames and name2fixturedefs.
367 Can only reduce names_closure, which means that the new closure will
368 always be a subset of the old one. The order is preserved.
370 This method is needed because direct parametrization may shadow some
371 of the fixtures that were included in the originally built dependency
372 tree. In this way the dependency tree can get pruned, and the closure
373 of argnames may get reduced.
374 """
375 closure: Set[str] = set()
376 working_set = set(self.initialnames)
377 while working_set:
378 argname = working_set.pop()
379 # Argname may be smth not included in the original names_closure,
380 # in which case we ignore it. This currently happens with pseudo
381 # FixtureDefs which wrap 'get_direct_param_fixture_func(request)'.
382 # So they introduce the new dependency 'request' which might have
383 # been missing in the original tree (closure).
384 if argname not in closure and argname in self.names_closure:
385 closure.add(argname)
386 if argname in self.name2fixturedefs:
387 working_set.update(self.name2fixturedefs[argname][-1].argnames)
389 self.names_closure[:] = sorted(closure, key=self.names_closure.index)
392class FixtureRequest:
393 """A request for a fixture from a test or fixture function.
395 A request object gives access to the requesting test context and has
396 an optional ``param`` attribute in case the fixture is parametrized
397 indirectly.
398 """
400 def __init__(self, pyfuncitem, *, _ispytest: bool = False) -> None:
401 check_ispytest(_ispytest)
402 self._pyfuncitem = pyfuncitem
403 #: Fixture for which this request is being performed.
404 self.fixturename: Optional[str] = None
405 self._scope = Scope.Function
406 self._fixture_defs: Dict[str, FixtureDef[Any]] = {}
407 fixtureinfo: FuncFixtureInfo = pyfuncitem._fixtureinfo
408 self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
409 self._arg2index: Dict[str, int] = {}
410 self._fixturemanager: FixtureManager = pyfuncitem.session._fixturemanager
411 # Notes on the type of `param`:
412 # -`request.param` is only defined in parametrized fixtures, and will raise
413 # AttributeError otherwise. Python typing has no notion of "undefined", so
414 # this cannot be reflected in the type.
415 # - Technically `param` is only (possibly) defined on SubRequest, not
416 # FixtureRequest, but the typing of that is still in flux so this cheats.
417 # - In the future we might consider using a generic for the param type, but
418 # for now just using Any.
419 self.param: Any
421 @property
422 def scope(self) -> "_ScopeName":
423 """Scope string, one of "function", "class", "module", "package", "session"."""
424 return self._scope.value
426 @property
427 def fixturenames(self) -> List[str]:
428 """Names of all active fixtures in this request."""
429 result = list(self._pyfuncitem._fixtureinfo.names_closure)
430 result.extend(set(self._fixture_defs).difference(result))
431 return result
433 @property
434 def node(self):
435 """Underlying collection node (depends on current request scope)."""
436 return self._getscopeitem(self._scope)
438 def _getnextfixturedef(self, argname: str) -> "FixtureDef[Any]":
439 fixturedefs = self._arg2fixturedefs.get(argname, None)
440 if fixturedefs is None:
441 # We arrive here because of a dynamic call to
442 # getfixturevalue(argname) usage which was naturally
443 # not known at parsing/collection time.
444 assert self._pyfuncitem.parent is not None
445 parentid = self._pyfuncitem.parent.nodeid
446 fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid)
447 # TODO: Fix this type ignore. Either add assert or adjust types.
448 # Can this be None here?
449 self._arg2fixturedefs[argname] = fixturedefs # type: ignore[assignment]
450 # fixturedefs list is immutable so we maintain a decreasing index.
451 index = self._arg2index.get(argname, 0) - 1
452 if fixturedefs is None or (-index > len(fixturedefs)):
453 raise FixtureLookupError(argname, self)
454 self._arg2index[argname] = index
455 return fixturedefs[index]
457 @property
458 def config(self) -> Config:
459 """The pytest config object associated with this request."""
460 return self._pyfuncitem.config # type: ignore[no-any-return]
462 @property
463 def function(self):
464 """Test function object if the request has a per-function scope."""
465 if self.scope != "function":
466 raise AttributeError(
467 f"function not available in {self.scope}-scoped context"
468 )
469 return self._pyfuncitem.obj
471 @property
472 def cls(self):
473 """Class (can be None) where the test function was collected."""
474 if self.scope not in ("class", "function"):
475 raise AttributeError(f"cls not available in {self.scope}-scoped context")
476 clscol = self._pyfuncitem.getparent(_pytest.python.Class)
477 if clscol:
478 return clscol.obj
480 @property
481 def instance(self):
482 """Instance (can be None) on which test function was collected."""
483 # unittest support hack, see _pytest.unittest.TestCaseFunction.
484 try:
485 return self._pyfuncitem._testcase
486 except AttributeError:
487 function = getattr(self, "function", None)
488 return getattr(function, "__self__", None)
490 @property
491 def module(self):
492 """Python module object where the test function was collected."""
493 if self.scope not in ("function", "class", "module"):
494 raise AttributeError(f"module not available in {self.scope}-scoped context")
495 return self._pyfuncitem.getparent(_pytest.python.Module).obj
497 @property
498 def path(self) -> Path:
499 """Path where the test function was collected."""
500 if self.scope not in ("function", "class", "module", "package"):
501 raise AttributeError(f"path not available in {self.scope}-scoped context")
502 # TODO: Remove ignore once _pyfuncitem is properly typed.
503 return self._pyfuncitem.path # type: ignore
505 @property
506 def keywords(self) -> MutableMapping[str, Any]:
507 """Keywords/markers dictionary for the underlying node."""
508 node: nodes.Node = self.node
509 return node.keywords
511 @property
512 def session(self) -> "Session":
513 """Pytest session object."""
514 return self._pyfuncitem.session # type: ignore[no-any-return]
516 def addfinalizer(self, finalizer: Callable[[], object]) -> None:
517 """Add finalizer/teardown function to be called without arguments after
518 the last test within the requesting test context finished execution."""
519 # XXX usually this method is shadowed by fixturedef specific ones.
520 self._addfinalizer(finalizer, scope=self.scope)
522 def _addfinalizer(self, finalizer: Callable[[], object], scope) -> None:
523 node = self._getscopeitem(scope)
524 node.addfinalizer(finalizer)
526 def applymarker(self, marker: Union[str, MarkDecorator]) -> None:
527 """Apply a marker to a single test function invocation.
529 This method is useful if you don't want to have a keyword/marker
530 on all function invocations.
532 :param marker:
533 An object created by a call to ``pytest.mark.NAME(...)``.
534 """
535 self.node.add_marker(marker)
537 def raiseerror(self, msg: Optional[str]) -> NoReturn:
538 """Raise a FixtureLookupError exception.
540 :param msg:
541 An optional custom error message.
542 """
543 raise self._fixturemanager.FixtureLookupError(None, self, msg)
545 def _fillfixtures(self) -> None:
546 item = self._pyfuncitem
547 fixturenames = getattr(item, "fixturenames", self.fixturenames)
548 for argname in fixturenames:
549 if argname not in item.funcargs:
550 item.funcargs[argname] = self.getfixturevalue(argname)
552 def getfixturevalue(self, argname: str) -> Any:
553 """Dynamically run a named fixture function.
555 Declaring fixtures via function argument is recommended where possible.
556 But if you can only decide whether to use another fixture at test
557 setup time, you may use this function to retrieve it inside a fixture
558 or test function body.
560 This method can be used during the test setup phase or the test run
561 phase, but during the test teardown phase a fixture's value may not
562 be available.
564 :param argname:
565 The fixture name.
566 :raises pytest.FixtureLookupError:
567 If the given fixture could not be found.
568 """
569 fixturedef = self._get_active_fixturedef(argname)
570 assert fixturedef.cached_result is not None, (
571 f'The fixture value for "{argname}" is not available. '
572 "This can happen when the fixture has already been torn down."
573 )
574 return fixturedef.cached_result[0]
576 def _get_active_fixturedef(
577 self, argname: str
578 ) -> Union["FixtureDef[object]", PseudoFixtureDef[object]]:
579 try:
580 return self._fixture_defs[argname]
581 except KeyError:
582 try:
583 fixturedef = self._getnextfixturedef(argname)
584 except FixtureLookupError:
585 if argname == "request":
586 cached_result = (self, [0], None)
587 return PseudoFixtureDef(cached_result, Scope.Function)
588 raise
589 # Remove indent to prevent the python3 exception
590 # from leaking into the call.
591 self._compute_fixture_value(fixturedef)
592 self._fixture_defs[argname] = fixturedef
593 return fixturedef
595 def _get_fixturestack(self) -> List["FixtureDef[Any]"]:
596 current = self
597 values: List[FixtureDef[Any]] = []
598 while isinstance(current, SubRequest):
599 values.append(current._fixturedef) # type: ignore[has-type]
600 current = current._parent_request
601 values.reverse()
602 return values
604 def _compute_fixture_value(self, fixturedef: "FixtureDef[object]") -> None:
605 """Create a SubRequest based on "self" and call the execute method
606 of the given FixtureDef object.
608 This will force the FixtureDef object to throw away any previous
609 results and compute a new fixture value, which will be stored into
610 the FixtureDef object itself.
611 """
612 # prepare a subrequest object before calling fixture function
613 # (latter managed by fixturedef)
614 argname = fixturedef.argname
615 funcitem = self._pyfuncitem
616 scope = fixturedef._scope
617 try:
618 callspec = funcitem.callspec
619 except AttributeError:
620 callspec = None
621 if callspec is not None and argname in callspec.params:
622 param = callspec.params[argname]
623 param_index = callspec.indices[argname]
624 # If a parametrize invocation set a scope it will override
625 # the static scope defined with the fixture function.
626 with suppress(KeyError):
627 scope = callspec._arg2scope[argname]
628 else:
629 param = NOTSET
630 param_index = 0
631 has_params = fixturedef.params is not None
632 fixtures_not_supported = getattr(funcitem, "nofuncargs", False)
633 if has_params and fixtures_not_supported:
634 msg = (
635 "{name} does not support fixtures, maybe unittest.TestCase subclass?\n"
636 "Node id: {nodeid}\n"
637 "Function type: {typename}"
638 ).format(
639 name=funcitem.name,
640 nodeid=funcitem.nodeid,
641 typename=type(funcitem).__name__,
642 )
643 fail(msg, pytrace=False)
644 if has_params:
645 frame = inspect.stack()[3]
646 frameinfo = inspect.getframeinfo(frame[0])
647 source_path = absolutepath(frameinfo.filename)
648 source_lineno = frameinfo.lineno
649 try:
650 source_path_str = str(
651 source_path.relative_to(funcitem.config.rootpath)
652 )
653 except ValueError:
654 source_path_str = str(source_path)
655 msg = (
656 "The requested fixture has no parameter defined for test:\n"
657 " {}\n\n"
658 "Requested fixture '{}' defined in:\n{}"
659 "\n\nRequested here:\n{}:{}".format(
660 funcitem.nodeid,
661 fixturedef.argname,
662 getlocation(fixturedef.func, funcitem.config.rootpath),
663 source_path_str,
664 source_lineno,
665 )
666 )
667 fail(msg, pytrace=False)
669 subrequest = SubRequest(
670 self, scope, param, param_index, fixturedef, _ispytest=True
671 )
673 # Check if a higher-level scoped fixture accesses a lower level one.
674 subrequest._check_scope(argname, self._scope, scope)
675 try:
676 # Call the fixture function.
677 fixturedef.execute(request=subrequest)
678 finally:
679 self._schedule_finalizers(fixturedef, subrequest)
681 def _schedule_finalizers(
682 self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest"
683 ) -> None:
684 # If fixture function failed it might have registered finalizers.
685 subrequest.node.addfinalizer(lambda: fixturedef.finish(request=subrequest))
687 def _check_scope(
688 self,
689 argname: str,
690 invoking_scope: Scope,
691 requested_scope: Scope,
692 ) -> None:
693 if argname == "request":
694 return
695 if invoking_scope > requested_scope:
696 # Try to report something helpful.
697 text = "\n".join(self._factorytraceback())
698 fail(
699 f"ScopeMismatch: You tried to access the {requested_scope.value} scoped "
700 f"fixture {argname} with a {invoking_scope.value} scoped request object, "
701 f"involved factories:\n{text}",
702 pytrace=False,
703 )
705 def _factorytraceback(self) -> List[str]:
706 lines = []
707 for fixturedef in self._get_fixturestack():
708 factory = fixturedef.func
709 fs, lineno = getfslineno(factory)
710 if isinstance(fs, Path):
711 session: Session = self._pyfuncitem.session
712 p = bestrelpath(session.path, fs)
713 else:
714 p = fs
715 args = _format_args(factory)
716 lines.append("%s:%d: def %s%s" % (p, lineno + 1, factory.__name__, args))
717 return lines
719 def _getscopeitem(
720 self, scope: Union[Scope, "_ScopeName"]
721 ) -> Union[nodes.Item, nodes.Collector]:
722 if isinstance(scope, str):
723 scope = Scope(scope)
724 if scope is Scope.Function:
725 # This might also be a non-function Item despite its attribute name.
726 node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem
727 elif scope is Scope.Package:
728 # FIXME: _fixturedef is not defined on FixtureRequest (this class),
729 # but on FixtureRequest (a subclass).
730 node = get_scope_package(self._pyfuncitem, self._fixturedef) # type: ignore[attr-defined]
731 else:
732 node = get_scope_node(self._pyfuncitem, scope)
733 if node is None and scope is Scope.Class:
734 # Fallback to function item itself.
735 node = self._pyfuncitem
736 assert node, 'Could not obtain a node for scope "{}" for function {!r}'.format(
737 scope, self._pyfuncitem
738 )
739 return node
741 def __repr__(self) -> str:
742 return "<FixtureRequest for %r>" % (self.node)
745@final
746class SubRequest(FixtureRequest):
747 """A sub request for handling getting a fixture from a test function/fixture."""
749 def __init__(
750 self,
751 request: "FixtureRequest",
752 scope: Scope,
753 param: Any,
754 param_index: int,
755 fixturedef: "FixtureDef[object]",
756 *,
757 _ispytest: bool = False,
758 ) -> None:
759 check_ispytest(_ispytest)
760 self._parent_request = request
761 self.fixturename = fixturedef.argname
762 if param is not NOTSET:
763 self.param = param
764 self.param_index = param_index
765 self._scope = scope
766 self._fixturedef = fixturedef
767 self._pyfuncitem = request._pyfuncitem
768 self._fixture_defs = request._fixture_defs
769 self._arg2fixturedefs = request._arg2fixturedefs
770 self._arg2index = request._arg2index
771 self._fixturemanager = request._fixturemanager
773 def __repr__(self) -> str:
774 return f"<SubRequest {self.fixturename!r} for {self._pyfuncitem!r}>"
776 def addfinalizer(self, finalizer: Callable[[], object]) -> None:
777 """Add finalizer/teardown function to be called without arguments after
778 the last test within the requesting test context finished execution."""
779 self._fixturedef.addfinalizer(finalizer)
781 def _schedule_finalizers(
782 self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest"
783 ) -> None:
784 # If the executing fixturedef was not explicitly requested in the argument list (via
785 # getfixturevalue inside the fixture call) then ensure this fixture def will be finished
786 # first.
787 if fixturedef.argname not in self.fixturenames:
788 fixturedef.addfinalizer(
789 functools.partial(self._fixturedef.finish, request=self)
790 )
791 super()._schedule_finalizers(fixturedef, subrequest)
794@final
795class FixtureLookupError(LookupError):
796 """Could not return a requested fixture (missing or invalid)."""
798 def __init__(
799 self, argname: Optional[str], request: FixtureRequest, msg: Optional[str] = None
800 ) -> None:
801 self.argname = argname
802 self.request = request
803 self.fixturestack = request._get_fixturestack()
804 self.msg = msg
806 def formatrepr(self) -> "FixtureLookupErrorRepr":
807 tblines: List[str] = []
808 addline = tblines.append
809 stack = [self.request._pyfuncitem.obj]
810 stack.extend(map(lambda x: x.func, self.fixturestack))
811 msg = self.msg
812 if msg is not None:
813 # The last fixture raise an error, let's present
814 # it at the requesting side.
815 stack = stack[:-1]
816 for function in stack:
817 fspath, lineno = getfslineno(function)
818 try:
819 lines, _ = inspect.getsourcelines(get_real_func(function))
820 except (OSError, IndexError, TypeError):
821 error_msg = "file %s, line %s: source code not available"
822 addline(error_msg % (fspath, lineno + 1))
823 else:
824 addline(f"file {fspath}, line {lineno + 1}")
825 for i, line in enumerate(lines):
826 line = line.rstrip()
827 addline(" " + line)
828 if line.lstrip().startswith("def"):
829 break
831 if msg is None:
832 fm = self.request._fixturemanager
833 available = set()
834 parentid = self.request._pyfuncitem.parent.nodeid
835 for name, fixturedefs in fm._arg2fixturedefs.items():
836 faclist = list(fm._matchfactories(fixturedefs, parentid))
837 if faclist:
838 available.add(name)
839 if self.argname in available:
840 msg = " recursive dependency involving fixture '{}' detected".format(
841 self.argname
842 )
843 else:
844 msg = f"fixture '{self.argname}' not found"
845 msg += "\n available fixtures: {}".format(", ".join(sorted(available)))
846 msg += "\n use 'pytest --fixtures [testpath]' for help on them."
848 return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname)
851class FixtureLookupErrorRepr(TerminalRepr):
852 def __init__(
853 self,
854 filename: Union[str, "os.PathLike[str]"],
855 firstlineno: int,
856 tblines: Sequence[str],
857 errorstring: str,
858 argname: Optional[str],
859 ) -> None:
860 self.tblines = tblines
861 self.errorstring = errorstring
862 self.filename = filename
863 self.firstlineno = firstlineno
864 self.argname = argname
866 def toterminal(self, tw: TerminalWriter) -> None:
867 # tw.line("FixtureLookupError: %s" %(self.argname), red=True)
868 for tbline in self.tblines:
869 tw.line(tbline.rstrip())
870 lines = self.errorstring.split("\n")
871 if lines:
872 tw.line(
873 f"{FormattedExcinfo.fail_marker} {lines[0].strip()}",
874 red=True,
875 )
876 for line in lines[1:]:
877 tw.line(
878 f"{FormattedExcinfo.flow_marker} {line.strip()}",
879 red=True,
880 )
881 tw.line()
882 tw.line("%s:%d" % (os.fspath(self.filename), self.firstlineno + 1))
885def fail_fixturefunc(fixturefunc, msg: str) -> NoReturn:
886 fs, lineno = getfslineno(fixturefunc)
887 location = f"{fs}:{lineno + 1}"
888 source = _pytest._code.Source(fixturefunc)
889 fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, pytrace=False)
892def call_fixture_func(
893 fixturefunc: "_FixtureFunc[FixtureValue]", request: FixtureRequest, kwargs
894) -> FixtureValue:
895 if is_generator(fixturefunc):
896 fixturefunc = cast(
897 Callable[..., Generator[FixtureValue, None, None]], fixturefunc
898 )
899 generator = fixturefunc(**kwargs)
900 try:
901 fixture_result = next(generator)
902 except StopIteration:
903 raise ValueError(f"{request.fixturename} did not yield a value") from None
904 finalizer = functools.partial(_teardown_yield_fixture, fixturefunc, generator)
905 request.addfinalizer(finalizer)
906 else:
907 fixturefunc = cast(Callable[..., FixtureValue], fixturefunc)
908 fixture_result = fixturefunc(**kwargs)
909 return fixture_result
912def _teardown_yield_fixture(fixturefunc, it) -> None:
913 """Execute the teardown of a fixture function by advancing the iterator
914 after the yield and ensure the iteration ends (if not it means there is
915 more than one yield in the function)."""
916 try:
917 next(it)
918 except StopIteration:
919 pass
920 else:
921 fail_fixturefunc(fixturefunc, "fixture function has more than one 'yield'")
924def _eval_scope_callable(
925 scope_callable: "Callable[[str, Config], _ScopeName]",
926 fixture_name: str,
927 config: Config,
928) -> "_ScopeName":
929 try:
930 # Type ignored because there is no typing mechanism to specify
931 # keyword arguments, currently.
932 result = scope_callable(fixture_name=fixture_name, config=config) # type: ignore[call-arg]
933 except Exception as e:
934 raise TypeError(
935 "Error evaluating {} while defining fixture '{}'.\n"
936 "Expected a function with the signature (*, fixture_name, config)".format(
937 scope_callable, fixture_name
938 )
939 ) from e
940 if not isinstance(result, str):
941 fail(
942 "Expected {} to return a 'str' while defining fixture '{}', but it returned:\n"
943 "{!r}".format(scope_callable, fixture_name, result),
944 pytrace=False,
945 )
946 return result
949@final
950class FixtureDef(Generic[FixtureValue]):
951 """A container for a fixture definition."""
953 def __init__(
954 self,
955 fixturemanager: "FixtureManager",
956 baseid: Optional[str],
957 argname: str,
958 func: "_FixtureFunc[FixtureValue]",
959 scope: Union[Scope, "_ScopeName", Callable[[str, Config], "_ScopeName"], None],
960 params: Optional[Sequence[object]],
961 unittest: bool = False,
962 ids: Optional[
963 Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]
964 ] = None,
965 ) -> None:
966 self._fixturemanager = fixturemanager
967 # The "base" node ID for the fixture.
968 #
969 # This is a node ID prefix. A fixture is only available to a node (e.g.
970 # a `Function` item) if the fixture's baseid is a parent of the node's
971 # nodeid (see the `iterparentnodeids` function for what constitutes a
972 # "parent" and a "prefix" in this context).
973 #
974 # For a fixture found in a Collector's object (e.g. a `Module`s module,
975 # a `Class`'s class), the baseid is the Collector's nodeid.
976 #
977 # For a fixture found in a conftest plugin, the baseid is the conftest's
978 # directory path relative to the rootdir.
979 #
980 # For other plugins, the baseid is the empty string (always matches).
981 self.baseid = baseid or ""
982 # Whether the fixture was found from a node or a conftest in the
983 # collection tree. Will be false for fixtures defined in non-conftest
984 # plugins.
985 self.has_location = baseid is not None
986 # The fixture factory function.
987 self.func = func
988 # The name by which the fixture may be requested.
989 self.argname = argname
990 if scope is None:
991 scope = Scope.Function
992 elif callable(scope):
993 scope = _eval_scope_callable(scope, argname, fixturemanager.config)
994 if isinstance(scope, str):
995 scope = Scope.from_user(
996 scope, descr=f"Fixture '{func.__name__}'", where=baseid
997 )
998 self._scope = scope
999 # If the fixture is directly parametrized, the parameter values.
1000 self.params: Optional[Sequence[object]] = params
1001 # If the fixture is directly parametrized, a tuple of explicit IDs to
1002 # assign to the parameter values, or a callable to generate an ID given
1003 # a parameter value.
1004 self.ids = ids
1005 # The names requested by the fixtures.
1006 self.argnames = getfuncargnames(func, name=argname, is_method=unittest)
1007 # Whether the fixture was collected from a unittest TestCase class.
1008 # Note that it really only makes sense to define autouse fixtures in
1009 # unittest TestCases.
1010 self.unittest = unittest
1011 # If the fixture was executed, the current value of the fixture.
1012 # Can change if the fixture is executed with different parameters.
1013 self.cached_result: Optional[_FixtureCachedResult[FixtureValue]] = None
1014 self._finalizers: List[Callable[[], object]] = []
1016 @property
1017 def scope(self) -> "_ScopeName":
1018 """Scope string, one of "function", "class", "module", "package", "session"."""
1019 return self._scope.value
1021 def addfinalizer(self, finalizer: Callable[[], object]) -> None:
1022 self._finalizers.append(finalizer)
1024 def finish(self, request: SubRequest) -> None:
1025 exc = None
1026 try:
1027 while self._finalizers:
1028 try:
1029 func = self._finalizers.pop()
1030 func()
1031 except BaseException as e:
1032 # XXX Only first exception will be seen by user,
1033 # ideally all should be reported.
1034 if exc is None:
1035 exc = e
1036 if exc:
1037 raise exc
1038 finally:
1039 ihook = request.node.ihook
1040 ihook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
1041 # Even if finalization fails, we invalidate the cached fixture
1042 # value and remove all finalizers because they may be bound methods
1043 # which will keep instances alive.
1044 self.cached_result = None
1045 self._finalizers = []
1047 def execute(self, request: SubRequest) -> FixtureValue:
1048 # Get required arguments and register our own finish()
1049 # with their finalization.
1050 for argname in self.argnames:
1051 fixturedef = request._get_active_fixturedef(argname)
1052 if argname != "request":
1053 # PseudoFixtureDef is only for "request".
1054 assert isinstance(fixturedef, FixtureDef)
1055 fixturedef.addfinalizer(functools.partial(self.finish, request=request))
1057 my_cache_key = self.cache_key(request)
1058 if self.cached_result is not None:
1059 # note: comparison with `==` can fail (or be expensive) for e.g.
1060 # numpy arrays (#6497).
1061 cache_key = self.cached_result[1]
1062 if my_cache_key is cache_key:
1063 if self.cached_result[2] is not None:
1064 _, val, tb = self.cached_result[2]
1065 raise val.with_traceback(tb)
1066 else:
1067 result = self.cached_result[0]
1068 return result
1069 # We have a previous but differently parametrized fixture instance
1070 # so we need to tear it down before creating a new one.
1071 self.finish(request)
1072 assert self.cached_result is None
1074 ihook = request.node.ihook
1075 result = ihook.pytest_fixture_setup(fixturedef=self, request=request)
1076 return result
1078 def cache_key(self, request: SubRequest) -> object:
1079 return request.param_index if not hasattr(request, "param") else request.param
1081 def __repr__(self) -> str:
1082 return "<FixtureDef argname={!r} scope={!r} baseid={!r}>".format(
1083 self.argname, self.scope, self.baseid
1084 )
1087def resolve_fixture_function(
1088 fixturedef: FixtureDef[FixtureValue], request: FixtureRequest
1089) -> "_FixtureFunc[FixtureValue]":
1090 """Get the actual callable that can be called to obtain the fixture
1091 value, dealing with unittest-specific instances and bound methods."""
1092 fixturefunc = fixturedef.func
1093 if fixturedef.unittest:
1094 if request.instance is not None:
1095 # Bind the unbound method to the TestCase instance.
1096 fixturefunc = fixturedef.func.__get__(request.instance) # type: ignore[union-attr]
1097 else:
1098 # The fixture function needs to be bound to the actual
1099 # request.instance so that code working with "fixturedef" behaves
1100 # as expected.
1101 if request.instance is not None:
1102 # Handle the case where fixture is defined not in a test class, but some other class
1103 # (for example a plugin class with a fixture), see #2270.
1104 if hasattr(fixturefunc, "__self__") and not isinstance(
1105 request.instance, fixturefunc.__self__.__class__ # type: ignore[union-attr]
1106 ):
1107 return fixturefunc
1108 fixturefunc = getimfunc(fixturedef.func)
1109 if fixturefunc != fixturedef.func:
1110 fixturefunc = fixturefunc.__get__(request.instance) # type: ignore[union-attr]
1111 return fixturefunc
1114def pytest_fixture_setup(
1115 fixturedef: FixtureDef[FixtureValue], request: SubRequest
1116) -> FixtureValue:
1117 """Execution of fixture setup."""
1118 kwargs = {}
1119 for argname in fixturedef.argnames:
1120 fixdef = request._get_active_fixturedef(argname)
1121 assert fixdef.cached_result is not None
1122 result, arg_cache_key, exc = fixdef.cached_result
1123 request._check_scope(argname, request._scope, fixdef._scope)
1124 kwargs[argname] = result
1126 fixturefunc = resolve_fixture_function(fixturedef, request)
1127 my_cache_key = fixturedef.cache_key(request)
1128 try:
1129 result = call_fixture_func(fixturefunc, request, kwargs)
1130 except TEST_OUTCOME:
1131 exc_info = sys.exc_info()
1132 assert exc_info[0] is not None
1133 if isinstance(
1134 exc_info[1], skip.Exception
1135 ) and not fixturefunc.__name__.startswith("xunit_setup"):
1136 exc_info[1]._use_item_location = True # type: ignore[attr-defined]
1137 fixturedef.cached_result = (None, my_cache_key, exc_info)
1138 raise
1139 fixturedef.cached_result = (result, my_cache_key, None)
1140 return result
1143def _ensure_immutable_ids(
1144 ids: Optional[Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]]
1145) -> Optional[Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]]:
1146 if ids is None:
1147 return None
1148 if callable(ids):
1149 return ids
1150 return tuple(ids)
1153def _params_converter(
1154 params: Optional[Iterable[object]],
1155) -> Optional[Tuple[object, ...]]:
1156 return tuple(params) if params is not None else None
1159def wrap_function_to_error_out_if_called_directly(
1160 function: FixtureFunction,
1161 fixture_marker: "FixtureFunctionMarker",
1162) -> FixtureFunction:
1163 """Wrap the given fixture function so we can raise an error about it being called directly,
1164 instead of used as an argument in a test function."""
1165 message = (
1166 'Fixture "{name}" called directly. Fixtures are not meant to be called directly,\n'
1167 "but are created automatically when test functions request them as parameters.\n"
1168 "See https://docs.pytest.org/en/stable/explanation/fixtures.html for more information about fixtures, and\n"
1169 "https://docs.pytest.org/en/stable/deprecations.html#calling-fixtures-directly about how to update your code."
1170 ).format(name=fixture_marker.name or function.__name__)
1172 @functools.wraps(function)
1173 def result(*args, **kwargs):
1174 fail(message, pytrace=False)
1176 # Keep reference to the original function in our own custom attribute so we don't unwrap
1177 # further than this point and lose useful wrappings like @mock.patch (#3774).
1178 result.__pytest_wrapped__ = _PytestWrapper(function) # type: ignore[attr-defined]
1180 return cast(FixtureFunction, result)
1183@final
1184@attr.s(frozen=True, auto_attribs=True)
1185class FixtureFunctionMarker:
1186 scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]"
1187 params: Optional[Tuple[object, ...]] = attr.ib(converter=_params_converter)
1188 autouse: bool = False
1189 ids: Optional[
1190 Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]
1191 ] = attr.ib(
1192 default=None,
1193 converter=_ensure_immutable_ids,
1194 )
1195 name: Optional[str] = None
1197 def __call__(self, function: FixtureFunction) -> FixtureFunction:
1198 if inspect.isclass(function):
1199 raise ValueError("class fixtures not supported (maybe in the future)")
1201 if getattr(function, "_pytestfixturefunction", False):
1202 raise ValueError(
1203 "fixture is being applied more than once to the same function"
1204 )
1206 function = wrap_function_to_error_out_if_called_directly(function, self)
1208 name = self.name or function.__name__
1209 if name == "request":
1210 location = getlocation(function)
1211 fail(
1212 "'request' is a reserved word for fixtures, use another name:\n {}".format(
1213 location
1214 ),
1215 pytrace=False,
1216 )
1218 # Type ignored because https://github.com/python/mypy/issues/2087.
1219 function._pytestfixturefunction = self # type: ignore[attr-defined]
1220 return function
1223@overload
1224def fixture(
1225 fixture_function: FixtureFunction,
1226 *,
1227 scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ...,
1228 params: Optional[Iterable[object]] = ...,
1229 autouse: bool = ...,
1230 ids: Optional[
1231 Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
1232 ] = ...,
1233 name: Optional[str] = ...,
1234) -> FixtureFunction:
1235 ...
1238@overload
1239def fixture( # noqa: F811
1240 fixture_function: None = ...,
1241 *,
1242 scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ...,
1243 params: Optional[Iterable[object]] = ...,
1244 autouse: bool = ...,
1245 ids: Optional[
1246 Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
1247 ] = ...,
1248 name: Optional[str] = None,
1249) -> FixtureFunctionMarker:
1250 ...
1253def fixture( # noqa: F811
1254 fixture_function: Optional[FixtureFunction] = None,
1255 *,
1256 scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = "function",
1257 params: Optional[Iterable[object]] = None,
1258 autouse: bool = False,
1259 ids: Optional[
1260 Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
1261 ] = None,
1262 name: Optional[str] = None,
1263) -> Union[FixtureFunctionMarker, FixtureFunction]:
1264 """Decorator to mark a fixture factory function.
1266 This decorator can be used, with or without parameters, to define a
1267 fixture function.
1269 The name of the fixture function can later be referenced to cause its
1270 invocation ahead of running tests: test modules or classes can use the
1271 ``pytest.mark.usefixtures(fixturename)`` marker.
1273 Test functions can directly use fixture names as input arguments in which
1274 case the fixture instance returned from the fixture function will be
1275 injected.
1277 Fixtures can provide their values to test functions using ``return`` or
1278 ``yield`` statements. When using ``yield`` the code block after the
1279 ``yield`` statement is executed as teardown code regardless of the test
1280 outcome, and must yield exactly once.
1282 :param scope:
1283 The scope for which this fixture is shared; one of ``"function"``
1284 (default), ``"class"``, ``"module"``, ``"package"`` or ``"session"``.
1286 This parameter may also be a callable which receives ``(fixture_name, config)``
1287 as parameters, and must return a ``str`` with one of the values mentioned above.
1289 See :ref:`dynamic scope` in the docs for more information.
1291 :param params:
1292 An optional list of parameters which will cause multiple invocations
1293 of the fixture function and all of the tests using it. The current
1294 parameter is available in ``request.param``.
1296 :param autouse:
1297 If True, the fixture func is activated for all tests that can see it.
1298 If False (the default), an explicit reference is needed to activate
1299 the fixture.
1301 :param ids:
1302 Sequence of ids each corresponding to the params so that they are
1303 part of the test id. If no ids are provided they will be generated
1304 automatically from the params.
1306 :param name:
1307 The name of the fixture. This defaults to the name of the decorated
1308 function. If a fixture is used in the same module in which it is
1309 defined, the function name of the fixture will be shadowed by the
1310 function arg that requests the fixture; one way to resolve this is to
1311 name the decorated function ``fixture_<fixturename>`` and then use
1312 ``@pytest.fixture(name='<fixturename>')``.
1313 """
1314 fixture_marker = FixtureFunctionMarker(
1315 scope=scope,
1316 params=params,
1317 autouse=autouse,
1318 ids=ids,
1319 name=name,
1320 )
1322 # Direct decoration.
1323 if fixture_function:
1324 return fixture_marker(fixture_function)
1326 return fixture_marker
1329def yield_fixture(
1330 fixture_function=None,
1331 *args,
1332 scope="function",
1333 params=None,
1334 autouse=False,
1335 ids=None,
1336 name=None,
1337):
1338 """(Return a) decorator to mark a yield-fixture factory function.
1340 .. deprecated:: 3.0
1341 Use :py:func:`pytest.fixture` directly instead.
1342 """
1343 warnings.warn(YIELD_FIXTURE, stacklevel=2)
1344 return fixture(
1345 fixture_function,
1346 *args,
1347 scope=scope,
1348 params=params,
1349 autouse=autouse,
1350 ids=ids,
1351 name=name,
1352 )
1355@fixture(scope="session")
1356def pytestconfig(request: FixtureRequest) -> Config:
1357 """Session-scoped fixture that returns the session's :class:`pytest.Config`
1358 object.
1360 Example::
1362 def test_foo(pytestconfig):
1363 if pytestconfig.getoption("verbose") > 0:
1364 ...
1366 """
1367 return request.config
1370def pytest_addoption(parser: Parser) -> None:
1371 parser.addini(
1372 "usefixtures",
1373 type="args",
1374 default=[],
1375 help="List of default fixtures to be used with this project",
1376 )
1379class FixtureManager:
1380 """pytest fixture definitions and information is stored and managed
1381 from this class.
1383 During collection fm.parsefactories() is called multiple times to parse
1384 fixture function definitions into FixtureDef objects and internal
1385 data structures.
1387 During collection of test functions, metafunc-mechanics instantiate
1388 a FuncFixtureInfo object which is cached per node/func-name.
1389 This FuncFixtureInfo object is later retrieved by Function nodes
1390 which themselves offer a fixturenames attribute.
1392 The FuncFixtureInfo object holds information about fixtures and FixtureDefs
1393 relevant for a particular function. An initial list of fixtures is
1394 assembled like this:
1396 - ini-defined usefixtures
1397 - autouse-marked fixtures along the collection chain up from the function
1398 - usefixtures markers at module/class/function level
1399 - test function funcargs
1401 Subsequently the funcfixtureinfo.fixturenames attribute is computed
1402 as the closure of the fixtures needed to setup the initial fixtures,
1403 i.e. fixtures needed by fixture functions themselves are appended
1404 to the fixturenames list.
1406 Upon the test-setup phases all fixturenames are instantiated, retrieved
1407 by a lookup of their FuncFixtureInfo.
1408 """
1410 FixtureLookupError = FixtureLookupError
1411 FixtureLookupErrorRepr = FixtureLookupErrorRepr
1413 def __init__(self, session: "Session") -> None:
1414 self.session = session
1415 self.config: Config = session.config
1416 self._arg2fixturedefs: Dict[str, List[FixtureDef[Any]]] = {}
1417 self._holderobjseen: Set[object] = set()
1418 # A mapping from a nodeid to a list of autouse fixtures it defines.
1419 self._nodeid_autousenames: Dict[str, List[str]] = {
1420 "": self.config.getini("usefixtures"),
1421 }
1422 session.config.pluginmanager.register(self, "funcmanage")
1424 def _get_direct_parametrize_args(self, node: nodes.Node) -> List[str]:
1425 """Return all direct parametrization arguments of a node, so we don't
1426 mistake them for fixtures.
1428 Check https://github.com/pytest-dev/pytest/issues/5036.
1430 These things are done later as well when dealing with parametrization
1431 so this could be improved.
1432 """
1433 parametrize_argnames: List[str] = []
1434 for marker in node.iter_markers(name="parametrize"):
1435 if not marker.kwargs.get("indirect", False):
1436 p_argnames, _ = ParameterSet._parse_parametrize_args(
1437 *marker.args, **marker.kwargs
1438 )
1439 parametrize_argnames.extend(p_argnames)
1441 return parametrize_argnames
1443 def getfixtureinfo(
1444 self, node: nodes.Node, func, cls, funcargs: bool = True
1445 ) -> FuncFixtureInfo:
1446 if funcargs and not getattr(node, "nofuncargs", False):
1447 argnames = getfuncargnames(func, name=node.name, cls=cls)
1448 else:
1449 argnames = ()
1451 usefixtures = tuple(
1452 arg for mark in node.iter_markers(name="usefixtures") for arg in mark.args
1453 )
1454 initialnames = usefixtures + argnames
1455 fm = node.session._fixturemanager
1456 initialnames, names_closure, arg2fixturedefs = fm.getfixtureclosure(
1457 initialnames, node, ignore_args=self._get_direct_parametrize_args(node)
1458 )
1459 return FuncFixtureInfo(argnames, initialnames, names_closure, arg2fixturedefs)
1461 def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None:
1462 nodeid = None
1463 try:
1464 p = absolutepath(plugin.__file__) # type: ignore[attr-defined]
1465 except AttributeError:
1466 pass
1467 else:
1468 # Construct the base nodeid which is later used to check
1469 # what fixtures are visible for particular tests (as denoted
1470 # by their test id).
1471 if p.name.startswith("conftest.py"):
1472 try:
1473 nodeid = str(p.parent.relative_to(self.config.rootpath))
1474 except ValueError:
1475 nodeid = ""
1476 if nodeid == ".":
1477 nodeid = ""
1478 if os.sep != nodes.SEP:
1479 nodeid = nodeid.replace(os.sep, nodes.SEP)
1481 self.parsefactories(plugin, nodeid)
1483 def _getautousenames(self, nodeid: str) -> Iterator[str]:
1484 """Return the names of autouse fixtures applicable to nodeid."""
1485 for parentnodeid in nodes.iterparentnodeids(nodeid):
1486 basenames = self._nodeid_autousenames.get(parentnodeid)
1487 if basenames:
1488 yield from basenames
1490 def getfixtureclosure(
1491 self,
1492 fixturenames: Tuple[str, ...],
1493 parentnode: nodes.Node,
1494 ignore_args: Sequence[str] = (),
1495 ) -> Tuple[Tuple[str, ...], List[str], Dict[str, Sequence[FixtureDef[Any]]]]:
1496 # Collect the closure of all fixtures, starting with the given
1497 # fixturenames as the initial set. As we have to visit all
1498 # factory definitions anyway, we also return an arg2fixturedefs
1499 # mapping so that the caller can reuse it and does not have
1500 # to re-discover fixturedefs again for each fixturename
1501 # (discovering matching fixtures for a given name/node is expensive).
1503 parentid = parentnode.nodeid
1504 fixturenames_closure = list(self._getautousenames(parentid))
1506 def merge(otherlist: Iterable[str]) -> None:
1507 for arg in otherlist:
1508 if arg not in fixturenames_closure:
1509 fixturenames_closure.append(arg)
1511 merge(fixturenames)
1513 # At this point, fixturenames_closure contains what we call "initialnames",
1514 # which is a set of fixturenames the function immediately requests. We
1515 # need to return it as well, so save this.
1516 initialnames = tuple(fixturenames_closure)
1518 arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {}
1519 lastlen = -1
1520 while lastlen != len(fixturenames_closure):
1521 lastlen = len(fixturenames_closure)
1522 for argname in fixturenames_closure:
1523 if argname in ignore_args:
1524 continue
1525 if argname in arg2fixturedefs:
1526 continue
1527 fixturedefs = self.getfixturedefs(argname, parentid)
1528 if fixturedefs:
1529 arg2fixturedefs[argname] = fixturedefs
1530 merge(fixturedefs[-1].argnames)
1532 def sort_by_scope(arg_name: str) -> Scope:
1533 try:
1534 fixturedefs = arg2fixturedefs[arg_name]
1535 except KeyError:
1536 return Scope.Function
1537 else:
1538 return fixturedefs[-1]._scope
1540 fixturenames_closure.sort(key=sort_by_scope, reverse=True)
1541 return initialnames, fixturenames_closure, arg2fixturedefs
1543 def pytest_generate_tests(self, metafunc: "Metafunc") -> None:
1544 """Generate new tests based on parametrized fixtures used by the given metafunc"""
1546 def get_parametrize_mark_argnames(mark: Mark) -> Sequence[str]:
1547 args, _ = ParameterSet._parse_parametrize_args(*mark.args, **mark.kwargs)
1548 return args
1550 for argname in metafunc.fixturenames:
1551 # Get the FixtureDefs for the argname.
1552 fixture_defs = metafunc._arg2fixturedefs.get(argname)
1553 if not fixture_defs:
1554 # Will raise FixtureLookupError at setup time if not parametrized somewhere
1555 # else (e.g @pytest.mark.parametrize)
1556 continue
1558 # If the test itself parametrizes using this argname, give it
1559 # precedence.
1560 if any(
1561 argname in get_parametrize_mark_argnames(mark)
1562 for mark in metafunc.definition.iter_markers("parametrize")
1563 ):
1564 continue
1566 # In the common case we only look at the fixture def with the
1567 # closest scope (last in the list). But if the fixture overrides
1568 # another fixture, while requesting the super fixture, keep going
1569 # in case the super fixture is parametrized (#1953).
1570 for fixturedef in reversed(fixture_defs):
1571 # Fixture is parametrized, apply it and stop.
1572 if fixturedef.params is not None:
1573 metafunc.parametrize(
1574 argname,
1575 fixturedef.params,
1576 indirect=True,
1577 scope=fixturedef.scope,
1578 ids=fixturedef.ids,
1579 )
1580 break
1582 # Not requesting the overridden super fixture, stop.
1583 if argname not in fixturedef.argnames:
1584 break
1586 # Try next super fixture, if any.
1588 def pytest_collection_modifyitems(self, items: List[nodes.Item]) -> None:
1589 # Separate parametrized setups.
1590 items[:] = reorder_items(items)
1592 def parsefactories(
1593 self, node_or_obj, nodeid=NOTSET, unittest: bool = False
1594 ) -> None:
1595 if nodeid is not NOTSET:
1596 holderobj = node_or_obj
1597 else:
1598 holderobj = node_or_obj.obj
1599 nodeid = node_or_obj.nodeid
1600 if holderobj in self._holderobjseen:
1601 return
1603 self._holderobjseen.add(holderobj)
1604 autousenames = []
1605 for name in dir(holderobj):
1606 # ugly workaround for one of the fspath deprecated property of node
1607 # todo: safely generalize
1608 if isinstance(holderobj, nodes.Node) and name == "fspath":
1609 continue
1611 # The attribute can be an arbitrary descriptor, so the attribute
1612 # access below can raise. safe_getatt() ignores such exceptions.
1613 obj = safe_getattr(holderobj, name, None)
1614 marker = getfixturemarker(obj)
1615 if not isinstance(marker, FixtureFunctionMarker):
1616 # Magic globals with __getattr__ might have got us a wrong
1617 # fixture attribute.
1618 continue
1620 if marker.name:
1621 name = marker.name
1623 # During fixture definition we wrap the original fixture function
1624 # to issue a warning if called directly, so here we unwrap it in
1625 # order to not emit the warning when pytest itself calls the
1626 # fixture function.
1627 obj = get_real_method(obj, holderobj)
1629 fixture_def = FixtureDef(
1630 fixturemanager=self,
1631 baseid=nodeid,
1632 argname=name,
1633 func=obj,
1634 scope=marker.scope,
1635 params=marker.params,
1636 unittest=unittest,
1637 ids=marker.ids,
1638 )
1640 faclist = self._arg2fixturedefs.setdefault(name, [])
1641 if fixture_def.has_location:
1642 faclist.append(fixture_def)
1643 else:
1644 # fixturedefs with no location are at the front
1645 # so this inserts the current fixturedef after the
1646 # existing fixturedefs from external plugins but
1647 # before the fixturedefs provided in conftests.
1648 i = len([f for f in faclist if not f.has_location])
1649 faclist.insert(i, fixture_def)
1650 if marker.autouse:
1651 autousenames.append(name)
1653 if autousenames:
1654 self._nodeid_autousenames.setdefault(nodeid or "", []).extend(autousenames)
1656 def getfixturedefs(
1657 self, argname: str, nodeid: str
1658 ) -> Optional[Sequence[FixtureDef[Any]]]:
1659 """Get a list of fixtures which are applicable to the given node id.
1661 :param str argname: Name of the fixture to search for.
1662 :param str nodeid: Full node id of the requesting test.
1663 :rtype: Sequence[FixtureDef]
1664 """
1665 try:
1666 fixturedefs = self._arg2fixturedefs[argname]
1667 except KeyError:
1668 return None
1669 return tuple(self._matchfactories(fixturedefs, nodeid))
1671 def _matchfactories(
1672 self, fixturedefs: Iterable[FixtureDef[Any]], nodeid: str
1673 ) -> Iterator[FixtureDef[Any]]:
1674 parentnodeids = set(nodes.iterparentnodeids(nodeid))
1675 for fixturedef in fixturedefs:
1676 if fixturedef.baseid in parentnodeids:
1677 yield fixturedef