Merge 887f1a1d38
into ac41898755
This commit is contained in:
commit
5f75e824b9
|
@ -210,7 +210,11 @@ class TracebackEntry:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def lineno(self) -> int:
|
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
|
@property
|
||||||
def frame(self) -> Frame:
|
def frame(self) -> Frame:
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# mypy: allow-untyped-defs
|
|
||||||
"""Python version compatibility code."""
|
"""Python version compatibility code."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
@ -12,8 +11,11 @@ from inspect import signature
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
|
from types import FunctionType
|
||||||
|
from types import MethodType
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
from typing import cast
|
||||||
from typing import Final
|
from typing import Final
|
||||||
from typing import NoReturn
|
from typing import NoReturn
|
||||||
|
|
||||||
|
@ -66,7 +68,8 @@ def is_async_function(func: object) -> bool:
|
||||||
return iscoroutinefunction(func) or inspect.isasyncgenfunction(func)
|
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)
|
function = get_real_func(function)
|
||||||
fn = Path(inspect.getfile(function))
|
fn = Path(inspect.getfile(function))
|
||||||
lineno = function.__code__.co_firstlineno
|
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)
|
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)."""
|
"""Return number of arguments used up by mock arguments (if any)."""
|
||||||
patchings = getattr(function, "patchings", None)
|
patchings = getattr(function, "patchings", None)
|
||||||
if not patchings:
|
if not patchings:
|
||||||
|
@ -222,7 +225,7 @@ class _PytestWrapper:
|
||||||
obj: Any
|
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
|
"""Get the real function object of the (possibly) wrapped object by
|
||||||
functools.wraps or functools.partial."""
|
functools.wraps or functools.partial."""
|
||||||
start_obj = obj
|
start_obj = obj
|
||||||
|
@ -249,7 +252,7 @@ def get_real_func(obj):
|
||||||
return 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
|
"""Attempt to obtain the real function object that might be wrapping
|
||||||
``obj``, while at the same time returning a bound method to ``holder`` if
|
``obj``, while at the same time returning a bound method to ``holder`` if
|
||||||
the original object was a bound method."""
|
the original object was a bound method."""
|
||||||
|
@ -263,11 +266,8 @@ def get_real_method(obj, holder):
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
def getimfunc(func):
|
def getimfunc(func: FunctionType | MethodType | Callable[..., Any]) -> FunctionType:
|
||||||
try:
|
return cast(FunctionType, getattr(func, "__func__", func))
|
||||||
return func.__func__
|
|
||||||
except AttributeError:
|
|
||||||
return func
|
|
||||||
|
|
||||||
|
|
||||||
def safe_getattr(object: Any, name: str, default: Any) -> Any:
|
def safe_getattr(object: Any, name: str, default: Any) -> Any:
|
||||||
|
|
|
@ -417,9 +417,10 @@ def _get_continue_on_failure(config: Config) -> bool:
|
||||||
|
|
||||||
|
|
||||||
class DoctestTextfile(Module):
|
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
|
import doctest
|
||||||
|
|
||||||
# Inspired by doctest.testfile; ideally we would use it directly,
|
# 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):
|
class DoctestModule(Module):
|
||||||
def collect(self) -> Iterable[DoctestItem]:
|
def collect(self) -> Iterable[DoctestItem]: # type: ignore[override]
|
||||||
import doctest
|
import doctest
|
||||||
|
|
||||||
class MockAwareDocTestFinder(doctest.DocTestFinder):
|
class MockAwareDocTestFinder(doctest.DocTestFinder):
|
||||||
|
|
|
@ -1100,7 +1100,8 @@ def resolve_fixture_function(
|
||||||
) -> _FixtureFunc[FixtureValue]:
|
) -> _FixtureFunc[FixtureValue]:
|
||||||
"""Get the actual callable that can be called to obtain the fixture
|
"""Get the actual callable that can be called to obtain the fixture
|
||||||
value."""
|
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
|
# The fixture function needs to be bound to the actual
|
||||||
# request.instance so that code working with "fixturedef" behaves
|
# request.instance so that code working with "fixturedef" behaves
|
||||||
# as expected.
|
# as expected.
|
||||||
|
@ -1112,11 +1113,11 @@ def resolve_fixture_function(
|
||||||
instance,
|
instance,
|
||||||
fixturefunc.__self__.__class__,
|
fixturefunc.__self__.__class__,
|
||||||
):
|
):
|
||||||
return fixturefunc
|
return cast(_FixtureFunc[FixtureValue], fixturefunc)
|
||||||
fixturefunc = getimfunc(fixturedef.func)
|
fixturefunc = getimfunc(fixturedef.func)
|
||||||
if fixturefunc != fixturedef.func:
|
if fixturefunc != fixturedef.func:
|
||||||
fixturefunc = fixturefunc.__get__(instance)
|
fixturefunc = fixturefunc.__get__(instance)
|
||||||
return fixturefunc
|
return cast(_FixtureFunc[FixtureValue], fixturefunc)
|
||||||
|
|
||||||
|
|
||||||
def pytest_fixture_setup(
|
def pytest_fixture_setup(
|
||||||
|
|
|
@ -535,6 +535,7 @@ class Session(nodes.Collector):
|
||||||
``Session`` collects the initial paths given as arguments to pytest.
|
``Session`` collects the initial paths given as arguments to pytest.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
parent: None
|
||||||
Interrupted = Interrupted
|
Interrupted = Interrupted
|
||||||
Failed = Failed
|
Failed = Failed
|
||||||
# Set on the session by runner.pytest_sessionstart.
|
# Set on the session by runner.pytest_sessionstart.
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# mypy: allow-untyped-defs
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
@ -20,6 +19,7 @@ from typing import TypeVar
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import pluggy
|
import pluggy
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
from _pytest._code import getfslineno
|
from _pytest._code import getfslineno
|
||||||
|
@ -54,9 +54,6 @@ SEP = "/"
|
||||||
tracebackcutdir = Path(_pytest.__file__).parent
|
tracebackcutdir = Path(_pytest.__file__).parent
|
||||||
|
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
|
||||||
|
|
||||||
|
|
||||||
def _imply_path(
|
def _imply_path(
|
||||||
node_type: type[Node],
|
node_type: type[Node],
|
||||||
path: Path | None,
|
path: Path | None,
|
||||||
|
@ -96,7 +93,7 @@ class NodeMeta(abc.ABCMeta):
|
||||||
progress on detangling the :class:`Node` classes.
|
progress on detangling the :class:`Node` classes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __call__(cls, *k, **kw) -> NoReturn:
|
def __call__(cls, *k: object, **kw: object) -> NoReturn:
|
||||||
msg = (
|
msg = (
|
||||||
"Direct construction of {name} has been deprecated, please use {name}.from_parent.\n"
|
"Direct construction of {name} has been deprecated, please use {name}.from_parent.\n"
|
||||||
"See "
|
"See "
|
||||||
|
@ -105,25 +102,6 @@ class NodeMeta(abc.ABCMeta):
|
||||||
).format(name=f"{cls.__module__}.{cls.__name__}")
|
).format(name=f"{cls.__module__}.{cls.__name__}")
|
||||||
fail(msg, pytrace=False)
|
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):
|
class Node(abc.ABC, metaclass=NodeMeta):
|
||||||
r"""Base class of :class:`Collector` and :class:`Item`, the components of
|
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
|
#: for methods not migrated to ``pathlib.Path`` yet, such as
|
||||||
#: :meth:`Item.reportinfo <pytest.Item.reportinfo>`. Will be deprecated in
|
#: :meth:`Item.reportinfo <pytest.Item.reportinfo>`. Will be deprecated in
|
||||||
#: a future release, prefer using :attr:`path` instead.
|
#: a future release, prefer using :attr:`path` instead.
|
||||||
|
name: str
|
||||||
|
parent: Node | None
|
||||||
|
config: Config
|
||||||
|
session: Session
|
||||||
fspath: LEGACY_PATH
|
fspath: LEGACY_PATH
|
||||||
|
|
||||||
|
_nodeid: str
|
||||||
# Use __slots__ to make attribute access faster.
|
# Use __slots__ to make attribute access faster.
|
||||||
# Note that __dict__ is still available.
|
# Note that __dict__ is still available.
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
|
@ -156,7 +139,7 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
parent: Node | None = None,
|
parent: Node | None,
|
||||||
config: Config | None = None,
|
config: Config | None = None,
|
||||||
session: Session | None = None,
|
session: Session | None = None,
|
||||||
fspath: LEGACY_PATH | 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.
|
#: Allow adding of extra keywords to use for matching.
|
||||||
self.extra_keyword_matches: set[str] = set()
|
self.extra_keyword_matches: set[str] = set()
|
||||||
|
|
||||||
if nodeid is not None:
|
self._nodeid = self._make_nodeid(
|
||||||
assert "::()" not in nodeid
|
name=self.name, parent=self.parent, given=nodeid
|
||||||
self._nodeid = nodeid
|
)
|
||||||
else:
|
|
||||||
if not self.parent:
|
|
||||||
raise TypeError("nodeid or parent must be provided")
|
|
||||||
self._nodeid = self.parent.nodeid + "::" + self.name
|
|
||||||
|
|
||||||
#: A place where plugins can store information on the node for their
|
#: A place where plugins can store information on the node for their
|
||||||
#: own use.
|
#: own use.
|
||||||
|
@ -215,7 +194,38 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
||||||
self._store = self.stash
|
self._store = self.stash
|
||||||
|
|
||||||
@classmethod
|
@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.
|
"""Public constructor for Nodes.
|
||||||
|
|
||||||
This indirection got introduced in order to enable removing
|
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)
|
return self.session.gethookproxy(self.path)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
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:
|
def warn(self, warning: Warning) -> None:
|
||||||
"""Issue a warning for this Node.
|
"""Issue a warning for this Node.
|
||||||
|
@ -598,7 +608,6 @@ class FSCollector(Collector, abc.ABC):
|
||||||
|
|
||||||
if nodeid and os.sep != SEP:
|
if nodeid and os.sep != SEP:
|
||||||
nodeid = nodeid.replace(os.sep, SEP)
|
nodeid = nodeid.replace(os.sep, SEP)
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=name,
|
name=name,
|
||||||
parent=parent,
|
parent=parent,
|
||||||
|
@ -611,11 +620,11 @@ class FSCollector(Collector, abc.ABC):
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_parent(
|
def from_parent(
|
||||||
cls,
|
cls,
|
||||||
parent,
|
parent: Node,
|
||||||
*,
|
*,
|
||||||
fspath: LEGACY_PATH | None = None,
|
fspath: LEGACY_PATH | None = None,
|
||||||
path: Path | None = None,
|
path: Path | None = None,
|
||||||
**kw,
|
**kw: Any,
|
||||||
) -> Self:
|
) -> Self:
|
||||||
"""The public constructor."""
|
"""The public constructor."""
|
||||||
return super().from_parent(parent=parent, fspath=fspath, path=path, **kw)
|
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):
|
class Item(Node, abc.ABC):
|
||||||
"""Base class of all test invocation items.
|
"""Base class of all test invocation items.
|
||||||
|
|
||||||
Note that for a single function there might be multiple test invocation items.
|
Note that for a single function there might be multiple test invocation items.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
nextitem = None
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name,
|
name: str,
|
||||||
parent=None,
|
parent: Node | None = None,
|
||||||
config: Config | None = None,
|
config: Config | None = None,
|
||||||
session: Session | None = None,
|
session: Session | None = None,
|
||||||
nodeid: str | None = None,
|
nodeid: str | None = None,
|
||||||
**kw,
|
**kw: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
# The first two arguments are intentionally passed positionally,
|
# The first two arguments are intentionally passed positionally,
|
||||||
# to keep plugins who define a node type which inherits from
|
# 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."""
|
"""Python test discovery, setup and run of test functions."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
@ -17,6 +16,7 @@ from pathlib import Path
|
||||||
import types
|
import types
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
from typing import cast
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import final
|
from typing import final
|
||||||
from typing import Generator
|
from typing import Generator
|
||||||
|
@ -27,6 +27,8 @@ from typing import Mapping
|
||||||
from typing import Pattern
|
from typing import Pattern
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
from typing import TypeVar
|
||||||
|
from typing import Union
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import _pytest
|
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)
|
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)
|
return Module.from_parent(parent, path=module_path)
|
||||||
|
|
||||||
|
|
||||||
|
@ -242,6 +244,7 @@ def pytest_pycollect_makeitem(
|
||||||
res.warn(PytestCollectionWarning(reason))
|
res.warn(PytestCollectionWarning(reason))
|
||||||
return res
|
return res
|
||||||
else:
|
else:
|
||||||
|
assert isinstance(obj, (types.FunctionType))
|
||||||
return list(collector._genfunctions(name, obj))
|
return list(collector._genfunctions(name, obj))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -252,22 +255,26 @@ class PyobjMixin(nodes.Node):
|
||||||
as its intended to always mix in before a node
|
as its intended to always mix in before a node
|
||||||
its position in the mro is unaffected"""
|
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
|
_ALLOW_MARKERS = True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def module(self):
|
def module(self) -> types.ModuleType | None:
|
||||||
"""Python module object this node was collected from (can be None)."""
|
"""Python module object this node was collected from (can be None)."""
|
||||||
node = self.getparent(Module)
|
node = self.getparent(Module)
|
||||||
return node.obj if node is not None else None
|
return node.obj if node is not None else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cls(self):
|
def cls(self) -> type | None:
|
||||||
"""Python class object this node was collected from (can be None)."""
|
"""Python class object this node was collected from (can be None)."""
|
||||||
node = self.getparent(Class)
|
node = self.getparent(Class)
|
||||||
return node.obj if node is not None else None
|
return node.obj if node is not None else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def instance(self):
|
def instance(self) -> object | None:
|
||||||
"""Python instance object the function is bound to.
|
"""Python instance object the function is bound to.
|
||||||
|
|
||||||
Returns None if not a test method, e.g. for a standalone test function,
|
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.
|
# Overridden by Function.
|
||||||
return None
|
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
|
@property
|
||||||
def obj(self):
|
def obj(self) -> Any:
|
||||||
"""Underlying Python object."""
|
"""Underlying Python object."""
|
||||||
obj = getattr(self, "_obj", None)
|
obj = getattr(self, "_obj", None)
|
||||||
if obj is None:
|
if obj is None:
|
||||||
self._obj = obj = self._getobj()
|
obj = self._getobj()
|
||||||
# XXX evil hack
|
self._assign_obj_with_markers(obj)
|
||||||
# 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)
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
@obj.setter
|
@obj.setter
|
||||||
def obj(self, value):
|
def obj(self, value: Any) -> None:
|
||||||
self._obj = value
|
self._obj = value
|
||||||
|
|
||||||
def _getobj(self):
|
def _getobj(self) -> Any:
|
||||||
"""Get the underlying Python object. May be overwritten by subclasses."""
|
"""Get the underlying Python object. May be overwritten by subclasses."""
|
||||||
# TODO: Improve the type of `parent` such that assert/ignore aren't needed.
|
# TODO: Improve the type of `parent` such that assert/ignore aren't needed.
|
||||||
assert self.parent is not None
|
assert self.parent is not None
|
||||||
|
@ -391,7 +402,7 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC):
|
||||||
return True
|
return True
|
||||||
return False
|
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):
|
if not getattr(self.obj, "__test__", True):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@ -404,10 +415,10 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC):
|
||||||
# In each class, nodes should be definition ordered.
|
# In each class, nodes should be definition ordered.
|
||||||
# __dict__ is definition ordered.
|
# __dict__ is definition ordered.
|
||||||
seen: set[str] = set()
|
seen: set[str] = set()
|
||||||
dict_values: list[list[nodes.Item | nodes.Collector]] = []
|
dict_values: list[list[nodes.Definition | nodes.Collector]] = []
|
||||||
ihook = self.ihook
|
ihook = self.ihook
|
||||||
for dic in dicts:
|
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 -
|
# Note: seems like the dict can change during iteration -
|
||||||
# be careful not to remove the list() without consideration.
|
# be careful not to remove the list() without consideration.
|
||||||
for name, obj in list(dic.items()):
|
for name, obj in list(dic.items()):
|
||||||
|
@ -434,14 +445,20 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC):
|
||||||
result.extend(values)
|
result.extend(values)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _genfunctions(self, name: str, funcobj) -> Iterator[Function]:
|
def _genfunctions(
|
||||||
|
self, name: str, funcobj: types.FunctionType
|
||||||
|
) -> Iterator[Function]:
|
||||||
modulecol = self.getparent(Module)
|
modulecol = self.getparent(Module)
|
||||||
assert modulecol is not None
|
assert modulecol is not None
|
||||||
module = modulecol.obj
|
module = modulecol.obj
|
||||||
clscol = self.getparent(Class)
|
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
|
fixtureinfo = definition._fixtureinfo
|
||||||
|
|
||||||
# pytest_generate_tests impls call metafunc.parametrize() which fills
|
# 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))
|
self.ihook.pytest_generate_tests.call_extra(methods, dict(metafunc=metafunc))
|
||||||
|
|
||||||
if not metafunc._calls:
|
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:
|
else:
|
||||||
# Direct parametrizations taking place in module/class-specific
|
# Direct parametrizations taking place in module/class-specific
|
||||||
# `metafunc.parametrize` calls may have shadowed some fixtures, so make sure
|
# `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:
|
for callspec in metafunc._calls:
|
||||||
subname = f"{name}[{callspec.id}]"
|
subname = f"{name}[{callspec.id}]"
|
||||||
yield Function.from_parent(
|
yield Function.from_parent(
|
||||||
self,
|
self, # type: ignore[arg-type]
|
||||||
name=subname,
|
name=subname,
|
||||||
callspec=callspec,
|
callspec=callspec,
|
||||||
fixtureinfo=fixtureinfo,
|
fixtureinfo=fixtureinfo,
|
||||||
|
@ -486,7 +503,7 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC):
|
||||||
def importtestmodule(
|
def importtestmodule(
|
||||||
path: Path,
|
path: Path,
|
||||||
config: Config,
|
config: Config,
|
||||||
):
|
) -> types.ModuleType:
|
||||||
# We assume we are only called once per module.
|
# We assume we are only called once per module.
|
||||||
importmode = config.getoption("--import-mode")
|
importmode = config.getoption("--import-mode")
|
||||||
try:
|
try:
|
||||||
|
@ -542,10 +559,12 @@ def importtestmodule(
|
||||||
class Module(nodes.File, PyCollector):
|
class Module(nodes.File, PyCollector):
|
||||||
"""Collector for test classes and functions in a Python module."""
|
"""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)
|
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_module_fixture()
|
||||||
self._register_setup_function_fixture()
|
self._register_setup_function_fixture()
|
||||||
self.session._fixturemanager.parsefactories(self)
|
self.session._fixturemanager.parsefactories(self)
|
||||||
|
@ -568,7 +587,9 @@ class Module(nodes.File, PyCollector):
|
||||||
if setup_module is None and teardown_module is None:
|
if setup_module is None and teardown_module is None:
|
||||||
return
|
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
|
module = request.module
|
||||||
if setup_module is not None:
|
if setup_module is not None:
|
||||||
_call_with_optional_argument(setup_module, module)
|
_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:
|
if setup_function is None and teardown_function is None:
|
||||||
return
|
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:
|
if request.instance is not None:
|
||||||
# in this case we are bound to an instance, so we need to let
|
# in this case we are bound to an instance, so we need to let
|
||||||
# setup_method handle this
|
# setup_method handle this
|
||||||
|
@ -642,9 +665,9 @@ class Package(nodes.Directory):
|
||||||
fspath: LEGACY_PATH | None,
|
fspath: LEGACY_PATH | None,
|
||||||
parent: nodes.Collector,
|
parent: nodes.Collector,
|
||||||
# NOTE: following args are unused:
|
# NOTE: following args are unused:
|
||||||
config=None,
|
config: Config | None = None,
|
||||||
session=None,
|
session: Session | None = None,
|
||||||
nodeid=None,
|
nodeid: str | None = None,
|
||||||
path: Path | None = None,
|
path: Path | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
# NOTE: Could be just the following, but kept as-is for compat.
|
# NOTE: Could be just the following, but kept as-is for compat.
|
||||||
|
@ -705,41 +728,50 @@ class Package(nodes.Directory):
|
||||||
yield from cols
|
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
|
"""Call the given function with the given argument if func accepts one argument, otherwise
|
||||||
calls func without arguments."""
|
calls func without arguments."""
|
||||||
arg_count = func.__code__.co_argcount
|
arg_count = func.__code__.co_argcount
|
||||||
if inspect.ismethod(func):
|
if inspect.ismethod(func):
|
||||||
arg_count -= 1
|
arg_count -= 1
|
||||||
if arg_count:
|
if arg_count:
|
||||||
func(arg)
|
func(arg) # type: ignore[call-arg]
|
||||||
else:
|
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
|
"""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.
|
xunit-style function, but only if not marked as a fixture to avoid calling it twice.
|
||||||
"""
|
"""
|
||||||
for name in names:
|
for name in names:
|
||||||
meth: object | None = getattr(obj, name, None)
|
meth: object | None = getattr(obj, name, None)
|
||||||
if meth is not None and fixtures.getfixturemarker(meth) is None:
|
if meth is not None and fixtures.getfixturemarker(meth) is None:
|
||||||
return meth
|
return cast(Union[types.FunctionType, types.MethodType], meth)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class Class(PyCollector):
|
class Class(PyCollector):
|
||||||
"""Collector for test methods (and nested classes) in a Python class."""
|
"""Collector for test methods (and nested classes) in a Python class."""
|
||||||
|
|
||||||
|
obj: type
|
||||||
|
|
||||||
@classmethod
|
@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."""
|
"""The public constructor."""
|
||||||
return super().from_parent(name=name, parent=parent, **kw)
|
return super().from_parent(name=name, parent=parent, **kw)
|
||||||
|
|
||||||
def newinstance(self):
|
def newinstance(self) -> Any:
|
||||||
return self.obj()
|
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):
|
if not safe_getattr(self.obj, "__test__", True):
|
||||||
return []
|
return []
|
||||||
if hasinit(self.obj):
|
if hasinit(self.obj):
|
||||||
|
@ -780,7 +812,9 @@ class Class(PyCollector):
|
||||||
if setup_class is None and teardown_class is None:
|
if setup_class is None and teardown_class is None:
|
||||||
return
|
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
|
cls = request.cls
|
||||||
if setup_class is not None:
|
if setup_class is not None:
|
||||||
func = getimfunc(setup_class)
|
func = getimfunc(setup_class)
|
||||||
|
@ -813,7 +847,9 @@ class Class(PyCollector):
|
||||||
if setup_method is None and teardown_method is None:
|
if setup_method is None and teardown_method is None:
|
||||||
return
|
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
|
instance = request.instance
|
||||||
method = request.function
|
method = request.function
|
||||||
if setup_method is not None:
|
if setup_method is not None:
|
||||||
|
@ -1096,13 +1132,15 @@ class Metafunc:
|
||||||
test function is defined.
|
test function is defined.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
definition: FunctionDefinition
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
definition: FunctionDefinition,
|
definition: FunctionDefinition,
|
||||||
fixtureinfo: fixtures.FuncFixtureInfo,
|
fixtureinfo: fixtures.FuncFixtureInfo,
|
||||||
config: Config,
|
config: Config,
|
||||||
cls=None,
|
cls: type | None = None,
|
||||||
module=None,
|
module: types.ModuleType | None = None,
|
||||||
*,
|
*,
|
||||||
_ispytest: bool = False,
|
_ispytest: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -1523,13 +1561,15 @@ class Function(PyobjMixin, nodes.Item):
|
||||||
# Disable since functions handle it themselves.
|
# Disable since functions handle it themselves.
|
||||||
_ALLOW_MARKERS = False
|
_ALLOW_MARKERS = False
|
||||||
|
|
||||||
|
obj: Callable[..., object]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
parent,
|
parent: PyCollector | Module | Class,
|
||||||
config: Config | None = None,
|
config: Config | None = None,
|
||||||
callspec: CallSpec2 | None = None,
|
callspec: CallSpec2 | None = None,
|
||||||
callobj=NOTSET,
|
callobj: Any = NOTSET,
|
||||||
keywords: Mapping[str, Any] | None = None,
|
keywords: Mapping[str, Any] | None = None,
|
||||||
session: Session | None = None,
|
session: Session | None = None,
|
||||||
fixtureinfo: FuncFixtureInfo | None = None,
|
fixtureinfo: FuncFixtureInfo | None = None,
|
||||||
|
@ -1576,7 +1616,7 @@ class Function(PyobjMixin, nodes.Item):
|
||||||
|
|
||||||
# todo: determine sound type limitations
|
# todo: determine sound type limitations
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_parent(cls, parent, **kw) -> Self:
|
def from_parent(cls, parent: Module | Class, **kw: Any) -> Self: # type: ignore[override]
|
||||||
"""The public constructor."""
|
"""The public constructor."""
|
||||||
return super().from_parent(parent=parent, **kw)
|
return super().from_parent(parent=parent, **kw)
|
||||||
|
|
||||||
|
@ -1585,30 +1625,27 @@ class Function(PyobjMixin, nodes.Item):
|
||||||
self._request = fixtures.TopRequest(self, _ispytest=True)
|
self._request = fixtures.TopRequest(self, _ispytest=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def function(self):
|
def function(self) -> types.FunctionType:
|
||||||
"""Underlying python 'function' object."""
|
"""Underlying python 'function' object."""
|
||||||
return getimfunc(self.obj)
|
return getimfunc(self.obj)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def instance(self):
|
def instance(self) -> Any | None:
|
||||||
try:
|
try:
|
||||||
return self._instance
|
return self._instance
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
if isinstance(self.parent, Class):
|
# Each Function gets a fresh class instance.
|
||||||
# Each Function gets a fresh class instance.
|
self._instance = self._getinstance()
|
||||||
self._instance = self._getinstance()
|
|
||||||
else:
|
|
||||||
self._instance = None
|
|
||||||
return self._instance
|
return self._instance
|
||||||
|
|
||||||
def _getinstance(self):
|
def _getinstance(self) -> Any | None:
|
||||||
if isinstance(self.parent, Class):
|
if isinstance(self.parent, Class):
|
||||||
# Each Function gets a fresh class instance.
|
# Each Function gets a fresh class instance.
|
||||||
return self.parent.newinstance()
|
return self.parent.newinstance()
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _getobj(self):
|
def _getobj(self) -> object:
|
||||||
instance = self.instance
|
instance = self.instance
|
||||||
if instance is not None:
|
if instance is not None:
|
||||||
parent_obj = instance
|
parent_obj = instance
|
||||||
|
@ -1618,7 +1655,7 @@ class Function(PyobjMixin, nodes.Item):
|
||||||
return getattr(parent_obj, self.originalname)
|
return getattr(parent_obj, self.originalname)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _pyfuncitem(self):
|
def _pyfuncitem(self) -> Self:
|
||||||
"""(compatonly) for code expecting pytest-2.2 style request objects."""
|
"""(compatonly) for code expecting pytest-2.2 style request objects."""
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@ -1673,6 +1710,8 @@ class FunctionDefinition(Function):
|
||||||
"""This class is a stop gap solution until we evolve to have actual function
|
"""This class is a stop gap solution until we evolve to have actual function
|
||||||
definition nodes and manage to get rid of ``metafunc``."""
|
definition nodes and manage to get rid of ``metafunc``."""
|
||||||
|
|
||||||
|
parent: Module | Class
|
||||||
|
|
||||||
def runtest(self) -> None:
|
def runtest(self) -> None:
|
||||||
raise RuntimeError("function definitions are not supposed to be run as tests")
|
raise RuntimeError("function definitions are not supposed to be run as tests")
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,7 @@ class UnitTestCase(Class):
|
||||||
# it.
|
# it.
|
||||||
return self.obj("runTest")
|
return self.obj("runTest")
|
||||||
|
|
||||||
def collect(self) -> Iterable[Item | Collector]:
|
def collect(self) -> Iterable[Item | Collector]: # type: ignore[override]
|
||||||
from unittest import TestLoader
|
from unittest import TestLoader
|
||||||
|
|
||||||
cls = self.obj
|
cls = self.obj
|
||||||
|
|
|
@ -325,7 +325,8 @@ class TestFunction:
|
||||||
session = Session.from_config(config)
|
session = Session.from_config(config)
|
||||||
session._fixturemanager = FixtureManager(session)
|
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 test_function_equality(self, pytester: Pytester) -> None:
|
||||||
def func1():
|
def func1():
|
||||||
|
|
|
@ -7,6 +7,7 @@ import re
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from typing import Callable
|
||||||
from typing import cast
|
from typing import cast
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
|
@ -49,7 +50,7 @@ class TestMetafunc:
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class DefinitionMock(python.FunctionDefinition):
|
class DefinitionMock(python.FunctionDefinition):
|
||||||
_nodeid: str
|
_nodeid: str
|
||||||
obj: object
|
obj: Callable[..., Any]
|
||||||
|
|
||||||
names = getfuncargnames(func)
|
names = getfuncargnames(func)
|
||||||
fixtureinfo: Any = FuncFixtureInfoMock(names)
|
fixtureinfo: Any = FuncFixtureInfoMock(names)
|
||||||
|
|
|
@ -634,6 +634,14 @@ class TestFunctional:
|
||||||
has_own, has_inherited = items
|
has_own, has_inherited = items
|
||||||
has_own_marker = has_own.get_closest_marker("c")
|
has_own_marker = has_own.get_closest_marker("c")
|
||||||
has_inherited_marker = has_inherited.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_own_marker is not None
|
||||||
assert has_inherited_marker is not None
|
assert has_inherited_marker is not None
|
||||||
assert has_own_marker.kwargs == {"location": "function"}
|
assert has_own_marker.kwargs == {"location": "function"}
|
||||||
|
|
|
@ -31,7 +31,7 @@ def test_node_direct_construction_deprecated() -> None:
|
||||||
" for more details."
|
" 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(
|
def test_subclassing_both_item_and_collector_deprecated(
|
||||||
|
|
Loading…
Reference in New Issue