Merge ec2bcc51ee
into ac41898755
This commit is contained in:
commit
9458b08720
|
@ -1,4 +1,3 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Python version compatibility code."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
@ -12,8 +11,11 @@ from inspect import signature
|
|||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from types import FunctionType
|
||||
from types import MethodType
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
from typing import Final
|
||||
from typing import NoReturn
|
||||
|
||||
|
@ -66,7 +68,8 @@ def is_async_function(func: object) -> bool:
|
|||
return iscoroutinefunction(func) or inspect.isasyncgenfunction(func)
|
||||
|
||||
|
||||
def getlocation(function, curdir: str | os.PathLike[str] | None = None) -> str:
|
||||
def getlocation(function: Any, curdir: str | os.PathLike[str] | None = None) -> str:
|
||||
# todo: declare a type alias for function, fixturefunction and callables/generators
|
||||
function = get_real_func(function)
|
||||
fn = Path(inspect.getfile(function))
|
||||
lineno = function.__code__.co_firstlineno
|
||||
|
@ -80,7 +83,7 @@ def getlocation(function, curdir: str | os.PathLike[str] | None = None) -> str:
|
|||
return "%s:%d" % (fn, lineno + 1)
|
||||
|
||||
|
||||
def num_mock_patch_args(function) -> int:
|
||||
def num_mock_patch_args(function: Callable[..., object]) -> int:
|
||||
"""Return number of arguments used up by mock arguments (if any)."""
|
||||
patchings = getattr(function, "patchings", None)
|
||||
if not patchings:
|
||||
|
@ -222,7 +225,7 @@ class _PytestWrapper:
|
|||
obj: Any
|
||||
|
||||
|
||||
def get_real_func(obj):
|
||||
def get_real_func(obj: Any) -> Any:
|
||||
"""Get the real function object of the (possibly) wrapped object by
|
||||
functools.wraps or functools.partial."""
|
||||
start_obj = obj
|
||||
|
@ -249,7 +252,7 @@ def get_real_func(obj):
|
|||
return obj
|
||||
|
||||
|
||||
def get_real_method(obj, holder):
|
||||
def get_real_method(obj: Any, holder: object) -> Any:
|
||||
"""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."""
|
||||
|
@ -263,11 +266,8 @@ def get_real_method(obj, holder):
|
|||
return obj
|
||||
|
||||
|
||||
def getimfunc(func):
|
||||
try:
|
||||
return func.__func__
|
||||
except AttributeError:
|
||||
return func
|
||||
def getimfunc(func: FunctionType | MethodType | Callable[..., Any]) -> FunctionType:
|
||||
return cast(FunctionType, getattr(func, "__func__", func))
|
||||
|
||||
|
||||
def safe_getattr(object: Any, name: str, default: Any) -> Any:
|
||||
|
|
|
@ -417,7 +417,8 @@ def _get_continue_on_failure(config: Config) -> bool:
|
|||
|
||||
|
||||
class DoctestTextfile(Module):
|
||||
obj = None
|
||||
# todo: this shouldnt be a module
|
||||
obj: None = None # type: ignore[assignment]
|
||||
|
||||
def collect(self) -> Iterable[DoctestItem]:
|
||||
import doctest
|
||||
|
|
|
@ -1100,7 +1100,8 @@ def resolve_fixture_function(
|
|||
) -> _FixtureFunc[FixtureValue]:
|
||||
"""Get the actual callable that can be called to obtain the fixture
|
||||
value."""
|
||||
fixturefunc = fixturedef.func
|
||||
# absuing any for the differences between FunctionTpye and Callable
|
||||
fixturefunc: Any = fixturedef.func
|
||||
# The fixture function needs to be bound to the actual
|
||||
# request.instance so that code working with "fixturedef" behaves
|
||||
# as expected.
|
||||
|
@ -1112,11 +1113,11 @@ def resolve_fixture_function(
|
|||
instance,
|
||||
fixturefunc.__self__.__class__,
|
||||
):
|
||||
return fixturefunc
|
||||
return cast(_FixtureFunc[FixtureValue], fixturefunc)
|
||||
fixturefunc = getimfunc(fixturedef.func)
|
||||
if fixturefunc != fixturedef.func:
|
||||
fixturefunc = fixturefunc.__get__(instance)
|
||||
return fixturefunc
|
||||
return cast(_FixtureFunc[FixtureValue], fixturefunc)
|
||||
|
||||
|
||||
def pytest_fixture_setup(
|
||||
|
|
|
@ -535,6 +535,7 @@ class Session(nodes.Collector):
|
|||
``Session`` collects the initial paths given as arguments to pytest.
|
||||
"""
|
||||
|
||||
parent: None
|
||||
Interrupted = Interrupted
|
||||
Failed = Failed
|
||||
# Set on the session by runner.pytest_sessionstart.
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# mypy: allow-untyped-defs
|
||||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
|
@ -96,7 +95,7 @@ class NodeMeta(abc.ABCMeta):
|
|||
progress on detangling the :class:`Node` classes.
|
||||
"""
|
||||
|
||||
def __call__(cls, *k, **kw) -> NoReturn:
|
||||
def __call__(cls, *k: object, **kw: object) -> NoReturn:
|
||||
msg = (
|
||||
"Direct construction of {name} has been deprecated, please use {name}.from_parent.\n"
|
||||
"See "
|
||||
|
@ -105,10 +104,10 @@ class NodeMeta(abc.ABCMeta):
|
|||
).format(name=f"{cls.__module__}.{cls.__name__}")
|
||||
fail(msg, pytrace=False)
|
||||
|
||||
def _create(cls: type[_T], *k, **kw) -> _T:
|
||||
def _create(cls: type[_T], *k: Any, **kw: Any) -> _T:
|
||||
try:
|
||||
return super().__call__(*k, **kw) # type: ignore[no-any-return,misc]
|
||||
except TypeError:
|
||||
except TypeError as e:
|
||||
sig = signature(getattr(cls, "__init__"))
|
||||
known_kw = {k: v for k, v in kw.items() if k in sig.parameters}
|
||||
from .warning_types import PytestDeprecationWarning
|
||||
|
@ -116,6 +115,7 @@ class NodeMeta(abc.ABCMeta):
|
|||
warnings.warn(
|
||||
PytestDeprecationWarning(
|
||||
f"{cls} is not using a cooperative constructor and only takes {set(known_kw)}.\n"
|
||||
f"Exception: {e}\n"
|
||||
"See https://docs.pytest.org/en/stable/deprecations.html"
|
||||
"#constructors-of-custom-pytest-node-subclasses-should-take-kwargs "
|
||||
"for more details."
|
||||
|
@ -138,8 +138,13 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
#: for methods not migrated to ``pathlib.Path`` yet, such as
|
||||
#: :meth:`Item.reportinfo <pytest.Item.reportinfo>`. Will be deprecated in
|
||||
#: a future release, prefer using :attr:`path` instead.
|
||||
name: str
|
||||
parent: Node | None
|
||||
config: Config
|
||||
session: Session
|
||||
fspath: LEGACY_PATH
|
||||
|
||||
_nodeid: str
|
||||
# Use __slots__ to make attribute access faster.
|
||||
# Note that __dict__ is still available.
|
||||
__slots__ = (
|
||||
|
@ -156,7 +161,7 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
parent: Node | None = None,
|
||||
parent: Node | None,
|
||||
config: Config | None = None,
|
||||
session: Session | None = None,
|
||||
fspath: LEGACY_PATH | None = None,
|
||||
|
@ -200,13 +205,9 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
#: Allow adding of extra keywords to use for matching.
|
||||
self.extra_keyword_matches: set[str] = set()
|
||||
|
||||
if nodeid is not None:
|
||||
assert "::()" not in nodeid
|
||||
self._nodeid = nodeid
|
||||
else:
|
||||
if not self.parent:
|
||||
raise TypeError("nodeid or parent must be provided")
|
||||
self._nodeid = self.parent.nodeid + "::" + self.name
|
||||
self._nodeid = self._make_nodeid(
|
||||
name=self.name, parent=self.parent, given=nodeid
|
||||
)
|
||||
|
||||
#: A place where plugins can store information on the node for their
|
||||
#: own use.
|
||||
|
@ -215,7 +216,16 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
self._store = self.stash
|
||||
|
||||
@classmethod
|
||||
def from_parent(cls, parent: Node, **kw) -> Self:
|
||||
def _make_nodeid(cls, name: str, parent: Node | None, given: str | None) -> str:
|
||||
if given is not None:
|
||||
assert "::()" not in given
|
||||
return given
|
||||
else:
|
||||
assert parent is not None
|
||||
return f"{parent.nodeid}::{name}"
|
||||
|
||||
@classmethod
|
||||
def from_parent(cls, parent: Node, **kw: Any) -> Self:
|
||||
"""Public constructor for Nodes.
|
||||
|
||||
This indirection got introduced in order to enable removing
|
||||
|
@ -238,7 +248,7 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
return self.session.gethookproxy(self.path)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "<{} {}>".format(self.__class__.__name__, getattr(self, "name", None))
|
||||
return f'<{self.__class__.__name__} { getattr(self, "name", None)}>'
|
||||
|
||||
def warn(self, warning: Warning) -> None:
|
||||
"""Issue a warning for this Node.
|
||||
|
@ -598,7 +608,6 @@ class FSCollector(Collector, abc.ABC):
|
|||
|
||||
if nodeid and os.sep != SEP:
|
||||
nodeid = nodeid.replace(os.sep, SEP)
|
||||
|
||||
super().__init__(
|
||||
name=name,
|
||||
parent=parent,
|
||||
|
@ -611,11 +620,11 @@ class FSCollector(Collector, abc.ABC):
|
|||
@classmethod
|
||||
def from_parent(
|
||||
cls,
|
||||
parent,
|
||||
parent: Node,
|
||||
*,
|
||||
fspath: LEGACY_PATH | None = None,
|
||||
path: Path | None = None,
|
||||
**kw,
|
||||
**kw: Any,
|
||||
) -> Self:
|
||||
"""The public constructor."""
|
||||
return super().from_parent(parent=parent, fspath=fspath, path=path, **kw)
|
||||
|
@ -652,16 +661,14 @@ class Item(Node, abc.ABC):
|
|||
Note that for a single function there might be multiple test invocation items.
|
||||
"""
|
||||
|
||||
nextitem = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
parent=None,
|
||||
name: str,
|
||||
parent: Node | None = None,
|
||||
config: Config | None = None,
|
||||
session: Session | None = None,
|
||||
nodeid: str | None = None,
|
||||
**kw,
|
||||
**kw: Any,
|
||||
) -> None:
|
||||
# The first two arguments are intentionally passed positionally,
|
||||
# to keep plugins who define a node type which inherits from
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Python test discovery, setup and run of test functions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
@ -17,6 +16,7 @@ from pathlib import Path
|
|||
import types
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
from typing import Dict
|
||||
from typing import final
|
||||
from typing import Generator
|
||||
|
@ -27,6 +27,8 @@ from typing import Mapping
|
|||
from typing import Pattern
|
||||
from typing import Sequence
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
import _pytest
|
||||
|
@ -203,7 +205,7 @@ def path_matches_patterns(path: Path, patterns: Iterable[str]) -> bool:
|
|||
return any(fnmatch_ex(pattern, path) for pattern in patterns)
|
||||
|
||||
|
||||
def pytest_pycollect_makemodule(module_path: Path, parent) -> Module:
|
||||
def pytest_pycollect_makemodule(module_path: Path, parent: nodes.FSCollector) -> Module:
|
||||
return Module.from_parent(parent, path=module_path)
|
||||
|
||||
|
||||
|
@ -242,6 +244,7 @@ def pytest_pycollect_makeitem(
|
|||
res.warn(PytestCollectionWarning(reason))
|
||||
return res
|
||||
else:
|
||||
assert isinstance(obj, (types.FunctionType))
|
||||
return list(collector._genfunctions(name, obj))
|
||||
return None
|
||||
|
||||
|
@ -252,22 +255,26 @@ class PyobjMixin(nodes.Node):
|
|||
as its intended to always mix in before a node
|
||||
its position in the mro is unaffected"""
|
||||
|
||||
def __init__(self, *k: Any, obj: Any | None = None, **kw: Any) -> None:
|
||||
super().__init__(*k, **kw)
|
||||
self._assign_obj_with_markers(obj)
|
||||
|
||||
_ALLOW_MARKERS = True
|
||||
|
||||
@property
|
||||
def module(self):
|
||||
def module(self) -> types.ModuleType | None:
|
||||
"""Python module object this node was collected from (can be None)."""
|
||||
node = self.getparent(Module)
|
||||
return node.obj if node is not None else None
|
||||
|
||||
@property
|
||||
def cls(self):
|
||||
def cls(self) -> type | None:
|
||||
"""Python class object this node was collected from (can be None)."""
|
||||
node = self.getparent(Class)
|
||||
return node.obj if node is not None else None
|
||||
|
||||
@property
|
||||
def instance(self):
|
||||
def instance(self) -> object | None:
|
||||
"""Python instance object the function is bound to.
|
||||
|
||||
Returns None if not a test method, e.g. for a standalone test function,
|
||||
|
@ -276,26 +283,30 @@ class PyobjMixin(nodes.Node):
|
|||
# Overridden by Function.
|
||||
return None
|
||||
|
||||
def _assign_obj_with_markers(self, obj: Any | None) -> None:
|
||||
self._obj = obj
|
||||
# XXX evil hack
|
||||
# used to avoid Function marker duplication
|
||||
if self._ALLOW_MARKERS and obj is not None:
|
||||
self.own_markers.extend(get_unpacked_marks(self.obj))
|
||||
# This assumes that `obj` is called before there is a chance
|
||||
# to add custom keys to `self.keywords`, so no fear of overriding.
|
||||
self.keywords.update((mark.name, mark) for mark in self.own_markers)
|
||||
|
||||
@property
|
||||
def obj(self):
|
||||
def obj(self) -> Any:
|
||||
"""Underlying Python object."""
|
||||
obj = getattr(self, "_obj", None)
|
||||
if obj is None:
|
||||
self._obj = obj = self._getobj()
|
||||
# XXX evil hack
|
||||
# used to avoid Function marker duplication
|
||||
if self._ALLOW_MARKERS:
|
||||
self.own_markers.extend(get_unpacked_marks(self.obj))
|
||||
# This assumes that `obj` is called before there is a chance
|
||||
# to add custom keys to `self.keywords`, so no fear of overriding.
|
||||
self.keywords.update((mark.name, mark) for mark in self.own_markers)
|
||||
obj = self._getobj()
|
||||
self._assign_obj_with_markers(obj)
|
||||
return obj
|
||||
|
||||
@obj.setter
|
||||
def obj(self, value):
|
||||
def obj(self, value: Any) -> None:
|
||||
self._obj = value
|
||||
|
||||
def _getobj(self):
|
||||
def _getobj(self) -> Any:
|
||||
"""Get the underlying Python object. May be overwritten by subclasses."""
|
||||
# TODO: Improve the type of `parent` such that assert/ignore aren't needed.
|
||||
assert self.parent is not None
|
||||
|
@ -434,14 +445,20 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC):
|
|||
result.extend(values)
|
||||
return result
|
||||
|
||||
def _genfunctions(self, name: str, funcobj) -> Iterator[Function]:
|
||||
def _genfunctions(
|
||||
self, name: str, funcobj: types.FunctionType
|
||||
) -> Iterator[Function]:
|
||||
modulecol = self.getparent(Module)
|
||||
assert modulecol is not None
|
||||
module = modulecol.obj
|
||||
clscol = self.getparent(Class)
|
||||
cls = clscol and clscol.obj or None
|
||||
cls: type | None = getattr(clscol, "obj", None)
|
||||
|
||||
definition = FunctionDefinition.from_parent(self, name=name, callobj=funcobj)
|
||||
definition = FunctionDefinition.from_parent(
|
||||
self, # type: ignore[arg-type]
|
||||
name=name,
|
||||
callobj=funcobj,
|
||||
)
|
||||
fixtureinfo = definition._fixtureinfo
|
||||
|
||||
# pytest_generate_tests impls call metafunc.parametrize() which fills
|
||||
|
@ -462,7 +479,7 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC):
|
|||
self.ihook.pytest_generate_tests.call_extra(methods, dict(metafunc=metafunc))
|
||||
|
||||
if not metafunc._calls:
|
||||
yield Function.from_parent(self, name=name, fixtureinfo=fixtureinfo)
|
||||
yield Function.from_parent(self, name=name, fixtureinfo=fixtureinfo) # type: ignore[arg-type]
|
||||
else:
|
||||
# Direct parametrizations taking place in module/class-specific
|
||||
# `metafunc.parametrize` calls may have shadowed some fixtures, so make sure
|
||||
|
@ -474,7 +491,7 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC):
|
|||
for callspec in metafunc._calls:
|
||||
subname = f"{name}[{callspec.id}]"
|
||||
yield Function.from_parent(
|
||||
self,
|
||||
self, # type: ignore[arg-type]
|
||||
name=subname,
|
||||
callspec=callspec,
|
||||
fixtureinfo=fixtureinfo,
|
||||
|
@ -486,7 +503,7 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC):
|
|||
def importtestmodule(
|
||||
path: Path,
|
||||
config: Config,
|
||||
):
|
||||
) -> types.ModuleType:
|
||||
# We assume we are only called once per module.
|
||||
importmode = config.getoption("--import-mode")
|
||||
try:
|
||||
|
@ -542,7 +559,9 @@ def importtestmodule(
|
|||
class Module(nodes.File, PyCollector):
|
||||
"""Collector for test classes and functions in a Python module."""
|
||||
|
||||
def _getobj(self):
|
||||
obj: types.ModuleType
|
||||
|
||||
def _getobj(self) -> types.ModuleType:
|
||||
return importtestmodule(self.path, self.config)
|
||||
|
||||
def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
|
||||
|
@ -568,7 +587,9 @@ class Module(nodes.File, PyCollector):
|
|||
if setup_module is None and teardown_module is None:
|
||||
return
|
||||
|
||||
def xunit_setup_module_fixture(request) -> Generator[None, None, None]:
|
||||
def xunit_setup_module_fixture(
|
||||
request: FixtureRequest,
|
||||
) -> Generator[None, None, None]:
|
||||
module = request.module
|
||||
if setup_module is not None:
|
||||
_call_with_optional_argument(setup_module, module)
|
||||
|
@ -599,7 +620,9 @@ class Module(nodes.File, PyCollector):
|
|||
if setup_function is None and teardown_function is None:
|
||||
return
|
||||
|
||||
def xunit_setup_function_fixture(request) -> Generator[None, None, None]:
|
||||
def xunit_setup_function_fixture(
|
||||
request: FixtureRequest,
|
||||
) -> Generator[None, None, None]:
|
||||
if request.instance is not None:
|
||||
# in this case we are bound to an instance, so we need to let
|
||||
# setup_method handle this
|
||||
|
@ -642,9 +665,9 @@ class Package(nodes.Directory):
|
|||
fspath: LEGACY_PATH | None,
|
||||
parent: nodes.Collector,
|
||||
# NOTE: following args are unused:
|
||||
config=None,
|
||||
session=None,
|
||||
nodeid=None,
|
||||
config: Config | None = None,
|
||||
session: Session | None = None,
|
||||
nodeid: str | None = None,
|
||||
path: Path | None = None,
|
||||
) -> None:
|
||||
# NOTE: Could be just the following, but kept as-is for compat.
|
||||
|
@ -705,38 +728,47 @@ class Package(nodes.Directory):
|
|||
yield from cols
|
||||
|
||||
|
||||
def _call_with_optional_argument(func, arg) -> None:
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def _call_with_optional_argument(
|
||||
func: Callable[[T], None] | Callable[[], None], arg: T
|
||||
) -> None:
|
||||
"""Call the given function with the given argument if func accepts one argument, otherwise
|
||||
calls func without arguments."""
|
||||
arg_count = func.__code__.co_argcount
|
||||
if inspect.ismethod(func):
|
||||
arg_count -= 1
|
||||
if arg_count:
|
||||
func(arg)
|
||||
func(arg) # type: ignore[call-arg]
|
||||
else:
|
||||
func()
|
||||
func() # type: ignore[call-arg]
|
||||
|
||||
|
||||
def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> object | None:
|
||||
def _get_first_non_fixture_func(
|
||||
obj: object, names: Iterable[str]
|
||||
) -> types.FunctionType | types.MethodType | None:
|
||||
"""Return the attribute from the given object to be used as a setup/teardown
|
||||
xunit-style function, but only if not marked as a fixture to avoid calling it twice.
|
||||
"""
|
||||
for name in names:
|
||||
meth: object | None = getattr(obj, name, None)
|
||||
if meth is not None and fixtures.getfixturemarker(meth) is None:
|
||||
return meth
|
||||
return cast(Union[types.FunctionType, types.MethodType], meth)
|
||||
return None
|
||||
|
||||
|
||||
class Class(PyCollector):
|
||||
"""Collector for test methods (and nested classes) in a Python class."""
|
||||
|
||||
obj: type
|
||||
|
||||
@classmethod
|
||||
def from_parent(cls, parent, *, name, obj=None, **kw) -> Self: # type: ignore[override]
|
||||
def from_parent(cls, parent: nodes.Node, *, name: str, **kw: Any) -> Self: # type: ignore[override]
|
||||
"""The public constructor."""
|
||||
return super().from_parent(name=name, parent=parent, **kw)
|
||||
|
||||
def newinstance(self):
|
||||
def newinstance(self) -> Any:
|
||||
return self.obj()
|
||||
|
||||
def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
|
||||
|
@ -780,7 +812,9 @@ class Class(PyCollector):
|
|||
if setup_class is None and teardown_class is None:
|
||||
return
|
||||
|
||||
def xunit_setup_class_fixture(request) -> Generator[None, None, None]:
|
||||
def xunit_setup_class_fixture(
|
||||
request: FixtureRequest,
|
||||
) -> Generator[None, None, None]:
|
||||
cls = request.cls
|
||||
if setup_class is not None:
|
||||
func = getimfunc(setup_class)
|
||||
|
@ -813,7 +847,9 @@ class Class(PyCollector):
|
|||
if setup_method is None and teardown_method is None:
|
||||
return
|
||||
|
||||
def xunit_setup_method_fixture(request) -> Generator[None, None, None]:
|
||||
def xunit_setup_method_fixture(
|
||||
request: FixtureRequest,
|
||||
) -> Generator[None, None, None]:
|
||||
instance = request.instance
|
||||
method = request.function
|
||||
if setup_method is not None:
|
||||
|
@ -1101,8 +1137,8 @@ class Metafunc:
|
|||
definition: FunctionDefinition,
|
||||
fixtureinfo: fixtures.FuncFixtureInfo,
|
||||
config: Config,
|
||||
cls=None,
|
||||
module=None,
|
||||
cls: type | None = None,
|
||||
module: types.ModuleType | None = None,
|
||||
*,
|
||||
_ispytest: bool = False,
|
||||
) -> None:
|
||||
|
@ -1523,13 +1559,15 @@ class Function(PyobjMixin, nodes.Item):
|
|||
# Disable since functions handle it themselves.
|
||||
_ALLOW_MARKERS = False
|
||||
|
||||
obj: Callable[..., object]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
parent,
|
||||
parent: PyCollector | Module | Class,
|
||||
config: Config | None = None,
|
||||
callspec: CallSpec2 | None = None,
|
||||
callobj=NOTSET,
|
||||
callobj: Any = NOTSET,
|
||||
keywords: Mapping[str, Any] | None = None,
|
||||
session: Session | None = None,
|
||||
fixtureinfo: FuncFixtureInfo | None = None,
|
||||
|
@ -1576,7 +1614,7 @@ class Function(PyobjMixin, nodes.Item):
|
|||
|
||||
# todo: determine sound type limitations
|
||||
@classmethod
|
||||
def from_parent(cls, parent, **kw) -> Self:
|
||||
def from_parent(cls, parent: Module | Class, **kw: Any) -> Self: # type: ignore[override]
|
||||
"""The public constructor."""
|
||||
return super().from_parent(parent=parent, **kw)
|
||||
|
||||
|
@ -1585,30 +1623,27 @@ class Function(PyobjMixin, nodes.Item):
|
|||
self._request = fixtures.TopRequest(self, _ispytest=True)
|
||||
|
||||
@property
|
||||
def function(self):
|
||||
def function(self) -> types.FunctionType:
|
||||
"""Underlying python 'function' object."""
|
||||
return getimfunc(self.obj)
|
||||
|
||||
@property
|
||||
def instance(self):
|
||||
def instance(self) -> Any | None:
|
||||
try:
|
||||
return self._instance
|
||||
except AttributeError:
|
||||
if isinstance(self.parent, Class):
|
||||
# Each Function gets a fresh class instance.
|
||||
self._instance = self._getinstance()
|
||||
else:
|
||||
self._instance = None
|
||||
self._instance = self._getinstance()
|
||||
|
||||
return self._instance
|
||||
|
||||
def _getinstance(self):
|
||||
def _getinstance(self) -> Any | None:
|
||||
if isinstance(self.parent, Class):
|
||||
# Each Function gets a fresh class instance.
|
||||
return self.parent.newinstance()
|
||||
else:
|
||||
return None
|
||||
|
||||
def _getobj(self):
|
||||
def _getobj(self) -> object:
|
||||
instance = self.instance
|
||||
if instance is not None:
|
||||
parent_obj = instance
|
||||
|
@ -1618,7 +1653,7 @@ class Function(PyobjMixin, nodes.Item):
|
|||
return getattr(parent_obj, self.originalname)
|
||||
|
||||
@property
|
||||
def _pyfuncitem(self):
|
||||
def _pyfuncitem(self) -> Self:
|
||||
"""(compatonly) for code expecting pytest-2.2 style request objects."""
|
||||
return self
|
||||
|
||||
|
|
|
@ -325,7 +325,8 @@ class TestFunction:
|
|||
session = Session.from_config(config)
|
||||
session._fixturemanager = FixtureManager(session)
|
||||
|
||||
return pytest.Function.from_parent(parent=session, **kwargs)
|
||||
# todo: implement intermediate node for testing
|
||||
return pytest.Function.from_parent(parent=session, **kwargs) # type: ignore[arg-type]
|
||||
|
||||
def test_function_equality(self, pytester: Pytester) -> None:
|
||||
def func1():
|
||||
|
|
|
@ -7,6 +7,7 @@ import re
|
|||
import sys
|
||||
import textwrap
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
from typing import Dict
|
||||
from typing import Iterator
|
||||
|
@ -49,7 +50,7 @@ class TestMetafunc:
|
|||
@dataclasses.dataclass
|
||||
class DefinitionMock(python.FunctionDefinition):
|
||||
_nodeid: str
|
||||
obj: object
|
||||
obj: Callable[..., Any]
|
||||
|
||||
names = getfuncargnames(func)
|
||||
fixtureinfo: Any = FuncFixtureInfoMock(names)
|
||||
|
|
|
@ -634,6 +634,14 @@ class TestFunctional:
|
|||
has_own, has_inherited = items
|
||||
has_own_marker = has_own.get_closest_marker("c")
|
||||
has_inherited_marker = has_inherited.get_closest_marker("c")
|
||||
|
||||
for item in items:
|
||||
print(item)
|
||||
for node in item.iter_parents():
|
||||
print(" ", node)
|
||||
for marker in node.own_markers:
|
||||
print(" ", marker)
|
||||
|
||||
assert has_own_marker is not None
|
||||
assert has_inherited_marker is not None
|
||||
assert has_own_marker.kwargs == {"location": "function"}
|
||||
|
|
|
@ -31,7 +31,7 @@ def test_node_direct_construction_deprecated() -> None:
|
|||
" for more details."
|
||||
),
|
||||
):
|
||||
nodes.Node(None, session=None) # type: ignore[arg-type]
|
||||
nodes.Node(None, parent=None, session=None) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def test_subclassing_both_item_and_collector_deprecated(
|
||||
|
|
Loading…
Reference in New Issue