Some py.path.local -> pathlib.Path
- Some conftest related functions - _confcutdir - Allow arbitrary os.PathLike[str] in gethookproxy.
This commit is contained in:
		
							parent
							
								
									902739cfc3
								
							
						
					
					
						commit
						ed658d6829
					
				| 
						 | 
				
			
			@ -50,9 +50,11 @@ from _pytest.compat import final
 | 
			
		|||
from _pytest.compat import importlib_metadata
 | 
			
		||||
from _pytest.outcomes import fail
 | 
			
		||||
from _pytest.outcomes import Skipped
 | 
			
		||||
from _pytest.pathlib import absolutepath
 | 
			
		||||
from _pytest.pathlib import bestrelpath
 | 
			
		||||
from _pytest.pathlib import import_path
 | 
			
		||||
from _pytest.pathlib import ImportMode
 | 
			
		||||
from _pytest.pathlib import resolve_package_path
 | 
			
		||||
from _pytest.store import Store
 | 
			
		||||
from _pytest.warning_types import PytestConfigWarning
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -102,9 +104,7 @@ class ExitCode(enum.IntEnum):
 | 
			
		|||
 | 
			
		||||
class ConftestImportFailure(Exception):
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        path: py.path.local,
 | 
			
		||||
        excinfo: Tuple[Type[Exception], Exception, TracebackType],
 | 
			
		||||
        self, path: Path, excinfo: Tuple[Type[Exception], Exception, TracebackType],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        super().__init__(path, excinfo)
 | 
			
		||||
        self.path = path
 | 
			
		||||
| 
						 | 
				
			
			@ -342,9 +342,9 @@ class PytestPluginManager(PluginManager):
 | 
			
		|||
        self._conftest_plugins: Set[types.ModuleType] = set()
 | 
			
		||||
 | 
			
		||||
        # State related to local conftest plugins.
 | 
			
		||||
        self._dirpath2confmods: Dict[py.path.local, List[types.ModuleType]] = {}
 | 
			
		||||
        self._dirpath2confmods: Dict[Path, List[types.ModuleType]] = {}
 | 
			
		||||
        self._conftestpath2mod: Dict[Path, types.ModuleType] = {}
 | 
			
		||||
        self._confcutdir: Optional[py.path.local] = None
 | 
			
		||||
        self._confcutdir: Optional[Path] = None
 | 
			
		||||
        self._noconftest = False
 | 
			
		||||
        self._duplicatepaths: Set[py.path.local] = set()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -479,9 +479,9 @@ class PytestPluginManager(PluginManager):
 | 
			
		|||
        All builtin and 3rd party plugins will have been loaded, however, so
 | 
			
		||||
        common options will not confuse our logic here.
 | 
			
		||||
        """
 | 
			
		||||
        current = py.path.local()
 | 
			
		||||
        current = Path.cwd()
 | 
			
		||||
        self._confcutdir = (
 | 
			
		||||
            current.join(namespace.confcutdir, abs=True)
 | 
			
		||||
            absolutepath(current / namespace.confcutdir)
 | 
			
		||||
            if namespace.confcutdir
 | 
			
		||||
            else None
 | 
			
		||||
        )
 | 
			
		||||
| 
						 | 
				
			
			@ -495,7 +495,7 @@ class PytestPluginManager(PluginManager):
 | 
			
		|||
            i = path.find("::")
 | 
			
		||||
            if i != -1:
 | 
			
		||||
                path = path[:i]
 | 
			
		||||
            anchor = current.join(path, abs=1)
 | 
			
		||||
            anchor = absolutepath(current / path)
 | 
			
		||||
            if anchor.exists():  # we found some file object
 | 
			
		||||
                self._try_load_conftest(anchor, namespace.importmode)
 | 
			
		||||
                foundanchor = True
 | 
			
		||||
| 
						 | 
				
			
			@ -503,24 +503,24 @@ class PytestPluginManager(PluginManager):
 | 
			
		|||
            self._try_load_conftest(current, namespace.importmode)
 | 
			
		||||
 | 
			
		||||
    def _try_load_conftest(
 | 
			
		||||
        self, anchor: py.path.local, importmode: Union[str, ImportMode]
 | 
			
		||||
        self, anchor: Path, importmode: Union[str, ImportMode]
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        self._getconftestmodules(anchor, importmode)
 | 
			
		||||
        # let's also consider test* subdirs
 | 
			
		||||
        if anchor.check(dir=1):
 | 
			
		||||
            for x in anchor.listdir("test*"):
 | 
			
		||||
                if x.check(dir=1):
 | 
			
		||||
        if anchor.is_dir():
 | 
			
		||||
            for x in anchor.glob("test*"):
 | 
			
		||||
                if x.is_dir():
 | 
			
		||||
                    self._getconftestmodules(x, importmode)
 | 
			
		||||
 | 
			
		||||
    @lru_cache(maxsize=128)
 | 
			
		||||
    def _getconftestmodules(
 | 
			
		||||
        self, path: py.path.local, importmode: Union[str, ImportMode],
 | 
			
		||||
        self, path: Path, importmode: Union[str, ImportMode],
 | 
			
		||||
    ) -> List[types.ModuleType]:
 | 
			
		||||
        if self._noconftest:
 | 
			
		||||
            return []
 | 
			
		||||
 | 
			
		||||
        if path.isfile():
 | 
			
		||||
            directory = path.dirpath()
 | 
			
		||||
        if path.is_file():
 | 
			
		||||
            directory = path.parent
 | 
			
		||||
        else:
 | 
			
		||||
            directory = path
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -528,18 +528,18 @@ class PytestPluginManager(PluginManager):
 | 
			
		|||
        # and allow users to opt into looking into the rootdir parent
 | 
			
		||||
        # directories instead of requiring to specify confcutdir.
 | 
			
		||||
        clist = []
 | 
			
		||||
        for parent in directory.parts():
 | 
			
		||||
            if self._confcutdir and self._confcutdir.relto(parent):
 | 
			
		||||
        for parent in reversed((directory, *directory.parents)):
 | 
			
		||||
            if self._confcutdir and parent in self._confcutdir.parents:
 | 
			
		||||
                continue
 | 
			
		||||
            conftestpath = parent.join("conftest.py")
 | 
			
		||||
            if conftestpath.isfile():
 | 
			
		||||
            conftestpath = parent / "conftest.py"
 | 
			
		||||
            if conftestpath.is_file():
 | 
			
		||||
                mod = self._importconftest(conftestpath, importmode)
 | 
			
		||||
                clist.append(mod)
 | 
			
		||||
        self._dirpath2confmods[directory] = clist
 | 
			
		||||
        return clist
 | 
			
		||||
 | 
			
		||||
    def _rget_with_confmod(
 | 
			
		||||
        self, name: str, path: py.path.local, importmode: Union[str, ImportMode],
 | 
			
		||||
        self, name: str, path: Path, importmode: Union[str, ImportMode],
 | 
			
		||||
    ) -> Tuple[types.ModuleType, Any]:
 | 
			
		||||
        modules = self._getconftestmodules(path, importmode)
 | 
			
		||||
        for mod in reversed(modules):
 | 
			
		||||
| 
						 | 
				
			
			@ -550,21 +550,21 @@ class PytestPluginManager(PluginManager):
 | 
			
		|||
        raise KeyError(name)
 | 
			
		||||
 | 
			
		||||
    def _importconftest(
 | 
			
		||||
        self, conftestpath: py.path.local, importmode: Union[str, ImportMode],
 | 
			
		||||
        self, conftestpath: Path, importmode: Union[str, ImportMode],
 | 
			
		||||
    ) -> types.ModuleType:
 | 
			
		||||
        # Use a resolved Path object as key to avoid loading the same conftest
 | 
			
		||||
        # twice with build systems that create build directories containing
 | 
			
		||||
        # symlinks to actual files.
 | 
			
		||||
        # Using Path().resolve() is better than py.path.realpath because
 | 
			
		||||
        # it resolves to the correct path/drive in case-insensitive file systems (#5792)
 | 
			
		||||
        key = Path(str(conftestpath)).resolve()
 | 
			
		||||
        key = conftestpath.resolve()
 | 
			
		||||
 | 
			
		||||
        with contextlib.suppress(KeyError):
 | 
			
		||||
            return self._conftestpath2mod[key]
 | 
			
		||||
 | 
			
		||||
        pkgpath = conftestpath.pypkgpath()
 | 
			
		||||
        pkgpath = resolve_package_path(conftestpath)
 | 
			
		||||
        if pkgpath is None:
 | 
			
		||||
            _ensure_removed_sysmodule(conftestpath.purebasename)
 | 
			
		||||
            _ensure_removed_sysmodule(conftestpath.stem)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            mod = import_path(conftestpath, mode=importmode)
 | 
			
		||||
| 
						 | 
				
			
			@ -577,10 +577,10 @@ class PytestPluginManager(PluginManager):
 | 
			
		|||
 | 
			
		||||
        self._conftest_plugins.add(mod)
 | 
			
		||||
        self._conftestpath2mod[key] = mod
 | 
			
		||||
        dirpath = conftestpath.dirpath()
 | 
			
		||||
        dirpath = conftestpath.parent
 | 
			
		||||
        if dirpath in self._dirpath2confmods:
 | 
			
		||||
            for path, mods in self._dirpath2confmods.items():
 | 
			
		||||
                if path and path.relto(dirpath) or path == dirpath:
 | 
			
		||||
                if path and dirpath in path.parents or path == dirpath:
 | 
			
		||||
                    assert mod not in mods
 | 
			
		||||
                    mods.append(mod)
 | 
			
		||||
        self.trace(f"loading conftestmodule {mod!r}")
 | 
			
		||||
| 
						 | 
				
			
			@ -588,7 +588,7 @@ class PytestPluginManager(PluginManager):
 | 
			
		|||
        return mod
 | 
			
		||||
 | 
			
		||||
    def _check_non_top_pytest_plugins(
 | 
			
		||||
        self, mod: types.ModuleType, conftestpath: py.path.local,
 | 
			
		||||
        self, mod: types.ModuleType, conftestpath: Path,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        if (
 | 
			
		||||
            hasattr(mod, "pytest_plugins")
 | 
			
		||||
| 
						 | 
				
			
			@ -1412,21 +1412,23 @@ class Config:
 | 
			
		|||
            assert type in [None, "string"]
 | 
			
		||||
            return value
 | 
			
		||||
 | 
			
		||||
    def _getconftest_pathlist(
 | 
			
		||||
        self, name: str, path: py.path.local
 | 
			
		||||
    ) -> Optional[List[py.path.local]]:
 | 
			
		||||
    def _getconftest_pathlist(self, name: str, path: Path) -> Optional[List[Path]]:
 | 
			
		||||
        try:
 | 
			
		||||
            mod, relroots = self.pluginmanager._rget_with_confmod(
 | 
			
		||||
                name, path, self.getoption("importmode")
 | 
			
		||||
            )
 | 
			
		||||
        except KeyError:
 | 
			
		||||
            return None
 | 
			
		||||
        modpath = py.path.local(mod.__file__).dirpath()
 | 
			
		||||
        values: List[py.path.local] = []
 | 
			
		||||
        modpath = Path(mod.__file__).parent
 | 
			
		||||
        values: List[Path] = []
 | 
			
		||||
        for relroot in relroots:
 | 
			
		||||
            if not isinstance(relroot, py.path.local):
 | 
			
		||||
            if isinstance(relroot, Path):
 | 
			
		||||
                pass
 | 
			
		||||
            elif isinstance(relroot, py.path.local):
 | 
			
		||||
                relroot = Path(relroot)
 | 
			
		||||
            else:
 | 
			
		||||
                relroot = relroot.replace("/", os.sep)
 | 
			
		||||
                relroot = modpath.join(relroot, abs=True)
 | 
			
		||||
                relroot = absolutepath(modpath / relroot)
 | 
			
		||||
            values.append(relroot)
 | 
			
		||||
        return values
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,7 @@ import traceback
 | 
			
		|||
import types
 | 
			
		||||
import warnings
 | 
			
		||||
from contextlib import contextmanager
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from typing import Any
 | 
			
		||||
from typing import Callable
 | 
			
		||||
from typing import Dict
 | 
			
		||||
| 
						 | 
				
			
			@ -525,7 +526,7 @@ class DoctestModule(pytest.Module):
 | 
			
		|||
 | 
			
		||||
        if self.fspath.basename == "conftest.py":
 | 
			
		||||
            module = self.config.pluginmanager._importconftest(
 | 
			
		||||
                self.fspath, self.config.getoption("importmode")
 | 
			
		||||
                Path(self.fspath), self.config.getoption("importmode")
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            try:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -371,22 +371,23 @@ def _in_venv(path: py.path.local) -> bool:
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
def pytest_ignore_collect(path: py.path.local, config: Config) -> Optional[bool]:
 | 
			
		||||
    ignore_paths = config._getconftest_pathlist("collect_ignore", path=path.dirpath())
 | 
			
		||||
    path_ = Path(path)
 | 
			
		||||
    ignore_paths = config._getconftest_pathlist("collect_ignore", path=path_.parent)
 | 
			
		||||
    ignore_paths = ignore_paths or []
 | 
			
		||||
    excludeopt = config.getoption("ignore")
 | 
			
		||||
    if excludeopt:
 | 
			
		||||
        ignore_paths.extend([py.path.local(x) for x in excludeopt])
 | 
			
		||||
        ignore_paths.extend(absolutepath(x) for x in excludeopt)
 | 
			
		||||
 | 
			
		||||
    if py.path.local(path) in ignore_paths:
 | 
			
		||||
    if path_ in ignore_paths:
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    ignore_globs = config._getconftest_pathlist(
 | 
			
		||||
        "collect_ignore_glob", path=path.dirpath()
 | 
			
		||||
        "collect_ignore_glob", path=path_.parent
 | 
			
		||||
    )
 | 
			
		||||
    ignore_globs = ignore_globs or []
 | 
			
		||||
    excludeglobopt = config.getoption("ignore_glob")
 | 
			
		||||
    if excludeglobopt:
 | 
			
		||||
        ignore_globs.extend([py.path.local(x) for x in excludeglobopt])
 | 
			
		||||
        ignore_globs.extend(absolutepath(x) for x in excludeglobopt)
 | 
			
		||||
 | 
			
		||||
    if any(fnmatch.fnmatch(str(path), str(glob)) for glob in ignore_globs):
 | 
			
		||||
        return True
 | 
			
		||||
| 
						 | 
				
			
			@ -512,12 +513,12 @@ class Session(nodes.FSCollector):
 | 
			
		|||
    def isinitpath(self, path: py.path.local) -> bool:
 | 
			
		||||
        return path in self._initialpaths
 | 
			
		||||
 | 
			
		||||
    def gethookproxy(self, fspath: py.path.local):
 | 
			
		||||
    def gethookproxy(self, fspath: "os.PathLike[str]"):
 | 
			
		||||
        # Check if we have the common case of running
 | 
			
		||||
        # hooks with all conftest.py files.
 | 
			
		||||
        pm = self.config.pluginmanager
 | 
			
		||||
        my_conftestmodules = pm._getconftestmodules(
 | 
			
		||||
            fspath, self.config.getoption("importmode")
 | 
			
		||||
            Path(fspath), self.config.getoption("importmode")
 | 
			
		||||
        )
 | 
			
		||||
        remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
 | 
			
		||||
        if remove_mods:
 | 
			
		||||
| 
						 | 
				
			
			@ -668,8 +669,9 @@ class Session(nodes.FSCollector):
 | 
			
		|||
            # No point in finding packages when collecting doctests.
 | 
			
		||||
            if not self.config.getoption("doctestmodules", False):
 | 
			
		||||
                pm = self.config.pluginmanager
 | 
			
		||||
                confcutdir = py.path.local(pm._confcutdir) if pm._confcutdir else None
 | 
			
		||||
                for parent in reversed(argpath.parts()):
 | 
			
		||||
                    if pm._confcutdir and pm._confcutdir.relto(parent):
 | 
			
		||||
                    if confcutdir and confcutdir.relto(parent):
 | 
			
		||||
                        break
 | 
			
		||||
 | 
			
		||||
                    if parent.isdir():
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -520,7 +520,7 @@ class FSCollector(Collector):
 | 
			
		|||
        """The public constructor."""
 | 
			
		||||
        return super().from_parent(parent=parent, fspath=fspath, **kw)
 | 
			
		||||
 | 
			
		||||
    def gethookproxy(self, fspath: py.path.local):
 | 
			
		||||
    def gethookproxy(self, fspath: "os.PathLike[str]"):
 | 
			
		||||
        warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2)
 | 
			
		||||
        return self.session.gethookproxy(fspath)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -653,7 +653,7 @@ class Package(Module):
 | 
			
		|||
            func = partial(_call_with_optional_argument, teardown_module, self.obj)
 | 
			
		||||
            self.addfinalizer(func)
 | 
			
		||||
 | 
			
		||||
    def gethookproxy(self, fspath: py.path.local):
 | 
			
		||||
    def gethookproxy(self, fspath: "os.PathLike[str]"):
 | 
			
		||||
        warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2)
 | 
			
		||||
        return self.session.gethookproxy(fspath)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,7 @@ from _pytest.compat import getfuncargnames
 | 
			
		|||
from _pytest.config import ExitCode
 | 
			
		||||
from _pytest.fixtures import FixtureRequest
 | 
			
		||||
from _pytest.pytester import get_public_names
 | 
			
		||||
from _pytest.pytester import Pytester
 | 
			
		||||
from _pytest.pytester import Testdir
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1961,8 +1962,10 @@ class TestAutouseManagement:
 | 
			
		|||
        reprec = testdir.inline_run("-v", "-s")
 | 
			
		||||
        reprec.assertoutcome(passed=4)
 | 
			
		||||
 | 
			
		||||
    def test_class_function_parametrization_finalization(self, testdir):
 | 
			
		||||
        p = testdir.makeconftest(
 | 
			
		||||
    def test_class_function_parametrization_finalization(
 | 
			
		||||
        self, pytester: Pytester
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        p = pytester.makeconftest(
 | 
			
		||||
            """
 | 
			
		||||
            import pytest
 | 
			
		||||
            import pprint
 | 
			
		||||
| 
						 | 
				
			
			@ -1984,7 +1987,7 @@ class TestAutouseManagement:
 | 
			
		|||
                request.addfinalizer(fin)
 | 
			
		||||
        """
 | 
			
		||||
        )
 | 
			
		||||
        testdir.makepyfile(
 | 
			
		||||
        pytester.makepyfile(
 | 
			
		||||
            """
 | 
			
		||||
            import pytest
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1996,8 +1999,7 @@ class TestAutouseManagement:
 | 
			
		|||
                    pass
 | 
			
		||||
        """
 | 
			
		||||
        )
 | 
			
		||||
        confcut = f"--confcutdir={testdir.tmpdir}"
 | 
			
		||||
        reprec = testdir.inline_run("-v", "-s", confcut)
 | 
			
		||||
        reprec = pytester.inline_run("-v", "-s", "--confcutdir", pytester.path)
 | 
			
		||||
        reprec.assertoutcome(passed=8)
 | 
			
		||||
        config = reprec.getcalls("pytest_unconfigure")[0].config
 | 
			
		||||
        values = config.pluginmanager._getconftestmodules(p, importmode="prepend")[
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -364,7 +364,9 @@ class TestCustomConftests:
 | 
			
		|||
    def test_collectignore_exclude_on_option(self, pytester: Pytester) -> None:
 | 
			
		||||
        pytester.makeconftest(
 | 
			
		||||
            """
 | 
			
		||||
            collect_ignore = ['hello', 'test_world.py']
 | 
			
		||||
            import py
 | 
			
		||||
            from pathlib import Path
 | 
			
		||||
            collect_ignore = [py.path.local('hello'), 'test_world.py', Path('bye')]
 | 
			
		||||
            def pytest_addoption(parser):
 | 
			
		||||
                parser.addoption("--XX", action="store_true", default=False)
 | 
			
		||||
            def pytest_configure(config):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -574,16 +574,16 @@ class TestConfigAPI:
 | 
			
		|||
            config.getvalue("x")
 | 
			
		||||
        assert config.getoption("x", 1) == 1
 | 
			
		||||
 | 
			
		||||
    def test_getconftest_pathlist(self, pytester: Pytester, tmpdir) -> None:
 | 
			
		||||
        somepath = tmpdir.join("x", "y", "z")
 | 
			
		||||
        p = tmpdir.join("conftest.py")
 | 
			
		||||
        p.write("pathlist = ['.', %r]" % str(somepath))
 | 
			
		||||
    def test_getconftest_pathlist(self, pytester: Pytester, tmp_path: Path) -> None:
 | 
			
		||||
        somepath = tmp_path.joinpath("x", "y", "z")
 | 
			
		||||
        p = tmp_path.joinpath("conftest.py")
 | 
			
		||||
        p.write_text(f"pathlist = ['.', {str(somepath)!r}]")
 | 
			
		||||
        config = pytester.parseconfigure(p)
 | 
			
		||||
        assert config._getconftest_pathlist("notexist", path=tmpdir) is None
 | 
			
		||||
        pl = config._getconftest_pathlist("pathlist", path=tmpdir) or []
 | 
			
		||||
        assert config._getconftest_pathlist("notexist", path=tmp_path) is None
 | 
			
		||||
        pl = config._getconftest_pathlist("pathlist", path=tmp_path) or []
 | 
			
		||||
        print(pl)
 | 
			
		||||
        assert len(pl) == 2
 | 
			
		||||
        assert pl[0] == tmpdir
 | 
			
		||||
        assert pl[0] == tmp_path
 | 
			
		||||
        assert pl[1] == somepath
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.parametrize("maybe_type", ["not passed", "None", '"string"'])
 | 
			
		||||
| 
						 | 
				
			
			@ -1935,10 +1935,10 @@ class TestPytestPluginsVariable:
 | 
			
		|||
        assert msg not in res.stdout.str()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_conftest_import_error_repr(tmpdir: py.path.local) -> None:
 | 
			
		||||
def test_conftest_import_error_repr(tmp_path: Path) -> None:
 | 
			
		||||
    """`ConftestImportFailure` should use a short error message and readable
 | 
			
		||||
    path to the failed conftest.py file."""
 | 
			
		||||
    path = tmpdir.join("foo/conftest.py")
 | 
			
		||||
    path = tmp_path.joinpath("foo/conftest.py")
 | 
			
		||||
    with pytest.raises(
 | 
			
		||||
        ConftestImportFailure,
 | 
			
		||||
        match=re.escape(f"RuntimeError: some error (from {path})"),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@ import textwrap
 | 
			
		|||
from pathlib import Path
 | 
			
		||||
from typing import cast
 | 
			
		||||
from typing import Dict
 | 
			
		||||
from typing import Generator
 | 
			
		||||
from typing import List
 | 
			
		||||
from typing import Optional
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -15,7 +16,7 @@ from _pytest.config import PytestPluginManager
 | 
			
		|||
from _pytest.monkeypatch import MonkeyPatch
 | 
			
		||||
from _pytest.pathlib import symlink_or_skip
 | 
			
		||||
from _pytest.pytester import Pytester
 | 
			
		||||
from _pytest.pytester import Testdir
 | 
			
		||||
from _pytest.tmpdir import TempPathFactory
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def ConftestWithSetinitial(path) -> PytestPluginManager:
 | 
			
		||||
| 
						 | 
				
			
			@ -25,12 +26,12 @@ def ConftestWithSetinitial(path) -> PytestPluginManager:
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
def conftest_setinitial(
 | 
			
		||||
    conftest: PytestPluginManager, args, confcutdir: Optional[py.path.local] = None
 | 
			
		||||
    conftest: PytestPluginManager, args, confcutdir: Optional["os.PathLike[str]"] = None
 | 
			
		||||
) -> None:
 | 
			
		||||
    class Namespace:
 | 
			
		||||
        def __init__(self) -> None:
 | 
			
		||||
            self.file_or_dir = args
 | 
			
		||||
            self.confcutdir = str(confcutdir)
 | 
			
		||||
            self.confcutdir = os.fspath(confcutdir) if confcutdir is not None else None
 | 
			
		||||
            self.noconftest = False
 | 
			
		||||
            self.pyargs = False
 | 
			
		||||
            self.importmode = "prepend"
 | 
			
		||||
| 
						 | 
				
			
			@ -42,54 +43,58 @@ def conftest_setinitial(
 | 
			
		|||
@pytest.mark.usefixtures("_sys_snapshot")
 | 
			
		||||
class TestConftestValueAccessGlobal:
 | 
			
		||||
    @pytest.fixture(scope="module", params=["global", "inpackage"])
 | 
			
		||||
    def basedir(self, request, tmpdir_factory):
 | 
			
		||||
        tmpdir = tmpdir_factory.mktemp("basedir", numbered=True)
 | 
			
		||||
        tmpdir.ensure("adir/conftest.py").write("a=1 ; Directory = 3")
 | 
			
		||||
        tmpdir.ensure("adir/b/conftest.py").write("b=2 ; a = 1.5")
 | 
			
		||||
    def basedir(
 | 
			
		||||
        self, request, tmp_path_factory: TempPathFactory
 | 
			
		||||
    ) -> Generator[Path, None, None]:
 | 
			
		||||
        tmpdir = tmp_path_factory.mktemp("basedir", numbered=True)
 | 
			
		||||
        tmpdir.joinpath("adir/b").mkdir(parents=True)
 | 
			
		||||
        tmpdir.joinpath("adir/conftest.py").write_text("a=1 ; Directory = 3")
 | 
			
		||||
        tmpdir.joinpath("adir/b/conftest.py").write_text("b=2 ; a = 1.5")
 | 
			
		||||
        if request.param == "inpackage":
 | 
			
		||||
            tmpdir.ensure("adir/__init__.py")
 | 
			
		||||
            tmpdir.ensure("adir/b/__init__.py")
 | 
			
		||||
            tmpdir.joinpath("adir/__init__.py").touch()
 | 
			
		||||
            tmpdir.joinpath("adir/b/__init__.py").touch()
 | 
			
		||||
 | 
			
		||||
        yield tmpdir
 | 
			
		||||
 | 
			
		||||
    def test_basic_init(self, basedir):
 | 
			
		||||
    def test_basic_init(self, basedir: Path) -> None:
 | 
			
		||||
        conftest = PytestPluginManager()
 | 
			
		||||
        p = basedir.join("adir")
 | 
			
		||||
        p = basedir / "adir"
 | 
			
		||||
        assert conftest._rget_with_confmod("a", p, importmode="prepend")[1] == 1
 | 
			
		||||
 | 
			
		||||
    def test_immediate_initialiation_and_incremental_are_the_same(self, basedir):
 | 
			
		||||
    def test_immediate_initialiation_and_incremental_are_the_same(
 | 
			
		||||
        self, basedir: Path
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        conftest = PytestPluginManager()
 | 
			
		||||
        assert not len(conftest._dirpath2confmods)
 | 
			
		||||
        conftest._getconftestmodules(basedir, importmode="prepend")
 | 
			
		||||
        snap1 = len(conftest._dirpath2confmods)
 | 
			
		||||
        assert snap1 == 1
 | 
			
		||||
        conftest._getconftestmodules(basedir.join("adir"), importmode="prepend")
 | 
			
		||||
        conftest._getconftestmodules(basedir / "adir", importmode="prepend")
 | 
			
		||||
        assert len(conftest._dirpath2confmods) == snap1 + 1
 | 
			
		||||
        conftest._getconftestmodules(basedir.join("b"), importmode="prepend")
 | 
			
		||||
        conftest._getconftestmodules(basedir / "b", importmode="prepend")
 | 
			
		||||
        assert len(conftest._dirpath2confmods) == snap1 + 2
 | 
			
		||||
 | 
			
		||||
    def test_value_access_not_existing(self, basedir):
 | 
			
		||||
    def test_value_access_not_existing(self, basedir: Path) -> None:
 | 
			
		||||
        conftest = ConftestWithSetinitial(basedir)
 | 
			
		||||
        with pytest.raises(KeyError):
 | 
			
		||||
            conftest._rget_with_confmod("a", basedir, importmode="prepend")
 | 
			
		||||
 | 
			
		||||
    def test_value_access_by_path(self, basedir):
 | 
			
		||||
    def test_value_access_by_path(self, basedir: Path) -> None:
 | 
			
		||||
        conftest = ConftestWithSetinitial(basedir)
 | 
			
		||||
        adir = basedir.join("adir")
 | 
			
		||||
        adir = basedir / "adir"
 | 
			
		||||
        assert conftest._rget_with_confmod("a", adir, importmode="prepend")[1] == 1
 | 
			
		||||
        assert (
 | 
			
		||||
            conftest._rget_with_confmod("a", adir.join("b"), importmode="prepend")[1]
 | 
			
		||||
            == 1.5
 | 
			
		||||
            conftest._rget_with_confmod("a", adir / "b", importmode="prepend")[1] == 1.5
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_value_access_with_confmod(self, basedir):
 | 
			
		||||
        startdir = basedir.join("adir", "b")
 | 
			
		||||
        startdir.ensure("xx", dir=True)
 | 
			
		||||
    def test_value_access_with_confmod(self, basedir: Path) -> None:
 | 
			
		||||
        startdir = basedir / "adir" / "b"
 | 
			
		||||
        startdir.joinpath("xx").mkdir()
 | 
			
		||||
        conftest = ConftestWithSetinitial(startdir)
 | 
			
		||||
        mod, value = conftest._rget_with_confmod("a", startdir, importmode="prepend")
 | 
			
		||||
        assert value == 1.5
 | 
			
		||||
        path = py.path.local(mod.__file__)
 | 
			
		||||
        assert path.dirpath() == basedir.join("adir", "b")
 | 
			
		||||
        assert path.dirpath() == basedir / "adir" / "b"
 | 
			
		||||
        assert path.purebasename.startswith("conftest")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -102,12 +107,12 @@ def test_conftest_in_nonpkg_with_init(tmp_path: Path, _sys_snapshot) -> None:
 | 
			
		|||
    ConftestWithSetinitial(tmp_path.joinpath("adir-1.0", "b"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_doubledash_considered(testdir: Testdir) -> None:
 | 
			
		||||
    conf = testdir.mkdir("--option")
 | 
			
		||||
    conf.join("conftest.py").ensure()
 | 
			
		||||
def test_doubledash_considered(pytester: Pytester) -> None:
 | 
			
		||||
    conf = pytester.mkdir("--option")
 | 
			
		||||
    conf.joinpath("conftest.py").touch()
 | 
			
		||||
    conftest = PytestPluginManager()
 | 
			
		||||
    conftest_setinitial(conftest, [conf.basename, conf.basename])
 | 
			
		||||
    values = conftest._getconftestmodules(py.path.local(conf), importmode="prepend")
 | 
			
		||||
    conftest_setinitial(conftest, [conf.name, conf.name])
 | 
			
		||||
    values = conftest._getconftestmodules(conf, importmode="prepend")
 | 
			
		||||
    assert len(values) == 1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -127,15 +132,18 @@ def test_conftest_global_import(pytester: Pytester) -> None:
 | 
			
		|||
    pytester.makeconftest("x=3")
 | 
			
		||||
    p = pytester.makepyfile(
 | 
			
		||||
        """
 | 
			
		||||
        import py, pytest
 | 
			
		||||
        from pathlib import Path
 | 
			
		||||
        import pytest
 | 
			
		||||
        from _pytest.config import PytestPluginManager
 | 
			
		||||
        conf = PytestPluginManager()
 | 
			
		||||
        mod = conf._importconftest(py.path.local("conftest.py"), importmode="prepend")
 | 
			
		||||
        mod = conf._importconftest(Path("conftest.py"), importmode="prepend")
 | 
			
		||||
        assert mod.x == 3
 | 
			
		||||
        import conftest
 | 
			
		||||
        assert conftest is mod, (conftest, mod)
 | 
			
		||||
        subconf = py.path.local().ensure("sub", "conftest.py")
 | 
			
		||||
        subconf.write("y=4")
 | 
			
		||||
        sub = Path("sub")
 | 
			
		||||
        sub.mkdir()
 | 
			
		||||
        subconf = sub / "conftest.py"
 | 
			
		||||
        subconf.write_text("y=4")
 | 
			
		||||
        mod2 = conf._importconftest(subconf, importmode="prepend")
 | 
			
		||||
        assert mod != mod2
 | 
			
		||||
        assert mod2.y == 4
 | 
			
		||||
| 
						 | 
				
			
			@ -147,19 +155,19 @@ def test_conftest_global_import(pytester: Pytester) -> None:
 | 
			
		|||
    assert res.ret == 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_conftestcutdir(testdir: Testdir) -> None:
 | 
			
		||||
    conf = testdir.makeconftest("")
 | 
			
		||||
    p = testdir.mkdir("x")
 | 
			
		||||
def test_conftestcutdir(pytester: Pytester) -> None:
 | 
			
		||||
    conf = pytester.makeconftest("")
 | 
			
		||||
    p = pytester.mkdir("x")
 | 
			
		||||
    conftest = PytestPluginManager()
 | 
			
		||||
    conftest_setinitial(conftest, [testdir.tmpdir], confcutdir=p)
 | 
			
		||||
    conftest_setinitial(conftest, [pytester.path], confcutdir=p)
 | 
			
		||||
    values = conftest._getconftestmodules(p, importmode="prepend")
 | 
			
		||||
    assert len(values) == 0
 | 
			
		||||
    values = conftest._getconftestmodules(conf.dirpath(), importmode="prepend")
 | 
			
		||||
    values = conftest._getconftestmodules(conf.parent, importmode="prepend")
 | 
			
		||||
    assert len(values) == 0
 | 
			
		||||
    assert Path(conf) not in conftest._conftestpath2mod
 | 
			
		||||
    # but we can still import a conftest directly
 | 
			
		||||
    conftest._importconftest(conf, importmode="prepend")
 | 
			
		||||
    values = conftest._getconftestmodules(conf.dirpath(), importmode="prepend")
 | 
			
		||||
    values = conftest._getconftestmodules(conf.parent, importmode="prepend")
 | 
			
		||||
    assert values[0].__file__.startswith(str(conf))
 | 
			
		||||
    # and all sub paths get updated properly
 | 
			
		||||
    values = conftest._getconftestmodules(p, importmode="prepend")
 | 
			
		||||
| 
						 | 
				
			
			@ -170,10 +178,8 @@ def test_conftestcutdir(testdir: Testdir) -> None:
 | 
			
		|||
def test_conftestcutdir_inplace_considered(pytester: Pytester) -> None:
 | 
			
		||||
    conf = pytester.makeconftest("")
 | 
			
		||||
    conftest = PytestPluginManager()
 | 
			
		||||
    conftest_setinitial(conftest, [conf.parent], confcutdir=py.path.local(conf.parent))
 | 
			
		||||
    values = conftest._getconftestmodules(
 | 
			
		||||
        py.path.local(conf.parent), importmode="prepend"
 | 
			
		||||
    )
 | 
			
		||||
    conftest_setinitial(conftest, [conf.parent], confcutdir=conf.parent)
 | 
			
		||||
    values = conftest._getconftestmodules(conf.parent, importmode="prepend")
 | 
			
		||||
    assert len(values) == 1
 | 
			
		||||
    assert values[0].__file__.startswith(str(conf))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -184,7 +190,7 @@ def test_setinitial_conftest_subdirs(pytester: Pytester, name: str) -> None:
 | 
			
		|||
    subconftest = sub.joinpath("conftest.py")
 | 
			
		||||
    subconftest.touch()
 | 
			
		||||
    conftest = PytestPluginManager()
 | 
			
		||||
    conftest_setinitial(conftest, [sub.parent], confcutdir=py.path.local(pytester.path))
 | 
			
		||||
    conftest_setinitial(conftest, [sub.parent], confcutdir=pytester.path)
 | 
			
		||||
    key = subconftest.resolve()
 | 
			
		||||
    if name not in ("whatever", ".dotdir"):
 | 
			
		||||
        assert key in conftest._conftestpath2mod
 | 
			
		||||
| 
						 | 
				
			
			@ -337,22 +343,19 @@ def test_conftest_existing_junitxml(pytester: Pytester) -> None:
 | 
			
		|||
    result.stdout.fnmatch_lines(["*--xyz*"])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_conftest_import_order(testdir: Testdir, monkeypatch: MonkeyPatch) -> None:
 | 
			
		||||
    ct1 = testdir.makeconftest("")
 | 
			
		||||
    sub = testdir.mkdir("sub")
 | 
			
		||||
    ct2 = sub.join("conftest.py")
 | 
			
		||||
    ct2.write("")
 | 
			
		||||
def test_conftest_import_order(pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
 | 
			
		||||
    ct1 = pytester.makeconftest("")
 | 
			
		||||
    sub = pytester.mkdir("sub")
 | 
			
		||||
    ct2 = sub / "conftest.py"
 | 
			
		||||
    ct2.write_text("")
 | 
			
		||||
 | 
			
		||||
    def impct(p, importmode):
 | 
			
		||||
        return p
 | 
			
		||||
 | 
			
		||||
    conftest = PytestPluginManager()
 | 
			
		||||
    conftest._confcutdir = testdir.tmpdir
 | 
			
		||||
    conftest._confcutdir = pytester.path
 | 
			
		||||
    monkeypatch.setattr(conftest, "_importconftest", impct)
 | 
			
		||||
    mods = cast(
 | 
			
		||||
        List[py.path.local],
 | 
			
		||||
        conftest._getconftestmodules(py.path.local(sub), importmode="prepend"),
 | 
			
		||||
    )
 | 
			
		||||
    mods = cast(List[Path], conftest._getconftestmodules(sub, importmode="prepend"))
 | 
			
		||||
    expected = [ct1, ct2]
 | 
			
		||||
    assert mods == expected
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,7 @@ from _pytest.config import ExitCode
 | 
			
		|||
from _pytest.config import PytestPluginManager
 | 
			
		||||
from _pytest.config.exceptions import UsageError
 | 
			
		||||
from _pytest.main import Session
 | 
			
		||||
from _pytest.pytester import Pytester
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
| 
						 | 
				
			
			@ -16,14 +17,16 @@ def pytestpm() -> PytestPluginManager:
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
class TestPytestPluginInteractions:
 | 
			
		||||
    def test_addhooks_conftestplugin(self, testdir, _config_for_test):
 | 
			
		||||
        testdir.makepyfile(
 | 
			
		||||
    def test_addhooks_conftestplugin(
 | 
			
		||||
        self, pytester: Pytester, _config_for_test
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        pytester.makepyfile(
 | 
			
		||||
            newhooks="""
 | 
			
		||||
            def pytest_myhook(xyz):
 | 
			
		||||
                "new hook"
 | 
			
		||||
        """
 | 
			
		||||
        )
 | 
			
		||||
        conf = testdir.makeconftest(
 | 
			
		||||
        conf = pytester.makeconftest(
 | 
			
		||||
            """
 | 
			
		||||
            import newhooks
 | 
			
		||||
            def pytest_addhooks(pluginmanager):
 | 
			
		||||
| 
						 | 
				
			
			@ -54,10 +57,10 @@ class TestPytestPluginInteractions:
 | 
			
		|||
        assert res.ret != 0
 | 
			
		||||
        res.stderr.fnmatch_lines(["*did not find*sys*"])
 | 
			
		||||
 | 
			
		||||
    def test_do_option_postinitialize(self, testdir):
 | 
			
		||||
        config = testdir.parseconfigure()
 | 
			
		||||
    def test_do_option_postinitialize(self, pytester: Pytester) -> None:
 | 
			
		||||
        config = pytester.parseconfigure()
 | 
			
		||||
        assert not hasattr(config.option, "test123")
 | 
			
		||||
        p = testdir.makepyfile(
 | 
			
		||||
        p = pytester.makepyfile(
 | 
			
		||||
            """
 | 
			
		||||
            def pytest_addoption(parser):
 | 
			
		||||
                parser.addoption('--test123', action="store_true",
 | 
			
		||||
| 
						 | 
				
			
			@ -120,20 +123,20 @@ class TestPytestPluginInteractions:
 | 
			
		|||
        finally:
 | 
			
		||||
            undo()
 | 
			
		||||
 | 
			
		||||
    def test_hook_proxy(self, testdir):
 | 
			
		||||
    def test_hook_proxy(self, pytester: Pytester) -> None:
 | 
			
		||||
        """Test the gethookproxy function(#2016)"""
 | 
			
		||||
        config = testdir.parseconfig()
 | 
			
		||||
        config = pytester.parseconfig()
 | 
			
		||||
        session = Session.from_config(config)
 | 
			
		||||
        testdir.makepyfile(**{"tests/conftest.py": "", "tests/subdir/conftest.py": ""})
 | 
			
		||||
        pytester.makepyfile(**{"tests/conftest.py": "", "tests/subdir/conftest.py": ""})
 | 
			
		||||
 | 
			
		||||
        conftest1 = testdir.tmpdir.join("tests/conftest.py")
 | 
			
		||||
        conftest2 = testdir.tmpdir.join("tests/subdir/conftest.py")
 | 
			
		||||
        conftest1 = pytester.path.joinpath("tests/conftest.py")
 | 
			
		||||
        conftest2 = pytester.path.joinpath("tests/subdir/conftest.py")
 | 
			
		||||
 | 
			
		||||
        config.pluginmanager._importconftest(conftest1, importmode="prepend")
 | 
			
		||||
        ihook_a = session.gethookproxy(testdir.tmpdir.join("tests"))
 | 
			
		||||
        ihook_a = session.gethookproxy(pytester.path / "tests")
 | 
			
		||||
        assert ihook_a is not None
 | 
			
		||||
        config.pluginmanager._importconftest(conftest2, importmode="prepend")
 | 
			
		||||
        ihook_b = session.gethookproxy(testdir.tmpdir.join("tests"))
 | 
			
		||||
        ihook_b = session.gethookproxy(pytester.path / "tests")
 | 
			
		||||
        assert ihook_a is not ihook_b
 | 
			
		||||
 | 
			
		||||
    def test_hook_with_addoption(self, testdir):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue