Merge pull request #8459 from bluetech/unnecessary-py-path-3
This commit is contained in:
		
						commit
						35df3e68d5
					
				| 
						 | 
				
			
			@ -670,7 +670,7 @@ class FixtureRequest:
 | 
			
		|||
                    "\n\nRequested here:\n{}:{}".format(
 | 
			
		||||
                        funcitem.nodeid,
 | 
			
		||||
                        fixturedef.argname,
 | 
			
		||||
                        getlocation(fixturedef.func, funcitem.config.rootdir),
 | 
			
		||||
                        getlocation(fixturedef.func, funcitem.config.rootpath),
 | 
			
		||||
                        source_path_str,
 | 
			
		||||
                        source_lineno,
 | 
			
		||||
                    )
 | 
			
		||||
| 
						 | 
				
			
			@ -728,7 +728,7 @@ class FixtureRequest:
 | 
			
		|||
            fs, lineno = getfslineno(factory)
 | 
			
		||||
            if isinstance(fs, Path):
 | 
			
		||||
                session: Session = self._pyfuncitem.session
 | 
			
		||||
                p = bestrelpath(Path(session.fspath), fs)
 | 
			
		||||
                p = bestrelpath(session.path, fs)
 | 
			
		||||
            else:
 | 
			
		||||
                p = fs
 | 
			
		||||
            args = _format_args(factory)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,6 +25,7 @@ import attr
 | 
			
		|||
import _pytest._code
 | 
			
		||||
from _pytest import nodes
 | 
			
		||||
from _pytest.compat import final
 | 
			
		||||
from _pytest.compat import LEGACY_PATH
 | 
			
		||||
from _pytest.compat import legacy_path
 | 
			
		||||
from _pytest.config import Config
 | 
			
		||||
from _pytest.config import directory_arg
 | 
			
		||||
