Merge pull request #12038 from bluetech/fixtures-rm-arg2index
fixtures: avoid mutable arg2index state in favor of looking up the request chain
This commit is contained in:
		
						commit
						00043f7f10
					
				| 
						 | 
					@ -348,7 +348,6 @@ class FixtureRequest(abc.ABC):
 | 
				
			||||||
        pyfuncitem: "Function",
 | 
					        pyfuncitem: "Function",
 | 
				
			||||||
        fixturename: Optional[str],
 | 
					        fixturename: Optional[str],
 | 
				
			||||||
        arg2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]],
 | 
					        arg2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]],
 | 
				
			||||||
        arg2index: Dict[str, int],
 | 
					 | 
				
			||||||
        fixture_defs: Dict[str, "FixtureDef[Any]"],
 | 
					        fixture_defs: Dict[str, "FixtureDef[Any]"],
 | 
				
			||||||
        *,
 | 
					        *,
 | 
				
			||||||
        _ispytest: bool = False,
 | 
					        _ispytest: bool = False,
 | 
				
			||||||
| 
						 | 
					@ -362,16 +361,6 @@ class FixtureRequest(abc.ABC):
 | 
				
			||||||
        # collection. Dynamically requested fixtures (using
 | 
					        # collection. Dynamically requested fixtures (using
 | 
				
			||||||
        # `request.getfixturevalue("foo")`) are added dynamically.
 | 
					        # `request.getfixturevalue("foo")`) are added dynamically.
 | 
				
			||||||
        self._arg2fixturedefs: Final = arg2fixturedefs
 | 
					        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
 | 
					        # The evaluated argnames so far, mapping to the FixtureDef they resolved
 | 
				
			||||||
        # to.
 | 
					        # to.
 | 
				
			||||||
        self._fixture_defs: Final = fixture_defs
 | 
					        self._fixture_defs: Final = fixture_defs
 | 
				
			||||||
| 
						 | 
					@ -427,11 +416,24 @@ class FixtureRequest(abc.ABC):
 | 
				
			||||||
        # The are no fixtures with this name applicable for the function.
 | 
					        # The are no fixtures with this name applicable for the function.
 | 
				
			||||||
        if not fixturedefs:
 | 
					        if not fixturedefs:
 | 
				
			||||||
            raise FixtureLookupError(argname, self)
 | 
					            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):
 | 
					        if -index > len(fixturedefs):
 | 
				
			||||||
            raise FixtureLookupError(argname, self)
 | 
					            raise FixtureLookupError(argname, self)
 | 
				
			||||||
        self._arg2index[argname] = index
 | 
					
 | 
				
			||||||
        return fixturedefs[index]
 | 
					        return fixturedefs[index]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
| 
						 | 
					@ -543,6 +545,16 @@ class FixtureRequest(abc.ABC):
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        return fixturedef.cached_result[0]
 | 
					        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(
 | 
					    def _get_active_fixturedef(
 | 
				
			||||||
        self, argname: str
 | 
					        self, argname: str
 | 
				
			||||||
    ) -> Union["FixtureDef[object]", PseudoFixtureDef[object]]:
 | 
					    ) -> Union["FixtureDef[object]", PseudoFixtureDef[object]]:
 | 
				
			||||||
| 
						 | 
					@ -560,11 +572,7 @@ class FixtureRequest(abc.ABC):
 | 
				
			||||||
        return fixturedef
 | 
					        return fixturedef
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_fixturestack(self) -> List["FixtureDef[Any]"]:
 | 
					    def _get_fixturestack(self) -> List["FixtureDef[Any]"]:
 | 
				
			||||||
        current = self
 | 
					        values = [request._fixturedef for request in self._iter_chain()]
 | 
				
			||||||
        values: List[FixtureDef[Any]] = []
 | 
					 | 
				
			||||||
        while isinstance(current, SubRequest):
 | 
					 | 
				
			||||||
            values.append(current._fixturedef)  # type: ignore[has-type]
 | 
					 | 
				
			||||||
            current = current._parent_request
 | 
					 | 
				
			||||||
        values.reverse()
 | 
					        values.reverse()
 | 
				
			||||||
        return values
 | 
					        return values
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -657,7 +665,6 @@ class TopRequest(FixtureRequest):
 | 
				
			||||||
            fixturename=None,
 | 
					            fixturename=None,
 | 
				
			||||||
            pyfuncitem=pyfuncitem,
 | 
					            pyfuncitem=pyfuncitem,
 | 
				
			||||||
            arg2fixturedefs=pyfuncitem._fixtureinfo.name2fixturedefs.copy(),
 | 
					            arg2fixturedefs=pyfuncitem._fixtureinfo.name2fixturedefs.copy(),
 | 
				
			||||||
            arg2index={},
 | 
					 | 
				
			||||||
            fixture_defs={},
 | 
					            fixture_defs={},
 | 
				
			||||||
            _ispytest=_ispytest,
 | 
					            _ispytest=_ispytest,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
| 
						 | 
					@ -703,12 +710,11 @@ class SubRequest(FixtureRequest):
 | 
				
			||||||
            fixturename=fixturedef.argname,
 | 
					            fixturename=fixturedef.argname,
 | 
				
			||||||
            fixture_defs=request._fixture_defs,
 | 
					            fixture_defs=request._fixture_defs,
 | 
				
			||||||
            arg2fixturedefs=request._arg2fixturedefs,
 | 
					            arg2fixturedefs=request._arg2fixturedefs,
 | 
				
			||||||
            arg2index=request._arg2index,
 | 
					 | 
				
			||||||
            _ispytest=_ispytest,
 | 
					            _ispytest=_ispytest,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self._parent_request: Final[FixtureRequest] = request
 | 
					        self._parent_request: Final[FixtureRequest] = request
 | 
				
			||||||
        self._scope_field: Final = scope
 | 
					        self._scope_field: Final = scope
 | 
				
			||||||
        self._fixturedef: Final = fixturedef
 | 
					        self._fixturedef: Final[FixtureDef[object]] = fixturedef
 | 
				
			||||||
        if param is not NOTSET:
 | 
					        if param is not NOTSET:
 | 
				
			||||||
            self.param = param
 | 
					            self.param = param
 | 
				
			||||||
        self.param_index: Final = param_index
 | 
					        self.param_index: Final = param_index
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue