Skip to content

Commit 1228b41

Browse files
committed
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.
1 parent 2534193 commit 1228b41

File tree

5 files changed

+24
-19
lines changed

5 files changed

+24
-19
lines changed

changelog/3747.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix compatibility problem with plugins and the the warning code issued by fixture functions when they are called directly.

src/_pytest/compat.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,21 @@ def get_real_func(obj):
249249
return obj
250250

251251

252+
def get_real_method(obj, holder):
253+
"""
254+
Attempts to obtain the real function object that might be wrapping ``obj``, while at the same time
255+
returning a bound method to ``holder`` if the original object was a bound method.
256+
"""
257+
try:
258+
is_method = hasattr(obj, "__func__")
259+
obj = get_real_func(obj)
260+
except Exception:
261+
return obj
262+
if is_method and hasattr(obj, "__get__") and callable(obj.__get__):
263+
obj = obj.__get__(holder)
264+
return obj
265+
266+
252267
def getfslineno(obj):
253268
# xxx let decorators etc specify a sane ordering
254269
obj = get_real_func(obj)

src/_pytest/fixtures.py

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
getfuncargnames,
3131
safe_getattr,
3232
FuncargnamesCompatAttr,
33+
get_real_method,
3334
)
3435
from _pytest.deprecated import FIXTURE_FUNCTION_CALL, RemovedInPytest4Warning
3536
from _pytest.outcomes import fail, TEST_OUTCOME
@@ -931,13 +932,6 @@ def pytest_fixture_setup(fixturedef, request):
931932
request._check_scope(argname, request.scope, fixdef.scope)
932933
kwargs[argname] = result
933934

934-
# if function has been defined with @pytest.fixture, we want to
935-
# pass the special __being_called_by_pytest parameter so we don't raise a warning
936-
# this is an ugly hack, see #3720 for an opportunity to improve this
937-
defined_using_fixture_decorator = hasattr(fixturedef.func, "_pytestfixturefunction")
938-
if defined_using_fixture_decorator:
939-
kwargs["__being_called_by_pytest"] = True
940-
941935
fixturefunc = resolve_fixture_function(fixturedef, request)
942936
my_cache_key = request.param_index
943937
try:
@@ -973,9 +967,7 @@ def wrap_function_to_warning_if_called_directly(function, fixture_marker):
973967
@functools.wraps(function)
974968
def result(*args, **kwargs):
975969
__tracebackhide__ = True
976-
__being_called_by_pytest = kwargs.pop("__being_called_by_pytest", False)
977-
if not __being_called_by_pytest:
978-
warnings.warn(warning, stacklevel=3)
970+
warnings.warn(warning, stacklevel=3)
979971
for x in function(*args, **kwargs):
980972
yield x
981973

@@ -984,9 +976,7 @@ def result(*args, **kwargs):
984976
@functools.wraps(function)
985977
def result(*args, **kwargs):
986978
__tracebackhide__ = True
987-
__being_called_by_pytest = kwargs.pop("__being_called_by_pytest", False)
988-
if not __being_called_by_pytest:
989-
warnings.warn(warning, stacklevel=3)
979+
warnings.warn(warning, stacklevel=3)
990980
return function(*args, **kwargs)
991981

992982
if six.PY2:
@@ -1279,9 +1269,10 @@ def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False):
12791269
# The attribute can be an arbitrary descriptor, so the attribute
12801270
# access below can raise. safe_getatt() ignores such exceptions.
12811271
obj = safe_getattr(holderobj, name, None)
1272+
marker = getfixturemarker(obj)
1273+
obj = get_real_method(obj, holderobj)
12821274
# fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
12831275
# or are "@pytest.fixture" marked
1284-
marker = getfixturemarker(obj)
12851276
if marker is None:
12861277
if not name.startswith(self._argprefix):
12871278
continue

testing/deprecated_test.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,6 @@ def test_func():
267267
)
268268

269269

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

testing/python/fixture.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1470,10 +1470,9 @@ def test_hello(self, item, fm):
14701470
print (faclist)
14711471
assert len(faclist) == 3
14721472
1473-
kwargs = {'__being_called_by_pytest': True}
1474-
assert faclist[0].func(item._request, **kwargs) == "conftest"
1475-
assert faclist[1].func(item._request, **kwargs) == "module"
1476-
assert faclist[2].func(item._request, **kwargs) == "class"
1473+
assert faclist[0].func(item._request) == "conftest"
1474+
assert faclist[1].func(item._request) == "module"
1475+
assert faclist[2].func(item._request) == "class"
14771476
"""
14781477
)
14791478
reprec = testdir.inline_run("-s")

0 commit comments

Comments
 (0)