| 
						 | 
				
			
			@ -301,7 +302,7 @@ def wrap_session(
 | 
			
		|||
    finally:
 | 
			
		||||
        # Explicitly break reference cycle.
 | 
			
		||||
        excinfo = None  # type: ignore
 | 
			
		||||
        session.startdir.chdir()
 | 
			
		||||
        os.chdir(session.startpath)
 | 
			
		||||
        if initstate >= 2:
 | 
			
		||||
            try:
 | 
			
		||||
                config.hook.pytest_sessionfinish(
 | 
			
		||||
| 
						 | 
				
			
			@ -476,7 +477,6 @@ class Session(nodes.FSCollector):
 | 
			
		|||
        self.shouldstop: Union[bool, str] = False
 | 
			
		||||
        self.shouldfail: Union[bool, str] = False
 | 
			
		||||
        self.trace = config.trace.root.get("collection")
 | 
			
		||||
        self.startdir = config.invocation_dir
 | 
			
		||||
        self._initialpaths: FrozenSet[Path] = frozenset()
 | 
			
		||||
 | 
			
		||||
        self._bestrelpathcache: Dict[Path, str] = _bestrelpath_cache(config.rootpath)
 | 
			
		||||
| 
						 | 
				
			
			@ -497,6 +497,24 @@ class Session(nodes.FSCollector):
 | 
			
		|||
            self.testscollected,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def startpath(self) -> Path:
 | 
			
		||||
        """The path from which pytest was invoked.
 | 
			
		||||
 | 
			
		||||
        .. versionadded:: 6.3.0
 | 
			
		||||
        """
 | 
			
		||||
        return self.config.invocation_params.dir
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def stardir(self) -> LEGACY_PATH:
 | 
			
		||||
        """The path from which pytest was invoked.
 | 
			
		||||
 | 
			
		||||
        Prefer to use ``startpath`` which is a :class:`pathlib.Path`.
 | 
			
		||||
 | 
			
		||||
        :type: LEGACY_PATH
 | 
			
		||||
        """
 | 
			
		||||
        return legacy_path(self.startpath)
 | 
			
		||||
 | 
			
		||||
    def _node_location_to_relpath(self, node_path: Path) -> str:
 | 
			
		||||
        # bestrelpath is a quite slow function.
 | 
			
		||||
        return self._bestrelpathcache[node_path]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,6 +32,7 @@ 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.store import Store
 | 
			
		||||
 | 
			
		||||
if TYPE_CHECKING:
 | 
			
		||||
| 
						 | 
				
			
			@ -517,13 +518,11 @@ class Collector(Node):
 | 
			
		|||
            excinfo.traceback = ntraceback.filter()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _check_initialpaths_for_relpath(
 | 
			
		||||
    session: "Session", fspath: LEGACY_PATH
 | 
			
		||||
) -> Optional[str]:
 | 
			
		||||
def _check_initialpaths_for_relpath(session: "Session", path: Path) -> Optional[str]:
 | 
			
		||||
    for initial_path in session._initialpaths:
 | 
			
		||||
        initial_path_ = legacy_path(initial_path)
 | 
			
		||||
        if fspath.common(initial_path_) == initial_path_:
 | 
			
		||||
            return fspath.relto(initial_path_)
 | 
			
		||||
        if commonpath(path, initial_path) == initial_path:
 | 
			
		||||
            rel = str(path.relative_to(initial_path))
 | 
			
		||||
            return "" if rel == "." else rel
 | 
			
		||||
    return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -538,7 +537,7 @@ class FSCollector(Collector):
 | 
			
		|||
        nodeid: Optional[str] = None,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        path, fspath = _imply_path(path, fspath=fspath)
 | 
			
		||||
        name = fspath.basename
 | 
			
		||||
        name = path.name
 | 
			
		||||
        if parent is not None and parent.path != path:
 | 
			
		||||
            try:
 | 
			
		||||
                rel = path.relative_to(parent.path)
 | 
			
		||||
| 
						 | 
				
			
			@ -547,7 +546,7 @@ class FSCollector(Collector):
 | 
			
		|||
            else:
 | 
			
		||||
                name = str(rel)
 | 
			
		||||
            name = name.replace(os.sep, SEP)
 | 
			
		||||
        self.path = Path(fspath)
 | 
			
		||||
        self.path = path
 | 
			
		||||
 | 
			
		||||
        session = session or parent.session
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -555,7 +554,7 @@ class FSCollector(Collector):
 | 
			
		|||
            try:
 | 
			
		||||
                nodeid = str(self.path.relative_to(session.config.rootpath))
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                nodeid = _check_initialpaths_for_relpath(session, fspath)
 | 
			
		||||
                nodeid = _check_initialpaths_for_relpath(session, path)
 | 
			
		||||
 | 
			
		||||
            if nodeid and os.sep != SEP:
 | 
			
		||||
                nodeid = nodeid.replace(os.sep, SEP)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -583,7 +583,7 @@ def resolve_package_path(path: Path) -> Optional[Path]:
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
def visit(
 | 
			
		||||
    path: str, recurse: Callable[["os.DirEntry[str]"], bool]
 | 
			
		||||
    path: Union[str, "os.PathLike[str]"], recurse: Callable[["os.DirEntry[str]"], bool]
 | 
			
		||||
) -> Iterator["os.DirEntry[str]"]:
 | 
			
		||||
    """Walk a directory recursively, in breadth-first order.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -657,3 +657,21 @@ def bestrelpath(directory: Path, dest: Path) -> str:
 | 
			
		|||
        # Forward from base to dest.
 | 
			
		||||
        *reldest.parts,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Originates from py. path.local.copy(), with siginficant trims and adjustments.
 | 
			
		||||
# TODO(py38): Replace with shutil.copytree(..., symlinks=True, dirs_exist_ok=True)
 | 
			
		||||
def copytree(source: Path, target: Path) -> None:
 | 
			
		||||
    """Recursively copy a source directory to target."""
 | 
			
		||||
    assert source.is_dir()
 | 
			
		||||
    for entry in visit(source, recurse=lambda entry: not entry.is_symlink()):
 | 
			
		||||
        x = Path(entry)
 | 
			
		||||
        relpath = x.relative_to(source)
 | 
			
		||||
        newx = target / relpath
 | 
			
		||||
        newx.parent.mkdir(exist_ok=True)
 | 
			
		||||
        if x.is_symlink():
 | 
			
		||||
            newx.symlink_to(os.readlink(x))
 | 
			
		||||
        elif x.is_file():
 | 
			
		||||
            shutil.copyfile(x, newx)
 | 
			
		||||
        elif x.is_dir():
 | 
			
		||||
            newx.mkdir(exist_ok=True)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -63,6 +63,7 @@ from _pytest.outcomes import fail
 | 
			
		|||
from _pytest.outcomes import importorskip
 | 
			
		||||
from _pytest.outcomes import skip
 | 
			
		||||
from _pytest.pathlib import bestrelpath
 | 
			
		||||
from _pytest.pathlib import copytree
 | 
			
		||||
from _pytest.pathlib import make_numbered_dir
 | 
			
		||||
from _pytest.reports import CollectReport
 | 
			
		||||
from _pytest.reports import TestReport
 | 
			
		||||
| 
						 | 
				
			
			@ -935,10 +936,7 @@ class Pytester:
 | 
			
		|||
            example_path = example_dir.joinpath(name)
 | 
			
		||||
 | 
			
		||||
        if example_path.is_dir() and not example_path.joinpath("__init__.py").is_file():
 | 
			
		||||
            # TODO: legacy_path.copy can copy files to existing directories,
 | 
			
		||||
            # while with shutil.copytree the destination directory cannot exist,
 | 
			
		||||
            # we will need to roll our own in order to drop legacy_path completely
 | 
			
		||||
            legacy_path(example_path).copy(legacy_path(self.path))
 | 
			
		||||
            copytree(example_path, self.path)
 | 
			
		||||
            return self.path
 | 
			
		||||
        elif example_path.is_file():
 | 
			
		||||
            result = self.path.joinpath(example_path.name)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -645,7 +645,7 @@ class Package(Module):
 | 
			
		|||
            session=session,
 | 
			
		||||
            nodeid=nodeid,
 | 
			
		||||
        )
 | 
			
		||||
        self.name = os.path.basename(str(fspath.dirname))
 | 
			
		||||
        self.name = path.parent.name
 | 
			
		||||
 | 
			
		||||
    def setup(self) -> None:
 | 
			
		||||
        # Not using fixtures to call setup_module here because autouse fixtures
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
import os
 | 
			
		||||
from io import StringIO
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from pprint import pprint
 | 
			
		||||
from typing import Any
 | 
			
		||||
from typing import cast
 | 
			
		||||
| 
						 | 
				
			
			@ -29,7 +29,6 @@ from _pytest._code.code import ReprTraceback
 | 
			
		|||
from _pytest._code.code import TerminalRepr
 | 
			
		||||
from _pytest._io import TerminalWriter
 | 
			
		||||
from _pytest.compat import final
 | 
			
		||||
from _pytest.compat import LEGACY_PATH
 | 
			
		||||
from _pytest.config import Config
 | 
			
		||||
from _pytest.nodes import Collector
 | 
			
		||||
from _pytest.nodes import Item
 | 
			
		||||
| 
						 | 
				
			
			@ -500,8 +499,8 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]:
 | 
			
		|||
    else:
 | 
			
		||||
        d["longrepr"] = report.longrepr
 | 
			
		||||
    for name in d:
 | 
			
		||||
        if isinstance(d[name], (LEGACY_PATH, Path)):
 | 
			
		||||
            d[name] = str(d[name])
 | 
			
		||||
        if isinstance(d[name], os.PathLike):
 | 
			
		||||
            d[name] = os.fspath(d[name])
 | 
			
		||||
        elif name == "result":
 | 
			
		||||
            d[name] = None  # for now
 | 
			
		||||
    return d
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,6 +37,8 @@ from _pytest._code import ExceptionInfo
 | 
			
		|||
from _pytest._code.code import ExceptionRepr
 | 
			
		||||
from _pytest._io.wcwidth import wcswidth
 | 
			
		||||
from _pytest.compat import final
 | 
			
		||||
from _pytest.compat import LEGACY_PATH
 | 
			
		||||
from _pytest.compat import legacy_path
 | 
			
		||||
from _pytest.config import _PluggyPlugin
 | 
			
		||||
from _pytest.config import Config
 | 
			
		||||
from _pytest.config import ExitCode
 | 
			
		||||
| 
						 | 
				
			
			@ -318,7 +320,6 @@ class TerminalReporter:
 | 
			
		|||
        self.stats: Dict[str, List[Any]] = {}
 | 
			
		||||
        self._main_color: Optional[str] = None
 | 
			
		||||
        self._known_types: Optional[List[str]] = None
 | 
			
		||||
        self.startdir = config.invocation_dir
 | 
			
		||||
        self.startpath = config.invocation_params.dir
 | 
			
		||||
        if file is None:
 | 
			
		||||
            file = sys.stdout
 | 
			
		||||
| 
						 | 
				
			
			@ -381,6 +382,16 @@ class TerminalReporter:
 | 
			
		|||
    def showlongtestinfo(self) -> bool:
 | 
			
		||||
        return self.verbosity > 0
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def startdir(self) -> LEGACY_PATH:
 | 
			
		||||
        """The directory from which pytest was invoked.
 | 
			
		||||
 | 
			
		||||
        Prefer to use ``startpath`` which is a :class:`pathlib.Path`.
 | 
			
		||||
 | 
			
		||||
        :type: LEGACY_PATH
 | 
			
		||||
        """
 | 
			
		||||
        return legacy_path(self.startpath)
 | 
			
		||||
 | 
			
		||||
    def hasopt(self, char: str) -> bool:
 | 
			
		||||
        char = {"xfailed": "x", "skipped": "s"}.get(char, char)
 | 
			
		||||
        return char in self.reportchars
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -933,11 +933,11 @@ def test_setup_only_available_in_subdir(pytester: Pytester) -> None:
 | 
			
		|||
            """\
 | 
			
		||||
            import pytest
 | 
			
		||||
            def pytest_runtest_setup(item):
 | 
			
		||||
                assert item.fspath.purebasename == "test_in_sub1"
 | 
			
		||||
                assert item.path.stem == "test_in_sub1"
 | 
			
		||||
            def pytest_runtest_call(item):
 | 
			
		||||
                assert item.fspath.purebasename == "test_in_sub1"
 | 
			
		||||
                assert item.path.stem == "test_in_sub1"
 | 
			
		||||
            def pytest_runtest_teardown(item):
 | 
			
		||||
                assert item.fspath.purebasename == "test_in_sub1"
 | 
			
		||||
                assert item.path.stem == "test_in_sub1"
 | 
			
		||||
            """
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
| 
						 | 
				
			
			@ -946,11 +946,11 @@ def test_setup_only_available_in_subdir(pytester: Pytester) -> None:
 | 
			
		|||
            """\
 | 
			
		||||
            import pytest
 | 
			
		||||
            def pytest_runtest_setup(item):
 | 
			
		||||
                assert item.fspath.purebasename == "test_in_sub2"
 | 
			
		||||
                assert item.path.stem == "test_in_sub2"
 | 
			
		||||
            def pytest_runtest_call(item):
 | 
			
		||||
                assert item.fspath.purebasename == "test_in_sub2"
 | 
			
		||||
                assert item.path.stem == "test_in_sub2"
 | 
			
		||||
            def pytest_runtest_teardown(item):
 | 
			
		||||
                assert item.fspath.purebasename == "test_in_sub2"
 | 
			
		||||
                assert item.path.stem == "test_in_sub2"
 | 
			
		||||
            """
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
| 
						 | 
				
			
			@ -1125,8 +1125,7 @@ class TestReportInfo:
 | 
			
		|||
    def test_func_reportinfo(self, pytester: Pytester) -> None:
 | 
			
		||||
        item = pytester.getitem("def test_func(): pass")
 | 
			
		||||
        fspath, lineno, modpath = item.reportinfo()
 | 
			
		||||
        with pytest.warns(DeprecationWarning):
 | 
			
		||||
            assert fspath == item.fspath
 | 
			
		||||
        assert str(fspath) == str(item.path)
 | 
			
		||||
        assert lineno == 0
 | 
			
		||||
        assert modpath == "test_func"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1141,8 +1140,7 @@ class TestReportInfo:
 | 
			
		|||
        classcol = pytester.collect_by_name(modcol, "TestClass")
 | 
			
		||||
        assert isinstance(classcol, Class)
 | 
			
		||||
        fspath, lineno, msg = classcol.reportinfo()
 | 
			
		||||
        with pytest.warns(DeprecationWarning):
 | 
			
		||||
            assert fspath == modcol.fspath
 | 
			
		||||
        assert str(fspath) == str(modcol.path)
 | 
			
		||||
        assert lineno == 1
 | 
			
		||||
        assert msg == "TestClass"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,6 @@ from typing import Type
 | 
			
		|||
 | 
			
		||||
import pytest
 | 
			
		||||
from _pytest import nodes
 | 
			
		||||
from _pytest.compat import legacy_path
 | 
			
		||||
from _pytest.pytester import Pytester
 | 
			
		||||
from _pytest.warning_types import PytestWarning
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -76,7 +75,7 @@ def test__check_initialpaths_for_relpath() -> None:
 | 
			
		|||
 | 
			
		||||
    session = cast(pytest.Session, FakeSession1)
 | 
			
		||||
 | 
			
		||||
    assert nodes._check_initialpaths_for_relpath(session, legacy_path(cwd)) == ""
 | 
			
		||||
    assert nodes._check_initialpaths_for_relpath(session, cwd) == ""
 | 
			
		||||
 | 
			
		||||
    sub = cwd / "file"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -85,9 +84,9 @@ def test__check_initialpaths_for_relpath() -> None:
 | 
			
		|||
 | 
			
		||||
    session = cast(pytest.Session, FakeSession2)
 | 
			
		||||
 | 
			
		||||
    assert nodes._check_initialpaths_for_relpath(session, legacy_path(sub)) == "file"
 | 
			
		||||
    assert nodes._check_initialpaths_for_relpath(session, sub) == "file"
 | 
			
		||||
 | 
			
		||||
    outside = legacy_path("/outside")
 | 
			
		||||
    outside = Path("/outside")
 | 
			
		||||
    assert nodes._check_initialpaths_for_relpath(session, outside) is None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,6 @@ from typing import Union
 | 
			
		|||
import pytest
 | 
			
		||||
from _pytest._code.code import ExceptionChainRepr
 | 
			
		||||
from _pytest._code.code import ExceptionRepr
 | 
			
		||||
from _pytest.compat import legacy_path
 | 
			
		||||
from _pytest.config import Config
 | 
			
		||||
from _pytest.pytester import Pytester
 | 
			
		||||
from _pytest.reports import CollectReport
 | 
			
		||||
| 
						 | 
				
			
			@ -225,18 +224,26 @@ class TestReportSerialization:
 | 
			
		|||
                assert newrep.longrepr == str(rep.longrepr)
 | 
			
		||||
 | 
			
		||||
    def test_paths_support(self, pytester: Pytester) -> None:
 | 
			
		||||
        """Report attributes which are py.path or pathlib objects should become strings."""
 | 
			
		||||
        """Report attributes which are path-like should become strings."""
 | 
			
		||||
        pytester.makepyfile(
 | 
			
		||||
            """
 | 
			
		||||
            def test_a():
 | 
			
		||||
                assert False
 | 
			
		||||
        """
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        class MyPathLike:
 | 
			
		||||
            def __init__(self, path: str) -> None:
 | 
			
		||||
                self.path = path
 | 
			
		||||
 | 
			
		||||
            def __fspath__(self) -> str:
 | 
			
		||||
                return self.path
 | 
			
		||||
 | 
			
		||||
        reprec = pytester.inline_run()
 | 
			
		||||
        reports = reprec.getreports("pytest_runtest_logreport")
 | 
			
		||||
        assert len(reports) == 3
 | 
			
		||||
        test_a_call = reports[1]
 | 
			
		||||
        test_a_call.path1 = legacy_path(pytester.path)  # type: ignore[attr-defined]
 | 
			
		||||
        test_a_call.path1 = MyPathLike(str(pytester.path))  # type: ignore[attr-defined]
 | 
			
		||||
        test_a_call.path2 = pytester.path  # type: ignore[attr-defined]
 | 
			
		||||
        data = test_a_call._to_json()
 | 
			
		||||
        assert data["path1"] == str(pytester.path)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue