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)
 | 
					The internal :class:`Config <_pytest.config.Config>` object (accessible via hooks or through the :fixture:`pytestconfig` fixture)
 | 
				
			||||||
will subsequently carry these attributes:
 | 
					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``
 | 
					- :attr:`config.inipath <_pytest.config.Config.inipath>`: the determined ``configfile``, may be ``None``
 | 
				
			||||||
  for historical reasons).
 | 
					  (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
 | 
					The ``rootdir`` is used as a reference directory for constructing test
 | 
				
			||||||
addresses ("nodeids") and can be used also by plugins for storing
 | 
					addresses ("nodeids") and can be used also by plugins for storing
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -78,7 +78,7 @@ class Cache:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def cache_dir_from_config(config: Config) -> Path:
 | 
					    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:
 | 
					    def warn(self, fmt: str, **args: object) -> None:
 | 
				
			||||||
        import warnings
 | 
					        import warnings
 | 
				
			||||||
| 
						 | 
					@ -264,7 +264,7 @@ class LFPlugin:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_last_failed_paths(self) -> Set[Path]:
 | 
					    def get_last_failed_paths(self) -> Set[Path]:
 | 
				
			||||||
        """Return a set with all Paths()s of the previously failed nodeids."""
 | 
					        """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}
 | 
					        result = {rootpath / nodeid.split("::")[0] for nodeid in self.lastfailed}
 | 
				
			||||||
        return {x for x in result if x.exists()}
 | 
					        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
 | 
					        # starting with .., ../.. if sensible
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            displaypath = cachedir.relative_to(str(config.rootdir))
 | 
					            displaypath = cachedir.relative_to(config.rootpath)
 | 
				
			||||||
        except ValueError:
 | 
					        except ValueError:
 | 
				
			||||||
            displaypath = cachedir
 | 
					            displaypath = cachedir
 | 
				
			||||||
        return "cachedir: {}".format(displaypath)
 | 
					        return "cachedir: {}".format(displaypath)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -47,6 +47,7 @@ from _pytest.compat import importlib_metadata
 | 
				
			||||||
from _pytest.compat import TYPE_CHECKING
 | 
					from _pytest.compat import TYPE_CHECKING
 | 
				
			||||||
from _pytest.outcomes import fail
 | 
					from _pytest.outcomes import fail
 | 
				
			||||||
from _pytest.outcomes import Skipped
 | 
					from _pytest.outcomes import Skipped
 | 
				
			||||||
 | 
					from _pytest.pathlib import bestrelpath
 | 
				
			||||||
from _pytest.pathlib import import_path
 | 
					from _pytest.pathlib import import_path
 | 
				
			||||||
from _pytest.pathlib import ImportMode
 | 
					from _pytest.pathlib import ImportMode
 | 
				
			||||||
from _pytest.pathlib import Path
 | 
					from _pytest.pathlib import Path
 | 
				
			||||||
| 
						 | 
					@ -520,7 +521,7 @@ class PytestPluginManager(PluginManager):
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            directory = path
 | 
					            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
 | 
					        # and allow users to opt into looking into the rootdir parent
 | 
				
			||||||
        # directories instead of requiring to specify confcutdir.
 | 
					        # directories instead of requiring to specify confcutdir.
 | 
				
			||||||
        clist = []
 | 
					        clist = []
 | 
				
			||||||
| 
						 | 
					@ -820,13 +821,13 @@ class Config:
 | 
				
			||||||
    :param PytestPluginManager pluginmanager:
 | 
					    :param PytestPluginManager pluginmanager:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :param InvocationParams invocation_params:
 | 
					    :param InvocationParams invocation_params:
 | 
				
			||||||
        Object containing the parameters regarding the ``pytest.main``
 | 
					        Object containing parameters regarding the :func:`pytest.main`
 | 
				
			||||||
        invocation.
 | 
					        invocation.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @attr.s(frozen=True)
 | 
					    @attr.s(frozen=True)
 | 
				
			||||||
    class InvocationParams:
 | 
					    class InvocationParams:
 | 
				
			||||||
        """Holds parameters passed during ``pytest.main()``
 | 
					        """Holds parameters passed during :func:`pytest.main`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        The object attributes are read-only.
 | 
					        The object attributes are read-only.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -841,11 +842,20 @@ class Config:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        args = attr.ib(type=Tuple[str, ...], converter=_args_converter)
 | 
					        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]]])
 | 
					        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)
 | 
					        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__(
 | 
					    def __init__(
 | 
				
			||||||
        self,
 | 
					        self,
 | 
				
			||||||
| 
						 | 
					@ -867,6 +877,10 @@ class Config:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.invocation_params = invocation_params
 | 
					        self.invocation_params = invocation_params
 | 
				
			||||||
 | 
					        """The parameters with which pytest was invoked.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :type: InvocationParams
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        _a = FILE_OR_DIR
 | 
					        _a = FILE_OR_DIR
 | 
				
			||||||
        self._parser = Parser(
 | 
					        self._parser = Parser(
 | 
				
			||||||
| 
						 | 
					@ -876,7 +890,7 @@ class Config:
 | 
				
			||||||
        self.pluginmanager = pluginmanager
 | 
					        self.pluginmanager = pluginmanager
 | 
				
			||||||
        """The plugin manager handles plugin registration and hook invocation.
 | 
					        """The plugin manager handles plugin registration and hook invocation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :type: PytestPluginManager.
 | 
					        :type: PytestPluginManager
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.trace = self.pluginmanager.trace.root.get("config")
 | 
					        self.trace = self.pluginmanager.trace.root.get("config")
 | 
				
			||||||
| 
						 | 
					@ -901,9 +915,55 @@ class Config:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def invocation_dir(self) -> py.path.local:
 | 
					    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))
 | 
					        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:
 | 
					    def add_cleanup(self, func: Callable[[], None]) -> None:
 | 
				
			||||||
        """Add a function to be called when the config object gets out of
 | 
					        """Add a function to be called when the config object gets out of
 | 
				
			||||||
        use (usually coninciding with pytest_unconfigure)."""
 | 
					        use (usually coninciding with pytest_unconfigure)."""
 | 
				
			||||||
| 
						 | 
					@ -977,9 +1037,9 @@ class Config:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def cwd_relative_nodeid(self, nodeid: str) -> str:
 | 
					    def cwd_relative_nodeid(self, nodeid: str) -> str:
 | 
				
			||||||
        # nodeid's are relative to the rootpath, compute relative to cwd.
 | 
					        # nodeid's are relative to the rootpath, compute relative to cwd.
 | 
				
			||||||
        if self.invocation_dir != self.rootdir:
 | 
					        if self.invocation_params.dir != self.rootpath:
 | 
				
			||||||
            fullpath = self.rootdir.join(nodeid)
 | 
					            fullpath = self.rootpath / nodeid
 | 
				
			||||||
            nodeid = self.invocation_dir.bestrelpath(fullpath)
 | 
					            nodeid = bestrelpath(self.invocation_params.dir, fullpath)
 | 
				
			||||||
        return nodeid
 | 
					        return nodeid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
| 
						 | 
					@ -1014,11 +1074,11 @@ class Config:
 | 
				
			||||||
            rootdir_cmd_arg=ns.rootdir or None,
 | 
					            rootdir_cmd_arg=ns.rootdir or None,
 | 
				
			||||||
            config=self,
 | 
					            config=self,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self.rootdir = py.path.local(str(rootpath))
 | 
					        self._rootpath = rootpath
 | 
				
			||||||
        self.inifile = py.path.local(str(inipath)) if inipath else None
 | 
					        self._inipath = inipath
 | 
				
			||||||
        self.inicfg = inicfg
 | 
					        self.inicfg = inicfg
 | 
				
			||||||
        self._parser.extra_info["rootdir"] = self.rootdir
 | 
					        self._parser.extra_info["rootdir"] = str(self.rootpath)
 | 
				
			||||||
        self._parser.extra_info["inifile"] = self.inifile
 | 
					        self._parser.extra_info["inifile"] = str(self.inipath)
 | 
				
			||||||
        self._parser.addini("addopts", "extra command line options", "args")
 | 
					        self._parser.addini("addopts", "extra command line options", "args")
 | 
				
			||||||
        self._parser.addini("minversion", "minimally required pytest version")
 | 
					        self._parser.addini("minversion", "minimally required pytest version")
 | 
				
			||||||
        self._parser.addini(
 | 
					        self._parser.addini(
 | 
				
			||||||
| 
						 | 
					@ -1110,8 +1170,8 @@ class Config:
 | 
				
			||||||
        self._validate_plugins()
 | 
					        self._validate_plugins()
 | 
				
			||||||
        self._warn_about_skipped_plugins()
 | 
					        self._warn_about_skipped_plugins()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.known_args_namespace.confcutdir is None and self.inifile:
 | 
					        if self.known_args_namespace.confcutdir is None and self.inipath is not None:
 | 
				
			||||||
            confcutdir = py.path.local(self.inifile).dirname
 | 
					            confcutdir = str(self.inipath.parent)
 | 
				
			||||||
            self.known_args_namespace.confcutdir = confcutdir
 | 
					            self.known_args_namespace.confcutdir = confcutdir
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            self.hook.pytest_load_initial_conftests(
 | 
					            self.hook.pytest_load_initial_conftests(
 | 
				
			||||||
| 
						 | 
					@ -1147,13 +1207,13 @@ class Config:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if not isinstance(minver, str):
 | 
					            if not isinstance(minver, str):
 | 
				
			||||||
                raise pytest.UsageError(
 | 
					                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__):
 | 
					            if Version(minver) > Version(pytest.__version__):
 | 
				
			||||||
                raise pytest.UsageError(
 | 
					                raise pytest.UsageError(
 | 
				
			||||||
                    "%s: 'minversion' requires pytest-%s, actual pytest-%s'"
 | 
					                    "%s: 'minversion' requires pytest-%s, actual pytest-%s'"
 | 
				
			||||||
                    % (self.inifile, minver, pytest.__version__,)
 | 
					                    % (self.inipath, minver, pytest.__version__,)
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _validate_config_options(self) -> None:
 | 
					    def _validate_config_options(self) -> None:
 | 
				
			||||||
| 
						 | 
					@ -1218,10 +1278,10 @@ class Config:
 | 
				
			||||||
                args, self.option, namespace=self.option
 | 
					                args, self.option, namespace=self.option
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            if not args:
 | 
					            if not args:
 | 
				
			||||||
                if self.invocation_dir == self.rootdir:
 | 
					                if self.invocation_params.dir == self.rootpath:
 | 
				
			||||||
                    args = self.getini("testpaths")
 | 
					                    args = self.getini("testpaths")
 | 
				
			||||||
                if not args:
 | 
					                if not args:
 | 
				
			||||||
                    args = [str(self.invocation_dir)]
 | 
					                    args = [str(self.invocation_params.dir)]
 | 
				
			||||||
            self.args = args
 | 
					            self.args = args
 | 
				
			||||||
        except PrintHelp:
 | 
					        except PrintHelp:
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
| 
						 | 
					@ -1324,10 +1384,10 @@ class Config:
 | 
				
			||||||
        #
 | 
					        #
 | 
				
			||||||
        if type == "pathlist":
 | 
					        if type == "pathlist":
 | 
				
			||||||
            # TODO: This assert is probably not valid in all cases.
 | 
					            # TODO: This assert is probably not valid in all cases.
 | 
				
			||||||
            assert self.inifile is not None
 | 
					            assert self.inipath is not None
 | 
				
			||||||
            dp = py.path.local(self.inifile).dirpath()
 | 
					            dp = self.inipath.parent
 | 
				
			||||||
            input_values = shlex.split(value) if isinstance(value, str) else value
 | 
					            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":
 | 
					        elif type == "args":
 | 
				
			||||||
            return shlex.split(value) if isinstance(value, str) else value
 | 
					            return shlex.split(value) if isinstance(value, str) else value
 | 
				
			||||||
        elif type == "linelist":
 | 
					        elif type == "linelist":
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -50,6 +50,7 @@ from _pytest.deprecated import FILLFUNCARGS
 | 
				
			||||||
from _pytest.mark import ParameterSet
 | 
					from _pytest.mark import ParameterSet
 | 
				
			||||||
from _pytest.outcomes import fail
 | 
					from _pytest.outcomes import fail
 | 
				
			||||||
from _pytest.outcomes import TEST_OUTCOME
 | 
					from _pytest.outcomes import TEST_OUTCOME
 | 
				
			||||||
 | 
					from _pytest.pathlib import absolutepath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if TYPE_CHECKING:
 | 
					if TYPE_CHECKING:
 | 
				
			||||||
    from typing import Deque
 | 
					    from typing import Deque
 | 
				
			||||||
| 
						 | 
					@ -1443,7 +1444,7 @@ class FixtureManager:
 | 
				
			||||||
    def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None:
 | 
					    def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None:
 | 
				
			||||||
        nodeid = None
 | 
					        nodeid = None
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            p = py.path.local(plugin.__file__)  # type: ignore[attr-defined]
 | 
					            p = absolutepath(plugin.__file__)  # type: ignore[attr-defined]
 | 
				
			||||||
        except AttributeError:
 | 
					        except AttributeError:
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
| 
						 | 
					@ -1452,8 +1453,13 @@ class FixtureManager:
 | 
				
			||||||
            # Construct the base nodeid which is later used to check
 | 
					            # Construct the base nodeid which is later used to check
 | 
				
			||||||
            # what fixtures are visible for particular tests (as denoted
 | 
					            # what fixtures are visible for particular tests (as denoted
 | 
				
			||||||
            # by their test id).
 | 
					            # by their test id).
 | 
				
			||||||
            if p.basename.startswith("conftest.py"):
 | 
					            if p.name.startswith("conftest.py"):
 | 
				
			||||||
                nodeid = p.dirpath().relto(self.config.rootdir)
 | 
					                try:
 | 
				
			||||||
 | 
					                    nodeid = str(p.parent.relative_to(self.config.rootpath))
 | 
				
			||||||
 | 
					                except ValueError:
 | 
				
			||||||
 | 
					                    nodeid = ""
 | 
				
			||||||
 | 
					                if nodeid == ".":
 | 
				
			||||||
 | 
					                    nodeid = ""
 | 
				
			||||||
                if os.sep != nodes.SEP:
 | 
					                if os.sep != nodes.SEP:
 | 
				
			||||||
                    nodeid = nodeid.replace(os.sep, nodes.SEP)
 | 
					                    nodeid = nodeid.replace(os.sep, nodes.SEP)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -603,7 +603,7 @@ class LoggingPlugin:
 | 
				
			||||||
        fpath = Path(fname)
 | 
					        fpath = Path(fname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not fpath.is_absolute():
 | 
					        if not fpath.is_absolute():
 | 
				
			||||||
            fpath = Path(str(self._config.rootdir), fpath)
 | 
					            fpath = self._config.rootpath / fpath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not fpath.parent.exists():
 | 
					        if not fpath.parent.exists():
 | 
				
			||||||
            fpath.parent.mkdir(exist_ok=True, parents=True)
 | 
					            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.fixtures import FixtureManager
 | 
				
			||||||
from _pytest.outcomes import exit
 | 
					from _pytest.outcomes import exit
 | 
				
			||||||
from _pytest.pathlib import absolutepath
 | 
					from _pytest.pathlib import absolutepath
 | 
				
			||||||
 | 
					from _pytest.pathlib import bestrelpath
 | 
				
			||||||
from _pytest.pathlib import Path
 | 
					from _pytest.pathlib import Path
 | 
				
			||||||
from _pytest.pathlib import visit
 | 
					from _pytest.pathlib import visit
 | 
				
			||||||
from _pytest.reports import CollectReport
 | 
					from _pytest.reports import CollectReport
 | 
				
			||||||
| 
						 | 
					@ -425,11 +426,11 @@ class Failed(Exception):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@attr.s
 | 
					@attr.s
 | 
				
			||||||
class _bestrelpath_cache(Dict[py.path.local, str]):
 | 
					class _bestrelpath_cache(Dict[Path, str]):
 | 
				
			||||||
    path = attr.ib(type=py.path.local)
 | 
					    path = attr.ib(type=Path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __missing__(self, path: py.path.local) -> str:
 | 
					    def __missing__(self, path: Path) -> str:
 | 
				
			||||||
        r = self.path.bestrelpath(path)  # type: str
 | 
					        r = bestrelpath(self.path, path)
 | 
				
			||||||
        self[path] = r
 | 
					        self[path] = r
 | 
				
			||||||
        return r
 | 
					        return r
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -444,8 +445,8 @@ class Session(nodes.FSCollector):
 | 
				
			||||||
    exitstatus = None  # type: Union[int, ExitCode]
 | 
					    exitstatus = None  # type: Union[int, ExitCode]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, config: Config) -> None:
 | 
					    def __init__(self, config: Config) -> None:
 | 
				
			||||||
        nodes.FSCollector.__init__(
 | 
					        super().__init__(
 | 
				
			||||||
            self, config.rootdir, parent=None, config=config, session=self, nodeid=""
 | 
					            config.rootdir, parent=None, config=config, session=self, nodeid=""
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self.testsfailed = 0
 | 
					        self.testsfailed = 0
 | 
				
			||||||
        self.testscollected = 0
 | 
					        self.testscollected = 0
 | 
				
			||||||
| 
						 | 
					@ -456,8 +457,8 @@ class Session(nodes.FSCollector):
 | 
				
			||||||
        self._initialpaths = frozenset()  # type: FrozenSet[py.path.local]
 | 
					        self._initialpaths = frozenset()  # type: FrozenSet[py.path.local]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._bestrelpathcache = _bestrelpath_cache(
 | 
					        self._bestrelpathcache = _bestrelpath_cache(
 | 
				
			||||||
            config.rootdir
 | 
					            config.rootpath
 | 
				
			||||||
        )  # type: Dict[py.path.local, str]
 | 
					        )  # type: Dict[Path, str]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.config.pluginmanager.register(self, name="session")
 | 
					        self.config.pluginmanager.register(self, name="session")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -475,7 +476,7 @@ class Session(nodes.FSCollector):
 | 
				
			||||||
            self.testscollected,
 | 
					            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.
 | 
					        # bestrelpath is a quite slow function.
 | 
				
			||||||
        return self._bestrelpathcache[node_path]
 | 
					        return self._bestrelpathcache[node_path]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -599,7 +600,9 @@ class Session(nodes.FSCollector):
 | 
				
			||||||
            initialpaths = []  # type: List[py.path.local]
 | 
					            initialpaths = []  # type: List[py.path.local]
 | 
				
			||||||
            for arg in args:
 | 
					            for arg in args:
 | 
				
			||||||
                fspath, parts = resolve_collection_argument(
 | 
					                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))
 | 
					                self._initial_parts.append((fspath, parts))
 | 
				
			||||||
                initialpaths.append(fspath)
 | 
					                initialpaths.append(fspath)
 | 
				
			||||||
| 
						 | 
					@ -817,7 +820,7 @@ def search_pypath(module_name: str) -> str:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def resolve_collection_argument(
 | 
					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]]:
 | 
					) -> Tuple[py.path.local, List[str]]:
 | 
				
			||||||
    """Parse path arguments optionally containing selection parts and return (fspath, names).
 | 
					    """Parse path arguments optionally containing selection parts and return (fspath, names).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -844,7 +847,7 @@ def resolve_collection_argument(
 | 
				
			||||||
    strpath, *parts = str(arg).split("::")
 | 
					    strpath, *parts = str(arg).split("::")
 | 
				
			||||||
    if as_pypath:
 | 
					    if as_pypath:
 | 
				
			||||||
        strpath = search_pypath(strpath)
 | 
					        strpath = search_pypath(strpath)
 | 
				
			||||||
    fspath = Path(str(invocation_dir), strpath)
 | 
					    fspath = invocation_path / strpath
 | 
				
			||||||
    fspath = absolutepath(fspath)
 | 
					    fspath = absolutepath(fspath)
 | 
				
			||||||
    if not fspath.exists():
 | 
					    if not fspath.exists():
 | 
				
			||||||
        msg = (
 | 
					        msg = (
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,6 +31,7 @@ from _pytest.mark.structures import Mark
 | 
				
			||||||
from _pytest.mark.structures import MarkDecorator
 | 
					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 Path
 | 
					from _pytest.pathlib import Path
 | 
				
			||||||
from _pytest.store import Store
 | 
					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
 | 
					        # It will be better to just always display paths relative to invocation_dir, but
 | 
				
			||||||
        # this requires a lot of plumbing (#6428).
 | 
					        # this requires a lot of plumbing (#6428).
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            abspath = Path(os.getcwd()) != Path(str(self.config.invocation_dir))
 | 
					            abspath = Path(os.getcwd()) != self.config.invocation_params.dir
 | 
				
			||||||
        except OSError:
 | 
					        except OSError:
 | 
				
			||||||
            abspath = True
 | 
					            abspath = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -597,10 +598,7 @@ class Item(Node):
 | 
				
			||||||
    @cached_property
 | 
					    @cached_property
 | 
				
			||||||
    def location(self) -> Tuple[str, Optional[int], str]:
 | 
					    def location(self) -> Tuple[str, Optional[int], str]:
 | 
				
			||||||
        location = self.reportinfo()
 | 
					        location = self.reportinfo()
 | 
				
			||||||
        if isinstance(location[0], py.path.local):
 | 
					        fspath = absolutepath(str(location[0]))
 | 
				
			||||||
            fspath = location[0]
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            fspath = py.path.local(location[0])
 | 
					 | 
				
			||||||
        relfspath = self.session._node_location_to_relpath(fspath)
 | 
					        relfspath = self.session._node_location_to_relpath(fspath)
 | 
				
			||||||
        assert type(location[2]) is str
 | 
					        assert type(location[2]) is str
 | 
				
			||||||
        return (relfspath, location[1], location[2])
 | 
					        return (relfspath, location[1], location[2])
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -366,8 +366,7 @@ def make_numbered_dir_with_cleanup(
 | 
				
			||||||
    raise e
 | 
					    raise e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def resolve_from_str(input: str, root: py.path.local) -> Path:
 | 
					def resolve_from_str(input: str, rootpath: Path) -> Path:
 | 
				
			||||||
    rootpath = Path(root)
 | 
					 | 
				
			||||||
    input = expanduser(input)
 | 
					    input = expanduser(input)
 | 
				
			||||||
    input = expandvars(input)
 | 
					    input = expandvars(input)
 | 
				
			||||||
    if isabs(input):
 | 
					    if isabs(input):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,6 +40,9 @@ from _pytest.config import ExitCode
 | 
				
			||||||
from _pytest.config.argparsing import Parser
 | 
					from _pytest.config.argparsing import Parser
 | 
				
			||||||
from _pytest.nodes import Item
 | 
					from _pytest.nodes import Item
 | 
				
			||||||
from _pytest.nodes import Node
 | 
					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 BaseReport
 | 
				
			||||||
from _pytest.reports import CollectReport
 | 
					from _pytest.reports import CollectReport
 | 
				
			||||||
from _pytest.reports import TestReport
 | 
					from _pytest.reports import TestReport
 | 
				
			||||||
| 
						 | 
					@ -297,9 +300,9 @@ class WarningReport:
 | 
				
			||||||
        if self.fslocation:
 | 
					        if self.fslocation:
 | 
				
			||||||
            if isinstance(self.fslocation, tuple) and len(self.fslocation) >= 2:
 | 
					            if isinstance(self.fslocation, tuple) and len(self.fslocation) >= 2:
 | 
				
			||||||
                filename, linenum = self.fslocation[:2]
 | 
					                filename, linenum = self.fslocation[:2]
 | 
				
			||||||
                relpath = py.path.local(filename).relto(config.invocation_dir)
 | 
					                relpath = bestrelpath(
 | 
				
			||||||
                if not relpath:
 | 
					                    config.invocation_params.dir, absolutepath(filename)
 | 
				
			||||||
                    relpath = str(filename)
 | 
					                )
 | 
				
			||||||
                return "{}:{}".format(relpath, linenum)
 | 
					                return "{}:{}".format(relpath, linenum)
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                return str(self.fslocation)
 | 
					                return str(self.fslocation)
 | 
				
			||||||
| 
						 | 
					@ -319,11 +322,12 @@ class TerminalReporter:
 | 
				
			||||||
        self._main_color = None  # type: Optional[str]
 | 
					        self._main_color = None  # type: Optional[str]
 | 
				
			||||||
        self._known_types = None  # type: Optional[List[str]]
 | 
					        self._known_types = None  # type: Optional[List[str]]
 | 
				
			||||||
        self.startdir = config.invocation_dir
 | 
					        self.startdir = config.invocation_dir
 | 
				
			||||||
 | 
					        self.startpath = config.invocation_params.dir
 | 
				
			||||||
        if file is None:
 | 
					        if file is None:
 | 
				
			||||||
            file = sys.stdout
 | 
					            file = sys.stdout
 | 
				
			||||||
        self._tw = _pytest.config.create_terminal_writer(config, file)
 | 
					        self._tw = _pytest.config.create_terminal_writer(config, file)
 | 
				
			||||||
        self._screen_width = self._tw.fullwidth
 | 
					        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.reportchars = getreportopt(config)
 | 
				
			||||||
        self.hasmarkup = self._tw.hasmarkup
 | 
					        self.hasmarkup = self._tw.hasmarkup
 | 
				
			||||||
        self.isatty = file.isatty()
 | 
					        self.isatty = file.isatty()
 | 
				
			||||||
| 
						 | 
					@ -385,19 +389,17 @@ class TerminalReporter:
 | 
				
			||||||
        return char in self.reportchars
 | 
					        return char in self.reportchars
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def write_fspath_result(self, nodeid: str, res, **markup: bool) -> None:
 | 
					    def write_fspath_result(self, nodeid: str, res, **markup: bool) -> None:
 | 
				
			||||||
        fspath = self.config.rootdir.join(nodeid.split("::")[0])
 | 
					        fspath = self.config.rootpath / 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).
 | 
					 | 
				
			||||||
        if self.currentfspath is None or fspath != self.currentfspath:
 | 
					        if self.currentfspath is None or fspath != self.currentfspath:
 | 
				
			||||||
            if self.currentfspath is not None and self._show_progress_info:
 | 
					            if self.currentfspath is not None and self._show_progress_info:
 | 
				
			||||||
                self._write_progress_information_filling_space()
 | 
					                self._write_progress_information_filling_space()
 | 
				
			||||||
            self.currentfspath = fspath
 | 
					            self.currentfspath = fspath
 | 
				
			||||||
            relfspath = self.startdir.bestrelpath(fspath)
 | 
					            relfspath = bestrelpath(self.startpath, fspath)
 | 
				
			||||||
            self._tw.line()
 | 
					            self._tw.line()
 | 
				
			||||||
            self._tw.write(relfspath + " ")
 | 
					            self._tw.write(relfspath + " ")
 | 
				
			||||||
        self._tw.write(res, flush=True, **markup)
 | 
					        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:
 | 
					        if self.currentfspath != prefix:
 | 
				
			||||||
            self._tw.line()
 | 
					            self._tw.line()
 | 
				
			||||||
            self.currentfspath = prefix
 | 
					            self.currentfspath = prefix
 | 
				
			||||||
| 
						 | 
					@ -709,14 +711,14 @@ class TerminalReporter:
 | 
				
			||||||
                    self.write_line(line)
 | 
					                    self.write_line(line)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def pytest_report_header(self, config: Config) -> List[str]:
 | 
					    def pytest_report_header(self, config: Config) -> List[str]:
 | 
				
			||||||
        line = "rootdir: %s" % config.rootdir
 | 
					        line = "rootdir: %s" % config.rootpath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if config.inifile:
 | 
					        if config.inipath:
 | 
				
			||||||
            line += ", configfile: " + config.rootdir.bestrelpath(config.inifile)
 | 
					            line += ", configfile: " + bestrelpath(config.rootpath, config.inipath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        testpaths = config.getini("testpaths")
 | 
					        testpaths = config.getini("testpaths")
 | 
				
			||||||
        if testpaths and config.args == 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))
 | 
					            line += ", testpaths: {}".format(", ".join(rel_paths))
 | 
				
			||||||
        result = [line]
 | 
					        result = [line]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -860,7 +862,7 @@ class TerminalReporter:
 | 
				
			||||||
            if self.verbosity >= 2 and nodeid.split("::")[0] != fspath.replace(
 | 
					            if self.verbosity >= 2 and nodeid.split("::")[0] != fspath.replace(
 | 
				
			||||||
                "\\", nodes.SEP
 | 
					                "\\", nodes.SEP
 | 
				
			||||||
            ):
 | 
					            ):
 | 
				
			||||||
                res += " <- " + self.startdir.bestrelpath(fspath)
 | 
					                res += " <- " + bestrelpath(self.startpath, fspath)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            res = "[location]"
 | 
					            res = "[location]"
 | 
				
			||||||
        return res + " "
 | 
					        return res + " "
 | 
				
			||||||
| 
						 | 
					@ -1102,7 +1104,7 @@ class TerminalReporter:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def show_skipped(lines: List[str]) -> None:
 | 
					        def show_skipped(lines: List[str]) -> None:
 | 
				
			||||||
            skipped = self.stats.get("skipped", [])  # type: List[CollectReport]
 | 
					            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:
 | 
					            if not fskips:
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
            verbose_word = skipped[0]._get_verbose_word(self.config)
 | 
					            verbose_word = skipped[0]._get_verbose_word(self.config)
 | 
				
			||||||
| 
						 | 
					@ -1230,7 +1232,7 @@ def _get_line_with_reprcrash_message(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _folded_skips(
 | 
					def _folded_skips(
 | 
				
			||||||
    startdir: py.path.local, skipped: Sequence[CollectReport],
 | 
					    startpath: Path, skipped: Sequence[CollectReport],
 | 
				
			||||||
) -> List[Tuple[int, str, Optional[int], str]]:
 | 
					) -> List[Tuple[int, str, Optional[int], str]]:
 | 
				
			||||||
    d = {}  # type: Dict[Tuple[str, Optional[int], str], List[CollectReport]]
 | 
					    d = {}  # type: Dict[Tuple[str, Optional[int], str], List[CollectReport]]
 | 
				
			||||||
    for event in skipped:
 | 
					    for event in skipped:
 | 
				
			||||||
| 
						 | 
					@ -1239,7 +1241,7 @@ def _folded_skips(
 | 
				
			||||||
        assert len(event.longrepr) == 3, (event, event.longrepr)
 | 
					        assert len(event.longrepr) == 3, (event, event.longrepr)
 | 
				
			||||||
        fspath, lineno, reason = event.longrepr
 | 
					        fspath, lineno, reason = event.longrepr
 | 
				
			||||||
        # For consistency, report all fspaths in relative form.
 | 
					        # For consistency, report all fspaths in relative form.
 | 
				
			||||||
        fspath = startdir.bestrelpath(py.path.local(fspath))
 | 
					        fspath = bestrelpath(startpath, Path(fspath))
 | 
				
			||||||
        keywords = getattr(event, "keywords", {})
 | 
					        keywords = getattr(event, "keywords", {})
 | 
				
			||||||
        # Folding reports with global pytestmark variable.
 | 
					        # Folding reports with global pytestmark variable.
 | 
				
			||||||
        # This is a workaround, because for now we cannot identify the scope of a skip marker
 | 
					        # 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.config import UsageError
 | 
				
			||||||
from _pytest.main import resolve_collection_argument
 | 
					from _pytest.main import resolve_collection_argument
 | 
				
			||||||
from _pytest.main import validate_basetemp
 | 
					from _pytest.main import validate_basetemp
 | 
				
			||||||
 | 
					from _pytest.pathlib import Path
 | 
				
			||||||
from _pytest.pytester import Testdir
 | 
					from _pytest.pytester import Testdir
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -108,73 +109,79 @@ def test_validate_basetemp_integration(testdir):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestResolveCollectionArgument:
 | 
					class TestResolveCollectionArgument:
 | 
				
			||||||
    @pytest.fixture
 | 
					    @pytest.fixture
 | 
				
			||||||
    def root(self, testdir):
 | 
					    def invocation_dir(self, testdir: Testdir) -> py.path.local:
 | 
				
			||||||
        testdir.syspathinsert(str(testdir.tmpdir / "src"))
 | 
					        testdir.syspathinsert(str(testdir.tmpdir / "src"))
 | 
				
			||||||
        testdir.chdir()
 | 
					        testdir.chdir()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pkg = testdir.tmpdir.join("src/pkg").ensure_dir()
 | 
					        pkg = testdir.tmpdir.join("src/pkg").ensure_dir()
 | 
				
			||||||
        pkg.join("__init__.py").ensure(file=True)
 | 
					        pkg.join("__init__.py").ensure()
 | 
				
			||||||
        pkg.join("test.py").ensure(file=True)
 | 
					        pkg.join("test.py").ensure()
 | 
				
			||||||
        return testdir.tmpdir
 | 
					        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."""
 | 
					        """File and parts."""
 | 
				
			||||||
        assert resolve_collection_argument(root, "src/pkg/test.py") == (
 | 
					        assert resolve_collection_argument(invocation_path, "src/pkg/test.py") == (
 | 
				
			||||||
            root / "src/pkg/test.py",
 | 
					            invocation_dir / "src/pkg/test.py",
 | 
				
			||||||
            [],
 | 
					            [],
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        assert resolve_collection_argument(root, "src/pkg/test.py::") == (
 | 
					        assert resolve_collection_argument(invocation_path, "src/pkg/test.py::") == (
 | 
				
			||||||
            root / "src/pkg/test.py",
 | 
					            invocation_dir / "src/pkg/test.py",
 | 
				
			||||||
            [""],
 | 
					            [""],
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        assert resolve_collection_argument(root, "src/pkg/test.py::foo::bar") == (
 | 
					        assert resolve_collection_argument(
 | 
				
			||||||
            root / "src/pkg/test.py",
 | 
					            invocation_path, "src/pkg/test.py::foo::bar"
 | 
				
			||||||
            ["foo", "bar"],
 | 
					        ) == (invocation_dir / "src/pkg/test.py", ["foo", "bar"])
 | 
				
			||||||
        )
 | 
					        assert resolve_collection_argument(
 | 
				
			||||||
        assert resolve_collection_argument(root, "src/pkg/test.py::foo::bar::") == (
 | 
					            invocation_path, "src/pkg/test.py::foo::bar::"
 | 
				
			||||||
            root / "src/pkg/test.py",
 | 
					        ) == (invocation_dir / "src/pkg/test.py", ["foo", "bar", ""])
 | 
				
			||||||
            ["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."""
 | 
					        """Directory and parts."""
 | 
				
			||||||
        assert resolve_collection_argument(root, "src/pkg") == (root / "src/pkg", [])
 | 
					        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(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",
 | 
					 | 
				
			||||||
            [],
 | 
					            [],
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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(
 | 
					        assert resolve_collection_argument(
 | 
				
			||||||
            root, "pkg.test::foo::bar", as_pypath=True
 | 
					            invocation_path, "pkg.test", as_pypath=True
 | 
				
			||||||
        ) == (root / "src/pkg/test.py", ["foo", "bar"],)
 | 
					        ) == (invocation_dir / "src/pkg/test.py", [])
 | 
				
			||||||
        assert resolve_collection_argument(root, "pkg", as_pypath=True) == (
 | 
					        assert resolve_collection_argument(
 | 
				
			||||||
            root / "src/pkg",
 | 
					            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(
 | 
					        with pytest.raises(
 | 
				
			||||||
            UsageError, match=r"package argument cannot contain :: selection parts"
 | 
					            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."""
 | 
					        """Given a file/module that does not exist raises UsageError."""
 | 
				
			||||||
        with pytest.raises(
 | 
					        with pytest.raises(
 | 
				
			||||||
            UsageError, match=re.escape("file or directory not found: foobar")
 | 
					            UsageError, match=re.escape("file or directory not found: foobar")
 | 
				
			||||||
        ):
 | 
					        ):
 | 
				
			||||||
            resolve_collection_argument(root, "foobar")
 | 
					            resolve_collection_argument(invocation_path, "foobar")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with pytest.raises(
 | 
					        with pytest.raises(
 | 
				
			||||||
            UsageError,
 | 
					            UsageError,
 | 
				
			||||||
| 
						 | 
					@ -182,12 +189,14 @@ class TestResolveCollectionArgument:
 | 
				
			||||||
                "module or package not found: foobar (missing __init__.py?)"
 | 
					                "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."""
 | 
					        """Absolute paths resolve back to absolute paths."""
 | 
				
			||||||
        full_path = str(root / "src")
 | 
					        full_path = str(invocation_dir / "src")
 | 
				
			||||||
        assert resolve_collection_argument(root, full_path) == (
 | 
					        assert resolve_collection_argument(invocation_path, full_path) == (
 | 
				
			||||||
            py.path.local(os.path.abspath("src")),
 | 
					            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
 | 
					        # ensure full paths given in the command-line without the drive letter resolve
 | 
				
			||||||
        # to the full path correctly (#7628)
 | 
					        # to the full path correctly (#7628)
 | 
				
			||||||
        drive, full_path_without_drive = os.path.splitdrive(full_path)
 | 
					        drive, full_path_without_drive = os.path.splitdrive(full_path)
 | 
				
			||||||
        assert resolve_collection_argument(root, full_path_without_drive) == (
 | 
					        assert resolve_collection_argument(
 | 
				
			||||||
            py.path.local(os.path.abspath("src")),
 | 
					            invocation_path, full_path_without_drive
 | 
				
			||||||
            [],
 | 
					        ) == (py.path.local(os.path.abspath("src")), [])
 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_module_full_path_without_drive(testdir):
 | 
					def test_module_full_path_without_drive(testdir):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,6 +18,7 @@ import pytest
 | 
				
			||||||
from _pytest._io.wcwidth import wcswidth
 | 
					from _pytest._io.wcwidth import wcswidth
 | 
				
			||||||
from _pytest.config import Config
 | 
					from _pytest.config import Config
 | 
				
			||||||
from _pytest.config import ExitCode
 | 
					from _pytest.config import ExitCode
 | 
				
			||||||
 | 
					from _pytest.pathlib import Path
 | 
				
			||||||
from _pytest.pytester import Testdir
 | 
					from _pytest.pytester import Testdir
 | 
				
			||||||
from _pytest.reports import BaseReport
 | 
					from _pytest.reports import BaseReport
 | 
				
			||||||
from _pytest.reports import CollectReport
 | 
					from _pytest.reports import CollectReport
 | 
				
			||||||
| 
						 | 
					@ -2085,7 +2086,7 @@ def test_skip_reasons_folding() -> None:
 | 
				
			||||||
    ev3.longrepr = longrepr
 | 
					    ev3.longrepr = longrepr
 | 
				
			||||||
    ev3.skipped = True
 | 
					    ev3.skipped = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    values = _folded_skips(py.path.local(), [ev1, ev2, ev3])
 | 
					    values = _folded_skips(Path.cwd(), [ev1, ev2, ev3])
 | 
				
			||||||
    assert len(values) == 1
 | 
					    assert len(values) == 1
 | 
				
			||||||
    num, fspath, lineno_, reason = values[0]
 | 
					    num, fspath, lineno_, reason = values[0]
 | 
				
			||||||
    assert num == 3
 | 
					    assert num == 3
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue