aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/pytest/py3/_pytest/nodes.py
diff options
context:
space:
mode:
authorrobot-piglet <robot-piglet@yandex-team.com>2025-05-05 12:31:52 +0300
committerrobot-piglet <robot-piglet@yandex-team.com>2025-05-05 12:41:33 +0300
commit6ff49ec58061f642c3a2f83c61eba12820787dfc (patch)
treec733ec9bdb15ed280080d31dea8725bfec717acd /contrib/python/pytest/py3/_pytest/nodes.py
parenteefca8305c6a545cc6b16dca3eb0d91dcef2adcd (diff)
downloadydb-6ff49ec58061f642c3a2f83c61eba12820787dfc.tar.gz
Intermediate changes
commit_hash:8b3bb826b17db8329ed1221f545c0645f12c552d
Diffstat (limited to 'contrib/python/pytest/py3/_pytest/nodes.py')
-rw-r--r--contrib/python/pytest/py3/_pytest/nodes.py80
1 files changed, 55 insertions, 25 deletions
diff --git a/contrib/python/pytest/py3/_pytest/nodes.py b/contrib/python/pytest/py3/_pytest/nodes.py
index a5313cb7656..73efe156e2f 100644
--- a/contrib/python/pytest/py3/_pytest/nodes.py
+++ b/contrib/python/pytest/py3/_pytest/nodes.py
@@ -1,6 +1,8 @@
-import os
-import warnings
+import abc
+from functools import cached_property
from inspect import signature
+import os
+import pathlib
from pathlib import Path
from typing import Any
from typing import Callable
@@ -17,16 +19,19 @@ from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
+import warnings
+
+import pluggy
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.compat import cached_property
from _pytest.compat import LEGACY_PATH
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
@@ -38,10 +43,11 @@ from _pytest.pathlib import commonpath
from _pytest.stash import Stash
from _pytest.warning_types import PytestWarning
+
if TYPE_CHECKING:
# Imported here due to circular import.
- from _pytest.main import Session
from _pytest._code.code import _TracebackStyle
+ from _pytest.main import Session
SEP = "/"
@@ -94,14 +100,6 @@ def iterparentnodeids(nodeid: str) -> Iterator[str]:
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: Optional[Path],
@@ -126,7 +124,21 @@ def _imply_path(
_NodeType = TypeVar("_NodeType", bound="Node")
-class NodeMeta(type):
+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__(self, *k, **kw):
msg = (
"Direct construction of {name} has been deprecated, please use {name}.from_parent.\n"
@@ -156,7 +168,7 @@ class NodeMeta(type):
return super().__call__(*k, **known_kw)
-class Node(metaclass=NodeMeta):
+class Node(abc.ABC, metaclass=NodeMeta):
r"""Base class of :class:`Collector` and :class:`Item`, the components of
the test collection tree.
@@ -167,9 +179,9 @@ class Node(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`. Will be deprecated in a future release, prefer
- #: using :attr:`path` instead.
+ #: :meth:`Item.reportinfo <pytest.Item.reportinfo>`. Will be deprecated in
fspath: LEGACY_PATH
+ #: a future release, prefer using :attr:`path` instead.
# Use __slots__ to make attribute access faster.
# Note that __dict__ is still available.
@@ -219,7 +231,7 @@ class Node(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: Path = _imply_path(type(self), path, fspath=fspath)
+ self.path: pathlib.Path = _imply_path(type(self), path, fspath=fspath)
# The explicit annotation is to avoid publicly exposing NodeKeywords.
#: Keywords/markers collected from all scopes.
@@ -264,7 +276,7 @@ class Node(metaclass=NodeMeta):
return cls._create(parent=parent, **kw)
@property
- def ihook(self):
+ def ihook(self) -> pluggy.HookRelay:
"""fspath-sensitive hook proxy used to call pytest hooks."""
return self.session.gethookproxy(self.path)
@@ -295,9 +307,7 @@ class Node(metaclass=NodeMeta):
# enforce type checks here to avoid getting a generic type error later otherwise.
if not isinstance(warning, Warning):
raise ValueError(
- "warning must be an instance of Warning or subclass, got {!r}".format(
- warning
- )
+ f"warning must be an instance of Warning or subclass, got {warning!r}"
)
path, lineno = get_fslocation_from_item(self)
assert lineno is not None
@@ -525,7 +535,7 @@ def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[i
return getattr(node, "fspath", "unknown location"), -1
-class Collector(Node):
+class Collector(Node, abc.ABC):
"""Base class of all collectors.
Collector create children through `collect()` and thus iteratively build
@@ -535,6 +545,7 @@ class Collector(Node):
class CollectError(Exception):
"""An error during collection, contains a custom message."""
+ @abc.abstractmethod
def collect(self) -> Iterable[Union["Item", "Collector"]]:
"""Collect children (items and collectors) for this collector."""
raise NotImplementedError("abstract")
@@ -579,7 +590,7 @@ def _check_initialpaths_for_relpath(session: "Session", path: Path) -> Optional[
return None
-class FSCollector(Collector):
+class FSCollector(Collector, abc.ABC):
"""Base class for filesystem collectors."""
def __init__(
@@ -657,14 +668,32 @@ class FSCollector(Collector):
return self.session.isinitpath(path)
-class File(FSCollector):
+class File(FSCollector, abc.ABC):
"""Base class for collecting tests from a file.
:ref:`non-python tests`.
"""
-class Item(Node):
+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):
"""Base class of all test invocation items.
Note that for a single function there might be multiple test invocation items.
@@ -730,6 +759,7 @@ class Item(Node):
PytestWarning,
)
+ @abc.abstractmethod
def runtest(self) -> None:
"""Run the test case for this item.