import re import warnings from pathlib import Path from typing import cast from typing import List from typing import Type import pytest from _pytest import nodes from _pytest.compat import legacy_path from _pytest.outcomes import OutcomeException from _pytest.pytester import Pytester from _pytest.warning_types import PytestWarning @pytest.mark.parametrize( ("nodeid", "expected"), ( ("", [""]), ("a", ["", "a"]), ("aa/b", ["", "aa", "aa/b"]), ("a/b/c", ["", "a", "a/b", "a/b/c"]), ("a/bbb/c::D", ["", "a", "a/bbb", "a/bbb/c", "a/bbb/c::D"]), ("a/b/c::D::eee", ["", "a", "a/b", "a/b/c", "a/b/c::D", "a/b/c::D::eee"]), ("::xx", ["", "::xx"]), # / only considered until first :: ("a/b/c::D/d::e", ["", "a", "a/b", "a/b/c", "a/b/c::D/d", "a/b/c::D/d::e"]), # : alone is not a separator. ("a/b::D:e:f::g", ["", "a", "a/b", "a/b::D:e:f", "a/b::D:e:f::g"]), # / not considered if a part of a test name ("a/b::c/d::e[/test]", ["", "a", "a/b", "a/b::c/d", "a/b::c/d::e[/test]"]), ), ) def test_iterparentnodeids(nodeid: str, expected: List[str]) -> None: result = list(nodes.iterparentnodeids(nodeid)) assert result == expected def test_node_from_parent_disallowed_arguments() -> None: with pytest.raises(TypeError, match="session is"): nodes.Node.from_parent(None, session=None) # type: ignore[arg-type] with pytest.raises(TypeError, match="config is"): nodes.Node.from_parent(None, config=None) # type: ignore[arg-type] def test_node_direct_construction_deprecated() -> None: with pytest.raises( OutcomeException, match=( "Direct construction of _pytest.nodes.Node has been deprecated, please " "use _pytest.nodes.Node.from_parent.\nSee " "https://docs.pytest.org/en/stable/deprecations.html#node-construction-changed-to-node-from-parent" " for more details." ), ): nodes.Node(None, session=None) # type: ignore[arg-type] def test_subclassing_both_item_and_collector_deprecated( request, tmp_path: Path ) -> None: """ Verifies we warn on diamond inheritance as well as correctly managing legacy inheritance constructors with missing args as found in plugins. """ # We do not expect any warnings messages to issued during class definition. with warnings.catch_warnings(): warnings.simplefilter("error") class SoWrong(nodes.Item, nodes.File): def __init__(self, fspath, parent): """Legacy ctor with legacy call # don't wana see""" super().__init__(fspath, parent) with pytest.warns(PytestWarning) as rec: SoWrong.from_parent( request.session, fspath=legacy_path(tmp_path / "broken.txt") ) messages = [str(x.message) for x in rec] assert any( re.search(".*SoWrong.* not using a cooperative constructor.*", x) for x in messages ) assert any( re.search("(?m)SoWrong .* should not be a collector", x) for x in messages ) @pytest.mark.parametrize( "warn_type, msg", [(DeprecationWarning, "deprecated"), (PytestWarning, "pytest")] ) def test_node_warn_is_no_longer_only_pytest_warnings( pytester: Pytester, warn_type: Type[Warning], msg: str ) -> None: items = pytester.getitems( """ def test(): pass """ ) with pytest.warns(warn_type, match=msg): items[0].warn(warn_type(msg)) def test_node_warning_enforces_warning_types(pytester: Pytester) -> None: items = pytester.getitems( """ def test(): pass """ ) with pytest.raises( ValueError, match="warning must be an instance of Warning or subclass" ): items[0].warn(Exception("ok")) # type: ignore[arg-type] def test__check_initialpaths_for_relpath() -> None: """Ensure that it handles dirs, and does not always use dirname.""" cwd = Path.cwd() class FakeSession1: _initialpaths = frozenset({cwd}) session = cast(pytest.Session, FakeSession1) assert nodes._check_initialpaths_for_relpath(session, cwd) == "" sub = cwd / "file" class FakeSession2: _initialpaths = frozenset({cwd}) session = cast(pytest.Session, FakeSession2) assert nodes._check_initialpaths_for_relpath(session, sub) == "file" outside = Path("/outside-this-does-not-exist") assert nodes._check_initialpaths_for_relpath(session, outside) is None def test_failure_with_changed_cwd(pytester: Pytester) -> None: """ Test failure lines should use absolute paths if cwd has changed since invocation, so the path is correct (#6428). """ p = pytester.makepyfile( """ import os import pytest @pytest.fixture def private_dir(): out_dir = 'ddd' os.mkdir(out_dir) old_dir = os.getcwd() os.chdir(out_dir) yield out_dir os.chdir(old_dir) def test_show_wrong_path(private_dir): assert False """ ) result = pytester.runpytest() result.stdout.fnmatch_lines([str(p) + ":*: AssertionError", "*1 failed in *"])