fix: update code to use new fixture definitions
This commit is contained in:
parent
46d2ccbfd4
commit
31c8fbed1e
|
@ -210,6 +210,7 @@ def ascii_escaped(val: bytes | str) -> str:
|
||||||
return ret.translate(_non_printable_ascii_translate_table)
|
return ret.translate(_non_printable_ascii_translate_table)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: remove and replace with FixtureFunctionDefinition
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class _PytestWrapper:
|
class _PytestWrapper:
|
||||||
"""Dummy wrapper around a function object for internal use only.
|
"""Dummy wrapper around a function object for internal use only.
|
||||||
|
@ -249,20 +250,6 @@ def get_real_func(obj):
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
def get_real_method(obj, holder):
|
|
||||||
"""Attempt 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: # pragma: no cover
|
|
||||||
return obj
|
|
||||||
if is_method and hasattr(obj, "__get__") and callable(obj.__get__):
|
|
||||||
obj = obj.__get__(holder)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
def getimfunc(func):
|
def getimfunc(func):
|
||||||
try:
|
try:
|
||||||
return func.__func__
|
return func.__func__
|
||||||
|
|
|
@ -45,8 +45,6 @@ from _pytest._io import TerminalWriter
|
||||||
from _pytest.compat import _PytestWrapper
|
from _pytest.compat import _PytestWrapper
|
||||||
from _pytest.compat import assert_never
|
from _pytest.compat import assert_never
|
||||||
from _pytest.compat import get_real_func
|
from _pytest.compat import get_real_func
|
||||||
|
|
||||||
# from _pytest.compat import get_real_method
|
|
||||||
from _pytest.compat import getfuncargnames
|
from _pytest.compat import getfuncargnames
|
||||||
from _pytest.compat import getimfunc
|
from _pytest.compat import getimfunc
|
||||||
from _pytest.compat import getlocation
|
from _pytest.compat import getlocation
|
||||||
|
@ -1146,31 +1144,6 @@ def pytest_fixture_setup(
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def wrap_function_to_error_out_if_called_directly(
|
|
||||||
function: FixtureFunction,
|
|
||||||
fixture_marker: "FixtureFunctionMarker",
|
|
||||||
) -> FixtureFunction:
|
|
||||||
"""Wrap the given fixture function so we can raise an error about it being called directly,
|
|
||||||
instead of used as an argument in a test function."""
|
|
||||||
name = fixture_marker.name or function.__name__
|
|
||||||
message = (
|
|
||||||
f'Fixture "{name}" called directly. Fixtures are not meant to be called directly,\n'
|
|
||||||
"but are created automatically when test functions request them as parameters.\n"
|
|
||||||
"See https://docs.pytest.org/en/stable/explanation/fixtures.html for more information about fixtures, and\n"
|
|
||||||
"https://docs.pytest.org/en/stable/deprecations.html#calling-fixtures-directly about how to update your code."
|
|
||||||
)
|
|
||||||
|
|
||||||
@functools.wraps(function)
|
|
||||||
def result(*args, **kwargs):
|
|
||||||
fail(message, pytrace=False)
|
|
||||||
|
|
||||||
# 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__ = _PytestWrapper(function) # type: ignore[attr-defined]
|
|
||||||
|
|
||||||
return cast(FixtureFunction, result)
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
@final
|
||||||
@dataclasses.dataclass(frozen=True)
|
@dataclasses.dataclass(frozen=True)
|
||||||
class FixtureFunctionMarker:
|
class FixtureFunctionMarker:
|
||||||
|
@ -1191,7 +1164,7 @@ class FixtureFunctionMarker:
|
||||||
if inspect.isclass(function):
|
if inspect.isclass(function):
|
||||||
raise ValueError("class fixtures not supported (maybe in the future)")
|
raise ValueError("class fixtures not supported (maybe in the future)")
|
||||||
|
|
||||||
if getattr(function, "_pytestfixturefunction", False):
|
if isinstance(function, FixtureFunctionDefinition):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"@pytest.fixture is being applied more than once to the same function {function.__name__!r}"
|
f"@pytest.fixture is being applied more than once to the same function {function.__name__!r}"
|
||||||
)
|
)
|
||||||
|
@ -1201,8 +1174,6 @@ class FixtureFunctionMarker:
|
||||||
|
|
||||||
fixture_definition = FixtureFunctionDefinition(function, self)
|
fixture_definition = FixtureFunctionDefinition(function, self)
|
||||||
|
|
||||||
# function = wrap_function_to_error_out_if_called_directly(function, self)
|
|
||||||
|
|
||||||
name = self.name or function.__name__
|
name = self.name or function.__name__
|
||||||
if name == "request":
|
if name == "request":
|
||||||
location = getlocation(function)
|
location = getlocation(function)
|
||||||
|
@ -1211,16 +1182,16 @@ class FixtureFunctionMarker:
|
||||||
pytrace=False,
|
pytrace=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Type ignored because https://github.com/python/mypy/issues/2087.
|
|
||||||
# function._pytestfixturefunction = self # type: ignore[attr-defined]
|
|
||||||
# return function
|
|
||||||
return fixture_definition
|
return fixture_definition
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "fixture"
|
return "fixture"
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: write docstring
|
||||||
class FixtureFunctionDefinition:
|
class FixtureFunctionDefinition:
|
||||||
|
"""Since deco_fixture is now an instance of FixtureFunctionDef the getsource function will not work on it."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
function: Callable[..., object],
|
function: Callable[..., object],
|
||||||
|
@ -1228,19 +1199,15 @@ class FixtureFunctionDefinition:
|
||||||
instance: Optional[type] = None,
|
instance: Optional[type] = None,
|
||||||
):
|
):
|
||||||
self.name = fixture_function_marker.name or function.__name__
|
self.name = fixture_function_marker.name or function.__name__
|
||||||
|
self.__name__ = self.name
|
||||||
self._pytestfixturefunction = fixture_function_marker
|
self._pytestfixturefunction = fixture_function_marker
|
||||||
self.__pytest_wrapped__ = _PytestWrapper(function)
|
self.__pytest_wrapped__ = _PytestWrapper(function)
|
||||||
self.fixture_function = function
|
|
||||||
self.fixture_function_marker = fixture_function_marker
|
self.fixture_function_marker = fixture_function_marker
|
||||||
self.scope = fixture_function_marker.scope
|
|
||||||
self.params = fixture_function_marker.params
|
|
||||||
self.autouse = fixture_function_marker.autouse
|
|
||||||
self.ids = fixture_function_marker.ids
|
|
||||||
self.fixture_function = function
|
self.fixture_function = function
|
||||||
self.instance = instance
|
self.instance = instance
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"fixture {self.fixture_function}"
|
return f"pytest_fixture({self.fixture_function})"
|
||||||
|
|
||||||
def __get__(self, instance, owner=None):
|
def __get__(self, instance, owner=None):
|
||||||
return FixtureFunctionDefinition(
|
return FixtureFunctionDefinition(
|
||||||
|
@ -1248,7 +1215,13 @@ class FixtureFunctionDefinition:
|
||||||
)
|
)
|
||||||
|
|
||||||
def __call__(self, *args: Any, **kwds: Any) -> Any:
|
def __call__(self, *args: Any, **kwds: Any) -> Any:
|
||||||
return self.get_real_func(*args, **kwds)
|
message = (
|
||||||
|
f'Fixture "{self.name}" called directly. Fixtures are not meant to be called directly,\n'
|
||||||
|
"but are created automatically when test functions request them as parameters.\n"
|
||||||
|
"See https://docs.pytest.org/en/stable/explanation/fixtures.html for more information about fixtures, and\n"
|
||||||
|
"https://docs.pytest.org/en/stable/deprecations.html#calling-fixtures-directly"
|
||||||
|
)
|
||||||
|
fail(message, pytrace=False)
|
||||||
|
|
||||||
def get_real_func(self):
|
def get_real_func(self):
|
||||||
if self.instance is not None:
|
if self.instance is not None:
|
||||||
|
@ -1792,26 +1765,19 @@ class FixtureManager:
|
||||||
# Magic globals with __getattr__ might have got us a wrong
|
# Magic globals with __getattr__ might have got us a wrong
|
||||||
# fixture attribute.
|
# fixture attribute.
|
||||||
continue
|
continue
|
||||||
|
if isinstance(obj, FixtureFunctionDefinition):
|
||||||
if marker.name:
|
if marker.name:
|
||||||
name = marker.name
|
name = marker.name
|
||||||
|
func = obj.get_real_func()
|
||||||
# During fixture definition we wrap the original fixture function
|
self._register_fixture(
|
||||||
# to issue a warning if called directly, so here we unwrap it in
|
name=name,
|
||||||
# order to not emit the warning when pytest itself calls the
|
nodeid=nodeid,
|
||||||
# fixture function.
|
func=func,
|
||||||
# func = get_real_method(obj, holderobj)
|
scope=marker.scope,
|
||||||
func = obj.get_real_func()
|
params=marker.params,
|
||||||
|
ids=marker.ids,
|
||||||
self._register_fixture(
|
autouse=marker.autouse,
|
||||||
name=name,
|
)
|
||||||
nodeid=nodeid,
|
|
||||||
func=func,
|
|
||||||
scope=marker.scope,
|
|
||||||
params=marker.params,
|
|
||||||
ids=marker.ids,
|
|
||||||
autouse=marker.autouse,
|
|
||||||
)
|
|
||||||
|
|
||||||
def getfixturedefs(
|
def getfixturedefs(
|
||||||
self, argname: str, node: nodes.Node
|
self, argname: str, node: nodes.Node
|
||||||
|
|
|
@ -478,12 +478,13 @@ def test_source_with_decorator() -> None:
|
||||||
def deco_fixture():
|
def deco_fixture():
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
src = inspect.getsource(deco_fixture)
|
# Since deco_fixture is now an instance of FixtureFunctionDef the getsource function will not work on it.
|
||||||
|
with pytest.raises(Exception):
|
||||||
|
inspect.getsource(deco_fixture)
|
||||||
|
src = inspect.getsource(deco_fixture.get_real_func())
|
||||||
assert src == " @pytest.fixture\n def deco_fixture():\n assert False\n"
|
assert src == " @pytest.fixture\n def deco_fixture():\n assert False\n"
|
||||||
# currently Source does not unwrap decorators, testing the
|
# Make sure the decorator is not a wrapped function
|
||||||
# existing behavior here for explicitness, but perhaps we should revisit/change this
|
assert not str(Source(deco_fixture)).startswith("@functools.wraps(function)")
|
||||||
# in the future
|
|
||||||
assert str(Source(deco_fixture)).startswith("@functools.wraps(function)")
|
|
||||||
assert (
|
assert (
|
||||||
textwrap.indent(str(Source(get_real_func(deco_fixture))), " ") + "\n" == src
|
textwrap.indent(str(Source(get_real_func(deco_fixture))), " ") + "\n" == src
|
||||||
)
|
)
|
||||||
|
|
|
@ -4465,6 +4465,21 @@ def test_fixture_double_decorator(pytester: Pytester) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_fixture_class(pytester: Pytester) -> None:
|
||||||
|
"""Check if an error is raised when using @pytest.fixture on a class."""
|
||||||
|
pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
class A:
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest()
|
||||||
|
result.assert_outcomes(errors=1)
|
||||||
|
|
||||||
|
|
||||||
def test_fixture_param_shadowing(pytester: Pytester) -> None:
|
def test_fixture_param_shadowing(pytester: Pytester) -> None:
|
||||||
"""Parametrized arguments would be shadowed if a fixture with the same name also exists (#5036)"""
|
"""Parametrized arguments would be shadowed if a fixture with the same name also exists (#5036)"""
|
||||||
pytester.makepyfile(
|
pytester.makepyfile(
|
||||||
|
|
|
@ -1310,7 +1310,7 @@ def test_collect_handles_raising_on_dunder_class(pytester: Pytester) -> None:
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
result = pytester.runpytest()
|
result = pytester.runpytest()
|
||||||
result.stdout.fnmatch_lines(["*1 passed in*"])
|
result.assert_outcomes(passed=1)
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
|
|
||||||
|
|
||||||
|
@ -1374,7 +1374,7 @@ def test_collect_pyargs_with_testpaths(
|
||||||
with monkeypatch.context() as mp:
|
with monkeypatch.context() as mp:
|
||||||
mp.chdir(root)
|
mp.chdir(root)
|
||||||
result = pytester.runpytest_subprocess()
|
result = pytester.runpytest_subprocess()
|
||||||
result.stdout.fnmatch_lines(["*1 passed in*"])
|
result.assert_outcomes(passed=1)
|
||||||
|
|
||||||
|
|
||||||
def test_initial_conftests_with_testpaths(pytester: Pytester) -> None:
|
def test_initial_conftests_with_testpaths(pytester: Pytester) -> None:
|
||||||
|
|
Loading…
Reference in New Issue