Skip to content

Prohibit pytest.mark.usefixtures in pytest.param - #12828

Merged
nicoddemus merged 9 commits into
pytest-dev:mainfrom
Hawk777:reject-usefixtures-in-param
Oct 9, 2024
Merged

nicoddemus merged 9 commits into
pytest-dev:mainfrom
Hawk777:reject-usefixtures-in-param

Conversation

@Hawk777

@Hawk777 Hawk777 commented Sep 20, 2024

Copy link
Copy Markdown
Contributor

No description provided.

It doesn’t work, but it fails in a non-obvious way: the marker is
accepted and then the fixture is silently not run. Fail with an
explanatory message instead to save debugging time.
@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided (automation) changelog entry is part of PR label Sep 20, 2024
@Hawk777

Hawk777 commented Sep 20, 2024

Copy link
Copy Markdown
Contributor Author

I’m not certain this is the best place for this change. As far as I know, pytest.param is only useful to either pass to pytest.mark.parametrize or to Metafunc.parametrize, and usefixtures doesn’t work in either of those places, so this seemed logical to me, but I don’t know Pytest’s architecture.

@Hawk777

Hawk777 commented Sep 20, 2024

Copy link
Copy Markdown
Contributor Author

See GH-4112.

@The-Compiler The-Compiler left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems reasonable to me, and definitely is a step forward from silently ignoring those. Could you add a test case for this as well? TestMarkersWithParametrization in testing/python/metafunc.py seems like the right place to me.

@Hawk777

Hawk777 commented Sep 24, 2024 •

Copy link
Copy Markdown
Contributor Author

