Skip to content

Commit 835765c

Browse files
authored
Merge pull request #12130 from bluetech/fixtures-inline
fixtures: inline some functions to streamline the code
2 parents 7e7503c + 882c4da commit 835765c

File tree

1 file changed

+92
-105
lines changed

1 file changed

+92
-105
lines changed

src/_pytest/fixtures.py

+92-105
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import _pytest
3636
from _pytest import nodes
3737
from _pytest._code import getfslineno
38+
from _pytest._code import Source
3839
from _pytest._code.code import FormattedExcinfo
3940
from _pytest._code.code import TerminalRepr
4041
from _pytest._io import TerminalWriter
@@ -410,41 +411,6 @@ def node(self):
410411
"""Underlying collection node (depends on current request scope)."""
411412
raise NotImplementedError()
412413

413-
def _getnextfixturedef(self, argname: str) -> "FixtureDef[Any]":
414-
fixturedefs = self._arg2fixturedefs.get(argname, None)
415-
if fixturedefs is None:
416-
# We arrive here because of a dynamic call to
417-
# getfixturevalue(argname) usage which was naturally
418-
# not known at parsing/collection time.
419-
fixturedefs = self._fixturemanager.getfixturedefs(argname, self._pyfuncitem)
420-
if fixturedefs is not None:
421-
self._arg2fixturedefs[argname] = fixturedefs
422-
# No fixtures defined with this name.
423-
if fixturedefs is None:
424-
raise FixtureLookupError(argname, self)
425-
# The are no fixtures with this name applicable for the function.
426-
if not fixturedefs:
427-
raise FixtureLookupError(argname, self)
428-
429-
# A fixture may override another fixture with the same name, e.g. a
430-
# fixture in a module can override a fixture in a conftest, a fixture in
431-
# a class can override a fixture in the module, and so on.
432-
# An overriding fixture can request its own name (possibly indirectly);
433-
# in this case it gets the value of the fixture it overrides, one level
434-
# up.
435-
# Check how many `argname`s deep we are, and take the next one.
436-
# `fixturedefs` is sorted from furthest to closest, so use negative
437-
# indexing to go in reverse.
438-
index = -1
439-
for request in self._iter_chain():
440-
if request.fixturename == argname:
441-
index -= 1
442-
# If already consumed all of the available levels, fail.
443-
if -index > len(fixturedefs):
444-
raise FixtureLookupError(argname, self)
445-
446-
return fixturedefs[index]
447-
448414
@property
449415
def config(self) -> Config:
450416
"""The pytest config object associated with this request."""
@@ -569,39 +535,53 @@ def _iter_chain(self) -> Iterator["SubRequest"]:
569535
def _get_active_fixturedef(
570536
self, argname: str
571537
) -> Union["FixtureDef[object]", PseudoFixtureDef[object]]:
538+
if argname == "request":
539+
cached_result = (self, [0], None)
540+
return PseudoFixtureDef(cached_result, Scope.Function)
541+
542+
# If we already finished computing a fixture by this name in this item,
543+
# return it.
572544
fixturedef = self._fixture_defs.get(argname)
573-
if fixturedef is None:
574-
try:
575-
fixturedef = self._getnextfixturedef(argname)
576-
except FixtureLookupError:
577-
if argname == "request":
578-
cached_result = (self, [0], None)
579-
return PseudoFixtureDef(cached_result, Scope.Function)
580-
raise
581-
self._compute_fixture_value(fixturedef)
582-
self._fixture_defs[argname] = fixturedef
583-
else:
545+
if fixturedef is not None:
584546
self._check_scope(fixturedef, fixturedef._scope)
585-
return fixturedef
586-
587-
def _get_fixturestack(self) -> List["FixtureDef[Any]"]:
588-
values = [request._fixturedef for request in self._iter_chain()]
589-
values.reverse()
590-
return values
547+
return fixturedef
591548

