Skip to content

Commit

Permalink
Merge pull request #3754 from nicoddemus/fix-function-call-warning
Browse files Browse the repository at this point in the history
Refactor direct fixture call warning to avoid incompatibility with plugins
  • Loading branch information
RonnyPfannschmidt authored Aug 2, 2018
2 parents 5db2e6c + 82a2174 commit 33769d0
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 19 deletions.
1 change: 1 addition & 0 deletions changelog/3747.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix compatibility problem with plugins and the warning code issued by fixture functions when they are called directly.
15 changes: 15 additions & 0 deletions src/_pytest/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,21 @@ def get_real_func(obj):
return obj


def get_real_method(obj, holder):
"""
Attempts to obtain the real function object that might be wrapping ``obj``, while at the same time
returning a bound method to ``holder`` if the original object was a bound method.
"""
try:
is_method = hasattr(obj, "__func__")
obj = get_real_func(obj)
except Exception:
return obj
if is_method and hasattr(obj, "__get__") and callable(obj.__get__):
obj = obj.__get__(holder)
return obj


def getfslineno(obj):
# xxx let decorators etc specify a sane ordering
obj = get_real_func(obj)
Expand Down
27 changes: 13 additions & 14 deletions src/_pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
getfuncargnames,
safe_getattr,
FuncargnamesCompatAttr,
get_real_method,
)
from _pytest.deprecated import FIXTURE_FUNCTION_CALL, RemovedInPytest4Warning
from _pytest.outcomes import fail, TEST_OUTCOME
Expand Down Expand Up @@ -931,13 +932,6 @@ def pytest_fixture_setup(fixturedef, request):
request._check_scope(argname, request.scope, fixdef.scope)
kwargs[argname] = result

# if function has been defined with @pytest.fixture, we want to
# pass the special __being_called_by_pytest parameter so we don't raise a warning
# this is an ugly hack, see #3720 for an opportunity to improve this
defined_using_fixture_decorator = hasattr(fixturedef.func, "_pytestfixturefunction")
if defined_using_fixture_decorator:
kwargs["__being_called_by_pytest"] = True

fixturefunc = resolve_fixture_function(fixturedef, request)
my_cache_key = request.param_index
try:
Expand Down Expand Up @@ -973,9 +967,7 @@ def wrap_function_to_warning_if_called_directly(function, fixture_marker):
@functools.wraps(function)
def result(*args, **kwargs):
__tracebackhide__ = True
__being_called_by_pytest = kwargs.pop("__being_called_by_pytest", False)
if not __being_called_by_pytest:
warnings.warn(warning, stacklevel=3)
warnings.warn(warning, stacklevel=3)
for x in function(*args, **kwargs):
yield x

Expand All @@ -984,9 +976,7 @@ def result(*args, **kwargs):
@functools.wraps(function)
def result(*args, **kwargs):
__tracebackhide__ = True
__being_called_by_pytest = kwargs.pop("__being_called_by_pytest", False)
if not __being_called_by_pytest:
warnings.warn(warning, stacklevel=3)
warnings.warn(warning, stacklevel=3)
return function(*args, **kwargs)

if six.PY2:
Expand Down Expand Up @@ -1279,9 +1269,9 @@ def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False):
# The attribute can be an arbitrary descriptor, so the attribute
# access below can raise. safe_getatt() ignores such exceptions.
obj = safe_getattr(holderobj, name, None)
marker = getfixturemarker(obj)
# fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
# or are "@pytest.fixture" marked
marker = getfixturemarker(obj)
if marker is None:
if not name.startswith(self._argprefix):
continue
Expand All @@ -1303,6 +1293,15 @@ def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False):
name = marker.name
assert not name.startswith(self._argprefix), FIXTURE_MSG.format(name)

# during fixture definition we wrap the original fixture function
# to issue a warning if called directly, so here we unwrap it in order to not emit the warning
# when pytest itself calls the fixture function
if six.PY2 and unittest:
# hack on Python 2 because of the unbound methods
obj = get_real_func(obj)
else:
obj = get_real_method(obj, holderobj)

fixture_def = FixtureDef(
self,
nodeid,
Expand Down
1 change: 0 additions & 1 deletion testing/deprecated_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,6 @@ def test_func():
)


# @pytest.mark.skipif(six.PY2, reason="We issue the warning in Python 3 only")
def test_call_fixture_function_deprecated():
"""Check if a warning is raised if a fixture function is called directly (#3661)"""

Expand Down
7 changes: 3 additions & 4 deletions testing/python/fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -1470,10 +1470,9 @@ def test_hello(self, item, fm):
print (faclist)
assert len(faclist) == 3
kwargs = {'__being_called_by_pytest': True}
assert faclist[0].func(item._request, **kwargs) == "conftest"
assert faclist[1].func(item._request, **kwargs) == "module"
assert faclist[2].func(item._request, **kwargs) == "class"
assert faclist[0].func(item._request) == "conftest"
assert faclist[1].func(item._request) == "module"
assert faclist[2].func(item._request) == "class"
"""
)
reprec = testdir.inline_run("-s")
Expand Down

0 comments on commit 33769d0

Please sign in to comment.