Skip to content

Commit

Permalink
Merge pull request #12038 from bluetech/fixtures-rm-arg2index
Browse files Browse the repository at this point in the history
fixtures: avoid mutable arg2index state in favor of looking up the request chain
  • Loading branch information
bluetech authored Mar 3, 2024
2 parents f4e1025 bd45ccd commit 00043f7
Showing 1 changed file with 28 additions and 22 deletions.
50 changes: 28 additions & 22 deletions src/_pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 348,6 @@ def __init__(
pyfuncitem: "Function",
fixturename: Optional[str],
arg2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]],
arg2index: Dict[str, int],
fixture_defs: Dict[str, "FixtureDef[Any]"],
*,
_ispytest: bool = False,
Expand All @@ -362,16 361,6 @@ def __init__(
# collection. Dynamically requested fixtures (using
# `request.getfixturevalue("foo")`) are added dynamically.
self._arg2fixturedefs: Final = arg2fixturedefs
# A fixture may override another fixture with the same name, e.g. a fixture
# in a module can override a fixture in a conftest, a fixture in a class can
# override a fixture in the module, and so on.
# An overriding fixture can request its own name; in this case it gets
# the value of the fixture it overrides, one level up.
# The _arg2index state keeps the current depth in the overriding chain.
# The fixturedefs list in _arg2fixturedefs for a given name is ordered from
# furthest to closest, so we use negative indexing -1, -2, ... to go from
# last to first.
self._arg2index: Final = arg2index
# The evaluated argnames so far, mapping to the FixtureDef they resolved
# to.
self._fixture_defs: Final = fixture_defs
Expand Down Expand Up @@ -427,11 416,24 @@ def _getnextfixturedef(self, argname: str) -> "FixtureDef[Any]":
# The are no fixtures with this name applicable for the function.
if not fixturedefs:
raise FixtureLookupError(argname, self)
index = self._arg2index.get(argname, 0) - 1
# The fixture requested its own name, but no remaining to override.

# A fixture may override another fixture with the same name, e.g. a
# fixture in a module can override a fixture in a conftest, a fixture in
# a class can override a fixture in the module, and so on.
# An overriding fixture can request its own name (possibly indirectly);
# in this case it gets the value of the fixture it overrides, one level
# up.
# Check how many `argname`s deep we are, and take the next one.
# `fixturedefs` is sorted from furthest to closest, so use negative
# indexing to go in reverse.
index = -1
for request in self._iter_chain():
if request.fixturename == argname:
index -= 1
# If already consumed all of the available levels, fail.
if -index > len(fixturedefs):
raise FixtureLookupError(argname, self)
self._arg2index[argname] = index

return fixturedefs[index]

@property
Expand Down Expand Up @@ -543,6 545,16 @@ def getfixturevalue(self, argname: str) -> Any:
)
return fixturedef.cached_result[0]

def _iter_chain(self) -> Iterator["SubRequest"]:
"""Yield all SubRequests in the chain, from self up.
Note: does *not* yield the TopRequest.
"""
current = self
while isinstance(current, SubRequest):
yield current
current = current._parent_request

def _get_active_fixturedef(
self, argname: str
) -> Union["FixtureDef[object]", PseudoFixtureDef[object]]:
Expand All @@ -560,11 572,7 @@ def _get_active_fixturedef(
return fixturedef

def _get_fixturestack(self) -> List["FixtureDef[Any]"]:
current = self
values: List[FixtureDef[Any]] = []
while isinstance(current, SubRequest):
values.append(current._fixturedef) # type: ignore[has-type]
current = current._parent_request
values = [request._fixturedef for request in self._iter_chain()]
values.reverse()
return values

Expand Down Expand Up @@ -657,7 665,6 @@ def __init__(self, pyfuncitem: "Function", *, _ispytest: bool = False) -> None:
fixturename=None,
pyfuncitem=pyfuncitem,
arg2fixturedefs=pyfuncitem._fixtureinfo.name2fixturedefs.copy(),
arg2index={},
fixture_defs={},
_ispytest=_ispytest,
)
Expand Down Expand Up @@ -703,12 710,11 @@ def __init__(
fixturename=fixturedef.argname,
fixture_defs=request._fixture_defs,
arg2fixturedefs=request._arg2fixturedefs,
arg2index=request._arg2index,
_ispytest=_ispytest,
)
self._parent_request: Final[FixtureRequest] = request
self._scope_field: Final = scope
self._fixturedef: Final = fixturedef
self._fixturedef: Final[FixtureDef[object]] = fixturedef
if param is not NOTSET:
self.param = param
self.param_index: Final = param_index
Expand Down

0 comments on commit 00043f7

Please sign in to comment.