Best Python code snippet using pytest
fixtures.py
Source:fixtures.py
1import functools2import inspect3import os4import sys5import warnings6from collections import defaultdict7from collections import deque8from pathlib import Path9from types import TracebackType10from typing import Any11from typing import Callable12from typing import cast13from typing import Dict14from typing import Generator15from typing import Generic16from typing import Iterable17from typing import Iterator18from typing import List19from typing import MutableMapping20from typing import Optional21from typing import overload22from typing import Sequence23from typing import Set24from typing import Tuple25from typing import Type26from typing import TYPE_CHECKING27from typing import TypeVar28from typing import Union29import attr30import _pytest31from _pytest import nodes32from _pytest._code import getfslineno33from _pytest._code.code import FormattedExcinfo34from _pytest._code.code import TerminalRepr35from _pytest._io import TerminalWriter36from _pytest.compat import _format_args37from _pytest.compat import _PytestWrapper38from _pytest.compat import assert_never39from _pytest.compat import final40from _pytest.compat import get_real_func41from _pytest.compat import get_real_method42from _pytest.compat import getfuncargnames43from _pytest.compat import getimfunc44from _pytest.compat import getlocation45from _pytest.compat import is_generator46from _pytest.compat import LEGACY_PATH47from _pytest.compat import legacy_path48from _pytest.compat import NOTSET49from _pytest.compat import safe_getattr50from _pytest.config import _PluggyPlugin51from _pytest.config import Config52from _pytest.config.argparsing import Parser53from _pytest.deprecated import check_ispytest54from _pytest.deprecated import FILLFUNCARGS55from _pytest.deprecated import NODE_FSPATH56from _pytest.deprecated import YIELD_FIXTURE57from _pytest.mark import Mark58from _pytest.mark import ParameterSet59from _pytest.mark.structures import MarkDecorator60from _pytest.outcomes import fail61from _pytest.outcomes import TEST_OUTCOME62from _pytest.pathlib import absolutepath63from _pytest.pathlib import bestrelpath64from _pytest.store import StoreKey65if TYPE_CHECKING:66 from typing import Deque67 from typing import NoReturn68 from typing_extensions import Literal69 from _pytest.main import Session70 from _pytest.python import CallSpec271 from _pytest.python import Function72 from _pytest.python import Metafunc73 _Scope = Literal["session", "package", "module", "class", "function"]74# The value of the fixture -- return/yield of the fixture function (type variable).75FixtureValue = TypeVar("FixtureValue")76# The type of the fixture function (type variable).77FixtureFunction = TypeVar("FixtureFunction", bound=Callable[..., object])78# The type of a fixture function (type alias generic in fixture value).79_FixtureFunc = Union[80 Callable[..., FixtureValue], Callable[..., Generator[FixtureValue, None, None]]81]82# The type of FixtureDef.cached_result (type alias generic in fixture value).83_FixtureCachedResult = Union[84 Tuple[85 # The result.86 FixtureValue,87 # Cache key.88 object,89 None,90 ],91 Tuple[92 None,93 # Cache key.94 object,95 # Exc info if raised.96 Tuple[Type[BaseException], BaseException, TracebackType],97 ],98]99@attr.s(frozen=True)100class PseudoFixtureDef(Generic[FixtureValue]):101 cached_result = attr.ib(type="_FixtureCachedResult[FixtureValue]")102 scope = attr.ib(type="_Scope")103def pytest_sessionstart(session: "Session") -> None:104 session._fixturemanager = FixtureManager(session)105def get_scope_package(node, fixturedef: "FixtureDef[object]"):106 import pytest107 cls = pytest.Package108 current = node109 fixture_package_name = "{}/{}".format(fixturedef.baseid, "__init__.py")110 while current and (111 type(current) is not cls or fixture_package_name != current.nodeid112 ):113 current = current.parent114 if current is None:115 return node.session116 return current117def get_scope_node(118 node: nodes.Node, scope: "_Scope"119) -> Optional[Union[nodes.Item, nodes.Collector]]:120 import _pytest.python121 if scope == "function":122 return node.getparent(nodes.Item)123 elif scope == "class":124 return node.getparent(_pytest.python.Class)125 elif scope == "module":126 return node.getparent(_pytest.python.Module)127 elif scope == "package":128 return node.getparent(_pytest.python.Package)129 elif scope == "session":130 return node.getparent(_pytest.main.Session)131 else:132 assert_never(scope)133# Used for storing artificial fixturedefs for direct parametrization.134name2pseudofixturedef_key = StoreKey[Dict[str, "FixtureDef[Any]"]]()135def add_funcarg_pseudo_fixture_def(136 collector: nodes.Collector, metafunc: "Metafunc", fixturemanager: "FixtureManager"137) -> None:138 # This function will transform all collected calls to functions139 # if they use direct funcargs (i.e. direct parametrization)140 # because we want later test execution to be able to rely on141 # an existing FixtureDef structure for all arguments.142 # XXX we can probably avoid this algorithm if we modify CallSpec2143 # to directly care for creating the fixturedefs within its methods.144 if not metafunc._calls[0].funcargs:145 # This function call does not have direct parametrization.146 return147 # Collect funcargs of all callspecs into a list of values.148 arg2params: Dict[str, List[object]] = {}149 arg2scope: Dict[str, _Scope] = {}150 for callspec in metafunc._calls:151 for argname, argvalue in callspec.funcargs.items():152 assert argname not in callspec.params153 callspec.params[argname] = argvalue154 arg2params_list = arg2params.setdefault(argname, [])155 callspec.indices[argname] = len(arg2params_list)156 arg2params_list.append(argvalue)157 if argname not in arg2scope:158 scopenum = callspec._arg2scopenum.get(argname, scopenum_function)159 arg2scope[argname] = scopes[scopenum]160 callspec.funcargs.clear()161 # Register artificial FixtureDef's so that later at test execution162 # time we can rely on a proper FixtureDef to exist for fixture setup.163 arg2fixturedefs = metafunc._arg2fixturedefs164 for argname, valuelist in arg2params.items():165 # If we have a scope that is higher than function, we need166 # to make sure we only ever create an according fixturedef on167 # a per-scope basis. We thus store and cache the fixturedef on the168 # node related to the scope.169 scope = arg2scope[argname]170 node = None171 if scope != "function":172 node = get_scope_node(collector, scope)173 if node is None:174 assert scope == "class" and isinstance(collector, _pytest.python.Module)175 # Use module-level collector for class-scope (for now).176 node = collector177 if node is None:178 name2pseudofixturedef = None179 else:180 default: Dict[str, FixtureDef[Any]] = {}181 name2pseudofixturedef = node._store.setdefault(182 name2pseudofixturedef_key, default183 )184 if name2pseudofixturedef is not None and argname in name2pseudofixturedef:185 arg2fixturedefs[argname] = [name2pseudofixturedef[argname]]186 else:187 fixturedef = FixtureDef(188 fixturemanager=fixturemanager,189 baseid="",190 argname=argname,191 func=get_direct_param_fixture_func,192 scope=arg2scope[argname],193 params=valuelist,194 unittest=False,195 ids=None,196 )197 arg2fixturedefs[argname] = [fixturedef]198 if name2pseudofixturedef is not None:199 name2pseudofixturedef[argname] = fixturedef200def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]:201 """Return fixturemarker or None if it doesn't exist or raised202 exceptions."""203 try:204 fixturemarker: Optional[FixtureFunctionMarker] = getattr(205 obj, "_pytestfixturefunction", None206 )207 except TEST_OUTCOME:208 # some objects raise errors like request (from flask import request)209 # we don't expect them to be fixture functions210 return None211 return fixturemarker212# Parametrized fixture key, helper alias for code below.213_Key = Tuple[object, ...]214def get_parametrized_fixture_keys(item: nodes.Item, scopenum: int) -> Iterator[_Key]:215 """Return list of keys for all parametrized arguments which match216 the specified scope."""217 assert scopenum < scopenum_function # function218 try:219 callspec = item.callspec # type: ignore[attr-defined]220 except AttributeError:221 pass222 else:223 cs: CallSpec2 = callspec224 # cs.indices.items() is random order of argnames. Need to225 # sort this so that different calls to226 # get_parametrized_fixture_keys will be deterministic.227 for argname, param_index in sorted(cs.indices.items()):228 if cs._arg2scopenum[argname] != scopenum:229 continue230 if scopenum == 0: # session231 key: _Key = (argname, param_index)232 elif scopenum == 1: # package233 key = (argname, param_index, item.path.parent)234 elif scopenum == 2: # module235 key = (argname, param_index, item.path)236 elif scopenum == 3: # class237 item_cls = item.cls # type: ignore[attr-defined]238 key = (argname, param_index, item.path, item_cls)239 yield key240# Algorithm for sorting on a per-parametrized resource setup basis.241# It is called for scopenum==0 (session) first and performs sorting242# down to the lower scopes such as to minimize number of "high scope"243# setups and teardowns.244def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]:245 argkeys_cache: Dict[int, Dict[nodes.Item, Dict[_Key, None]]] = {}246 items_by_argkey: Dict[int, Dict[_Key, Deque[nodes.Item]]] = {}247 for scopenum in range(scopenum_function):248 d: Dict[nodes.Item, Dict[_Key, None]] = {}249 argkeys_cache[scopenum] = d250 item_d: Dict[_Key, Deque[nodes.Item]] = defaultdict(deque)251 items_by_argkey[scopenum] = item_d252 for item in items:253 keys = dict.fromkeys(get_parametrized_fixture_keys(item, scopenum), None)254 if keys:255 d[item] = keys256 for key in keys:257 item_d[key].append(item)258 items_dict = dict.fromkeys(items, None)259 return list(reorder_items_atscope(items_dict, argkeys_cache, items_by_argkey, 0))260def fix_cache_order(261 item: nodes.Item,262 argkeys_cache: Dict[int, Dict[nodes.Item, Dict[_Key, None]]],263 items_by_argkey: Dict[int, Dict[_Key, "Deque[nodes.Item]"]],264) -> None:265 for scopenum in range(scopenum_function):266 for key in argkeys_cache[scopenum].get(item, []):267 items_by_argkey[scopenum][key].appendleft(item)268def reorder_items_atscope(269 items: Dict[nodes.Item, None],270 argkeys_cache: Dict[int, Dict[nodes.Item, Dict[_Key, None]]],271 items_by_argkey: Dict[int, Dict[_Key, "Deque[nodes.Item]"]],272 scopenum: int,273) -> Dict[nodes.Item, None]:274 if scopenum >= scopenum_function or len(items) < 3:275 return items276 ignore: Set[Optional[_Key]] = set()277 items_deque = deque(items)278 items_done: Dict[nodes.Item, None] = {}279 scoped_items_by_argkey = items_by_argkey[scopenum]280 scoped_argkeys_cache = argkeys_cache[scopenum]281 while items_deque:282 no_argkey_group: Dict[nodes.Item, None] = {}283 slicing_argkey = None284 while items_deque:285 item = items_deque.popleft()286 if item in items_done or item in no_argkey_group:287 continue288 argkeys = dict.fromkeys(289 (k for k in scoped_argkeys_cache.get(item, []) if k not in ignore), None290 )291 if not argkeys:292 no_argkey_group[item] = None293 else:294 slicing_argkey, _ = argkeys.popitem()295 # We don't have to remove relevant items from later in the296 # deque because they'll just be ignored.297 matching_items = [298 i for i in scoped_items_by_argkey[slicing_argkey] if i in items299 ]300 for i in reversed(matching_items):301 fix_cache_order(i, argkeys_cache, items_by_argkey)302 items_deque.appendleft(i)303 break304 if no_argkey_group:305 no_argkey_group = reorder_items_atscope(306 no_argkey_group, argkeys_cache, items_by_argkey, scopenum + 1307 )308 for item in no_argkey_group:309 items_done[item] = None310 ignore.add(slicing_argkey)311 return items_done312def _fillfuncargs(function: "Function") -> None:313 """Fill missing fixtures for a test function, old public API (deprecated)."""314 warnings.warn(FILLFUNCARGS.format(name="pytest._fillfuncargs()"), stacklevel=2)315 _fill_fixtures_impl(function)316def fillfixtures(function: "Function") -> None:317 """Fill missing fixtures for a test function (deprecated)."""318 warnings.warn(319 FILLFUNCARGS.format(name="_pytest.fixtures.fillfixtures()"), stacklevel=2320 )321 _fill_fixtures_impl(function)322def _fill_fixtures_impl(function: "Function") -> None:323 """Internal implementation to fill fixtures on the given function object."""324 try:325 request = function._request326 except AttributeError:327 # XXX this special code path is only expected to execute328 # with the oejskit plugin. It uses classes with funcargs329 # and we thus have to work a bit to allow this.330 fm = function.session._fixturemanager331 assert function.parent is not None332 fi = fm.getfixtureinfo(function.parent, function.obj, None)333 function._fixtureinfo = fi334 request = function._request = FixtureRequest(function, _ispytest=True)335 fm.session._setupstate.setup(function)336 request._fillfixtures()337 # Prune out funcargs for jstests.338 function.funcargs = {name: function.funcargs[name] for name in fi.argnames}339 else:340 request._fillfixtures()341def get_direct_param_fixture_func(request):342 return request.param343@attr.s(slots=True)344class FuncFixtureInfo:345 # Original function argument names.346 argnames = attr.ib(type=Tuple[str, ...])347 # Argnames that function immediately requires. These include argnames +348 # fixture names specified via usefixtures and via autouse=True in fixture349 # definitions.350 initialnames = attr.ib(type=Tuple[str, ...])351 names_closure = attr.ib(type=List[str])352 name2fixturedefs = attr.ib(type=Dict[str, Sequence["FixtureDef[Any]"]])353 def prune_dependency_tree(self) -> None:354 """Recompute names_closure from initialnames and name2fixturedefs.355 Can only reduce names_closure, which means that the new closure will356 always be a subset of the old one. The order is preserved.357 This method is needed because direct parametrization may shadow some358 of the fixtures that were included in the originally built dependency359 tree. In this way the dependency tree can get pruned, and the closure360 of argnames may get reduced.361 """362 closure: Set[str] = set()363 working_set = set(self.initialnames)364 while working_set:365 argname = working_set.pop()366 # Argname may be smth not included in the original names_closure,367 # in which case we ignore it. This currently happens with pseudo368 # FixtureDefs which wrap 'get_direct_param_fixture_func(request)'.369 # So they introduce the new dependency 'request' which might have370 # been missing in the original tree (closure).371 if argname not in closure and argname in self.names_closure:372 closure.add(argname)373 if argname in self.name2fixturedefs:374 working_set.update(self.name2fixturedefs[argname][-1].argnames)375 self.names_closure[:] = sorted(closure, key=self.names_closure.index)376class FixtureRequest:377 """A request for a fixture from a test or fixture function.378 A request object gives access to the requesting test context and has379 an optional ``param`` attribute in case the fixture is parametrized380 indirectly.381 """382 def __init__(self, pyfuncitem, *, _ispytest: bool = False) -> None:383 check_ispytest(_ispytest)384 self._pyfuncitem = pyfuncitem385 #: Fixture for which this request is being performed.386 self.fixturename: Optional[str] = None387 #: Scope string, one of "function", "class", "module", "session".388 self.scope: _Scope = "function"389 self._fixture_defs: Dict[str, FixtureDef[Any]] = {}390 fixtureinfo: FuncFixtureInfo = pyfuncitem._fixtureinfo391 self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()392 self._arg2index: Dict[str, int] = {}393 self._fixturemanager: FixtureManager = pyfuncitem.session._fixturemanager394 @property395 def fixturenames(self) -> List[str]:396 """Names of all active fixtures in this request."""397 result = list(self._pyfuncitem._fixtureinfo.names_closure)398 result.extend(set(self._fixture_defs).difference(result))399 return result400 @property401 def node(self):402 """Underlying collection node (depends on current request scope)."""403 return self._getscopeitem(self.scope)404 def _getnextfixturedef(self, argname: str) -> "FixtureDef[Any]":405 fixturedefs = self._arg2fixturedefs.get(argname, None)406 if fixturedefs is None:407 # We arrive here because of a dynamic call to408 # getfixturevalue(argname) usage which was naturally409 # not known at parsing/collection time.410 assert self._pyfuncitem.parent is not None411 parentid = self._pyfuncitem.parent.nodeid412 fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid)413 # TODO: Fix this type ignore. Either add assert or adjust types.414 # Can this be None here?415 self._arg2fixturedefs[argname] = fixturedefs # type: ignore[assignment]416 # fixturedefs list is immutable so we maintain a decreasing index.417 index = self._arg2index.get(argname, 0) - 1418 if fixturedefs is None or (-index > len(fixturedefs)):419 raise FixtureLookupError(argname, self)420 self._arg2index[argname] = index421 return fixturedefs[index]422 @property423 def config(self) -> Config:424 """The pytest config object associated with this request."""425 return self._pyfuncitem.config # type: ignore[no-any-return]426 @property427 def function(self):428 """Test function object if the request has a per-function scope."""429 if self.scope != "function":430 raise AttributeError(431 f"function not available in {self.scope}-scoped context"432 )433 return self._pyfuncitem.obj434 @property435 def cls(self):436 """Class (can be None) where the test function was collected."""437 if self.scope not in ("class", "function"):438 raise AttributeError(f"cls not available in {self.scope}-scoped context")439 clscol = self._pyfuncitem.getparent(_pytest.python.Class)440 if clscol:441 return clscol.obj442 @property443 def instance(self):444 """Instance (can be None) on which test function was collected."""445 # unittest support hack, see _pytest.unittest.TestCaseFunction.446 try:447 return self._pyfuncitem._testcase448 except AttributeError:449 function = getattr(self, "function", None)450 return getattr(function, "__self__", None)451 @property452 def module(self):453 """Python module object where the test function was collected."""454 if self.scope not in ("function", "class", "module"):455 raise AttributeError(f"module not available in {self.scope}-scoped context")456 return self._pyfuncitem.getparent(_pytest.python.Module).obj457 @property458 def fspath(self) -> LEGACY_PATH:459 """(deprecated) The file system path of the test module which collected this test."""460 warnings.warn(NODE_FSPATH.format(type=type(self).__name__), stacklevel=2)461 return legacy_path(self.path)462 @property463 def path(self) -> Path:464 if self.scope not in ("function", "class", "module", "package"):465 raise AttributeError(f"module not available in {self.scope}-scoped context")466 # TODO: Remove ignore once _pyfuncitem is properly typed.467 return self._pyfuncitem.path # type: ignore468 @property469 def keywords(self) -> MutableMapping[str, Any]:470 """Keywords/markers dictionary for the underlying node."""471 node: nodes.Node = self.node472 return node.keywords473 @property474 def session(self) -> "Session":475 """Pytest session object."""476 return self._pyfuncitem.session # type: ignore[no-any-return]477 def addfinalizer(self, finalizer: Callable[[], object]) -> None:478 """Add finalizer/teardown function to be called after the last test479 within the requesting test context finished execution."""480 # XXX usually this method is shadowed by fixturedef specific ones.481 self._addfinalizer(finalizer, scope=self.scope)482 def _addfinalizer(self, finalizer: Callable[[], object], scope) -> None:483 node = self._getscopeitem(scope)484 node.addfinalizer(finalizer)485 def applymarker(self, marker: Union[str, MarkDecorator]) -> None:486 """Apply a marker to a single test function invocation.487 This method is useful if you don't want to have a keyword/marker488 on all function invocations.489 :param marker:490 A :class:`pytest.MarkDecorator` object created by a call491 to ``pytest.mark.NAME(...)``.492 """493 self.node.add_marker(marker)494 def raiseerror(self, msg: Optional[str]) -> "NoReturn":495 """Raise a FixtureLookupError with the given message."""496 raise self._fixturemanager.FixtureLookupError(None, self, msg)497 def _fillfixtures(self) -> None:498 item = self._pyfuncitem499 fixturenames = getattr(item, "fixturenames", self.fixturenames)500 for argname in fixturenames:501 if argname not in item.funcargs:502 item.funcargs[argname] = self.getfixturevalue(argname)503 def getfixturevalue(self, argname: str) -> Any:504 """Dynamically run a named fixture function.505 Declaring fixtures via function argument is recommended where possible.506 But if you can only decide whether to use another fixture at test507 setup time, you may use this function to retrieve it inside a fixture508 or test function body.509 :raises pytest.FixtureLookupError:510 If the given fixture could not be found.511 """512 fixturedef = self._get_active_fixturedef(argname)513 assert fixturedef.cached_result is not None514 return fixturedef.cached_result[0]515 def _get_active_fixturedef(516 self, argname: str517 ) -> Union["FixtureDef[object]", PseudoFixtureDef[object]]:518 try:519 return self._fixture_defs[argname]520 except KeyError:521 try:522 fixturedef = self._getnextfixturedef(argname)523 except FixtureLookupError:524 if argname == "request":525 cached_result = (self, [0], None)526 scope: _Scope = "function"527 return PseudoFixtureDef(cached_result, scope)528 raise529 # Remove indent to prevent the python3 exception530 # from leaking into the call.531 self._compute_fixture_value(fixturedef)532 self._fixture_defs[argname] = fixturedef533 return fixturedef534 def _get_fixturestack(self) -> List["FixtureDef[Any]"]:535 current = self536 values: List[FixtureDef[Any]] = []537 while isinstance(current, SubRequest):538 values.append(current._fixturedef) # type: ignore[has-type]539 current = current._parent_request540 values.reverse()541 return values542 def _compute_fixture_value(self, fixturedef: "FixtureDef[object]") -> None:543 """Create a SubRequest based on "self" and call the execute method544 of the given FixtureDef object.545 This will force the FixtureDef object to throw away any previous546 results and compute a new fixture value, which will be stored into547 the FixtureDef object itself.548 """549 # prepare a subrequest object before calling fixture function550 # (latter managed by fixturedef)551 argname = fixturedef.argname552 funcitem = self._pyfuncitem553 scope = fixturedef.scope554 try:555 param = funcitem.callspec.getparam(argname)556 except (AttributeError, ValueError):557 param = NOTSET558 param_index = 0559 has_params = fixturedef.params is not None560 fixtures_not_supported = getattr(funcitem, "nofuncargs", False)561 if has_params and fixtures_not_supported:562 msg = (563 "{name} does not support fixtures, maybe unittest.TestCase subclass?\n"564 "Node id: {nodeid}\n"565 "Function type: {typename}"566 ).format(567 name=funcitem.name,568 nodeid=funcitem.nodeid,569 typename=type(funcitem).__name__,570 )571 fail(msg, pytrace=False)572 if has_params:573 frame = inspect.stack()[3]574 frameinfo = inspect.getframeinfo(frame[0])575 source_path = absolutepath(frameinfo.filename)576 source_lineno = frameinfo.lineno577 try:578 source_path_str = str(579 source_path.relative_to(funcitem.config.rootpath)580 )581 except ValueError:582 source_path_str = str(source_path)583 msg = (584 "The requested fixture has no parameter defined for test:\n"585 " {}\n\n"586 "Requested fixture '{}' defined in:\n{}"587 "\n\nRequested here:\n{}:{}".format(588 funcitem.nodeid,589 fixturedef.argname,590 getlocation(fixturedef.func, funcitem.config.rootpath),591 source_path_str,592 source_lineno,593 )594 )595 fail(msg, pytrace=False)596 else:597 param_index = funcitem.callspec.indices[argname]598 # If a parametrize invocation set a scope it will override599 # the static scope defined with the fixture function.600 paramscopenum = funcitem.callspec._arg2scopenum.get(argname)601 if paramscopenum is not None:602 scope = scopes[paramscopenum]603 subrequest = SubRequest(604 self, scope, param, param_index, fixturedef, _ispytest=True605 )606 # Check if a higher-level scoped fixture accesses a lower level one.607 subrequest._check_scope(argname, self.scope, scope)608 try:609 # Call the fixture function.610 fixturedef.execute(request=subrequest)611 finally:612 self._schedule_finalizers(fixturedef, subrequest)613 def _schedule_finalizers(614 self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest"615 ) -> None:616 # If fixture function failed it might have registered finalizers.617 subrequest.node.addfinalizer(lambda: fixturedef.finish(request=subrequest))618 def _check_scope(619 self,620 argname: str,621 invoking_scope: "_Scope",622 requested_scope: "_Scope",623 ) -> None:624 if argname == "request":625 return626 if scopemismatch(invoking_scope, requested_scope):627 # Try to report something helpful.628 lines = self._factorytraceback()629 fail(630 "ScopeMismatch: You tried to access the %r scoped "631 "fixture %r with a %r scoped request object, "632 "involved factories\n%s"633 % ((requested_scope, argname, invoking_scope, "\n".join(lines))),634 pytrace=False,635 )636 def _factorytraceback(self) -> List[str]:637 lines = []638 for fixturedef in self._get_fixturestack():639 factory = fixturedef.func640 fs, lineno = getfslineno(factory)641 if isinstance(fs, Path):642 session: Session = self._pyfuncitem.session643 p = bestrelpath(session.path, fs)644 else:645 p = fs646 args = _format_args(factory)647 lines.append("%s:%d: def %s%s" % (p, lineno + 1, factory.__name__, args))648 return lines649 def _getscopeitem(self, scope: "_Scope") -> Union[nodes.Item, nodes.Collector]:650 if scope == "function":651 # This might also be a non-function Item despite its attribute name.652 node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem653 elif scope == "package":654 # FIXME: _fixturedef is not defined on FixtureRequest (this class),655 # but on FixtureRequest (a subclass).656 node = get_scope_package(self._pyfuncitem, self._fixturedef) # type: ignore[attr-defined]657 else:658 node = get_scope_node(self._pyfuncitem, scope)659 if node is None and scope == "class":660 # Fallback to function item itself.661 node = self._pyfuncitem662 assert node, 'Could not obtain a node for scope "{}" for function {!r}'.format(663 scope, self._pyfuncitem664 )665 return node666 def __repr__(self) -> str:667 return "<FixtureRequest for %r>" % (self.node)668@final669class SubRequest(FixtureRequest):670 """A sub request for handling getting a fixture from a test function/fixture."""671 def __init__(672 self,673 request: "FixtureRequest",674 scope: "_Scope",675 param: Any,676 param_index: int,677 fixturedef: "FixtureDef[object]",678 *,679 _ispytest: bool = False,680 ) -> None:681 check_ispytest(_ispytest)682 self._parent_request = request683 self.fixturename = fixturedef.argname684 if param is not NOTSET:685 self.param = param686 self.param_index = param_index687 self.scope = scope688 self._fixturedef = fixturedef689 self._pyfuncitem = request._pyfuncitem690 self._fixture_defs = request._fixture_defs691 self._arg2fixturedefs = request._arg2fixturedefs692 self._arg2index = request._arg2index693 self._fixturemanager = request._fixturemanager694 def __repr__(self) -> str:695 return f"<SubRequest {self.fixturename!r} for {self._pyfuncitem!r}>"696 def addfinalizer(self, finalizer: Callable[[], object]) -> None:697 """Add finalizer/teardown function to be called after the last test698 within the requesting test context finished execution."""699 self._fixturedef.addfinalizer(finalizer)700 def _schedule_finalizers(701 self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest"702 ) -> None:703 # If the executing fixturedef was not explicitly requested in the argument list (via704 # getfixturevalue inside the fixture call) then ensure this fixture def will be finished705 # first.706 if fixturedef.argname not in self.fixturenames:707 fixturedef.addfinalizer(708 functools.partial(self._fixturedef.finish, request=self)709 )710 super()._schedule_finalizers(fixturedef, subrequest)711scopes: List["_Scope"] = ["session", "package", "module", "class", "function"]712scopenum_function = scopes.index("function")713def scopemismatch(currentscope: "_Scope", newscope: "_Scope") -> bool:714 return scopes.index(newscope) > scopes.index(currentscope)715def scope2index(scope: str, descr: str, where: Optional[str] = None) -> int:716 """Look up the index of ``scope`` and raise a descriptive value error717 if not defined."""718 strscopes: Sequence[str] = scopes719 try:720 return strscopes.index(scope)721 except ValueError:722 fail(723 "{} {}got an unexpected scope value '{}'".format(724 descr, f"from {where} " if where else "", scope725 ),726 pytrace=False,727 )728@final729class FixtureLookupError(LookupError):730 """Could not return a requested fixture (missing or invalid)."""731 def __init__(732 self, argname: Optional[str], request: FixtureRequest, msg: Optional[str] = None733 ) -> None:734 self.argname = argname735 self.request = request736 self.fixturestack = request._get_fixturestack()737 self.msg = msg738 def formatrepr(self) -> "FixtureLookupErrorRepr":739 tblines: List[str] = []740 addline = tblines.append741 stack = [self.request._pyfuncitem.obj]742 stack.extend(map(lambda x: x.func, self.fixturestack))743 msg = self.msg744 if msg is not None:745 # The last fixture raise an error, let's present746 # it at the requesting side.747 stack = stack[:-1]748 for function in stack:749 fspath, lineno = getfslineno(function)750 try:751 lines, _ = inspect.getsourcelines(get_real_func(function))752 except (OSError, IndexError, TypeError):753 error_msg = "file %s, line %s: source code not available"754 addline(error_msg % (fspath, lineno + 1))755 else:756 addline(f"file {fspath}, line {lineno + 1}")757 for i, line in enumerate(lines):758 line = line.rstrip()759 addline(" " + line)760 if line.lstrip().startswith("def"):761 break762 if msg is None:763 fm = self.request._fixturemanager764 available = set()765 parentid = self.request._pyfuncitem.parent.nodeid766 for name, fixturedefs in fm._arg2fixturedefs.items():767 faclist = list(fm._matchfactories(fixturedefs, parentid))768 if faclist:769 available.add(name)770 if self.argname in available:771 msg = " recursive dependency involving fixture '{}' detected".format(772 self.argname773 )774 else:775 msg = f"fixture '{self.argname}' not found"776 msg += "\n available fixtures: {}".format(", ".join(sorted(available)))777 msg += "\n use 'pytest --fixtures [testpath]' for help on them."778 return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname)779class FixtureLookupErrorRepr(TerminalRepr):780 def __init__(781 self,782 filename: Union[str, "os.PathLike[str]"],783 firstlineno: int,784 tblines: Sequence[str],785 errorstring: str,786 argname: Optional[str],787 ) -> None:788 self.tblines = tblines789 self.errorstring = errorstring790 self.filename = filename791 self.firstlineno = firstlineno792 self.argname = argname793 def toterminal(self, tw: TerminalWriter) -> None:794 # tw.line("FixtureLookupError: %s" %(self.argname), red=True)795 for tbline in self.tblines:796 tw.line(tbline.rstrip())797 lines = self.errorstring.split("\n")798 if lines:799 tw.line(800 f"{FormattedExcinfo.fail_marker} {lines[0].strip()}",801 red=True,802 )803 for line in lines[1:]:804 tw.line(805 f"{FormattedExcinfo.flow_marker} {line.strip()}",806 red=True,807 )808 tw.line()809 tw.line("%s:%d" % (os.fspath(self.filename), self.firstlineno + 1))810def fail_fixturefunc(fixturefunc, msg: str) -> "NoReturn":811 fs, lineno = getfslineno(fixturefunc)812 location = f"{fs}:{lineno + 1}"813 source = _pytest._code.Source(fixturefunc)814 fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, pytrace=False)815def call_fixture_func(816 fixturefunc: "_FixtureFunc[FixtureValue]", request: FixtureRequest, kwargs817) -> FixtureValue:818 if is_generator(fixturefunc):819 fixturefunc = cast(820 Callable[..., Generator[FixtureValue, None, None]], fixturefunc821 )822 generator = fixturefunc(**kwargs)823 try:824 fixture_result = next(generator)825 except StopIteration:826 raise ValueError(f"{request.fixturename} did not yield a value") from None827 finalizer = functools.partial(_teardown_yield_fixture, fixturefunc, generator)828 request.addfinalizer(finalizer)829 else:830 fixturefunc = cast(Callable[..., FixtureValue], fixturefunc)831 fixture_result = fixturefunc(**kwargs)832 return fixture_result833def _teardown_yield_fixture(fixturefunc, it) -> None:834 """Execute the teardown of a fixture function by advancing the iterator835 after the yield and ensure the iteration ends (if not it means there is836 more than one yield in the function)."""837 try:838 next(it)839 except StopIteration:840 pass841 else:842 fail_fixturefunc(fixturefunc, "fixture function has more than one 'yield'")843def _eval_scope_callable(844 scope_callable: "Callable[[str, Config], _Scope]",845 fixture_name: str,846 config: Config,847) -> "_Scope":848 try:849 # Type ignored because there is no typing mechanism to specify850 # keyword arguments, currently.851 result = scope_callable(fixture_name=fixture_name, config=config) # type: ignore[call-arg]852 except Exception as e:853 raise TypeError(854 "Error evaluating {} while defining fixture '{}'.\n"855 "Expected a function with the signature (*, fixture_name, config)".format(856 scope_callable, fixture_name857 )858 ) from e859 if not isinstance(result, str):860 fail(861 "Expected {} to return a 'str' while defining fixture '{}', but it returned:\n"862 "{!r}".format(scope_callable, fixture_name, result),863 pytrace=False,864 )865 return result866@final867class FixtureDef(Generic[FixtureValue]):868 """A container for a factory definition."""869 def __init__(870 self,871 fixturemanager: "FixtureManager",872 baseid: Optional[str],873 argname: str,874 func: "_FixtureFunc[FixtureValue]",875 scope: "Union[_Scope, Callable[[str, Config], _Scope]]",876 params: Optional[Sequence[object]],877 unittest: bool = False,878 ids: Optional[879 Union[880 Tuple[Union[None, str, float, int, bool], ...],881 Callable[[Any], Optional[object]],882 ]883 ] = None,884 ) -> None:885 self._fixturemanager = fixturemanager886 self.baseid = baseid or ""887 self.has_location = baseid is not None888 self.func = func889 self.argname = argname890 if callable(scope):891 scope_ = _eval_scope_callable(scope, argname, fixturemanager.config)892 else:893 scope_ = scope894 self.scopenum = scope2index(895 # TODO: Check if the `or` here is really necessary.896 scope_ or "function", # type: ignore[unreachable]897 descr=f"Fixture '{func.__name__}'",898 where=baseid,899 )900 self.scope = scope_901 self.params: Optional[Sequence[object]] = params902 self.argnames: Tuple[str, ...] = getfuncargnames(903 func, name=argname, is_method=unittest904 )905 self.unittest = unittest906 self.ids = ids907 self.cached_result: Optional[_FixtureCachedResult[FixtureValue]] = None908 self._finalizers: List[Callable[[], object]] = []909 def addfinalizer(self, finalizer: Callable[[], object]) -> None:910 self._finalizers.append(finalizer)911 def finish(self, request: SubRequest) -> None:912 exc = None913 try:914 while self._finalizers:915 try:916 func = self._finalizers.pop()917 func()918 except BaseException as e:919 # XXX Only first exception will be seen by user,920 # ideally all should be reported.921 if exc is None:922 exc = e923 if exc:924 raise exc925 finally:926 hook = self._fixturemanager.session.gethookproxy(request.node.path)927 hook.pytest_fixture_post_finalizer(fixturedef=self, request=request)928 # Even if finalization fails, we invalidate the cached fixture929 # value and remove all finalizers because they may be bound methods930 # which will keep instances alive.931 self.cached_result = None932 self._finalizers = []933 def execute(self, request: SubRequest) -> FixtureValue:934 # Get required arguments and register our own finish()935 # with their finalization.936 for argname in self.argnames:937 fixturedef = request._get_active_fixturedef(argname)938 if argname != "request":939 # PseudoFixtureDef is only for "request".940 assert isinstance(fixturedef, FixtureDef)941 fixturedef.addfinalizer(functools.partial(self.finish, request=request))942 my_cache_key = self.cache_key(request)943 if self.cached_result is not None:944 # note: comparison with `==` can fail (or be expensive) for e.g.945 # numpy arrays (#6497).946 cache_key = self.cached_result[1]947 if my_cache_key is cache_key:948 if self.cached_result[2] is not None:949 _, val, tb = self.cached_result[2]950 raise val.with_traceback(tb)951 else:952 result = self.cached_result[0]953 return result954 # We have a previous but differently parametrized fixture instance955 # so we need to tear it down before creating a new one.956 self.finish(request)957 assert self.cached_result is None958 hook = self._fixturemanager.session.gethookproxy(request.node.path)959 result = hook.pytest_fixture_setup(fixturedef=self, request=request)960 return result961 def cache_key(self, request: SubRequest) -> object:962 return request.param_index if not hasattr(request, "param") else request.param963 def __repr__(self) -> str:964 return "<FixtureDef argname={!r} scope={!r} baseid={!r}>".format(965 self.argname, self.scope, self.baseid966 )967def resolve_fixture_function(968 fixturedef: FixtureDef[FixtureValue], request: FixtureRequest969) -> "_FixtureFunc[FixtureValue]":970 """Get the actual callable that can be called to obtain the fixture971 value, dealing with unittest-specific instances and bound methods."""972 fixturefunc = fixturedef.func973 if fixturedef.unittest:974 if request.instance is not None:975 # Bind the unbound method to the TestCase instance.976 fixturefunc = fixturedef.func.__get__(request.instance) # type: ignore[union-attr]977 else:978 # The fixture function needs to be bound to the actual979 # request.instance so that code working with "fixturedef" behaves980 # as expected.981 if request.instance is not None:982 # Handle the case where fixture is defined not in a test class, but some other class983 # (for example a plugin class with a fixture), see #2270.984 if hasattr(fixturefunc, "__self__") and not isinstance(985 request.instance, fixturefunc.__self__.__class__ # type: ignore[union-attr]986 ):987 return fixturefunc988 fixturefunc = getimfunc(fixturedef.func)989 if fixturefunc != fixturedef.func:990 fixturefunc = fixturefunc.__get__(request.instance) # type: ignore[union-attr]991 return fixturefunc992def pytest_fixture_setup(993 fixturedef: FixtureDef[FixtureValue], request: SubRequest994) -> FixtureValue:995 """Execution of fixture setup."""996 kwargs = {}997 for argname in fixturedef.argnames:998 fixdef = request._get_active_fixturedef(argname)999 assert fixdef.cached_result is not None1000 result, arg_cache_key, exc = fixdef.cached_result1001 request._check_scope(argname, request.scope, fixdef.scope)1002 kwargs[argname] = result1003 fixturefunc = resolve_fixture_function(fixturedef, request)1004 my_cache_key = fixturedef.cache_key(request)1005 try:1006 result = call_fixture_func(fixturefunc, request, kwargs)1007 except TEST_OUTCOME:1008 exc_info = sys.exc_info()1009 assert exc_info[0] is not None1010 fixturedef.cached_result = (None, my_cache_key, exc_info)1011 raise1012 fixturedef.cached_result = (result, my_cache_key, None)1013 return result1014def _ensure_immutable_ids(1015 ids: Optional[1016 Union[1017 Iterable[Union[None, str, float, int, bool]],1018 Callable[[Any], Optional[object]],1019 ]1020 ],1021) -> Optional[1022 Union[1023 Tuple[Union[None, str, float, int, bool], ...],1024 Callable[[Any], Optional[object]],1025 ]1026]:1027 if ids is None:1028 return None1029 if callable(ids):1030 return ids1031 return tuple(ids)1032def _params_converter(1033 params: Optional[Iterable[object]],1034) -> Optional[Tuple[object, ...]]:1035 return tuple(params) if params is not None else None1036def wrap_function_to_error_out_if_called_directly(1037 function: FixtureFunction,1038 fixture_marker: "FixtureFunctionMarker",1039) -> FixtureFunction:1040 """Wrap the given fixture function so we can raise an error about it being called directly,1041 instead of used as an argument in a test function."""1042 message = (1043 'Fixture "{name}" called directly. Fixtures are not meant to be called directly,\n'1044 "but are created automatically when test functions request them as parameters.\n"1045 "See https://docs.pytest.org/en/stable/fixture.html for more information about fixtures, and\n"1046 "https://docs.pytest.org/en/stable/deprecations.html#calling-fixtures-directly about how to update your code."1047 ).format(name=fixture_marker.name or function.__name__)1048 @functools.wraps(function)1049 def result(*args, **kwargs):1050 fail(message, pytrace=False)1051 # Keep reference to the original function in our own custom attribute so we don't unwrap1052 # further than this point and lose useful wrappings like @mock.patch (#3774).1053 result.__pytest_wrapped__ = _PytestWrapper(function) # type: ignore[attr-defined]1054 return cast(FixtureFunction, result)1055@final1056@attr.s(frozen=True)1057class FixtureFunctionMarker:1058 scope = attr.ib(type="Union[_Scope, Callable[[str, Config], _Scope]]")1059 params = attr.ib(type=Optional[Tuple[object, ...]], converter=_params_converter)1060 autouse = attr.ib(type=bool, default=False)1061 ids = attr.ib(1062 type=Union[1063 Tuple[Union[None, str, float, int, bool], ...],1064 Callable[[Any], Optional[object]],1065 ],1066 default=None,1067 converter=_ensure_immutable_ids,1068 )1069 name = attr.ib(type=Optional[str], default=None)1070 def __call__(self, function: FixtureFunction) -> FixtureFunction:1071 if inspect.isclass(function):1072 raise ValueError("class fixtures not supported (maybe in the future)")1073 if getattr(function, "_pytestfixturefunction", False):1074 raise ValueError(1075 "fixture is being applied more than once to the same function"1076 )1077 function = wrap_function_to_error_out_if_called_directly(function, self)1078 name = self.name or function.__name__1079 if name == "request":1080 location = getlocation(function)1081 fail(1082 "'request' is a reserved word for fixtures, use another name:\n {}".format(1083 location1084 ),1085 pytrace=False,1086 )1087 # Type ignored because https://github.com/python/mypy/issues/2087.1088 function._pytestfixturefunction = self # type: ignore[attr-defined]1089 return function1090@overload1091def fixture(1092 fixture_function: FixtureFunction,1093 *,1094 scope: "Union[_Scope, Callable[[str, Config], _Scope]]" = ...,1095 params: Optional[Iterable[object]] = ...,1096 autouse: bool = ...,1097 ids: Optional[1098 Union[1099 Iterable[Union[None, str, float, int, bool]],1100 Callable[[Any], Optional[object]],1101 ]1102 ] = ...,1103 name: Optional[str] = ...,1104) -> FixtureFunction:1105 ...1106@overload1107def fixture(1108 fixture_function: None = ...,1109 *,1110 scope: "Union[_Scope, Callable[[str, Config], _Scope]]" = ...,1111 params: Optional[Iterable[object]] = ...,1112 autouse: bool = ...,1113 ids: Optional[1114 Union[1115 Iterable[Union[None, str, float, int, bool]],1116 Callable[[Any], Optional[object]],1117 ]1118 ] = ...,1119 name: Optional[str] = None,1120) -> FixtureFunctionMarker:1121 ...1122def fixture(1123 fixture_function: Optional[FixtureFunction] = None,1124 *,1125 scope: "Union[_Scope, Callable[[str, Config], _Scope]]" = "function",1126 params: Optional[Iterable[object]] = None,1127 autouse: bool = False,1128 ids: Optional[1129 Union[1130 Iterable[Union[None, str, float, int, bool]],1131 Callable[[Any], Optional[object]],1132 ]1133 ] = None,1134 name: Optional[str] = None,1135) -> Union[FixtureFunctionMarker, FixtureFunction]:1136 """Decorator to mark a fixture factory function.1137 This decorator can be used, with or without parameters, to define a1138 fixture function.1139 The name of the fixture function can later be referenced to cause its1140 invocation ahead of running tests: test modules or classes can use the1141 ``pytest.mark.usefixtures(fixturename)`` marker.1142 Test functions can directly use fixture names as input arguments in which1143 case the fixture instance returned from the fixture function will be1144 injected.1145 Fixtures can provide their values to test functions using ``return`` or1146 ``yield`` statements. When using ``yield`` the code block after the1147 ``yield`` statement is executed as teardown code regardless of the test1148 outcome, and must yield exactly once.1149 :param scope:1150 The scope for which this fixture is shared; one of ``"function"``1151 (default), ``"class"``, ``"module"``, ``"package"`` or ``"session"``.1152 This parameter may also be a callable which receives ``(fixture_name, config)``1153 as parameters, and must return a ``str`` with one of the values mentioned above.1154 See :ref:`dynamic scope` in the docs for more information.1155 :param params:1156 An optional list of parameters which will cause multiple invocations1157 of the fixture function and all of the tests using it. The current1158 parameter is available in ``request.param``.1159 :param autouse:1160 If True, the fixture func is activated for all tests that can see it.1161 If False (the default), an explicit reference is needed to activate1162 the fixture.1163 :param ids:1164 List of string ids each corresponding to the params so that they are1165 part of the test id. If no ids are provided they will be generated1166 automatically from the params.1167 :param name:1168 The name of the fixture. This defaults to the name of the decorated1169 function. If a fixture is used in the same module in which it is1170 defined, the function name of the fixture will be shadowed by the1171 function arg that requests the fixture; one way to resolve this is to1172 name the decorated function ``fixture_<fixturename>`` and then use1173 ``@pytest.fixture(name='<fixturename>')``.1174 """1175 fixture_marker = FixtureFunctionMarker(1176 scope=scope,1177 params=params,1178 autouse=autouse,1179 ids=ids,1180 name=name,1181 )1182 # Direct decoration.1183 if fixture_function:1184 return fixture_marker(fixture_function)1185 return fixture_marker1186def yield_fixture(1187 fixture_function=None,1188 *args,1189 scope="function",1190 params=None,1191 autouse=False,1192 ids=None,1193 name=None,1194):1195 """(Return a) decorator to mark a yield-fixture factory function.1196 .. deprecated:: 3.01197 Use :py:func:`pytest.fixture` directly instead.1198 """1199 warnings.warn(YIELD_FIXTURE, stacklevel=2)1200 return fixture(1201 fixture_function,1202 *args,1203 scope=scope,1204 params=params,1205 autouse=autouse,1206 ids=ids,1207 name=name,1208 )1209@fixture(scope="session")1210def pytestconfig(request: FixtureRequest) -> Config:1211 """Session-scoped fixture that returns the session's :class:`pytest.Config`1212 object.1213 Example::1214 def test_foo(pytestconfig):1215 if pytestconfig.getoption("verbose") > 0:1216 ...1217 """1218 return request.config1219def pytest_addoption(parser: Parser) -> None:1220 parser.addini(1221 "usefixtures",1222 type="args",1223 default=[],1224 help="list of default fixtures to be used with this project",1225 )1226class FixtureManager:1227 """pytest fixture definitions and information is stored and managed1228 from this class.1229 During collection fm.parsefactories() is called multiple times to parse1230 fixture function definitions into FixtureDef objects and internal1231 data structures.1232 During collection of test functions, metafunc-mechanics instantiate1233 a FuncFixtureInfo object which is cached per node/func-name.1234 This FuncFixtureInfo object is later retrieved by Function nodes1235 which themselves offer a fixturenames attribute.1236 The FuncFixtureInfo object holds information about fixtures and FixtureDefs1237 relevant for a particular function. An initial list of fixtures is1238 assembled like this:1239 - ini-defined usefixtures1240 - autouse-marked fixtures along the collection chain up from the function1241 - usefixtures markers at module/class/function level1242 - test function funcargs1243 Subsequently the funcfixtureinfo.fixturenames attribute is computed1244 as the closure of the fixtures needed to setup the initial fixtures,1245 i.e. fixtures needed by fixture functions themselves are appended1246 to the fixturenames list.1247 Upon the test-setup phases all fixturenames are instantiated, retrieved1248 by a lookup of their FuncFixtureInfo.1249 """1250 FixtureLookupError = FixtureLookupError1251 FixtureLookupErrorRepr = FixtureLookupErrorRepr1252 def __init__(self, session: "Session") -> None:1253 self.session = session1254 self.config: Config = session.config1255 self._arg2fixturedefs: Dict[str, List[FixtureDef[Any]]] = {}1256 self._holderobjseen: Set[object] = set()1257 # A mapping from a nodeid to a list of autouse fixtures it defines.1258 self._nodeid_autousenames: Dict[str, List[str]] = {1259 "": self.config.getini("usefixtures"),1260 }1261 session.config.pluginmanager.register(self, "funcmanage")1262 def _get_direct_parametrize_args(self, node: nodes.Node) -> List[str]:1263 """Return all direct parametrization arguments of a node, so we don't1264 mistake them for fixtures.1265 Check https://github.com/pytest-dev/pytest/issues/5036.1266 These things are done later as well when dealing with parametrization1267 so this could be improved.1268 """1269 parametrize_argnames: List[str] = []1270 for marker in node.iter_markers(name="parametrize"):1271 if not marker.kwargs.get("indirect", False):1272 p_argnames, _ = ParameterSet._parse_parametrize_args(1273 *marker.args, **marker.kwargs1274 )1275 parametrize_argnames.extend(p_argnames)1276 return parametrize_argnames1277 def getfixtureinfo(1278 self, node: nodes.Node, func, cls, funcargs: bool = True1279 ) -> FuncFixtureInfo:1280 if funcargs and not getattr(node, "nofuncargs", False):1281 argnames = getfuncargnames(func, name=node.name, cls=cls)1282 else:1283 argnames = ()1284 usefixtures = tuple(1285 arg for mark in node.iter_markers(name="usefixtures") for arg in mark.args1286 )1287 initialnames = usefixtures + argnames1288 fm = node.session._fixturemanager1289 initialnames, names_closure, arg2fixturedefs = fm.getfixtureclosure(1290 initialnames, node, ignore_args=self._get_direct_parametrize_args(node)1291 )1292 return FuncFixtureInfo(argnames, initialnames, names_closure, arg2fixturedefs)1293 def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None:1294 nodeid = None1295 try:1296 p = absolutepath(plugin.__file__) # type: ignore[attr-defined]1297 except AttributeError:1298 pass1299 else:1300 # Construct the base nodeid which is later used to check1301 # what fixtures are visible for particular tests (as denoted1302 # by their test id).1303 if p.name.startswith("conftest.py"):1304 try:1305 nodeid = str(p.parent.relative_to(self.config.rootpath))1306 except ValueError:1307 nodeid = ""1308 if nodeid == ".":1309 nodeid = ""1310 if os.sep != nodes.SEP:1311 nodeid = nodeid.replace(os.sep, nodes.SEP)1312 self.parsefactories(plugin, nodeid)1313 def _getautousenames(self, nodeid: str) -> Iterator[str]:1314 """Return the names of autouse fixtures applicable to nodeid."""1315 for parentnodeid in nodes.iterparentnodeids(nodeid):1316 basenames = self._nodeid_autousenames.get(parentnodeid)1317 if basenames:1318 yield from basenames1319 def getfixtureclosure(1320 self,1321 fixturenames: Tuple[str, ...],1322 parentnode: nodes.Node,1323 ignore_args: Sequence[str] = (),1324 ) -> Tuple[Tuple[str, ...], List[str], Dict[str, Sequence[FixtureDef[Any]]]]:1325 # Collect the closure of all fixtures, starting with the given1326 # fixturenames as the initial set. As we have to visit all1327 # factory definitions anyway, we also return an arg2fixturedefs1328 # mapping so that the caller can reuse it and does not have1329 # to re-discover fixturedefs again for each fixturename1330 # (discovering matching fixtures for a given name/node is expensive).1331 parentid = parentnode.nodeid1332 fixturenames_closure = list(self._getautousenames(parentid))1333 def merge(otherlist: Iterable[str]) -> None:1334 for arg in otherlist:1335 if arg not in fixturenames_closure:1336 fixturenames_closure.append(arg)1337 merge(fixturenames)1338 # At this point, fixturenames_closure contains what we call "initialnames",1339 # which is a set of fixturenames the function immediately requests. We1340 # need to return it as well, so save this.1341 initialnames = tuple(fixturenames_closure)1342 arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {}1343 lastlen = -11344 while lastlen != len(fixturenames_closure):1345 lastlen = len(fixturenames_closure)1346 for argname in fixturenames_closure:1347 if argname in ignore_args:1348 continue1349 if argname in arg2fixturedefs:1350 continue1351 fixturedefs = self.getfixturedefs(argname, parentid)1352 if fixturedefs:1353 arg2fixturedefs[argname] = fixturedefs1354 merge(fixturedefs[-1].argnames)1355 def sort_by_scope(arg_name: str) -> int:1356 try:1357 fixturedefs = arg2fixturedefs[arg_name]1358 except KeyError:1359 return scopes.index("function")1360 else:1361 return fixturedefs[-1].scopenum1362 fixturenames_closure.sort(key=sort_by_scope)1363 return initialnames, fixturenames_closure, arg2fixturedefs1364 def pytest_generate_tests(self, metafunc: "Metafunc") -> None:1365 """Generate new tests based on parametrized fixtures used by the given metafunc"""1366 def get_parametrize_mark_argnames(mark: Mark) -> Sequence[str]:1367 args, _ = ParameterSet._parse_parametrize_args(*mark.args, **mark.kwargs)1368 return args1369 for argname in metafunc.fixturenames:1370 # Get the FixtureDefs for the argname.1371 fixture_defs = metafunc._arg2fixturedefs.get(argname)1372 if not fixture_defs:1373 # Will raise FixtureLookupError at setup time if not parametrized somewhere1374 # else (e.g @pytest.mark.parametrize)1375 continue1376 # If the test itself parametrizes using this argname, give it1377 # precedence.1378 if any(1379 argname in get_parametrize_mark_argnames(mark)1380 for mark in metafunc.definition.iter_markers("parametrize")1381 ):1382 continue1383 # In the common case we only look at the fixture def with the1384 # closest scope (last in the list). But if the fixture overrides1385 # another fixture, while requesting the super fixture, keep going1386 # in case the super fixture is parametrized (#1953).1387 for fixturedef in reversed(fixture_defs):1388 # Fixture is parametrized, apply it and stop.1389 if fixturedef.params is not None:1390 metafunc.parametrize(1391 argname,1392 fixturedef.params,1393 indirect=True,1394 scope=fixturedef.scope,1395 ids=fixturedef.ids,1396 )1397 break1398 # Not requesting the overridden super fixture, stop.1399 if argname not in fixturedef.argnames:1400 break1401 # Try next super fixture, if any.1402 def pytest_collection_modifyitems(self, items: List[nodes.Item]) -> None:1403 # Separate parametrized setups.1404 items[:] = reorder_items(items)1405 def parsefactories(1406 self, node_or_obj, nodeid=NOTSET, unittest: bool = False1407 ) -> None:1408 if nodeid is not NOTSET:1409 holderobj = node_or_obj1410 else:1411 holderobj = node_or_obj.obj1412 nodeid = node_or_obj.nodeid1413 if holderobj in self._holderobjseen:1414 return1415 self._holderobjseen.add(holderobj)1416 autousenames = []1417 for name in dir(holderobj):1418 # ugly workaround for one of the fspath deprecated property of node1419 # todo: safely generalize1420 if isinstance(holderobj, nodes.Node) and name == "fspath":1421 continue1422 # The attribute can be an arbitrary descriptor, so the attribute1423 # access below can raise. safe_getatt() ignores such exceptions.1424 obj = safe_getattr(holderobj, name, None)1425 marker = getfixturemarker(obj)1426 if not isinstance(marker, FixtureFunctionMarker):1427 # Magic globals with __getattr__ might have got us a wrong1428 # fixture attribute.1429 continue1430 if marker.name:1431 name = marker.name1432 # During fixture definition we wrap the original fixture function1433 # to issue a warning if called directly, so here we unwrap it in1434 # order to not emit the warning when pytest itself calls the1435 # fixture function.1436 obj = get_real_method(obj, holderobj)1437 fixture_def = FixtureDef(1438 fixturemanager=self,1439 baseid=nodeid,1440 argname=name,1441 func=obj,1442 scope=marker.scope,1443 params=marker.params,1444 unittest=unittest,1445 ids=marker.ids,1446 )1447 faclist = self._arg2fixturedefs.setdefault(name, [])1448 if fixture_def.has_location:1449 faclist.append(fixture_def)1450 else:1451 # fixturedefs with no location are at the front1452 # so this inserts the current fixturedef after the1453 # existing fixturedefs from external plugins but1454 # before the fixturedefs provided in conftests.1455 i = len([f for f in faclist if not f.has_location])1456 faclist.insert(i, fixture_def)1457 if marker.autouse:1458 autousenames.append(name)1459 if autousenames:1460 self._nodeid_autousenames.setdefault(nodeid or "", []).extend(autousenames)1461 def getfixturedefs(1462 self, argname: str, nodeid: str1463 ) -> Optional[Sequence[FixtureDef[Any]]]:1464 """Get a list of fixtures which are applicable to the given node id.1465 :param str argname: Name of the fixture to search for.1466 :param str nodeid: Full node id of the requesting test.1467 :rtype: Sequence[FixtureDef]1468 """1469 try:1470 fixturedefs = self._arg2fixturedefs[argname]1471 except KeyError:1472 return None1473 return tuple(self._matchfactories(fixturedefs, nodeid))1474 def _matchfactories(1475 self, fixturedefs: Iterable[FixtureDef[Any]], nodeid: str1476 ) -> Iterator[FixtureDef[Any]]:1477 parentnodeids = set(nodes.iterparentnodeids(nodeid))1478 for fixturedef in fixturedefs:1479 if fixturedef.baseid in parentnodeids:...
nodes.py
Source:nodes.py
1import os2import warnings3from pathlib import Path4from typing import Any5from typing import Callable6from typing import Iterable7from typing import Iterator8from typing import List9from typing import MutableMapping10from typing import Optional11from typing import overload12from typing import Set13from typing import Tuple14from typing import Type15from typing import TYPE_CHECKING16from typing import TypeVar17from typing import Union18import _pytest._code19from _pytest._code import getfslineno20from _pytest._code.code import ExceptionInfo21from _pytest._code.code import TerminalRepr22from _pytest.compat import cached_property23from _pytest.compat import LEGACY_PATH24from _pytest.compat import legacy_path25from _pytest.config import Config26from _pytest.config import ConftestImportFailure27from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH28from _pytest.deprecated import NODE_FSPATH29from _pytest.mark.structures import Mark30from _pytest.mark.structures import MarkDecorator31from _pytest.mark.structures import NodeKeywords32from _pytest.outcomes import fail33from _pytest.pathlib import absolutepath34from _pytest.pathlib import commonpath35from _pytest.store import Store36if TYPE_CHECKING:37 # Imported here due to circular import.38 from _pytest.main import Session39 from _pytest._code.code import _TracebackStyle40SEP = "/"41tracebackcutdir = Path(_pytest.__file__).parent42def iterparentnodeids(nodeid: str) -> Iterator[str]:43 """Return the parent node IDs of a given node ID, inclusive.44 For the node ID45 "testing/code/test_excinfo.py::TestFormattedExcinfo::test_repr_source"46 the result would be47 ""48 "testing"49 "testing/code"50 "testing/code/test_excinfo.py"51 "testing/code/test_excinfo.py::TestFormattedExcinfo"52 "testing/code/test_excinfo.py::TestFormattedExcinfo::test_repr_source"53 Note that / components are only considered until the first ::.54 """55 pos = 056 first_colons: Optional[int] = nodeid.find("::")57 if first_colons == -1:58 first_colons = None59 # The root Session node - always present.60 yield ""61 # Eagerly consume SEP parts until first colons.62 while True:63 at = nodeid.find(SEP, pos, first_colons)64 if at == -1:65 break66 if at > 0:67 yield nodeid[:at]68 pos = at + len(SEP)69 # Eagerly consume :: parts.70 while True:71 at = nodeid.find("::", pos)72 if at == -1:73 break74 if at > 0:75 yield nodeid[:at]76 pos = at + len("::")77 # The node ID itself.78 if nodeid:79 yield nodeid80def _imply_path(81 path: Optional[Path], fspath: Optional[LEGACY_PATH]82) -> Tuple[Path, LEGACY_PATH]:83 if path is not None:84 if fspath is not None:85 if Path(fspath) != path:86 raise ValueError(87 f"Path({fspath!r}) != {path!r}\n"88 "if both path and fspath are given they need to be equal"89 )90 assert Path(fspath) == path, f"{fspath} != {path}"91 else:92 fspath = legacy_path(path)93 return path, fspath94 else:95 assert fspath is not None96 return Path(fspath), fspath97_NodeType = TypeVar("_NodeType", bound="Node")98class NodeMeta(type):99 def __call__(self, *k, **kw):100 msg = (101 "Direct construction of {name} has been deprecated, please use {name}.from_parent.\n"102 "See "103 "https://docs.pytest.org/en/stable/deprecations.html#node-construction-changed-to-node-from-parent"104 " for more details."105 ).format(name=self.__name__)106 fail(msg, pytrace=False)107 def _create(self, *k, **kw):108 return super().__call__(*k, **kw)109class Node(metaclass=NodeMeta):110 """Base class for Collector and Item, the components of the test111 collection tree.112 Collector subclasses have children; Items are leaf nodes.113 """114 # Use __slots__ to make attribute access faster.115 # Note that __dict__ is still available.116 __slots__ = (117 "name",118 "parent",119 "config",120 "session",121 "path",122 "_nodeid",123 "_store",124 "__dict__",125 )126 def __init__(127 self,128 name: str,129 parent: "Optional[Node]" = None,130 config: Optional[Config] = None,131 session: "Optional[Session]" = None,132 fspath: Optional[LEGACY_PATH] = None,133 path: Optional[Path] = None,134 nodeid: Optional[str] = None,135 ) -> None:136 #: A unique name within the scope of the parent node.137 self.name = name138 #: The parent collector node.139 self.parent = parent140 #: The pytest config object.141 if config:142 self.config: Config = config143 else:144 if not parent:145 raise TypeError("config or parent must be provided")146 self.config = parent.config147 #: The pytest session this node is part of.148 if session:149 self.session = session150 else:151 if not parent:152 raise TypeError("session or parent must be provided")153 self.session = parent.session154 #: Filesystem path where this node was collected from (can be None).155 self.path = _imply_path(path or getattr(parent, "path", None), fspath=fspath)[0]156 # The explicit annotation is to avoid publicly exposing NodeKeywords.157 #: Keywords/markers collected from all scopes.158 self.keywords: MutableMapping[str, Any] = NodeKeywords(self)159 #: The marker objects belonging to this node.160 self.own_markers: List[Mark] = []161 #: Allow adding of extra keywords to use for matching.162 self.extra_keyword_matches: Set[str] = set()163 if nodeid is not None:164 assert "::()" not in nodeid165 self._nodeid = nodeid166 else:167 if not self.parent:168 raise TypeError("nodeid or parent must be provided")169 self._nodeid = self.parent.nodeid170 if self.name != "()":171 self._nodeid += "::" + self.name172 # A place where plugins can store information on the node for their173 # own use. Currently only intended for internal plugins.174 self._store = Store()175 @property176 def fspath(self) -> LEGACY_PATH:177 """(deprecated) returns a legacy_path copy of self.path"""178 warnings.warn(NODE_FSPATH.format(type=type(self).__name__), stacklevel=2)179 return legacy_path(self.path)180 @fspath.setter181 def fspath(self, value: LEGACY_PATH) -> None:182 warnings.warn(NODE_FSPATH.format(type=type(self).__name__), stacklevel=2)183 self.path = Path(value)184 @classmethod185 def from_parent(cls, parent: "Node", **kw):186 """Public constructor for Nodes.187 This indirection got introduced in order to enable removing188 the fragile logic from the node constructors.189 Subclasses can use ``super().from_parent(...)`` when overriding the190 construction.191 :param parent: The parent node of this Node.192 """193 if "config" in kw:194 raise TypeError("config is not a valid argument for from_parent")195 if "session" in kw:196 raise TypeError("session is not a valid argument for from_parent")197 return cls._create(parent=parent, **kw)198 @property199 def ihook(self):200 """fspath-sensitive hook proxy used to call pytest hooks."""201 return self.session.gethookproxy(self.path)202 def __repr__(self) -> str:203 return "<{} {}>".format(self.__class__.__name__, getattr(self, "name", None))204 def warn(self, warning: Warning) -> None:205 """Issue a warning for this Node.206 Warnings will be displayed after the test session, unless explicitly suppressed.207 :param Warning warning:208 The warning instance to issue.209 :raises ValueError: If ``warning`` instance is not a subclass of Warning.210 Example usage:211 .. code-block:: python212 node.warn(PytestWarning("some message"))213 node.warn(UserWarning("some message"))214 .. versionchanged:: 6.2215 Any subclass of :class:`Warning` is now accepted, rather than only216 :class:`PytestWarning <pytest.PytestWarning>` subclasses.217 """218 # enforce type checks here to avoid getting a generic type error later otherwise.219 if not isinstance(warning, Warning):220 raise ValueError(221 "warning must be an instance of Warning or subclass, got {!r}".format(222 warning223 )224 )225 path, lineno = get_fslocation_from_item(self)226 assert lineno is not None227 warnings.warn_explicit(228 warning,229 category=None,230 filename=str(path),231 lineno=lineno + 1,232 )233 # Methods for ordering nodes.234 @property235 def nodeid(self) -> str:236 """A ::-separated string denoting its collection tree address."""237 return self._nodeid238 def __hash__(self) -> int:239 return hash(self._nodeid)240 def setup(self) -> None:241 pass242 def teardown(self) -> None:243 pass244 def listchain(self) -> List["Node"]:245 """Return list of all parent collectors up to self, starting from246 the root of collection tree."""247 chain = []248 item: Optional[Node] = self249 while item is not None:250 chain.append(item)251 item = item.parent252 chain.reverse()253 return chain254 def add_marker(255 self, marker: Union[str, MarkDecorator], append: bool = True256 ) -> None:257 """Dynamically add a marker object to the node.258 :param append:259 Whether to append the marker, or prepend it.260 """261 from _pytest.mark import MARK_GEN262 if isinstance(marker, MarkDecorator):263 marker_ = marker264 elif isinstance(marker, str):265 marker_ = getattr(MARK_GEN, marker)266 else:267 raise ValueError("is not a string or pytest.mark.* Marker")268 self.keywords[marker_.name] = marker_269 if append:270 self.own_markers.append(marker_.mark)271 else:272 self.own_markers.insert(0, marker_.mark)273 def iter_markers(self, name: Optional[str] = None) -> Iterator[Mark]:274 """Iterate over all markers of the node.275 :param name: If given, filter the results by the name attribute.276 """277 return (x[1] for x in self.iter_markers_with_node(name=name))278 def iter_markers_with_node(279 self, name: Optional[str] = None280 ) -> Iterator[Tuple["Node", Mark]]:281 """Iterate over all markers of the node.282 :param name: If given, filter the results by the name attribute.283 :returns: An iterator of (node, mark) tuples.284 """285 for node in reversed(self.listchain()):286 for mark in node.own_markers:287 if name is None or getattr(mark, "name", None) == name:288 yield node, mark289 @overload290 def get_closest_marker(self, name: str) -> Optional[Mark]:291 ...292 @overload293 def get_closest_marker(self, name: str, default: Mark) -> Mark:294 ...295 def get_closest_marker(296 self, name: str, default: Optional[Mark] = None297 ) -> Optional[Mark]:298 """Return the first marker matching the name, from closest (for299 example function) to farther level (for example module level).300 :param default: Fallback return value if no marker was found.301 :param name: Name to filter by.302 """303 return next(self.iter_markers(name=name), default)304 def listextrakeywords(self) -> Set[str]:305 """Return a set of all extra keywords in self and any parents."""306 extra_keywords: Set[str] = set()307 for item in self.listchain():308 extra_keywords.update(item.extra_keyword_matches)309 return extra_keywords310 def listnames(self) -> List[str]:311 return [x.name for x in self.listchain()]312 def addfinalizer(self, fin: Callable[[], object]) -> None:313 """Register a function to be called when this node is finalized.314 This method can only be called when this node is active315 in a setup chain, for example during self.setup().316 """317 self.session._setupstate.addfinalizer(fin, self)318 def getparent(self, cls: Type[_NodeType]) -> Optional[_NodeType]:319 """Get the next parent node (including self) which is an instance of320 the given class."""321 current: Optional[Node] = self322 while current and not isinstance(current, cls):323 current = current.parent324 assert current is None or isinstance(current, cls)325 return current326 def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None:327 pass328 def _repr_failure_py(329 self,330 excinfo: ExceptionInfo[BaseException],331 style: "Optional[_TracebackStyle]" = None,332 ) -> TerminalRepr:333 from _pytest.fixtures import FixtureLookupError334 if isinstance(excinfo.value, ConftestImportFailure):335 excinfo = ExceptionInfo.from_exc_info(excinfo.value.excinfo)336 if isinstance(excinfo.value, fail.Exception):337 if not excinfo.value.pytrace:338 style = "value"339 if isinstance(excinfo.value, FixtureLookupError):340 return excinfo.value.formatrepr()341 if self.config.getoption("fulltrace", False):342 style = "long"343 else:344 tb = _pytest._code.Traceback([excinfo.traceback[-1]])345 self._prunetraceback(excinfo)346 if len(excinfo.traceback) == 0:347 excinfo.traceback = tb348 if style == "auto":349 style = "long"350 # XXX should excinfo.getrepr record all data and toterminal() process it?351 if style is None:352 if self.config.getoption("tbstyle", "auto") == "short":353 style = "short"354 else:355 style = "long"356 if self.config.getoption("verbose", 0) > 1:357 truncate_locals = False358 else:359 truncate_locals = True360 # excinfo.getrepr() formats paths relative to the CWD if `abspath` is False.361 # It is possible for a fixture/test to change the CWD while this code runs, which362 # would then result in the user seeing confusing paths in the failure message.363 # To fix this, if the CWD changed, always display the full absolute path.364 # It will be better to just always display paths relative to invocation_dir, but365 # this requires a lot of plumbing (#6428).366 try:367 abspath = Path(os.getcwd()) != self.config.invocation_params.dir368 except OSError:369 abspath = True370 return excinfo.getrepr(371 funcargs=True,372 abspath=abspath,373 showlocals=self.config.getoption("showlocals", False),374 style=style,375 tbfilter=False, # pruned already, or in --fulltrace mode.376 truncate_locals=truncate_locals,377 )378 def repr_failure(379 self,380 excinfo: ExceptionInfo[BaseException],381 style: "Optional[_TracebackStyle]" = None,382 ) -> Union[str, TerminalRepr]:383 """Return a representation of a collection or test failure.384 :param excinfo: Exception information for the failure.385 """386 return self._repr_failure_py(excinfo, style)387def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[int]]:388 """Try to extract the actual location from a node, depending on available attributes:389 * "location": a pair (path, lineno)390 * "obj": a Python object that the node wraps.391 * "fspath": just a path392 :rtype: A tuple of (str|Path, int) with filename and line number.393 """394 # See Item.location.395 location: Optional[Tuple[str, Optional[int], str]] = getattr(node, "location", None)396 if location is not None:397 return location[:2]398 obj = getattr(node, "obj", None)399 if obj is not None:400 return getfslineno(obj)401 return getattr(node, "fspath", "unknown location"), -1402class Collector(Node):403 """Collector instances create children through collect() and thus404 iteratively build a tree."""405 class CollectError(Exception):406 """An error during collection, contains a custom message."""407 def collect(self) -> Iterable[Union["Item", "Collector"]]:408 """Return a list of children (items and collectors) for this409 collection node."""410 raise NotImplementedError("abstract")411 # TODO: This omits the style= parameter which breaks Liskov Substitution.412 def repr_failure( # type: ignore[override]413 self, excinfo: ExceptionInfo[BaseException]414 ) -> Union[str, TerminalRepr]:415 """Return a representation of a collection failure.416 :param excinfo: Exception information for the failure.417 """418 if isinstance(excinfo.value, self.CollectError) and not self.config.getoption(419 "fulltrace", False420 ):421 exc = excinfo.value422 return str(exc.args[0])423 # Respect explicit tbstyle option, but default to "short"424 # (_repr_failure_py uses "long" with "fulltrace" option always).425 tbstyle = self.config.getoption("tbstyle", "auto")426 if tbstyle == "auto":427 tbstyle = "short"428 return self._repr_failure_py(excinfo, style=tbstyle)429 def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None:430 if hasattr(self, "path"):431 traceback = excinfo.traceback432 ntraceback = traceback.cut(path=self.path)433 if ntraceback == traceback:434 ntraceback = ntraceback.cut(excludepath=tracebackcutdir)435 excinfo.traceback = ntraceback.filter()436def _check_initialpaths_for_relpath(session: "Session", path: Path) -> Optional[str]:437 for initial_path in session._initialpaths:438 if commonpath(path, initial_path) == initial_path:439 rel = str(path.relative_to(initial_path))440 return "" if rel == "." else rel441 return None442class FSCollector(Collector):443 def __init__(444 self,445 fspath: Optional[LEGACY_PATH],446 path: Optional[Path],447 parent=None,448 config: Optional[Config] = None,449 session: Optional["Session"] = None,450 nodeid: Optional[str] = None,451 ) -> None:452 path, fspath = _imply_path(path, fspath=fspath)453 name = path.name454 if parent is not None and parent.path != path:455 try:456 rel = path.relative_to(parent.path)457 except ValueError:458 pass459 else:460 name = str(rel)461 name = name.replace(os.sep, SEP)462 self.path = path463 session = session or parent.session464 if nodeid is None:465 try:466 nodeid = str(self.path.relative_to(session.config.rootpath))467 except ValueError:468 nodeid = _check_initialpaths_for_relpath(session, path)469 if nodeid and os.sep != SEP:470 nodeid = nodeid.replace(os.sep, SEP)471 super().__init__(472 name, parent, config, session, nodeid=nodeid, fspath=fspath, path=path473 )474 @classmethod475 def from_parent(476 cls,477 parent,478 *,479 fspath: Optional[LEGACY_PATH] = None,480 path: Optional[Path] = None,481 **kw,482 ):483 """The public constructor."""484 path, fspath = _imply_path(path, fspath=fspath)485 return super().from_parent(parent=parent, fspath=fspath, path=path, **kw)486 def gethookproxy(self, fspath: "os.PathLike[str]"):487 warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2)488 return self.session.gethookproxy(fspath)489 def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> bool:490 warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2)491 return self.session.isinitpath(path)492class File(FSCollector):493 """Base class for collecting tests from a file.494 :ref:`non-python tests`.495 """496class Item(Node):497 """A basic test invocation item.498 Note that for a single function there might be multiple test invocation items.499 """500 nextitem = None501 def __init__(502 self,503 name,504 parent=None,505 config: Optional[Config] = None,506 session: Optional["Session"] = None,507 nodeid: Optional[str] = None,508 ) -> None:509 super().__init__(name, parent, config, session, nodeid=nodeid)510 self._report_sections: List[Tuple[str, str, str]] = []511 #: A list of tuples (name, value) that holds user defined properties512 #: for this test.513 self.user_properties: List[Tuple[str, object]] = []514 def runtest(self) -> None:515 raise NotImplementedError("runtest must be implemented by Item subclass")516 def add_report_section(self, when: str, key: str, content: str) -> None:517 """Add a new report section, similar to what's done internally to add518 stdout and stderr captured output::519 item.add_report_section("call", "stdout", "report section contents")520 :param str when:521 One of the possible capture states, ``"setup"``, ``"call"``, ``"teardown"``.522 :param str key:523 Name of the section, can be customized at will. Pytest uses ``"stdout"`` and524 ``"stderr"`` internally.525 :param str content:526 The full contents as a string.527 """528 if content:529 self._report_sections.append((when, key, content))530 def reportinfo(self) -> Tuple[Union[LEGACY_PATH, str], Optional[int], str]:531 # TODO: enable Path objects in reportinfo532 return legacy_path(self.path), None, ""533 @cached_property534 def location(self) -> Tuple[str, Optional[int], str]:535 location = self.reportinfo()536 fspath = absolutepath(str(location[0]))537 relfspath = self.session._node_location_to_relpath(fspath)538 assert type(location[2]) is str...
legacypath.py
Source:legacypath.py
...295 input_values = shlex.split(value) if isinstance(value, str) else value296 return [legacy_path(str(dp / x)) for x in input_values]297 else:298 raise ValueError(f"unknown configuration type: {type}", value)299def Node_fspath(self: Node) -> LEGACY_PATH:300 """(deprecated) returns a legacy_path copy of self.path"""301 return legacy_path(self.path)302def Node_fspath_set(self: Node, value: LEGACY_PATH) -> None:303 self.path = Path(value)304@hookimpl(tryfirst=True)305def pytest_load_initial_conftests(early_config: Config) -> None:306 """Monkeypatch legacy path attributes in several classes, as early as possible."""307 mp = MonkeyPatch()308 early_config.add_cleanup(mp.undo)309 # Add Cache.makedir().310 mp.setattr(Cache, "makedir", Cache_makedir, raising=False)311 # Add FixtureRequest.fspath property.312 mp.setattr(FixtureRequest, "fspath", property(FixtureRequest_fspath), raising=False)313 # Add TerminalReporter.startdir property....
deprecated.py
Source:deprecated.py
1"""Deprecation messages and bits of code used elsewhere in the codebase that2is planned to be removed in the next pytest release.3Keeping it in a central location makes it easy to track what is deprecated and should4be removed when the time comes.5All constants defined in this module should be either instances of6:class:`PytestWarning`, or :class:`UnformattedWarning`7in case of warnings which need to format their messages.8"""9from warnings import warn10from _pytest.warning_types import PytestDeprecationWarning11from _pytest.warning_types import UnformattedWarning12# set of plugins which have been integrated into the core; we use this list to ignore13# them during registration to avoid conflicts14DEPRECATED_EXTERNAL_PLUGINS = {15 "pytest_catchlog",16 "pytest_capturelog",17 "pytest_faulthandler",18}19FILLFUNCARGS = UnformattedWarning(20 PytestDeprecationWarning,21 "{name} is deprecated, use "22 "function._request._fillfixtures() instead if you cannot avoid reaching into internals.",23)24PYTEST_COLLECT_MODULE = UnformattedWarning(25 PytestDeprecationWarning,26 "pytest.collect.{name} was moved to pytest.{name}\n"27 "Please update to the new name.",28)29YIELD_FIXTURE = PytestDeprecationWarning(30 "@pytest.yield_fixture is deprecated.\n"31 "Use @pytest.fixture instead; they are the same."32)33MINUS_K_DASH = PytestDeprecationWarning(34 "The `-k '-expr'` syntax to -k is deprecated.\nUse `-k 'not expr'` instead."35)36MINUS_K_COLON = PytestDeprecationWarning(37 "The `-k 'expr:'` syntax to -k is deprecated.\n"38 "Please open an issue if you use this and want a replacement."39)40WARNING_CAPTURED_HOOK = PytestDeprecationWarning(41 "The pytest_warning_captured is deprecated and will be removed in a future release.\n"42 "Please use pytest_warning_recorded instead."43)44FSCOLLECTOR_GETHOOKPROXY_ISINITPATH = PytestDeprecationWarning(45 "The gethookproxy() and isinitpath() methods of FSCollector and Package are deprecated; "46 "use self.session.gethookproxy() and self.session.isinitpath() instead. "47)48STRICT_OPTION = PytestDeprecationWarning(49 "The --strict option is deprecated, use --strict-markers instead."50)51PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.")52UNITTEST_SKIP_DURING_COLLECTION = PytestDeprecationWarning(53 "Raising unittest.SkipTest to skip tests during collection is deprecated. "54 "Use pytest.skip() instead."55)56ARGUMENT_PERCENT_DEFAULT = PytestDeprecationWarning(57 'pytest now uses argparse. "%default" should be changed to "%(default)s"',58)59ARGUMENT_TYPE_STR_CHOICE = UnformattedWarning(60 PytestDeprecationWarning,61 "`type` argument to addoption() is the string {typ!r}."62 " For choices this is optional and can be omitted, "63 " but when supplied should be a type (for example `str` or `int`)."64 " (options: {names})",65)66ARGUMENT_TYPE_STR = UnformattedWarning(67 PytestDeprecationWarning,68 "`type` argument to addoption() is the string {typ!r}, "69 " but when supplied should be a type (for example `str` or `int`)."70 " (options: {names})",71)72NODE_FSPATH = UnformattedWarning(73 PytestDeprecationWarning,74 "{type}.fspath is deprecated and will be replaced by {type}.path.\n"75 "see https://docs.pytest.org/en/latest/deprecations.html#node-fspath-in-favor-of-pathlib-and-node-path",76)77HOOK_LEGACY_PATH_ARG = UnformattedWarning(78 PytestDeprecationWarning,79 "The ({pylib_path_arg}: py.path.local) argument is deprecated, please use ({pathlib_path_arg}: pathlib.Path)\n"80 "see https://docs.pytest.org/en/latest/deprecations.html"81 "#py-path-local-arguments-for-hooks-replaced-with-pathlib-path",82)83WARNS_NONE_ARG = PytestDeprecationWarning(84 "Passing None to catch any warning has been deprecated, pass no arguments instead:\n"85 " Replace pytest.warns(None) by simply pytest.warns()."86)87# You want to make some `__init__` or function "private".88#89# def my_private_function(some, args):90# ...91#92# Do this:93#94# def my_private_function(some, args, *, _ispytest: bool = False):95# check_ispytest(_ispytest)96# ...97#98# Change all internal/allowed calls to99#100# my_private_function(some, args, _ispytest=True)101#102# All other calls will get the default _ispytest=False and trigger103# the warning (possibly error in the future).104def check_ispytest(ispytest: bool) -> None:105 if not ispytest:...
Looking for an in-depth tutorial around pytest? LambdaTest covers the detailed pytest tutorial that has everything related to the pytest, from setting up the pytest framework to automation testing. Delve deeper into pytest testing by exploring advanced use cases like parallel testing, pytest fixtures, parameterization, executing multiple test cases from a single file, and more.
Skim our below pytest tutorial playlist to get started with automation testing using the pytest framework.
https://www.youtube.com/playlist?list=PLZMWkkQEwOPlcGgDmHl8KkXKeLF83XlrP
Get 100 minutes of automation test minutes FREE!!