updates
This commit is contained in:
@@ -1,52 +1,47 @@
|
||||
# mypy: allow-untyped-defs
|
||||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Iterable
|
||||
from collections.abc import Iterator
|
||||
from collections.abc import MutableMapping
|
||||
from functools import cached_property
|
||||
from functools import lru_cache
|
||||
import os
|
||||
import pathlib
|
||||
import warnings
|
||||
from inspect import signature
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
from typing import NoReturn
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import MutableMapping
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
import warnings
|
||||
|
||||
import pluggy
|
||||
from typing import Union
|
||||
|
||||
import _pytest._code
|
||||
from _pytest._code import getfslineno
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
from _pytest._code.code import TerminalRepr
|
||||
from _pytest._code.code import Traceback
|
||||
from _pytest._code.code import TracebackStyle
|
||||
from _pytest.compat import cached_property
|
||||
from _pytest.compat import LEGACY_PATH
|
||||
from _pytest.compat import signature
|
||||
from _pytest.config import Config
|
||||
from _pytest.config import ConftestImportFailure
|
||||
from _pytest.config.compat import _check_path
|
||||
from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH
|
||||
from _pytest.deprecated import NODE_CTOR_FSPATH_ARG
|
||||
from _pytest.mark.structures import Mark
|
||||
from _pytest.mark.structures import MarkDecorator
|
||||
from _pytest.mark.structures import NodeKeywords
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.pathlib import absolutepath
|
||||
from _pytest.pathlib import commonpath
|
||||
from _pytest.stash import Stash
|
||||
from _pytest.warning_types import PytestWarning
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
# Imported here due to circular import.
|
||||
from _pytest.main import Session
|
||||
from _pytest._code.code import _TracebackStyle
|
||||
|
||||
|
||||
SEP = "/"
|
||||
@@ -54,13 +49,63 @@ SEP = "/"
|
||||
tracebackcutdir = Path(_pytest.__file__).parent
|
||||
|
||||
|
||||
_T = TypeVar("_T")
|
||||
def iterparentnodeids(nodeid: str) -> Iterator[str]:
|
||||
"""Return the parent node IDs of a given node ID, inclusive.
|
||||
|
||||
For the node ID
|
||||
|
||||
"testing/code/test_excinfo.py::TestFormattedExcinfo::test_repr_source"
|
||||
|
||||
the result would be
|
||||
|
||||
""
|
||||
"testing"
|
||||
"testing/code"
|
||||
"testing/code/test_excinfo.py"
|
||||
"testing/code/test_excinfo.py::TestFormattedExcinfo"
|
||||
"testing/code/test_excinfo.py::TestFormattedExcinfo::test_repr_source"
|
||||
|
||||
Note that / components are only considered until the first ::.
|
||||
"""
|
||||
pos = 0
|
||||
first_colons: Optional[int] = nodeid.find("::")
|
||||
if first_colons == -1:
|
||||
first_colons = None
|
||||
# The root Session node - always present.
|
||||
yield ""
|
||||
# Eagerly consume SEP parts until first colons.
|
||||
while True:
|
||||
at = nodeid.find(SEP, pos, first_colons)
|
||||
if at == -1:
|
||||
break
|
||||
if at > 0:
|
||||
yield nodeid[:at]
|
||||
pos = at + len(SEP)
|
||||
# Eagerly consume :: parts.
|
||||
while True:
|
||||
at = nodeid.find("::", pos)
|
||||
if at == -1:
|
||||
break
|
||||
if at > 0:
|
||||
yield nodeid[:at]
|
||||
pos = at + len("::")
|
||||
# The node ID itself.
|
||||
if nodeid:
|
||||
yield nodeid
|
||||
|
||||
|
||||
def _check_path(path: Path, fspath: LEGACY_PATH) -> None:
|
||||
if Path(fspath) != path:
|
||||
raise ValueError(
|
||||
f"Path({fspath!r}) != {path!r}\n"
|
||||
"if both path and fspath are given they need to be equal"
|
||||
)
|
||||
|
||||
|
||||
def _imply_path(
|
||||
node_type: type[Node],
|
||||
path: Path | None,
|
||||
fspath: LEGACY_PATH | None,
|
||||
node_type: Type["Node"],
|
||||
path: Optional[Path],
|
||||
fspath: Optional[LEGACY_PATH],
|
||||
) -> Path:
|
||||
if fspath is not None:
|
||||
warnings.warn(
|
||||
@@ -81,51 +126,37 @@ def _imply_path(
|
||||
_NodeType = TypeVar("_NodeType", bound="Node")
|
||||
|
||||
|
||||
class NodeMeta(abc.ABCMeta):
|
||||
"""Metaclass used by :class:`Node` to enforce that direct construction raises
|
||||
:class:`Failed`.
|
||||
|
||||
This behaviour supports the indirection introduced with :meth:`Node.from_parent`,
|
||||
the named constructor to be used instead of direct construction. The design
|
||||
decision to enforce indirection with :class:`NodeMeta` was made as a
|
||||
temporary aid for refactoring the collection tree, which was diagnosed to
|
||||
have :class:`Node` objects whose creational patterns were overly entangled.
|
||||
Once the refactoring is complete, this metaclass can be removed.
|
||||
|
||||
See https://github.com/pytest-dev/pytest/projects/3 for an overview of the
|
||||
progress on detangling the :class:`Node` classes.
|
||||
"""
|
||||
|
||||
def __call__(cls, *k, **kw) -> NoReturn:
|
||||
class NodeMeta(type):
|
||||
def __call__(self, *k, **kw):
|
||||
msg = (
|
||||
"Direct construction of {name} has been deprecated, please use {name}.from_parent.\n"
|
||||
"See "
|
||||
"https://docs.pytest.org/en/stable/deprecations.html#node-construction-changed-to-node-from-parent"
|
||||
" for more details."
|
||||
).format(name=f"{cls.__module__}.{cls.__name__}")
|
||||
).format(name=f"{self.__module__}.{self.__name__}")
|
||||
fail(msg, pytrace=False)
|
||||
|
||||
def _create(cls: type[_T], *k, **kw) -> _T:
|
||||
def _create(self, *k, **kw):
|
||||
try:
|
||||
return super().__call__(*k, **kw) # type: ignore[no-any-return,misc]
|
||||
return super().__call__(*k, **kw)
|
||||
except TypeError:
|
||||
sig = signature(getattr(cls, "__init__"))
|
||||
sig = signature(getattr(self, "__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"{self} 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]
|
||||
return super().__call__(*k, **known_kw)
|
||||
|
||||
|
||||
class Node(abc.ABC, metaclass=NodeMeta):
|
||||
class Node(metaclass=NodeMeta):
|
||||
r"""Base class of :class:`Collector` and :class:`Item`, the components of
|
||||
the test collection tree.
|
||||
|
||||
@@ -136,32 +167,32 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
||||
# Implemented in the legacypath plugin.
|
||||
#: A ``LEGACY_PATH`` copy of the :attr:`path` attribute. Intended for usage
|
||||
#: 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.
|
||||
#: :meth:`Item.reportinfo`. Will be deprecated in a future release, prefer
|
||||
#: using :attr:`path` instead.
|
||||
fspath: LEGACY_PATH
|
||||
|
||||
# Use __slots__ to make attribute access faster.
|
||||
# Note that __dict__ is still available.
|
||||
__slots__ = (
|
||||
"__dict__",
|
||||
"_nodeid",
|
||||
"_store",
|
||||
"config",
|
||||
"name",
|
||||
"parent",
|
||||
"path",
|
||||
"config",
|
||||
"session",
|
||||
"path",
|
||||
"_nodeid",
|
||||
"_store",
|
||||
"__dict__",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
parent: Node | None = None,
|
||||
config: Config | None = None,
|
||||
session: Session | None = None,
|
||||
fspath: LEGACY_PATH | None = None,
|
||||
path: Path | None = None,
|
||||
nodeid: str | None = None,
|
||||
parent: "Optional[Node]" = None,
|
||||
config: Optional[Config] = None,
|
||||
session: "Optional[Session]" = None,
|
||||
fspath: Optional[LEGACY_PATH] = None,
|
||||
path: Optional[Path] = None,
|
||||
nodeid: Optional[str] = None,
|
||||
) -> None:
|
||||
#: A unique name within the scope of the parent node.
|
||||
self.name: str = name
|
||||
@@ -188,17 +219,17 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
||||
if path is None and fspath is None:
|
||||
path = getattr(parent, "path", None)
|
||||
#: Filesystem path where this node was collected from (can be None).
|
||||
self.path: pathlib.Path = _imply_path(type(self), path, fspath=fspath)
|
||||
self.path: Path = _imply_path(type(self), path, fspath=fspath)
|
||||
|
||||
# The explicit annotation is to avoid publicly exposing NodeKeywords.
|
||||
#: Keywords/markers collected from all scopes.
|
||||
self.keywords: MutableMapping[str, Any] = NodeKeywords(self)
|
||||
|
||||
#: The marker objects belonging to this node.
|
||||
self.own_markers: list[Mark] = []
|
||||
self.own_markers: List[Mark] = []
|
||||
|
||||
#: 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:
|
||||
assert "::()" not in nodeid
|
||||
@@ -215,7 +246,7 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
||||
self._store = self.stash
|
||||
|
||||
@classmethod
|
||||
def from_parent(cls, parent: Node, **kw) -> Self:
|
||||
def from_parent(cls, parent: "Node", **kw):
|
||||
"""Public constructor for Nodes.
|
||||
|
||||
This indirection got introduced in order to enable removing
|
||||
@@ -233,7 +264,7 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
||||
return cls._create(parent=parent, **kw)
|
||||
|
||||
@property
|
||||
def ihook(self) -> pluggy.HookRelay:
|
||||
def ihook(self):
|
||||
"""fspath-sensitive hook proxy used to call pytest hooks."""
|
||||
return self.session.gethookproxy(self.path)
|
||||
|
||||
@@ -264,7 +295,9 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
||||
# enforce type checks here to avoid getting a generic type error later otherwise.
|
||||
if not isinstance(warning, Warning):
|
||||
raise ValueError(
|
||||
f"warning must be an instance of Warning or subclass, got {warning!r}"
|
||||
"warning must be an instance of Warning or subclass, got {!r}".format(
|
||||
warning
|
||||
)
|
||||
)
|
||||
path, lineno = get_fslocation_from_item(self)
|
||||
assert lineno is not None
|
||||
@@ -291,29 +324,23 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
||||
def teardown(self) -> None:
|
||||
pass
|
||||
|
||||
def iter_parents(self) -> Iterator[Node]:
|
||||
"""Iterate over all parent collectors starting from and including self
|
||||
up to the root of the collection tree.
|
||||
def listchain(self) -> List["Node"]:
|
||||
"""Return list of all parent collectors up to self, starting from
|
||||
the root of collection tree.
|
||||
|
||||
.. versionadded:: 8.1
|
||||
:returns: The nodes.
|
||||
"""
|
||||
parent: Node | None = self
|
||||
while parent is not None:
|
||||
yield parent
|
||||
parent = parent.parent
|
||||
|
||||
def listchain(self) -> list[Node]:
|
||||
"""Return a list of all parent collectors starting from the root of the
|
||||
collection tree down to and including self."""
|
||||
chain = []
|
||||
item: Node | None = self
|
||||
item: Optional[Node] = self
|
||||
while item is not None:
|
||||
chain.append(item)
|
||||
item = item.parent
|
||||
chain.reverse()
|
||||
return chain
|
||||
|
||||
def add_marker(self, marker: str | MarkDecorator, append: bool = True) -> None:
|
||||
def add_marker(
|
||||
self, marker: Union[str, MarkDecorator], append: bool = True
|
||||
) -> None:
|
||||
"""Dynamically add a marker object to the node.
|
||||
|
||||
:param marker:
|
||||
@@ -335,7 +362,7 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
||||
else:
|
||||
self.own_markers.insert(0, marker_.mark)
|
||||
|
||||
def iter_markers(self, name: str | None = None) -> Iterator[Mark]:
|
||||
def iter_markers(self, name: Optional[str] = None) -> Iterator[Mark]:
|
||||
"""Iterate over all markers of the node.
|
||||
|
||||
:param name: If given, filter the results by the name attribute.
|
||||
@@ -344,25 +371,29 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
||||
return (x[1] for x in self.iter_markers_with_node(name=name))
|
||||
|
||||
def iter_markers_with_node(
|
||||
self, name: str | None = None
|
||||
) -> Iterator[tuple[Node, Mark]]:
|
||||
self, name: Optional[str] = None
|
||||
) -> Iterator[Tuple["Node", Mark]]:
|
||||
"""Iterate over all markers of the node.
|
||||
|
||||
:param name: If given, filter the results by the name attribute.
|
||||
:returns: An iterator of (node, mark) tuples.
|
||||
"""
|
||||
for node in self.iter_parents():
|
||||
for node in reversed(self.listchain()):
|
||||
for mark in node.own_markers:
|
||||
if name is None or getattr(mark, "name", None) == name:
|
||||
yield node, mark
|
||||
|
||||
@overload
|
||||
def get_closest_marker(self, name: str) -> Mark | None: ...
|
||||
def get_closest_marker(self, name: str) -> Optional[Mark]:
|
||||
...
|
||||
|
||||
@overload
|
||||
def get_closest_marker(self, name: str, default: Mark) -> Mark: ...
|
||||
def get_closest_marker(self, name: str, default: Mark) -> Mark:
|
||||
...
|
||||
|
||||
def get_closest_marker(self, name: str, default: Mark | None = None) -> Mark | None:
|
||||
def get_closest_marker(
|
||||
self, name: str, default: Optional[Mark] = None
|
||||
) -> Optional[Mark]:
|
||||
"""Return the first marker matching the name, from closest (for
|
||||
example function) to farther level (for example module level).
|
||||
|
||||
@@ -371,14 +402,14 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
||||
"""
|
||||
return next(self.iter_markers(name=name), default)
|
||||
|
||||
def listextrakeywords(self) -> set[str]:
|
||||
def listextrakeywords(self) -> Set[str]:
|
||||
"""Return a set of all extra keywords in self and any parents."""
|
||||
extra_keywords: set[str] = set()
|
||||
extra_keywords: Set[str] = set()
|
||||
for item in self.listchain():
|
||||
extra_keywords.update(item.extra_keyword_matches)
|
||||
return extra_keywords
|
||||
|
||||
def listnames(self) -> list[str]:
|
||||
def listnames(self) -> List[str]:
|
||||
return [x.name for x in self.listchain()]
|
||||
|
||||
def addfinalizer(self, fin: Callable[[], object]) -> None:
|
||||
@@ -390,17 +421,18 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
||||
"""
|
||||
self.session._setupstate.addfinalizer(fin, self)
|
||||
|
||||
def getparent(self, cls: type[_NodeType]) -> _NodeType | None:
|
||||
"""Get the closest parent node (including self) which is an instance of
|
||||
def getparent(self, cls: Type[_NodeType]) -> Optional[_NodeType]:
|
||||
"""Get the next parent node (including self) which is an instance of
|
||||
the given class.
|
||||
|
||||
:param cls: The node class to search for.
|
||||
:returns: The node, if found.
|
||||
"""
|
||||
for node in self.iter_parents():
|
||||
if isinstance(node, cls):
|
||||
return node
|
||||
return None
|
||||
current: Optional[Node] = self
|
||||
while current and not isinstance(current, cls):
|
||||
current = current.parent
|
||||
assert current is None or isinstance(current, cls)
|
||||
return current
|
||||
|
||||
def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback:
|
||||
return excinfo.traceback
|
||||
@@ -408,19 +440,19 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
||||
def _repr_failure_py(
|
||||
self,
|
||||
excinfo: ExceptionInfo[BaseException],
|
||||
style: TracebackStyle | None = None,
|
||||
style: "Optional[_TracebackStyle]" = None,
|
||||
) -> TerminalRepr:
|
||||
from _pytest.fixtures import FixtureLookupError
|
||||
|
||||
if isinstance(excinfo.value, ConftestImportFailure):
|
||||
excinfo = ExceptionInfo.from_exception(excinfo.value.cause)
|
||||
excinfo = ExceptionInfo.from_exc_info(excinfo.value.excinfo)
|
||||
if isinstance(excinfo.value, fail.Exception):
|
||||
if not excinfo.value.pytrace:
|
||||
style = "value"
|
||||
if isinstance(excinfo.value, FixtureLookupError):
|
||||
return excinfo.value.formatrepr()
|
||||
|
||||
tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback]
|
||||
tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]]
|
||||
if self.config.getoption("fulltrace", False):
|
||||
style = "long"
|
||||
tbfilter = False
|
||||
@@ -435,13 +467,11 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
||||
else:
|
||||
style = "long"
|
||||
|
||||
if self.config.get_verbosity() > 1:
|
||||
if self.config.getoption("verbose", 0) > 1:
|
||||
truncate_locals = False
|
||||
else:
|
||||
truncate_locals = True
|
||||
|
||||
truncate_args = False if self.config.get_verbosity() > 2 else True
|
||||
|
||||
# excinfo.getrepr() formats paths relative to the CWD if `abspath` is False.
|
||||
# It is possible for a fixture/test to change the CWD while this code runs, which
|
||||
# would then result in the user seeing confusing paths in the failure message.
|
||||
@@ -460,14 +490,13 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
||||
style=style,
|
||||
tbfilter=tbfilter,
|
||||
truncate_locals=truncate_locals,
|
||||
truncate_args=truncate_args,
|
||||
)
|
||||
|
||||
def repr_failure(
|
||||
self,
|
||||
excinfo: ExceptionInfo[BaseException],
|
||||
style: TracebackStyle | None = None,
|
||||
) -> str | TerminalRepr:
|
||||
style: "Optional[_TracebackStyle]" = None,
|
||||
) -> Union[str, TerminalRepr]:
|
||||
"""Return a representation of a collection or test failure.
|
||||
|
||||
.. seealso:: :ref:`non-python tests`
|
||||
@@ -477,26 +506,26 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
||||
return self._repr_failure_py(excinfo, style)
|
||||
|
||||
|
||||
def get_fslocation_from_item(node: Node) -> tuple[str | Path, int | None]:
|
||||
def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[int]]:
|
||||
"""Try to extract the actual location from a node, depending on available attributes:
|
||||
|
||||
* "location": a pair (path, lineno)
|
||||
* "obj": a Python object that the node wraps.
|
||||
* "path": just a path
|
||||
* "fspath": just a path
|
||||
|
||||
:rtype: A tuple of (str|Path, int) with filename and 0-based line number.
|
||||
"""
|
||||
# See Item.location.
|
||||
location: tuple[str, int | None, str] | None = getattr(node, "location", None)
|
||||
location: Optional[Tuple[str, Optional[int], str]] = getattr(node, "location", None)
|
||||
if location is not None:
|
||||
return location[:2]
|
||||
obj = getattr(node, "obj", None)
|
||||
if obj is not None:
|
||||
return getfslineno(obj)
|
||||
return getattr(node, "path", "unknown location"), -1
|
||||
return getattr(node, "fspath", "unknown location"), -1
|
||||
|
||||
|
||||
class Collector(Node, abc.ABC):
|
||||
class Collector(Node):
|
||||
"""Base class of all collectors.
|
||||
|
||||
Collector create children through `collect()` and thus iteratively build
|
||||
@@ -506,15 +535,14 @@ class Collector(Node, abc.ABC):
|
||||
class CollectError(Exception):
|
||||
"""An error during collection, contains a custom message."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def collect(self) -> Iterable[Item | Collector]:
|
||||
def collect(self) -> Iterable[Union["Item", "Collector"]]:
|
||||
"""Collect children (items and collectors) for this collector."""
|
||||
raise NotImplementedError("abstract")
|
||||
|
||||
# TODO: This omits the style= parameter which breaks Liskov Substitution.
|
||||
def repr_failure( # type: ignore[override]
|
||||
self, excinfo: ExceptionInfo[BaseException]
|
||||
) -> str | TerminalRepr:
|
||||
) -> Union[str, TerminalRepr]:
|
||||
"""Return a representation of a collection failure.
|
||||
|
||||
:param excinfo: Exception information for the failure.
|
||||
@@ -539,37 +567,31 @@ class Collector(Node, abc.ABC):
|
||||
ntraceback = traceback.cut(path=self.path)
|
||||
if ntraceback == traceback:
|
||||
ntraceback = ntraceback.cut(excludepath=tracebackcutdir)
|
||||
return ntraceback.filter(excinfo)
|
||||
return excinfo.traceback.filter(excinfo)
|
||||
return excinfo.traceback
|
||||
|
||||
|
||||
@lru_cache(maxsize=1000)
|
||||
def _check_initialpaths_for_relpath(
|
||||
initial_paths: frozenset[Path], path: Path
|
||||
) -> str | None:
|
||||
if path in initial_paths:
|
||||
return ""
|
||||
|
||||
for parent in path.parents:
|
||||
if parent in initial_paths:
|
||||
return str(path.relative_to(parent))
|
||||
|
||||
def _check_initialpaths_for_relpath(session: "Session", path: Path) -> Optional[str]:
|
||||
for initial_path in session._initialpaths:
|
||||
if commonpath(path, initial_path) == initial_path:
|
||||
rel = str(path.relative_to(initial_path))
|
||||
return "" if rel == "." else rel
|
||||
return None
|
||||
|
||||
|
||||
class FSCollector(Collector, abc.ABC):
|
||||
class FSCollector(Collector):
|
||||
"""Base class for filesystem collectors."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
fspath: LEGACY_PATH | None = None,
|
||||
path_or_parent: Path | Node | None = None,
|
||||
path: Path | None = None,
|
||||
name: str | None = None,
|
||||
parent: Node | None = None,
|
||||
config: Config | None = None,
|
||||
session: Session | None = None,
|
||||
nodeid: str | None = None,
|
||||
fspath: Optional[LEGACY_PATH] = None,
|
||||
path_or_parent: Optional[Union[Path, Node]] = None,
|
||||
path: Optional[Path] = None,
|
||||
name: Optional[str] = None,
|
||||
parent: Optional[Node] = None,
|
||||
config: Optional[Config] = None,
|
||||
session: Optional["Session"] = None,
|
||||
nodeid: Optional[str] = None,
|
||||
) -> None:
|
||||
if path_or_parent:
|
||||
if isinstance(path_or_parent, Node):
|
||||
@@ -600,7 +622,7 @@ class FSCollector(Collector, abc.ABC):
|
||||
try:
|
||||
nodeid = str(self.path.relative_to(session.config.rootpath))
|
||||
except ValueError:
|
||||
nodeid = _check_initialpaths_for_relpath(session._initialpaths, path)
|
||||
nodeid = _check_initialpaths_for_relpath(session, path)
|
||||
|
||||
if nodeid and os.sep != SEP:
|
||||
nodeid = nodeid.replace(os.sep, SEP)
|
||||
@@ -619,40 +641,30 @@ class FSCollector(Collector, abc.ABC):
|
||||
cls,
|
||||
parent,
|
||||
*,
|
||||
fspath: LEGACY_PATH | None = None,
|
||||
path: Path | None = None,
|
||||
fspath: Optional[LEGACY_PATH] = None,
|
||||
path: Optional[Path] = None,
|
||||
**kw,
|
||||
) -> Self:
|
||||
):
|
||||
"""The public constructor."""
|
||||
return super().from_parent(parent=parent, fspath=fspath, path=path, **kw)
|
||||
|
||||
def gethookproxy(self, fspath: "os.PathLike[str]"):
|
||||
warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2)
|
||||
return self.session.gethookproxy(fspath)
|
||||
|
||||
class File(FSCollector, abc.ABC):
|
||||
def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> bool:
|
||||
warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2)
|
||||
return self.session.isinitpath(path)
|
||||
|
||||
|
||||
class File(FSCollector):
|
||||
"""Base class for collecting tests from a file.
|
||||
|
||||
:ref:`non-python tests`.
|
||||
"""
|
||||
|
||||
|
||||
class Directory(FSCollector, abc.ABC):
|
||||
"""Base class for collecting files from a directory.
|
||||
|
||||
A basic directory collector does the following: goes over the files and
|
||||
sub-directories in the directory and creates collectors for them by calling
|
||||
the hooks :hook:`pytest_collect_directory` and :hook:`pytest_collect_file`,
|
||||
after checking that they are not ignored using
|
||||
:hook:`pytest_ignore_collect`.
|
||||
|
||||
The default directory collectors are :class:`~pytest.Dir` and
|
||||
:class:`~pytest.Package`.
|
||||
|
||||
.. versionadded:: 8.0
|
||||
|
||||
:ref:`custom directory collectors`.
|
||||
"""
|
||||
|
||||
|
||||
class Item(Node, abc.ABC):
|
||||
class Item(Node):
|
||||
"""Base class of all test invocation items.
|
||||
|
||||
Note that for a single function there might be multiple test invocation items.
|
||||
@@ -664,9 +676,9 @@ class Item(Node, abc.ABC):
|
||||
self,
|
||||
name,
|
||||
parent=None,
|
||||
config: Config | None = None,
|
||||
session: Session | None = None,
|
||||
nodeid: str | None = None,
|
||||
config: Optional[Config] = None,
|
||||
session: Optional["Session"] = None,
|
||||
nodeid: Optional[str] = None,
|
||||
**kw,
|
||||
) -> None:
|
||||
# The first two arguments are intentionally passed positionally,
|
||||
@@ -681,11 +693,11 @@ class Item(Node, abc.ABC):
|
||||
nodeid=nodeid,
|
||||
**kw,
|
||||
)
|
||||
self._report_sections: list[tuple[str, str, str]] = []
|
||||
self._report_sections: List[Tuple[str, str, str]] = []
|
||||
|
||||
#: A list of tuples (name, value) that holds user defined properties
|
||||
#: for this test.
|
||||
self.user_properties: list[tuple[str, object]] = []
|
||||
self.user_properties: List[Tuple[str, object]] = []
|
||||
|
||||
self._check_item_and_collector_diamond_inheritance()
|
||||
|
||||
@@ -718,7 +730,6 @@ class Item(Node, abc.ABC):
|
||||
PytestWarning,
|
||||
)
|
||||
|
||||
@abc.abstractmethod
|
||||
def runtest(self) -> None:
|
||||
"""Run the test case for this item.
|
||||
|
||||
@@ -745,7 +756,7 @@ class Item(Node, abc.ABC):
|
||||
if content:
|
||||
self._report_sections.append((when, key, content))
|
||||
|
||||
def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]:
|
||||
def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]:
|
||||
"""Get location information for this item for test reports.
|
||||
|
||||
Returns a tuple with three elements:
|
||||
@@ -759,14 +770,14 @@ class Item(Node, abc.ABC):
|
||||
return self.path, None, ""
|
||||
|
||||
@cached_property
|
||||
def location(self) -> tuple[str, int | None, str]:
|
||||
def location(self) -> Tuple[str, Optional[int], str]:
|
||||
"""
|
||||
Returns a tuple of ``(relfspath, lineno, testname)`` for this item
|
||||
where ``relfspath`` is file path relative to ``config.rootpath``
|
||||
and lineno is a 0-based line number.
|
||||
"""
|
||||
location = self.reportinfo()
|
||||
path = absolutepath(location[0])
|
||||
path = absolutepath(os.fspath(location[0]))
|
||||
relfspath = self.session._node_location_to_relpath(path)
|
||||
assert type(location[2]) is str
|
||||
return (relfspath, location[1], location[2])
|
||||
|
||||
Reference in New Issue
Block a user