Merge pull request #7685 from bluetech/py-to-pathlib-2

config: start migrating Config.{rootdir,inifile} from py.path.local to pathlib
This commit is contained in:
Ran Benita 2020-09-04 18:42:52 +03:00 committed by GitHub
commit 885d969484
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 204 additions and 119 deletions

View File

@ -0,0 +1,3 @@
Added two new attributes :attr:`rootpath <_pytest.config.Config.rootpath>` and :attr:`inipath <_pytest.config.Config.inipath>` to :class:`Config <_pytest.config.Config>`.
These attributes are :class:`pathlib.Path` versions of the existing :attr:`rootdir <_pytest.config.Config.rootdir>` and :attr:`inifile <_pytest.config.Config.inifile>` attributes,
and should be preferred over them when possible.

View File

@ -180,10 +180,15 @@ are never merged - the first match wins.
The internal :class:`Config <_pytest.config.Config>` object (accessible via hooks or through the :fixture:`pytestconfig` fixture)
will subsequently carry these attributes:
- ``config.rootdir``: the determined root directory, guaranteed to exist.
- :attr:`config.rootpath <_pytest.config.Config.rootpath>`: the determined root directory, guaranteed to exist.
- ``config.inifile``: the determined ``configfile``, may be ``None`` (it is named ``inifile``
for historical reasons).
- :attr:`config.inipath <_pytest.config.Config.inipath>`: the determined ``configfile``, may be ``None``
(it is named ``inipath`` for historical reasons).
.. versionadded:: 6.1
The ``config.rootpath`` and ``config.inipath`` properties. They are :class:`pathlib.Path`
versions of the older ``config.rootdir`` and ``config.inifile``, which have type
``py.path.local``, and still exist for backward compatibility.
The ``rootdir`` is used as a reference directory for constructing test
addresses ("nodeids") and can be used also by plugins for storing

View File

@ -78,7 +78,7 @@ class Cache:
@staticmethod
def cache_dir_from_config(config: Config) -> Path:
return resolve_from_str(config.getini("cache_dir"), config.rootdir)
return resolve_from_str(config.getini("cache_dir"), config.rootpath)
def warn(self, fmt: str, **args: object) -> None:
import warnings
@ -264,7 +264,7 @@ class LFPlugin:
def get_last_failed_paths(self) -> Set[Path]:
"""Return a set with all Paths()s of the previously failed nodeids."""
rootpath = Path(str(self.config.rootdir))
rootpath = self.config.rootpath
result = {rootpath / nodeid.split("::")[0] for nodeid in self.lastfailed}
return {x for x in result if x.exists()}
@ -495,7 +495,7 @@ def pytest_report_header(config: Config) -> Optional[str]:
# starting with .., ../.. if sensible
try:
displaypath = cachedir.relative_to(str(config.rootdir))
displaypath = cachedir.relative_to(config.rootpath)
except ValueError:
displaypath = cachedir
return "cachedir: {}".format(displaypath)

View File

@ -47,6 +47,7 @@ from _pytest.compat import importlib_metadata
from _pytest.compat import TYPE_CHECKING
from _pytest.outcomes import fail
from _pytest.outcomes import Skipped
from _pytest.pathlib import bestrelpath
from _pytest.pathlib import import_path
from _pytest.pathlib import ImportMode
from _pytest.pathlib import Path
@ -520,7 +521,7 @@ class PytestPluginManager(PluginManager):
else:
directory = path
# XXX these days we may rather want to use config.rootdir
# XXX these days we may rather want to use config.rootpath
# and allow users to opt into looking into the rootdir parent
# directories instead of requiring to specify confcutdir.
clist = []
@ -820,13 +821,13 @@ class Config:
:param PytestPluginManager pluginmanager:
:param InvocationParams invocation_params:
Object containing the parameters regarding the ``pytest.main``
Object containing parameters regarding the :func:`pytest.main`
invocation.
"""
@attr.s(frozen=True)
class InvocationParams:
"""Holds parameters passed during ``pytest.main()``
"""Holds parameters passed during :func:`pytest.main`.
The object attributes are read-only.
@ -841,11 +842,20 @@ class Config:
"""
args = attr.ib(type=Tuple[str, ...], converter=_args_converter)
"""Tuple of command-line arguments as passed to ``pytest.main()``."""
"""The command-line arguments as passed to :func:`pytest.main`.
:type: Tuple[str, ...]
"""
plugins = attr.ib(type=Optional[Sequence[Union[str, _PluggyPlugin]]])
"""List of extra plugins, might be `None`."""
"""Extra plugins, might be `None`.
:type: Optional[Sequence[Union[str, plugin]]]
"""
dir = attr.ib(type=Path)
"""Directory from which ``pytest.main()`` was invoked."""
"""The directory from which :func:`pytest.main` was invoked.
:type: pathlib.Path
"""
def __init__(
self,
@ -867,6 +877,10 @@ class Config:
"""
self.invocation_params = invocation_params
"""The parameters with which pytest was invoked.
:type: InvocationParams
"""
_a = FILE_OR_DIR
self._parser = Parser(
@ -876,7 +890,7 @@ class Config:
self.pluginmanager = pluginmanager
"""The plugin manager handles plugin registration and hook invocation.
:type: PytestPluginManager.
:type: PytestPluginManager
"""
self.trace = self.pluginmanager.trace.root.get("config")
@ -901,9 +915,55 @@ class Config:
@property
def invocation_dir(self) -> py.path.local:
"""Backward compatibility."""
"""The directory from which pytest was invoked.
Prefer to use :attr:`invocation_params.dir <InvocationParams.dir>`,
which is a :class:`pathlib.Path`.
:type: py.path.local
"""
return py.path.local(str(self.invocation_params.dir))
@property
def rootpath(self) -> Path:
"""The path to the :ref:`rootdir <rootdir>`.
:type: pathlib.Path
.. versionadded:: 6.1
"""
return self._rootpath
@property
def rootdir(self) -> py.path.local:
"""The path to the :ref:`rootdir <rootdir>`.
Prefer to use :attr:`rootpath`, which is a :class:`pathlib.Path`.
:type: py.path.local
"""
return py.path.local(str(self.rootpath))
@property
def inipath(self) -> Optional[Path]:
"""The path to the :ref:`configfile <configfiles>`.
:type: Optional[pathlib.Path]
.. versionadded:: 6.1
"""
return self._inipath
@property
def inifile(self) -> Optional[py.path.local]:
"""The path to the :ref:`configfile <configfiles>`.
Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`.
:type: Optional[py.path.local]
"""
return py.path.local(str(self.inipath)) if self.inipath else None
def add_cleanup(self, func: Callable[[], None]) -> None:
"""Add a function to be called when the config object gets out of
use (usually coninciding with pytest_unconfigure)."""
@ -977,9 +1037,9 @@ class Config:
def cwd_relative_nodeid(self, nodeid: str) -> str:
# nodeid's are relative to the rootpath, compute relative to cwd.
if self.invocation_dir != self.rootdir:
fullpath = self.rootdir.join(nodeid)
nodeid = self.invocation_dir.bestrelpath(fullpath)
if self.invocation_params.dir != self.rootpath:
fullpath = self.rootpath / nodeid
nodeid = bestrelpath(self.invocation_params.dir, fullpath)
return nodeid
@classmethod
@ -1014,11 +1074,11 @@ class Config:
rootdir_cmd_arg=ns.rootdir or None,
config=self,
)
self.rootdir = py.path.local(str(rootpath))
self.inifile = py.path.local(str(inipath)) if inipath else None
self._rootpath = rootpath
self._inipath = inipath
self.inicfg = inicfg
self._parser.extra_info["rootdir"] = self.rootdir
self._parser.extra_info["inifile"] = self.inifile
self._parser.extra_info["rootdir"] = str(self.rootpath)
self._parser.extra_info["inifile"] = str(self.inipath)
self._parser.addini("addopts", "extra command line options", "args")
self._parser.addini("minversion", "minimally required pytest version")
self._parser.addini(
@ -1110,8 +1170,8 @@ class Config:
self._validate_plugins()
self._warn_about_skipped_plugins()
if self.known_args_namespace.confcutdir is None and self.inifile:
confcutdir = py.path.local(self.inifile).dirname
if self.known_args_namespace.confcutdir is None and self.inipath is not None:
confcutdir = str(self.inipath.parent)
self.known_args_namespace.confcutdir = confcutdir
try:
self.hook.pytest_load_initial_conftests(
@ -1147,13 +1207,13 @@ class Config:
if not isinstance(minver, str):
raise pytest.UsageError(
"%s: 'minversion' must be a single value" % self.inifile
"%s: 'minversion' must be a single value" % self.inipath
)
if Version(minver) > Version(pytest.__version__):
raise pytest.UsageError(
"%s: 'minversion' requires pytest-%s, actual pytest-%s'"
% (self.inifile, minver, pytest.__version__,)
% (self.inipath, minver, pytest.__version__,)
)
def _validate_config_options(self) -> None:
@ -1218,10 +1278,10 @@ class Config:
args, self.option, namespace=self.option
)
if not args:
if self.invocation_dir == self.rootdir:
if self.invocation_params.dir == self.rootpath:
args = self.getini("testpaths")
if not args:
args = [str(self.invocation_dir)]
args = [str(self.invocation_params.dir)]
self.args = args
except PrintHelp:
pass
@ -1324,10 +1384,10 @@ class Config:
#
if type == "pathlist":
# TODO: This assert is probably not valid in all cases.
assert self.inifile is not None
dp = py.path.local(self.inifile).dirpath()
assert self.inipath is not None
dp = self.inipath.parent
input_values = shlex.split(value) if isinstance(value, str) else value
return [dp.join(x, abs=True) for x in input_values]
return [py.path.local(str(dp / x)) for x in input_values]
elif type == "args":
return shlex.split(value) if isinstance(value, str) else value
elif type == "linelist":

View File

@ -50,6 +50,7 @@ from _pytest.deprecated import FILLFUNCARGS
from _pytest.mark import ParameterSet
from _pytest.outcomes import fail
from _pytest.outcomes import TEST_OUTCOME
from _pytest.pathlib import absolutepath
if TYPE_CHECKING:
from typing import Deque
@ -1443,7 +1444,7 @@ class FixtureManager:
def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None:
nodeid = None
try:
p = py.path.local(plugin.__file__) # type: ignore[attr-defined]
p = absolutepath(plugin.__file__) # type: ignore[attr-defined]
except AttributeError:
pass
else:
@ -1452,8 +1453,13 @@ class FixtureManager:
# Construct the base nodeid which is later used to check
# what fixtures are visible for particular tests (as denoted
# by their test id).
if p.basename.startswith("conftest.py"):
nodeid = p.dirpath().relto(self.config.rootdir)
if p.name.startswith("conftest.py"):
try:
nodeid = str(p.parent.relative_to(self.config.rootpath))
except ValueError:
nodeid = ""
if nodeid == ".":
nodeid = ""
if os.sep != nodes.SEP:
nodeid = nodeid.replace(os.sep, nodes.SEP)

View File

@ -603,7 +603,7 @@ class LoggingPlugin:
fpath = Path(fname)
if not fpath.is_absolute():
fpath = Path(str(self._config.rootdir), fpath)
fpath = self._config.rootpath / fpath
if not fpath.parent.exists():
fpath.parent.mkdir(exist_ok=True, parents=True)

View File

@ -33,6 +33,7 @@ from _pytest.config.argparsing import Parser
from _pytest.fixtures import FixtureManager
from _pytest.outcomes import exit
from _pytest.pathlib import absolutepath
from _pytest.pathlib import bestrelpath
from _pytest.pathlib import Path
from _pytest.pathlib import visit
from _pytest.reports import CollectReport
@ -425,11 +426,11 @@ class Failed(Exception):
@attr.s
class _bestrelpath_cache(Dict[py.path.local, str]):
path = attr.ib(type=py.path.local)
class _bestrelpath_cache(Dict[Path, str]):
path = attr.ib(type=Path)
def __missing__(self, path: py.path.local) -> str:
r = self.path.bestrelpath(path) # type: str
def __missing__(self, path: Path) -> str:
r = bestrelpath(self.path, path)
self[path] = r
return r
@ -444,8 +445,8 @@ class Session(nodes.FSCollector):
exitstatus = None # type: Union[int, ExitCode]
def __init__(self, config: Config) -> None:
nodes.FSCollector.__init__(
self, config.rootdir, parent=None, config=config, session=self, nodeid=""
super().__init__(
config.rootdir, parent=None, config=config, session=self, nodeid=""
)
self.testsfailed = 0
self.testscollected = 0
@ -456,8 +457,8 @@ class Session(nodes.FSCollector):
self._initialpaths = frozenset() # type: FrozenSet[py.path.local]
self._bestrelpathcache = _bestrelpath_cache(
config.rootdir
) # type: Dict[py.path.local, str]
config.rootpath
) # type: Dict[Path, str]
self.config.pluginmanager.register(self, name="session")
@ -475,7 +476,7 @@ class Session(nodes.FSCollector):
self.testscollected,
)
def _node_location_to_relpath(self, node_path: py.path.local) -> str:
def _node_location_to_relpath(self, node_path: Path) -> str:
# bestrelpath is a quite slow function.
return self._bestrelpathcache[node_path]
@ -599,7 +600,9 @@ class Session(nodes.FSCollector):
initialpaths = [] # type: List[py.path.local]
for arg in args:
fspath, parts = resolve_collection_argument(
self.config.invocation_dir, arg, as_pypath=self.config.option.pyargs
self.config.invocation_params.dir,
arg,
as_pypath=self.config.option.pyargs,
)
self._initial_parts.append((fspath, parts))
initialpaths.append(fspath)
@ -817,7 +820,7 @@ def search_pypath(module_name: str) -> str:
def resolve_collection_argument(
invocation_dir: py.path.local, arg: str, *, as_pypath: bool = False
invocation_path: Path, arg: str, *, as_pypath: bool = False
) -> Tuple[py.path.local, List[str]]:
"""Parse path arguments optionally containing selection parts and return (fspath, names).
@ -844,7 +847,7 @@ def resolve_collection_argument(
strpath, *parts = str(arg).split("::")
if as_pypath:
strpath = search_pypath(strpath)
fspath = Path(str(invocation_dir), strpath)
fspath = invocation_path / strpath
fspath = absolutepath(fspath)
if not fspath.exists():
msg = (

View File

@ -31,6 +31,7 @@ from _pytest.mark.structures import Mark
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 Path
from _pytest.store import Store
@ -401,7 +402,7 @@ class Node(metaclass=NodeMeta):
# It will be better to just always display paths relative to invocation_dir, but
# this requires a lot of plumbing (#6428).
try:
abspath = Path(os.getcwd()) != Path(str(self.config.invocation_dir))
abspath = Path(os.getcwd()) != self.config.invocation_params.dir
except OSError:
abspath = True
@ -597,10 +598,7 @@ class Item(Node):
@cached_property
def location(self) -> Tuple[str, Optional[int], str]:
location = self.reportinfo()
if isinstance(location[0], py.path.local):
fspath = location[0]
else:
fspath = py.path.local(location[0])
fspath = absolutepath(str(location[0]))
relfspath = self.session._node_location_to_relpath(fspath)
assert type(location[2]) is str
return (relfspath, location[1], location[2])

View File

@ -366,8 +366,7 @@ def make_numbered_dir_with_cleanup(
raise e
def resolve_from_str(input: str, root: py.path.local) -> Path:
rootpath = Path(root)
def resolve_from_str(input: str, rootpath: Path) -> Path:
input = expanduser(input)
input = expandvars(input)
if isabs(input):

View File

@ -40,6 +40,9 @@ from _pytest.config import ExitCode
from _pytest.config.argparsing import Parser
from _pytest.nodes import Item
from _pytest.nodes import Node
from _pytest.pathlib import absolutepath
from _pytest.pathlib import bestrelpath
from _pytest.pathlib import Path
from _pytest.reports import BaseReport
from _pytest.reports import CollectReport
from _pytest.reports import TestReport
@ -297,9 +300,9 @@ class WarningReport:
if self.fslocation:
if isinstance(self.fslocation, tuple) and len(self.fslocation) >= 2:
filename, linenum = self.fslocation[:2]
relpath = py.path.local(filename).relto(config.invocation_dir)
if not relpath:
relpath = str(filename)
relpath = bestrelpath(
config.invocation_params.dir, absolutepath(filename)
)
return "{}:{}".format(relpath, linenum)
else:
return str(self.fslocation)
@ -319,11 +322,12 @@ class TerminalReporter:
self._main_color = None # type: Optional[str]
self._known_types = None # type: Optional[List[str]]
self.startdir = config.invocation_dir
self.startpath = config.invocation_params.dir
if file is None:
file = sys.stdout
self._tw = _pytest.config.create_terminal_writer(config, file)
self._screen_width = self._tw.fullwidth
self.currentfspath = None # type: Any
self.currentfspath = None # type: Union[None, Path, str, int]
self.reportchars = getreportopt(config)
self.hasmarkup = self._tw.hasmarkup
self.isatty = file.isatty()
@ -385,19 +389,17 @@ class TerminalReporter:
return char in self.reportchars
def write_fspath_result(self, nodeid: str, res, **markup: bool) -> None:
fspath = self.config.rootdir.join(nodeid.split("::")[0])
# NOTE: explicitly check for None to work around py bug, and for less
# overhead in general (https://github.com/pytest-dev/py/pull/207).
fspath = self.config.rootpath / nodeid.split("::")[0]
if self.currentfspath is None or fspath != self.currentfspath:
if self.currentfspath is not None and self._show_progress_info:
self._write_progress_information_filling_space()
self.currentfspath = fspath
relfspath = self.startdir.bestrelpath(fspath)
relfspath = bestrelpath(self.startpath, fspath)
self._tw.line()
self._tw.write(relfspath + " ")
self._tw.write(res, flush=True, **markup)
def write_ensure_prefix(self, prefix, extra: str = "", **kwargs) -> None:
def write_ensure_prefix(self, prefix: str, extra: str = "", **kwargs) -> None:
if self.currentfspath != prefix:
self._tw.line()
self.currentfspath = prefix
@ -709,14 +711,14 @@ class TerminalReporter:
self.write_line(line)
def pytest_report_header(self, config: Config) -> List[str]:
line = "rootdir: %s" % config.rootdir
line = "rootdir: %s" % config.rootpath
if config.inifile:
line += ", configfile: " + config.rootdir.bestrelpath(config.inifile)
if config.inipath:
line += ", configfile: " + bestrelpath(config.rootpath, config.inipath)
testpaths = config.getini("testpaths")
if testpaths and config.args == testpaths:
rel_paths = [config.rootdir.bestrelpath(x) for x in testpaths]
rel_paths = [bestrelpath(config.rootpath, x) for x in testpaths]
line += ", testpaths: {}".format(", ".join(rel_paths))
result = [line]
@ -860,7 +862,7 @@ class TerminalReporter:
if self.verbosity >= 2 and nodeid.split("::")[0] != fspath.replace(
"\\", nodes.SEP
):
res += " <- " + self.startdir.bestrelpath(fspath)
res += " <- " + bestrelpath(self.startpath, fspath)
else:
res = "[location]"
return res + " "
@ -1102,7 +1104,7 @@ class TerminalReporter:
def show_skipped(lines: List[str]) -> None:
skipped = self.stats.get("skipped", []) # type: List[CollectReport]
fskips = _folded_skips(self.startdir, skipped) if skipped else []
fskips = _folded_skips(self.startpath, skipped) if skipped else []
if not fskips:
return
verbose_word = skipped[0]._get_verbose_word(self.config)
@ -1230,7 +1232,7 @@ def _get_line_with_reprcrash_message(
def _folded_skips(
startdir: py.path.local, skipped: Sequence[CollectReport],
startpath: Path, skipped: Sequence[CollectReport],
) -> List[Tuple[int, str, Optional[int], str]]:
d = {} # type: Dict[Tuple[str, Optional[int], str], List[CollectReport]]
for event in skipped:
@ -1239,7 +1241,7 @@ def _folded_skips(
assert len(event.longrepr) == 3, (event, event.longrepr)
fspath, lineno, reason = event.longrepr
# For consistency, report all fspaths in relative form.
fspath = startdir.bestrelpath(py.path.local(fspath))
fspath = bestrelpath(startpath, Path(fspath))
keywords = getattr(event, "keywords", {})
# Folding reports with global pytestmark variable.
# This is a workaround, because for now we cannot identify the scope of a skip marker

View File

@ -10,6 +10,7 @@ from _pytest.config import ExitCode
from _pytest.config import UsageError
from _pytest.main import resolve_collection_argument
from _pytest.main import validate_basetemp
from _pytest.pathlib import Path
from _pytest.pytester import Testdir
@ -108,73 +109,79 @@ def test_validate_basetemp_integration(testdir):
class TestResolveCollectionArgument:
@pytest.fixture
def root(self, testdir):
def invocation_dir(self, testdir: Testdir) -> py.path.local:
testdir.syspathinsert(str(testdir.tmpdir / "src"))
testdir.chdir()
pkg = testdir.tmpdir.join("src/pkg").ensure_dir()
pkg.join("__init__.py").ensure(file=True)
pkg.join("test.py").ensure(file=True)
pkg.join("__init__.py").ensure()
pkg.join("test.py").ensure()
return testdir.tmpdir
def test_file(self, root):
@pytest.fixture
def invocation_path(self, invocation_dir: py.path.local) -> Path:
return Path(str(invocation_dir))
def test_file(self, invocation_dir: py.path.local, invocation_path: Path) -> None:
"""File and parts."""
assert resolve_collection_argument(root, "src/pkg/test.py") == (
root / "src/pkg/test.py",
assert resolve_collection_argument(invocation_path, "src/pkg/test.py") == (
invocation_dir / "src/pkg/test.py",
[],
)
assert resolve_collection_argument(root, "src/pkg/test.py::") == (
root / "src/pkg/test.py",
assert resolve_collection_argument(invocation_path, "src/pkg/test.py::") == (
invocation_dir / "src/pkg/test.py",
[""],
)
assert resolve_collection_argument(root, "src/pkg/test.py::foo::bar") == (
root / "src/pkg/test.py",
["foo", "bar"],
)
assert resolve_collection_argument(root, "src/pkg/test.py::foo::bar::") == (
root / "src/pkg/test.py",
["foo", "bar", ""],
)
assert resolve_collection_argument(
invocation_path, "src/pkg/test.py::foo::bar"
) == (invocation_dir / "src/pkg/test.py", ["foo", "bar"])
assert resolve_collection_argument(
invocation_path, "src/pkg/test.py::foo::bar::"
) == (invocation_dir / "src/pkg/test.py", ["foo", "bar", ""])
def test_dir(self, root: py.path.local) -> None:
def test_dir(self, invocation_dir: py.path.local, invocation_path: Path) -> None:
"""Directory and parts."""
assert resolve_collection_argument(root, "src/pkg") == (root / "src/pkg", [])
with pytest.raises(
UsageError, match=r"directory argument cannot contain :: selection parts"
):
resolve_collection_argument(root, "src/pkg::")
with pytest.raises(
UsageError, match=r"directory argument cannot contain :: selection parts"
):
resolve_collection_argument(root, "src/pkg::foo::bar")
def test_pypath(self, root: py.path.local) -> None:
"""Dotted name and parts."""
assert resolve_collection_argument(root, "pkg.test", as_pypath=True) == (
root / "src/pkg/test.py",
assert resolve_collection_argument(invocation_path, "src/pkg") == (
invocation_dir / "src/pkg",
[],
)
with pytest.raises(
UsageError, match=r"directory argument cannot contain :: selection parts"
):
resolve_collection_argument(invocation_path, "src/pkg::")
with pytest.raises(
UsageError, match=r"directory argument cannot contain :: selection parts"
):
resolve_collection_argument(invocation_path, "src/pkg::foo::bar")
def test_pypath(self, invocation_dir: py.path.local, invocation_path: Path) -> None:
"""Dotted name and parts."""
assert resolve_collection_argument(
root, "pkg.test::foo::bar", as_pypath=True
) == (root / "src/pkg/test.py", ["foo", "bar"],)
assert resolve_collection_argument(root, "pkg", as_pypath=True) == (
root / "src/pkg",
invocation_path, "pkg.test", as_pypath=True
) == (invocation_dir / "src/pkg/test.py", [])
assert resolve_collection_argument(
invocation_path, "pkg.test::foo::bar", as_pypath=True
) == (invocation_dir / "src/pkg/test.py", ["foo", "bar"])
assert resolve_collection_argument(invocation_path, "pkg", as_pypath=True) == (
invocation_dir / "src/pkg",
[],
)
with pytest.raises(
UsageError, match=r"package argument cannot contain :: selection parts"
):
resolve_collection_argument(root, "pkg::foo::bar", as_pypath=True)
resolve_collection_argument(
invocation_path, "pkg::foo::bar", as_pypath=True
)
def test_does_not_exist(self, root):
def test_does_not_exist(self, invocation_path: Path) -> None:
"""Given a file/module that does not exist raises UsageError."""
with pytest.raises(
UsageError, match=re.escape("file or directory not found: foobar")
):
resolve_collection_argument(root, "foobar")
resolve_collection_argument(invocation_path, "foobar")
with pytest.raises(
UsageError,
@ -182,12 +189,14 @@ class TestResolveCollectionArgument:
"module or package not found: foobar (missing __init__.py?)"
),
):
resolve_collection_argument(root, "foobar", as_pypath=True)
resolve_collection_argument(invocation_path, "foobar", as_pypath=True)
def test_absolute_paths_are_resolved_correctly(self, root):
def test_absolute_paths_are_resolved_correctly(
self, invocation_dir: py.path.local, invocation_path: Path
) -> None:
"""Absolute paths resolve back to absolute paths."""
full_path = str(root / "src")
assert resolve_collection_argument(root, full_path) == (
full_path = str(invocation_dir / "src")
assert resolve_collection_argument(invocation_path, full_path) == (
py.path.local(os.path.abspath("src")),
[],
)
@ -195,10 +204,9 @@ class TestResolveCollectionArgument:
# ensure full paths given in the command-line without the drive letter resolve
# to the full path correctly (#7628)
drive, full_path_without_drive = os.path.splitdrive(full_path)
assert resolve_collection_argument(root, full_path_without_drive) == (
py.path.local(os.path.abspath("src")),
[],
)
assert resolve_collection_argument(
invocation_path, full_path_without_drive
) == (py.path.local(os.path.abspath("src")), [])
def test_module_full_path_without_drive(testdir):

View File

@ -18,6 +18,7 @@ import pytest
from _pytest._io.wcwidth import wcswidth
from _pytest.config import Config
from _pytest.config import ExitCode
from _pytest.pathlib import Path
from _pytest.pytester import Testdir
from _pytest.reports import BaseReport
from _pytest.reports import CollectReport
@ -2085,7 +2086,7 @@ def test_skip_reasons_folding() -> None:
ev3.longrepr = longrepr
ev3.skipped = True
values = _folded_skips(py.path.local(), [ev1, ev2, ev3])
values = _folded_skips(Path.cwd(), [ev1, ev2, ev3])
assert len(values) == 1
num, fspath, lineno_, reason = values[0]
assert num == 3