-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Deprecate calling fixture functions directly #3705
Deprecate calling fixture functions directly #3705
Conversation
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.
looks good, I wonder if we want to split out some of the earlier commits so that we can tackle #3704 sooner
src/_pytest/fixtures.py
Outdated
"""Wrap the given fixture function so we can issue warnings about it being called directly, instead of | ||
used as an argument in a test function. | ||
|
||
The warning is emited only in Python 2, because I did'nt find a reliable way to make the wrapper function |
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.
"emitted", "Python 3" "didn't" I believe :)
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.
Fixed, thanks!
@@ -964,6 +1011,8 @@ def __call__(self, function): | |||
"fixture is being applied more than once to the same function" | |||
) | |||
|
|||
function = wrap_function_to_warning_if_called_directly(function, 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.
(just double checked, good that this is going to features
):
changing a decorator from returning the original function to returning a wrapper always seems to break things -- I'm hoping nobody was depending on decorator ordering for @fixture
before :)
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.
Sure! 😁
from _pytest.tmpdir import tmpdir | ||
|
||
tmpdir = tmpdir(request, tmpdir_factory) | ||
tmpdir = tmpdir_factory.mktemp("basedir", numbered=True) |
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.
could this also just take the tmpdir
fixture as a positional?
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.
No because this basedir
is module
-scoped, and tmpdir
is function
-scoped. 😉
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.
oh right of course!
I can tackle #3704 soon after we merge this, I don't think the merging will take long. If it does for some reason, I will split it to a separate PR. |
Marked |
00edd81
to
571ef67
Compare
|
||
if is_yield_function: | ||
|
||
@functools.wraps(function) |
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.
functools.wraps is broken on python2 and needs to set __wrapped__
or so to get the correct metadata
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.
Ahh thanks, will try that!
@functools.wraps(function) | ||
def result(*args, **kwargs): | ||
__tracebackhide__ = True | ||
__being_called_by_pytest = kwargs.pop("__being_called_by_pytest", False) |
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.
instead of introducing this kwarg, why not just unpack the real function from the definition object
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.
That is a great idea, thanks! I wished I have thought about it myself. 😁
I've tried and it works well, except when we have test functions which are staticmethod
s.
Here's an illustrative example:
import pytest
class Test(object):
@staticmethod
def test_something():
pass
@pytest.fixture
def fix(self):
return 1
@staticmethod
def test_fix(fix):
assert fix == 1
test_something
passes, but test_fix
fails because we can't create the fixture because request.instance
is None
at that point.
The place where I did the unwrapping was in resolve_fixture_function
:
def resolve_fixture_function(fixturedef, request):
"""Gets the actual callable that can be called to obtain the fixture value, dealing with unittest-specific
instances and bound methods.
"""
fixturefunc = get_real_func(fixturedef.func) # <- unwrapping!
if fixturedef.unittest:
if request.instance is not None:
# bind the unbound method to the TestCase instance
fixturefunc = fixturefunc.__get__(request.instance)
else:
# the fixture function needs to be bound to the actual
# request.instance so that code working with "fixturedef" behaves
# as expected.
if request.instance is not None:
#is_method = getimfunc(fixturedef.func) != fixturedef.func
is_method = inspect.ismethod(fixturedef.func)
if is_method:
fixturefunc = fixturefunc.__get__(request.instance)
return fixturefunc
This issue affects 6 of our tests, on py27 and py36.
Any suggestions on how to proceed? The unwrapping before calling is much cleaner, but I'm not sure how to deal with staticmethods in that case.
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.
this seems to me like we should open a follow-up issue and go for the mechanism you currently have as enabling the cleaner way requires a re-factoring/bugfixing in fixture lookup/requesting
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.
Just to make sure I follow, keep using the keyword argument approach, and warning only in Python 3? I can try to make it work with Python 2 if you think setting __wrapped__
will work.
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.
please try setting wrapped - wrt the keyword argument approach - i dont like it but as far as i understood we need to sort out a structural thing to get real extraction working
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.
so we keep kwargs, and we try to set up the warnings for py2 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.
Follow up in #3720
src/_pytest/pytester.py
Outdated
@@ -669,6 +669,11 @@ def copy_example(self, name=None): | |||
else: | |||
raise LookupError("example is not found as a file or directory") | |||
|
|||
def run_example(self, name=None, *pytest_args): |
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.
required this one seems problematic due to leaving state behind, as is this api is only oblivious/sensible to core dev's
i'm pretty sure a plugin author would misuse it running it twice
for now i propose using the markers to declare what example to copy to keep apart the folder structure prep and the test running
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.
Good point, I can remove it.
This will now issue a RemovedInPytest4Warning when the user calls a fixture function directly, instead of requesting it from test functions as is expected Fix pytest-dev#3661
571ef67
to
6e57d12
Compare
Pushed the changes that trigger the warning on Python 2, thanks @RonnyPfannschmidt for the tip. |
The 3 first commits are small refactorings that I felt doing while implementing the actual task, so I recommend reading the commits separately.
I had to use a "wrapper" around the original fixture function to issue a warning, and one difficulty I had was to make this work reliably in Python 2 because
@itertools.wraps
doesn't preserve the signature. I believe this is fine though, because we probably will drop Python 2 by the time we release4.0
.Surprisingly, we had a few tests in our test suite which did call fixture functions directly, I just fixed those while keeping the same semantics.
Fix #3661