This commit is contained in:
Ronny Pfannschmidt 2024-07-03 15:37:53 +02:00 committed by GitHub
commit 5f75e824b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 188 additions and 120 deletions

View File

@ -210,7 +210,11 @@ class TracebackEntry:
@property
def lineno(self) -> int:
return self._rawentry.tb_lineno - 1
if self._rawentry.tb_lineno is None:
# how did i trigger this 😱
return -1 # type: ignore[unreachable]
else:
return self._rawentry.tb_lineno - 1
@property
def frame(self) -> Frame:

View File

@ -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:

View File

@ -417,9 +417,10 @@ 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]:
def collect(self) -> Iterable[DoctestItem]: # type: ignore[override]
import doctest
# Inspired by doctest.testfile; ideally we would use it directly,
@ -497,7 +498,7 @@ def _patch_unwrap_mock_aware() -> Generator[None, None, None]:
class DoctestModule(Module):
def collect(self) -> Iterable[DoctestItem]:
def collect(self) -> Iterable[DoctestItem]: # type: ignore[override]
import doctest
class MockAwareDocTestFinder(doctest.DocTestFinder):

View File

@ -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(

View File

@ -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.

View File

@ -1,4 +1,3 @@
# mypy: allow-untyped-defs
from __future__ import annotations
import abc
@ -20,6 +19,7 @@ from typing import TypeVar
import warnings
import pluggy
from typing_extensions import Self
import _pytest._code
from _pytest._code import getfslineno
@ -54,9 +54,6 @@ SEP = "/"
tracebackcutdir = Path(_pytest.__file__).parent
_T = TypeVar("_T")
def _imply_path(
node_type: type[Node],
path: Path | None,
@ -96,7 +93,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,25 +102,6 @@ class NodeMeta(abc.ABCMeta):
).format(name=f"{cls.__module__}.{cls.__name__}")
fail(msg, pytrace=False)
def _create(cls: type[_T], *k, **kw) -> _T:
try:
return super().__call__(*k, **kw) # type: ignore[no-any-return,misc]
except TypeError:
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
warnings.warn(
PytestDeprecationWarning(
f"{cls} is not using a cooperative constructor and only takes {set(known_kw)}.\n"
"See https://docs.pytest.org/en/stable/deprecations.html"
"#constructors-of-custom-pytest-node-subclasses-should-take-kwargs "
"for more details."
)
)
return super().__call__(*k, **known_kw) # type: ignore[no-any-return,misc]
class Node(abc.ABC, metaclass=NodeMeta):
r"""Base class of :class:`Collector` and :class:`Item`, the components of
@ -138,8 +116,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 +139,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 +183,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 +194,38 @@ 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 _create(cls, *k: object, **kw: object) -> Self:
callit = super(type(cls), NodeMeta).__call__ # type: ignore[misc]
try:
return cast(Self, callit(cls, *k, **kw))
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
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."
)
)
return cast(Self, callit(cls, *k, **known_kw))
@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)
@ -646,22 +655,25 @@ class Directory(FSCollector, abc.ABC):
"""
class Definition(Collector, abc.ABC):
@abc.abstractmethod
def collect(self) -> Iterable[Item]: ...
class Item(Node, abc.ABC):
"""Base class of all test invocation items.
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

View File

@ -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
@ -391,7 +402,7 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC):
return True
return False
def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
def collect(self) -> Iterable[nodes.Definition | nodes.Collector]:
if not getattr(self.obj, "__test__", True):
return []
@ -404,10 +415,10 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC):
# In each class, nodes should be definition ordered.
# __dict__ is definition ordered.
seen: set[str] = set()
dict_values: list[list[nodes.Item | nodes.Collector]] = []
dict_values: list[list[nodes.Definition | nodes.Collector]] = []
ihook = self.ihook
for dic in dicts:
values: list[nodes.Item | nodes.Collector] = []
values: list[nodes.Definition | nodes.Collector] = []
# Note: seems like the dict can change during iteration -
# be careful not to remove the list() without consideration.
for name, obj in list(dic.items()):
@ -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,10 +559,12 @@ 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]:
def collect(self) -> Iterable[nodes.Collector]:
self._register_setup_module_fixture()
self._register_setup_function_fixture()
self.session._fixturemanager.parsefactories(self)
@ -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,41 +728,50 @@ 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]:
def collect(self) -> Iterable[nodes.Collector]:
if not safe_getattr(self.obj, "__test__", True):
return []
if hasinit(self.obj):
@ -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:
@ -1096,13 +1132,15 @@ class Metafunc:
test function is defined.
"""
definition: FunctionDefinition
def __init__(
self,
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 +1561,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 +1616,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 +1625,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
# Each Function gets a fresh class instance.
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 +1655,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
@ -1673,6 +1710,8 @@ class FunctionDefinition(Function):
"""This class is a stop gap solution until we evolve to have actual function
definition nodes and manage to get rid of ``metafunc``."""
parent: Module | Class
def runtest(self) -> None:
raise RuntimeError("function definitions are not supposed to be run as tests")

View File

@ -81,7 +81,7 @@ class UnitTestCase(Class):
# it.
return self.obj("runTest")
def collect(self) -> Iterable[Item | Collector]:
def collect(self) -> Iterable[Item | Collector]: # type: ignore[override]
from unittest import TestLoader
cls = self.obj

View File

@ -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():

View File

@ -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)

View File

@ -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"}

View File

@ -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(