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