Skip to content

Commit

Permalink
Use a custom holder class so we can be sure __pytest_wrapper__ was se…
Browse files Browse the repository at this point in the history
…t by us
  • Loading branch information
nicoddemus committed Aug 9, 2018
1 parent ef8ec01 commit 67106f0
Show file tree
Hide file tree
Showing 3 changed files with 18 additions and 7 deletions.
16 changes: 14 additions & 2 deletions src/_pytest/compat.py
Expand Up @@ -228,6 +228,18 @@ def ascii_escaped(val):
return val.encode("unicode-escape")


class _PytestWrapper(object):
"""Dummy wrapper around a function object for internal use only.
Used to correctly unwrap the underlying function object
when we are creating fixtures, because we wrap the function object ourselves with a decorator
to issue warnings when the fixture function is called directly.
"""

def __init__(self, obj):
self.obj = obj


def get_real_func(obj):
""" gets the real function object of the (possibly) wrapped object by
functools.wraps or functools.partial.
Expand All @@ -238,8 +250,8 @@ def get_real_func(obj):
# to trigger a warning if it gets called directly instead of by pytest: we don't
# want to unwrap further than this otherwise we lose useful wrappings like @mock.patch (#3774)
new_obj = getattr(obj, "__pytest_wrapped__", None)
if new_obj is not None:
obj = new_obj
if isinstance(new_obj, _PytestWrapper):
obj = new_obj.obj
break
new_obj = getattr(obj, "__wrapped__", None)
if new_obj is None:
Expand Down
3 changes: 2 additions & 1 deletion src/_pytest/fixtures.py
Expand Up @@ -31,6 +31,7 @@
safe_getattr,
FuncargnamesCompatAttr,
get_real_method,
_PytestWrapper,
)
from _pytest.deprecated import FIXTURE_FUNCTION_CALL, RemovedInPytest4Warning
from _pytest.outcomes import fail, TEST_OUTCOME
Expand Down Expand Up @@ -981,7 +982,7 @@ def result(*args, **kwargs):

# keep reference to the original function in our own custom attribute so we don't unwrap
# further than this point and lose useful wrappings like @mock.patch (#3774)
result.__pytest_wrapped__ = function
result.__pytest_wrapped__ = _PytestWrapper(function)

return result

Expand Down
6 changes: 2 additions & 4 deletions testing/test_compat.py
Expand Up @@ -5,7 +5,7 @@
import six

import pytest
from _pytest.compat import is_generator, get_real_func, safe_getattr
from _pytest.compat import is_generator, get_real_func, safe_getattr, _PytestWrapper
from _pytest.outcomes import OutcomeException


Expand All @@ -29,8 +29,6 @@ def __repr__(self):
return "<Evil left={left}>".format(left=self.left)

def __getattr__(self, attr):
if attr == "__pytest_wrapped__":
raise AttributeError
if not self.left:
raise RuntimeError("its over")
self.left -= 1
Expand Down Expand Up @@ -66,7 +64,7 @@ def func():

# special case for __pytest_wrapped__ attribute: used to obtain the function up until the point
# a function was wrapped by pytest itself
wrapped_func2.__pytest_wrapped__ = wrapped_func
wrapped_func2.__pytest_wrapped__ = _PytestWrapper(wrapped_func)
assert get_real_func(wrapped_func2) is wrapped_func


Expand Down

0 comments on commit 67106f0

Please sign in to comment.