Skip to content

Commit 33769d0

Browse files
Merge pull request #3754 from nicoddemus/fix-function-call-warning
Refactor direct fixture call warning to avoid incompatibility with plugins
2 parents 5db2e6c + 82a2174 commit 33769d0

File tree

5 files changed

+32
-19
lines changed

5 files changed

+32
-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 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: 13 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,9 @@ 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)
12821273
# fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
12831274
# or are "@pytest.fixture" marked
1284-
marker = getfixturemarker(obj)
12851275
if marker is None:
12861276
if not name.startswith(self._argprefix):
12871277
continue
@@ -1303,6 +1293,15 @@ def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False):
13031293
name = marker.name
13041294
assert not name.startswith(self._argprefix), FIXTURE_MSG.format(name)
13051295

1296+
# during fixture definition we wrap the original fixture function
1297+
# to issue a warning if called directly, so here we unwrap it in order to not emit the warning
1298+
# when pytest itself calls the fixture function
1299+
if six.PY2 and unittest:
1300+
# hack on Python 2 because of the unbound methods
1301+
obj = get_real_func(obj)
1302+
else:
1303+
obj = get_real_method(obj, holderobj)
1304+
13061305
fixture_def = FixtureDef(
13071306
self,
13081307
nodeid,

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)