I can’t seem to run the tests locally (ERROR: /tmp/pytest/pyproject.toml: 'minversion' requires pytest-2.0, actual pytest-0.1.dev1+gc40a0d5'), so I’ve tried to do that in 0cb2599 and I’ll let CI see if I got it right.

@Hawk777

Hawk777 commented Sep 24, 2024

Copy link
Copy Markdown
Contributor Author

Looks like a pass. That fixup commit should probably be squashed once you’ve reviewed it (the merge/squash guidelines in CONTRIBUTING.md say that you’ll do that, but let me know if you prefer that I do).

@Hawk777

Hawk777 commented Oct 4, 2024

Copy link
Copy Markdown
Contributor Author

@The-Compiler just checking if there’s anything else you were waiting for from me? Not sure if you get notifications or not for messages that don’t ping you. No hurry obviously.

@The-Compiler

Copy link
Copy Markdown
Member

Sorry for the radio silence! Seems fine to me, though I'd like a second pair of eyes on the question whether this is a good place to do this kind of thing. @RonnyPfannschmidt @nicoddemus any opinions?

@nicoddemus nicoddemus left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Hawk777 this is a great start!

Sorry for the delay, and thanks for the ping.

Please take a look at my comments.

Comment thread testing/python/metafunc.py Outdated
Comment on lines +2118 to +2123
def test_param_usefixtures_rejected(self, pytester: Pytester) -> None:
"""Verify that passing usefixtures to pytest.param fails loudly rather than silently."""
pytest.raises(
ValueError,
lambda: pytest.param("x", [1], marks=pytest.mark.usefixtures("foo")),
)

@nicoddemus nicoddemus Oct 4, 2024 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better for us to have an integration test instead, because in this it is important for us to see how the error will appear to the user: for example, it should not blow up an entire traceback of the pytest internals, it should show a succinct message pointing out the fixture, location, etc.

I would start by using pytest.fail(..., pytrace=False) or raising UsageError, whichever produces a better result.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nicoddemus UsageError and ValueError look the same to me, like this (other than the exception name, obviously):

___________________ ERROR collecting test_foo.py _____________________
test_foo.py:6: in <module>
    pytest.param(
/usr/lib/python3.12/site-packages/_pytest/mark/__init__.py:72: in param
    return ParameterSet.param(*values, marks=marks, id=id)
/usr/lib/python3.12/site-packages/_pytest/mark/structures.py:92: in param
    raise ValueError(
E   ValueError: pytest.param cannot add pytest.mark.usefixtures; see https://github.com/pytest-dev/pytest/issues/4112

while pytest.fail looks like this:

___________________ ERROR collecting test_foo.py _____________________
pytest.param cannot add pytest.mark.usefixtures; see https://github.com/pytest-dev/pytest/issues/4112

So it looks like pytest.fail produces the least output (and without any Pytest internals mentioned). However, it also doesn’t mention the line number on the user test file where the error occurred (remember this happens at import time, not execution time, so it also doesn’t mention the function name), which the others (since they include traces) do. Is that a problem? It seems to me it would be useful to point the user at the specific param call that has the problem, but if you prefer the shorter output, I’ll go ahead with that.

So, given the above tradeoff, what’s your preference? ValueError, UsageError, or pytest.fail?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I see thanks.

Let's keep it ValueError for now then, I agree that the location/line number is really important -- due to how pytest.param is exposed in the API, this is reasonable to do for now (we could do some sys.frame() shenaningans to get the pytest.param location invocation and build up a message ourselves, but not sure it is worth doing that for this quite uncommon occurrence).

But we should move forward with the integration test I mentioned: it is important that we check that test_foo.py:6 and pytest.param( appear in the message (from your example posted above).

Also, we probably should put a link to the docs somewhere explaining that (possibly we can add a note to pytest.param documentation), rather than pointing to the GitHub issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I will definitely do the integration test change, just waiting to do that until we’ve nailed down what we want the behaviour to be (no point changing the test twice).

In terms of a link to documentation, my understanding was that one should be able to usefixtures in param, but it just didn’t work right now due to a bug. Is it actually intended that usefixtures not be used in param? Or do you think, even though it’s a bug, it should be in the manual (typically I don’t see bugs documented in manuals, but rather only bug trackers, on most projects)?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I will definitely do the integration test change, just waiting to do that until we’ve nailed down what we want the behaviour to be (no point changing the test twice).

Oh sure, thanks, just wanted to take the opportunity to point out what I would like to see checked in the integration test.

Is it actually intended that usefixtures not be used in param?

We don't have any plans to support usefixtures there (nor in fixtures), so we might as well place the error (as you are doing) and leave a note in the docs (even if that changes in the future, I think it is harmless to let users know the current situation explicitly).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was hoping to make fixing this one commit, but I had no idea CI was going to come in and add extra commits, so that didn’t work out. So this is now b3f232a and ccc1e18, plus anything else that needs to follow it.

Comment thread changelog/4112.improvement.rst Outdated
Comment thread src/_pytest/mark/structures.py Outdated

@nicoddemus nicoddemus left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! 🙇

@Hawk777

Hawk777 commented Oct 4, 2024

Copy link
Copy Markdown
Contributor Author

The warnings in the docs build AFAICT are unrelated to this patch, so I think this is good to go now?

@nicoddemus

Copy link
Copy Markdown
Member

It is related:

 /home/docs/checkouts/readthedocs.org/user_builds/pytest/envs/12828/lib/python3.12/site-packages/_pytest/mark/__init__.py:docstring of _pytest.mark.param:21: WARNING: undefined label: 'pytest.mark.usefixtures' [ref.ref]

But I just fixed it!

I will leave it open for a few more days to give others a chance to review it, otherwise I will merge it.

Thanks again!

@Hawk777

Hawk777 commented Oct 5, 2024

Copy link
Copy Markdown
Contributor Author

Oh, sorry, somehow I didn’t see that message. I only saw things about Spinx 90 compatibility issues in CSS and Javascript references, or something like that (I can’t find the page any more now that the new commit is running in CI). Thanks for the review!

@nicoddemus
nicoddemus merged commit 23256dd into pytest-dev:main Oct 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:chronographer:provided (automation) changelog entry is part of PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants