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:
commit
885d969484
|
@ -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.
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 = (
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue