Refactor direct fixture call warning to avoid incompatibility with plugins
This refactors the code so we have the real function object right during collection. This avoids having to unwrap it later and lose attached information such as "async" functions. Fix #3747
This commit is contained in:
parent
253419316c
commit
c6b11b9f62
|
@ -0,0 +1 @@
|
||||||
|
Fix compatibility problem with plugins and the the warning code issued by fixture functions when they are called directly.
|
|
@ -249,6 +249,21 @@ def get_real_func(obj):
|
||||||
return 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):
|
def getfslineno(obj):
|
||||||
# xxx let decorators etc specify a sane ordering
|
# xxx let decorators etc specify a sane ordering
|
||||||
obj = get_real_func(obj)
|
obj = get_real_func(obj)
|
||||||
|
|
|
@ -30,6 +30,7 @@ from _pytest.compat import (
|
||||||
getfuncargnames,
|
getfuncargnames,
|
||||||
safe_getattr,
|
safe_getattr,
|
||||||
FuncargnamesCompatAttr,
|
FuncargnamesCompatAttr,
|
||||||
|
get_real_method,
|
||||||
)
|
)
|
||||||
from _pytest.deprecated import FIXTURE_FUNCTION_CALL, RemovedInPytest4Warning
|
from _pytest.deprecated import FIXTURE_FUNCTION_CALL, RemovedInPytest4Warning
|
||||||
from _pytest.outcomes import fail, TEST_OUTCOME
|
from _pytest.outcomes import fail, TEST_OUTCOME
|
||||||
|
@ -931,13 +932,6 @@ def pytest_fixture_setup(fixturedef, request):
|
||||||
request._check_scope(argname, request.scope, fixdef.scope)
|
request._check_scope(argname, request.scope, fixdef.scope)
|
||||||
kwargs[argname] = result
|
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)
|
fixturefunc = resolve_fixture_function(fixturedef, request)
|
||||||
my_cache_key = request.param_index
|
my_cache_key = request.param_index
|
||||||
try:
|
try:
|
||||||
|
@ -973,9 +967,7 @@ def wrap_function_to_warning_if_called_directly(function, fixture_marker):
|
||||||
@functools.wraps(function)
|
@functools.wraps(function)
|
||||||
def result(*args, **kwargs):
|
def result(*args, **kwargs):
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
__being_called_by_pytest = kwargs.pop("__being_called_by_pytest", False)
|
warnings.warn(warning, stacklevel=3)
|
||||||
if not __being_called_by_pytest:
|
|
||||||
warnings.warn(warning, stacklevel=3)
|
|
||||||
for x in function(*args, **kwargs):
|
for x in function(*args, **kwargs):
|
||||||
yield x
|
yield x
|
||||||
|
|
||||||
|
@ -984,9 +976,7 @@ def wrap_function_to_warning_if_called_directly(function, fixture_marker):
|
||||||
@functools.wraps(function)
|
@functools.wraps(function)
|
||||||
def result(*args, **kwargs):
|
def result(*args, **kwargs):
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
__being_called_by_pytest = kwargs.pop("__being_called_by_pytest", False)
|
warnings.warn(warning, stacklevel=3)
|
||||||
if not __being_called_by_pytest:
|
|
||||||
warnings.warn(warning, stacklevel=3)
|
|
||||||
return function(*args, **kwargs)
|
return function(*args, **kwargs)
|
||||||
|
|
||||||
if six.PY2:
|
if six.PY2:
|
||||||
|
@ -1279,9 +1269,9 @@ class FixtureManager(object):
|
||||||
# The attribute can be an arbitrary descriptor, so the attribute
|
# The attribute can be an arbitrary descriptor, so the attribute
|
||||||
# access below can raise. safe_getatt() ignores such exceptions.
|
# access below can raise. safe_getatt() ignores such exceptions.
|
||||||
obj = safe_getattr(holderobj, name, None)
|
obj = safe_getattr(holderobj, name, None)
|
||||||
|
marker = getfixturemarker(obj)
|
||||||
# fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
|
# fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
|
||||||
# or are "@pytest.fixture" marked
|
# or are "@pytest.fixture" marked
|
||||||
marker = getfixturemarker(obj)
|
|
||||||
if marker is None:
|
if marker is None:
|
||||||
if not name.startswith(self._argprefix):
|
if not name.startswith(self._argprefix):
|
||||||
continue
|
continue
|
||||||
|
@ -1303,6 +1293,15 @@ class FixtureManager(object):
|
||||||
name = marker.name
|
name = marker.name
|
||||||
assert not name.startswith(self._argprefix), FIXTURE_MSG.format(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(
|
fixture_def = FixtureDef(
|
||||||
self,
|
self,
|
||||||
nodeid,
|
nodeid,
|
||||||
|
|
|
@ -267,7 +267,6 @@ def test_pytest_plugins_in_non_top_level_conftest_deprecated_no_false_positives(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# @pytest.mark.skipif(six.PY2, reason="We issue the warning in Python 3 only")
|
|
||||||
def test_call_fixture_function_deprecated():
|
def test_call_fixture_function_deprecated():
|
||||||
"""Check if a warning is raised if a fixture function is called directly (#3661)"""
|
"""Check if a warning is raised if a fixture function is called directly (#3661)"""
|
||||||
|
|
||||||
|
|
|
@ -1470,10 +1470,9 @@ class TestFixtureManagerParseFactories(object):
|
||||||
print (faclist)
|
print (faclist)
|
||||||
assert len(faclist) == 3
|
assert len(faclist) == 3
|
||||||
|
|
||||||
kwargs = {'__being_called_by_pytest': True}
|
assert faclist[0].func(item._request) == "conftest"
|
||||||
assert faclist[0].func(item._request, **kwargs) == "conftest"
|
assert faclist[1].func(item._request) == "module"
|
||||||
assert faclist[1].func(item._request, **kwargs) == "module"
|
assert faclist[2].func(item._request) == "class"
|
||||||
assert faclist[2].func(item._request, **kwargs) == "class"
|
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
reprec = testdir.inline_run("-s")
|
reprec = testdir.inline_run("-s")
|
||||||
|
|
Loading…
Reference in New Issue