592-
def _compute_fixture_value(self, fixturedef: "FixtureDef[object]") -> None:
593-
"""Create a SubRequest based on "self" and call the execute method
594-
of the given FixtureDef object.
549+
# Find the appropriate fixturedef.
550+
fixturedefs = self._arg2fixturedefs.get(argname, None)
551+
if fixturedefs is None:
552+
# We arrive here because of a dynamic call to
553+
# getfixturevalue(argname) which was naturally
554+
# not known at parsing/collection time.
555+
fixturedefs = self._fixturemanager.getfixturedefs(argname, self._pyfuncitem)
556+
if fixturedefs is not None:
557+
self._arg2fixturedefs[argname] = fixturedefs
558+
# No fixtures defined with this name.
559+
if fixturedefs is None:
560+
raise FixtureLookupError(argname, self)
561+
# The are no fixtures with this name applicable for the function.
562+
if not fixturedefs:
563+
raise FixtureLookupError(argname, self)
564+
# A fixture may override another fixture with the same name, e.g. a
565+
# fixture in a module can override a fixture in a conftest, a fixture in
566+
# a class can override a fixture in the module, and so on.
567+
# An overriding fixture can request its own name (possibly indirectly);
568+
# in this case it gets the value of the fixture it overrides, one level
569+
# up.
570+
# Check how many `argname`s deep we are, and take the next one.
571+
# `fixturedefs` is sorted from furthest to closest, so use negative
572+
# indexing to go in reverse.
573+
index = -1
574+
for request in self._iter_chain():
575+
if request.fixturename == argname:
576+
index -= 1
577+
# If already consumed all of the available levels, fail.
578+
if -index > len(fixturedefs):
579+
raise FixtureLookupError(argname, self)
580+
fixturedef = fixturedefs[index]
595581

596-
If the FixtureDef has cached the result it will do nothing, otherwise it will
597-
setup and run the fixture, cache the value, and schedule a finalizer for it.
598-
"""
599-
# prepare a subrequest object before calling fixture function
600-
# (latter managed by fixturedef)
601-
argname = fixturedef.argname
602-
funcitem = self._pyfuncitem
582+
# Prepare a SubRequest object for calling the fixture.
603583
try:
604-
callspec = funcitem.callspec
584+
callspec = self._pyfuncitem.callspec
605585
except AttributeError:
606586
callspec = None
607587
if callspec is not None and argname in callspec.params:
@@ -613,48 +593,56 @@ def _compute_fixture_value(self, fixturedef: "FixtureDef[object]") -> None:
613593
param = NOTSET
614594
param_index = 0
615595
scope = fixturedef._scope
616-
617-
has_params = fixturedef.params is not None
618-
fixtures_not_supported = getattr(funcitem, "nofuncargs", False)
619-
if has_params and fixtures_not_supported:
620-
msg = (
621-
f"{funcitem.name} does not support fixtures, maybe unittest.TestCase subclass?\n"
622-
f"Node id: {funcitem.nodeid}\n"
623-
f"Function type: {type(funcitem).__name__}"
624-
)
625-
fail(msg, pytrace=False)
626-
if has_params:
627-
frame = inspect.stack()[3]
628-
frameinfo = inspect.getframeinfo(frame[0])
629-
source_path = absolutepath(frameinfo.filename)
630-
source_lineno = frameinfo.lineno
631-
try:
632-
source_path_str = str(
633-
source_path.relative_to(funcitem.config.rootpath)
634-
)
635-
except ValueError:
636-
source_path_str = str(source_path)
637-
location = getlocation(fixturedef.func, funcitem.config.rootpath)
638-
msg = (
639-
"The requested fixture has no parameter defined for test:\n"
640-
f" {funcitem.nodeid}\n\n"
641-
f"Requested fixture '{fixturedef.argname}' defined in:\n"
642-
f"{location}\n\n"
643-
f"Requested here:\n"
644-
f"{source_path_str}:{source_lineno}"
645-
)
646-
fail(msg, pytrace=False)
647-
648-
# Check if a higher-level scoped fixture accesses a lower level one.
596+
self._check_fixturedef_without_param(fixturedef)
649597
self._check_scope(fixturedef, scope)
650-
651598
subrequest = SubRequest(
652599
self, scope, param, param_index, fixturedef, _ispytest=True
653600
)
654601

655602
# Make sure the fixture value is cached, running it if it isn't
656603
fixturedef.execute(request=subrequest)
657604

605+
self._fixture_defs[argname] = fixturedef
606+
return fixturedef
607+
608+
def _check_fixturedef_without_param(self, fixturedef: "FixtureDef[object]") -> None:
609+
"""Check that this request is allowed to execute this fixturedef without
610+
a param."""
611+
funcitem = self._pyfuncitem
612+
has_params = fixturedef.params is not None
613+
fixtures_not_supported = getattr(funcitem, "nofuncargs", False)
614+
if has_params and fixtures_not_supported:
615+
msg = (
616+
f"{funcitem.name} does not support fixtures, maybe unittest.TestCase subclass?\n"
617+
f"Node id: {funcitem.nodeid}\n"
618+
f"Function type: {type(funcitem).__name__}"
619+
)
620+
fail(msg, pytrace=False)
621+
if has_params:
622+
frame = inspect.stack()[3]
623+
frameinfo = inspect.getframeinfo(frame[0])
624+
source_path = absolutepath(frameinfo.filename)
625+
source_lineno = frameinfo.lineno
626+
try:
627+
source_path_str = str(source_path.relative_to(funcitem.config.rootpath))
628+
except ValueError:
629+
source_path_str = str(source_path)
630+
location = getlocation(fixturedef.func, funcitem.config.rootpath)
631+
msg = (
632+
"The requested fixture has no parameter defined for test:\n"
633+
f" {funcitem.nodeid}\n\n"
634+
f"Requested fixture '{fixturedef.argname}' defined in:\n"
635+
f"{location}\n\n"
636+
f"Requested here:\n"
637+
f"{source_path_str}:{source_lineno}"
638+
)
639+
fail(msg, pytrace=False)
640+
641+
def _get_fixturestack(self) -> List["FixtureDef[Any]"]:
642+
values = [request._fixturedef for request in self._iter_chain()]
643+
values.reverse()
644+
return values
645+
658646

659647
@final
660648
class TopRequest(FixtureRequest):
@@ -877,13 +865,6 @@ def toterminal(self, tw: TerminalWriter) -> None:
877865
tw.line("%s:%d" % (os.fspath(self.filename), self.firstlineno + 1))
878866

879867

880-
def fail_fixturefunc(fixturefunc, msg: str) -> NoReturn:
881-
fs, lineno = getfslineno(fixturefunc)
882-
location = f"{fs}:{lineno + 1}"
883-
source = _pytest._code.Source(fixturefunc)
884-
fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, pytrace=False)
885-
886-
887868
def call_fixture_func(
888869
fixturefunc: "_FixtureFunc[FixtureValue]", request: FixtureRequest, kwargs
889870
) -> FixtureValue:
@@ -913,7 +894,13 @@ def _teardown_yield_fixture(fixturefunc, it) -> None:
913894
except StopIteration:
914895
pass
915896
else:
916-
fail_fixturefunc(fixturefunc, "fixture function has more than one 'yield'")
897+
fs, lineno = getfslineno(fixturefunc)
898+
fail(
899+
f"fixture function has more than one 'yield':\n\n"
900+
f"{Source(fixturefunc).indent()}\n"
901+
f"{fs}:{lineno + 1}",
902+
pytrace=False,
903+
)
917904

918905

919906
def _eval_scope_callable(

0 commit comments

Comments
 (0)