From b8604de79254ee92c5dda8a99b2280e207880bc7 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sun, 25 Apr 2021 23:52:34 +0200 Subject: [PATCH] Return support for the broken File/Item hybrids * adds deprecation * ads necessary support code in node construction --- src/_pytest/main.py | 2 +- src/_pytest/nodes.py | 64 +++++++++++++++++++++++++++++++++---------- testing/test_nodes.py | 17 +++++++++--- 3 files changed, 63 insertions(+), 20 deletions(-) diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 7543495dd88..e068fcb0c77 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -484,7 +484,7 @@ def __init__(self, config: Config) -> None: @classmethod def from_config(cls, config: Config) -> "Session": - session: Session = cls._create(config) + session: Session = cls._create(config=config) return session def __repr__(self) -> str: diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index e7397655f5f..cb22a4f0515 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -1,8 +1,10 @@ import os 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 Iterable from typing import Iterator from typing import List @@ -126,7 +128,14 @@ def __call__(self, *k, **kw): fail(msg, pytrace=False) def _create(self, *k, **kw): - return super().__call__(*k, **kw) + try: + return super().__call__(*k, **kw) + except TypeError: + sig = signature(cast(Type[Node], self).__init__) + sig.replace() + known_kw = {k: v for k, v in kw.items() if k in sig.parameters} + + return super().__call__(*k, **known_kw) class Node(metaclass=NodeMeta): @@ -540,23 +549,35 @@ def _check_initialpaths_for_relpath(session: "Session", path: Path) -> Optional[ class FSCollector(Collector): def __init__( self, - fspath: Optional[LEGACY_PATH], - path: Optional[Path], - parent=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): + assert parent is None + parent = cast(FSCollector, path_or_parent) + elif isinstance(path_or_parent, Path): + assert path is None + path = path_or_parent + + assert parent is not None path, fspath = _imply_path(path, fspath=fspath) - name = path.name - if parent is not None and parent.path != path: - try: - rel = path.relative_to(parent.path) - except ValueError: - pass - else: - name = str(rel) - name = name.replace(os.sep, SEP) + if name is None: + name = path.name + if parent is not None and parent.path != path: + try: + rel = path.relative_to(parent.path) + except ValueError: + pass + else: + name = str(rel) + name = name.replace(os.sep, SEP) self.path = path session = session or parent.session @@ -571,7 +592,12 @@ def __init__( nodeid = nodeid.replace(os.sep, SEP) super().__init__( - name, parent, config, session, nodeid=nodeid, fspath=fspath, path=path + name=name, + parent=parent, + config=config, + session=session, + nodeid=nodeid, + path=path, ) @classmethod @@ -631,8 +657,16 @@ def __init__( config: Optional[Config] = None, session: Optional["Session"] = None, nodeid: Optional[str] = None, + **kw, ) -> None: - super().__init__(name, parent, config, session, nodeid=nodeid) + super().__init__( + name=name, + parent=parent, + config=config, + session=session, + nodeid=nodeid, + **kw, + ) self._report_sections: List[Tuple[str, str, str]] = [] #: A list of tuples (name, value) that holds user defined properties diff --git a/testing/test_nodes.py b/testing/test_nodes.py index bdc5cd76fcd..aa674761386 100644 --- a/testing/test_nodes.py +++ b/testing/test_nodes.py @@ -39,14 +39,23 @@ def test_node_from_parent_disallowed_arguments() -> None: nodes.Node.from_parent(None, config=None) # type: ignore[arg-type] -def test_subclassing_both_item_and_collector_warns() -> None: +def test_subclassing_both_item_and_collector_warns(request, tmp_path: Path) -> None: + from _pytest.compat import legacy_path with pytest.warns( - PytestWarning, match="SoWrong is a Item subclass and should not be a collector" + PytestWarning, + match=( + "(?m)SoWrong is an Item subclass and should not be a collector, however its bases File are collectors.\n" + "Please split the Collectors and the Item into separate node types.\nTODO:.*" + ), ): - class SoWrong(nodes.Item, nodes.File): - pass + class SoWrong(nodes.File, nodes.Item): + def __init__(self, fspath, parent): + """Legacy ctor with legacy call # don't wana see""" + super().__init__(fspath, parent) + + SoWrong.from_parent(request.session, fspath=legacy_path(tmp_path / "broken.txt")) @pytest.mark.parametrize(