-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
fixtures: some tweaks & improvements #11209
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
It makes for a more debuggable repr. Before: <function FixtureRequest._schedule_finalizers.<locals>.<lambda> at 0x7fe4ae32d440> After: functools.partial(<bound method FixtureDef.finish of <FixtureDef argname='hello_package' scope='package' baseid=''>>, request=<SubRequest 'hello_package' for <Function test_hello>>)
I can't imagine why we would want to test for a prefix here.
From understanding the code better I see this is the correct fix. The fixturedefs can be None if `request.getfixturevalue("doesnotexist")` is used. In practice there is no change in behavior because this mapping is used as `self._arg2fixturedefs.get(argname, None)` which ends up the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @bluetech, great docs as always!
"""A container for a fixture definition.""" | ||
"""A container for a fixture definition. | ||
|
||
Note: At this time, only explicitly documented fields and methods are |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cache_result
is explicitly documented, but we consider it internal according to the changelog... is this definition incomplete, or am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think cached_result
is not documented, maybe I'm missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm yeah sorry, I was not clear and I misunderstood: I thought "documented" meant the comments we leave on the attributes and that would appear in the docs, but that's not the case:
https://docs.pytest.org/en/stable/reference/reference.html#fixturedef
Only scope
is explicitly documented, sorry for the confusion!
src/_pytest/fixtures.py
Outdated
self._arg2fixturedefs: Dict[str, List[FixtureDef[Any]]] = {} | ||
self._holderobjseen: Set[object] = set() | ||
# Maps a fixture name (argname) to all of the FixtureDefs in the test | ||
# suite defined with this name. Populated by parsefactories(). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Including external plugins, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, might be worth clarifying that then:
# suite defined with this name. Populated by parsefactories(). | |
# suite and external plugins defined with this name. Populated by parsefactories(). |
@@ -1697,11 +1722,16 @@ def parsefactories( # noqa: F811 | |||
def getfixturedefs( | |||
self, argname: str, nodeid: str | |||
) -> Optional[Sequence[FixtureDef[Any]]]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is getfixturedefs
public API? If not, we could (in a future PR of course) change the return value to make the subtle difference between None
and empty list more explicit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not public. I'm not sure how to make it better though, interested to hear what you had in mind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had not thought specifics when I left the comment, but perhaps something to make it more type-safe and avoid chances for misuse:
@dataclass(frozen=True)
class UndefinedFixture:
pass
@dataclass(frozen=True)
class NoFixturesApplicable:
pass
def getfixturedefs(
self, argname: str, nodeid: str
) -> Sequence[FixtureDef[Any]] | NoFixturesApplicable | UndefinedFixture:
This would force the caller to handle each individual case...
However perhaps this is sufficient:
@dataclass(frozen=True)
class UndefinedFixture:
pass
def getfixturedefs(
self, argname: str, nodeid: str
) -> Sequence[FixtureDef[Any]] | UndefinedFixture:
The original code feels like a small smell because we need to document the difference between None
and []
, and it is easy to treat both equally in a bool context.
But not so sure if worth it or not.
FixtureDef is used in the `pytest_fixture_setup` hook so needs to be public. However since its current internals are quite dubious (and not all marked with `_` prefix) I've added an explicit note that only documented fields/methods are considered public. Refs pytest-dev#7469.
Not used since 310b67b.
7c7e16e
to
40ed678
Compare
# No fixtures defined with this name. | ||
if fixturedefs is None: | ||
raise FixtureLookupError(argname, self) | ||
# 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 | ||
if fixturedefs is None or (-index > len(fixturedefs)): | ||
# The fixture requested its own name, but no remaining to override. | ||
if -index > len(fixturedefs): | ||
raise FixtureLookupError(argname, self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should either merge the conditions or give a extra indication of the difference between not found, not applicable and no override avaliable
i'd prefer to add extra inication
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, I split the conditions to make the distinct cases clearer. But FixtureLookupError
does some complex message formatting, it will require a bit of effort to pass the info along cleanly. I might do it in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about putting in some helpfull text constants right now as 3rd parameter and then following up with more intentional formatting whenever
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy to attempt this in a follow up, in case @bluetech prefers to move this along.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy to leave this for a follow-up as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nicoddemus I'll be happy if you try it, it will require some changes in FixtureLookupError
. (Note that FixtureLookupError
if exported and its ctor is not doing the _ispytest
check, though I doubt there are plugins with instantiate it directly).
FixtureLookupError
already tries to handle the last case directly "recursive dependency involving fixture 'fix' detected", though it will be nice to distinguish between the first ("not found") and second ("not visible to this test") cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gave this a quick try, but the result was not much better and it became a bit more complex I'm afraid.
These are some relatively small changes I made while going over the fixture code. Please see the commits.