Fix error with --import-mode=importlib and modules containing dataclasses or pickle (#7870)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com> Fixes #7856, fixes #7859
This commit is contained in:
		
							parent
							
								
									366c36a168
								
							
						
					
					
						commit
						b706a2c048
					
				
							
								
								
									
										1
									
								
								AUTHORS
								
								
								
								
							
							
						
						
									
										1
									
								
								AUTHORS
								
								
								
								
							| 
						 | 
					@ -98,6 +98,7 @@ Dominic Mortlock
 | 
				
			||||||
Duncan Betts
 | 
					Duncan Betts
 | 
				
			||||||
Edison Gustavo Muenz
 | 
					Edison Gustavo Muenz
 | 
				
			||||||
Edoardo Batini
 | 
					Edoardo Batini
 | 
				
			||||||
 | 
					Edson Tadeu M. Manoel
 | 
				
			||||||
Eduardo Schettino
 | 
					Eduardo Schettino
 | 
				
			||||||
Eli Boyarski
 | 
					Eli Boyarski
 | 
				
			||||||
Elizaveta Shashkova
 | 
					Elizaveta Shashkova
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					:ref:`--import-mode=importlib <import-modes>` now works with features that
 | 
				
			||||||
 | 
					depend on modules being on :py:data:`sys.modules`, such as :mod:`pickle` and :mod:`dataclasses`.
 | 
				
			||||||
| 
						 | 
					@ -151,7 +151,7 @@ This layout prevents a lot of common pitfalls and has many benefits, which are b
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. note::
 | 
					.. note::
 | 
				
			||||||
    The new ``--import-mode=importlib`` (see :ref:`import-modes`) doesn't have
 | 
					    The new ``--import-mode=importlib`` (see :ref:`import-modes`) doesn't have
 | 
				
			||||||
    any of the drawbacks above because ``sys.path`` and ``sys.modules`` are not changed when importing
 | 
					    any of the drawbacks above because ``sys.path`` is not changed when importing
 | 
				
			||||||
    test modules, so users that run
 | 
					    test modules, so users that run
 | 
				
			||||||
    into this issue are strongly encouraged to try it and report if the new option works well for them.
 | 
					    into this issue are strongly encouraged to try it and report if the new option works well for them.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,14 +16,14 @@ import process can be controlled through the ``--import-mode`` command-line flag
 | 
				
			||||||
these values:
 | 
					these values:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* ``prepend`` (default): the directory path containing each module will be inserted into the *beginning*
 | 
					* ``prepend`` (default): the directory path containing each module will be inserted into the *beginning*
 | 
				
			||||||
  of ``sys.path`` if not already there, and then imported with the `__import__ <https://docs.python.org/3/library/functions.html#__import__>`__ builtin.
 | 
					  of :py:data:`sys.path` if not already there, and then imported with the `__import__ <https://docs.python.org/3/library/functions.html#__import__>`__ builtin.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  This requires test module names to be unique when the test directory tree is not arranged in
 | 
					  This requires test module names to be unique when the test directory tree is not arranged in
 | 
				
			||||||
  packages, because the modules will put in ``sys.modules`` after importing.
 | 
					  packages, because the modules will put in :py:data:`sys.modules` after importing.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  This is the classic mechanism, dating back from the time Python 2 was still supported.
 | 
					  This is the classic mechanism, dating back from the time Python 2 was still supported.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* ``append``: the directory containing each module is appended to the end of ``sys.path`` if not already
 | 
					* ``append``: the directory containing each module is appended to the end of :py:data:`sys.path` if not already
 | 
				
			||||||
  there, and imported with ``__import__``.
 | 
					  there, and imported with ``__import__``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  This better allows to run test modules against installed versions of a package even if the
 | 
					  This better allows to run test modules against installed versions of a package even if the
 | 
				
			||||||
| 
						 | 
					@ -41,17 +41,14 @@ these values:
 | 
				
			||||||
  we advocate for using :ref:`src <src-layout>` layouts.
 | 
					  we advocate for using :ref:`src <src-layout>` layouts.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Same as ``prepend``, requires test module names to be unique when the test directory tree is
 | 
					  Same as ``prepend``, requires test module names to be unique when the test directory tree is
 | 
				
			||||||
  not arranged in packages, because the modules will put in ``sys.modules`` after importing.
 | 
					  not arranged in packages, because the modules will put in :py:data:`sys.modules` after importing.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* ``importlib``: new in pytest-6.0, this mode uses `importlib <https://docs.python.org/3/library/importlib.html>`__ to import test modules. This gives full control over the import process, and doesn't require
 | 
					* ``importlib``: new in pytest-6.0, this mode uses `importlib <https://docs.python.org/3/library/importlib.html>`__ to import test modules. This gives full control over the import process, and doesn't require changing :py:data:`sys.path`.
 | 
				
			||||||
  changing ``sys.path`` or ``sys.modules`` at all.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  For this reason this doesn't require test module names to be unique at all, but also makes test
 | 
					  For this reason this doesn't require test module names to be unique, but also makes test
 | 
				
			||||||
  modules non-importable by each other. This was made possible in previous modes, for tests not residing
 | 
					  modules non-importable by each other.
 | 
				
			||||||
  in Python packages, because of the side-effects of changing ``sys.path`` and ``sys.modules``
 | 
					 | 
				
			||||||
  mentioned above. Users which require this should turn their tests into proper packages instead.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  We intend to make ``importlib`` the default in future releases.
 | 
					  We intend to make ``importlib`` the default in future releases, depending on feedback.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
``prepend`` and ``append`` import modes scenarios
 | 
					``prepend`` and ``append`` import modes scenarios
 | 
				
			||||||
-------------------------------------------------
 | 
					-------------------------------------------------
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -481,7 +481,9 @@ class PytestPluginManager(PluginManager):
 | 
				
			||||||
    #
 | 
					    #
 | 
				
			||||||
    # Internal API for local conftest plugin handling.
 | 
					    # Internal API for local conftest plugin handling.
 | 
				
			||||||
    #
 | 
					    #
 | 
				
			||||||
    def _set_initial_conftests(self, namespace: argparse.Namespace) -> None:
 | 
					    def _set_initial_conftests(
 | 
				
			||||||
 | 
					        self, namespace: argparse.Namespace, rootpath: Path
 | 
				
			||||||
 | 
					    ) -> None:
 | 
				
			||||||
        """Load initial conftest files given a preparsed "namespace".
 | 
					        """Load initial conftest files given a preparsed "namespace".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        As conftest files may add their own command line options which have
 | 
					        As conftest files may add their own command line options which have
 | 
				
			||||||
| 
						 | 
					@ -507,26 +509,24 @@ class PytestPluginManager(PluginManager):
 | 
				
			||||||
                path = path[:i]
 | 
					                path = path[:i]
 | 
				
			||||||
            anchor = absolutepath(current / path)
 | 
					            anchor = absolutepath(current / path)
 | 
				
			||||||
            if anchor.exists():  # we found some file object
 | 
					            if anchor.exists():  # we found some file object
 | 
				
			||||||
                self._try_load_conftest(anchor, namespace.importmode)
 | 
					                self._try_load_conftest(anchor, namespace.importmode, rootpath)
 | 
				
			||||||
                foundanchor = True
 | 
					                foundanchor = True
 | 
				
			||||||
        if not foundanchor:
 | 
					        if not foundanchor:
 | 
				
			||||||
            self._try_load_conftest(current, namespace.importmode)
 | 
					            self._try_load_conftest(current, namespace.importmode, rootpath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _try_load_conftest(
 | 
					    def _try_load_conftest(
 | 
				
			||||||
        self, anchor: Path, importmode: Union[str, ImportMode]
 | 
					        self, anchor: Path, importmode: Union[str, ImportMode], rootpath: Path
 | 
				
			||||||
    ) -> None:
 | 
					    ) -> None:
 | 
				
			||||||
        self._getconftestmodules(anchor, importmode)
 | 
					        self._getconftestmodules(anchor, importmode, rootpath)
 | 
				
			||||||
        # let's also consider test* subdirs
 | 
					        # let's also consider test* subdirs
 | 
				
			||||||
        if anchor.is_dir():
 | 
					        if anchor.is_dir():
 | 
				
			||||||
            for x in anchor.glob("test*"):
 | 
					            for x in anchor.glob("test*"):
 | 
				
			||||||
                if x.is_dir():
 | 
					                if x.is_dir():
 | 
				
			||||||
                    self._getconftestmodules(x, importmode)
 | 
					                    self._getconftestmodules(x, importmode, rootpath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @lru_cache(maxsize=128)
 | 
					    @lru_cache(maxsize=128)
 | 
				
			||||||
    def _getconftestmodules(
 | 
					    def _getconftestmodules(
 | 
				
			||||||
        self,
 | 
					        self, path: Path, importmode: Union[str, ImportMode], rootpath: Path
 | 
				
			||||||
        path: Path,
 | 
					 | 
				
			||||||
        importmode: Union[str, ImportMode],
 | 
					 | 
				
			||||||
    ) -> List[types.ModuleType]:
 | 
					    ) -> List[types.ModuleType]:
 | 
				
			||||||
        if self._noconftest:
 | 
					        if self._noconftest:
 | 
				
			||||||
            return []
 | 
					            return []
 | 
				
			||||||
| 
						 | 
					@ -545,7 +545,7 @@ class PytestPluginManager(PluginManager):
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
            conftestpath = parent / "conftest.py"
 | 
					            conftestpath = parent / "conftest.py"
 | 
				
			||||||
            if conftestpath.is_file():
 | 
					            if conftestpath.is_file():
 | 
				
			||||||
                mod = self._importconftest(conftestpath, importmode)
 | 
					                mod = self._importconftest(conftestpath, importmode, rootpath)
 | 
				
			||||||
                clist.append(mod)
 | 
					                clist.append(mod)
 | 
				
			||||||
        self._dirpath2confmods[directory] = clist
 | 
					        self._dirpath2confmods[directory] = clist
 | 
				
			||||||
        return clist
 | 
					        return clist
 | 
				
			||||||
| 
						 | 
					@ -555,8 +555,9 @@ class PytestPluginManager(PluginManager):
 | 
				
			||||||
        name: str,
 | 
					        name: str,
 | 
				
			||||||
        path: Path,
 | 
					        path: Path,
 | 
				
			||||||
        importmode: Union[str, ImportMode],
 | 
					        importmode: Union[str, ImportMode],
 | 
				
			||||||
 | 
					        rootpath: Path,
 | 
				
			||||||
    ) -> Tuple[types.ModuleType, Any]:
 | 
					    ) -> Tuple[types.ModuleType, Any]:
 | 
				
			||||||
        modules = self._getconftestmodules(path, importmode)
 | 
					        modules = self._getconftestmodules(path, importmode, rootpath=rootpath)
 | 
				
			||||||
        for mod in reversed(modules):
 | 
					        for mod in reversed(modules):
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                return mod, getattr(mod, name)
 | 
					                return mod, getattr(mod, name)
 | 
				
			||||||
| 
						 | 
					@ -565,9 +566,7 @@ class PytestPluginManager(PluginManager):
 | 
				
			||||||
        raise KeyError(name)
 | 
					        raise KeyError(name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _importconftest(
 | 
					    def _importconftest(
 | 
				
			||||||
        self,
 | 
					        self, conftestpath: Path, importmode: Union[str, ImportMode], rootpath: Path
 | 
				
			||||||
        conftestpath: Path,
 | 
					 | 
				
			||||||
        importmode: Union[str, ImportMode],
 | 
					 | 
				
			||||||
    ) -> types.ModuleType:
 | 
					    ) -> types.ModuleType:
 | 
				
			||||||
        # Use a resolved Path object as key to avoid loading the same conftest
 | 
					        # Use a resolved Path object as key to avoid loading the same conftest
 | 
				
			||||||
        # twice with build systems that create build directories containing
 | 
					        # twice with build systems that create build directories containing
 | 
				
			||||||
| 
						 | 
					@ -584,7 +583,7 @@ class PytestPluginManager(PluginManager):
 | 
				
			||||||
            _ensure_removed_sysmodule(conftestpath.stem)
 | 
					            _ensure_removed_sysmodule(conftestpath.stem)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            mod = import_path(conftestpath, mode=importmode)
 | 
					            mod = import_path(conftestpath, mode=importmode, root=rootpath)
 | 
				
			||||||
        except Exception as e:
 | 
					        except Exception as e:
 | 
				
			||||||
            assert e.__traceback__ is not None
 | 
					            assert e.__traceback__ is not None
 | 
				
			||||||
            exc_info = (type(e), e, e.__traceback__)
 | 
					            exc_info = (type(e), e, e.__traceback__)
 | 
				
			||||||
| 
						 | 
					@ -1086,7 +1085,9 @@ class Config:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @hookimpl(trylast=True)
 | 
					    @hookimpl(trylast=True)
 | 
				
			||||||
    def pytest_load_initial_conftests(self, early_config: "Config") -> None:
 | 
					    def pytest_load_initial_conftests(self, early_config: "Config") -> None:
 | 
				
			||||||
        self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
 | 
					        self.pluginmanager._set_initial_conftests(
 | 
				
			||||||
 | 
					            early_config.known_args_namespace, rootpath=early_config.rootpath
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _initini(self, args: Sequence[str]) -> None:
 | 
					    def _initini(self, args: Sequence[str]) -> None:
 | 
				
			||||||
        ns, unknown_args = self._parser.parse_known_and_unknown_args(
 | 
					        ns, unknown_args = self._parser.parse_known_and_unknown_args(
 | 
				
			||||||
| 
						 | 
					@ -1437,10 +1438,12 @@ class Config:
 | 
				
			||||||
            assert type in [None, "string"]
 | 
					            assert type in [None, "string"]
 | 
				
			||||||
            return value
 | 
					            return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _getconftest_pathlist(self, name: str, path: Path) -> Optional[List[Path]]:
 | 
					    def _getconftest_pathlist(
 | 
				
			||||||
 | 
					        self, name: str, path: Path, rootpath: Path
 | 
				
			||||||
 | 
					    ) -> Optional[List[Path]]:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            mod, relroots = self.pluginmanager._rget_with_confmod(
 | 
					            mod, relroots = self.pluginmanager._rget_with_confmod(
 | 
				
			||||||
                name, path, self.getoption("importmode")
 | 
					                name, path, self.getoption("importmode"), rootpath
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        except KeyError:
 | 
					        except KeyError:
 | 
				
			||||||
            return None
 | 
					            return None
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -534,11 +534,13 @@ class DoctestModule(pytest.Module):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.path.name == "conftest.py":
 | 
					        if self.path.name == "conftest.py":
 | 
				
			||||||
            module = self.config.pluginmanager._importconftest(
 | 
					            module = self.config.pluginmanager._importconftest(
 | 
				
			||||||
                self.path, self.config.getoption("importmode")
 | 
					                self.path,
 | 
				
			||||||
 | 
					                self.config.getoption("importmode"),
 | 
				
			||||||
 | 
					                rootpath=self.config.rootpath,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                module = import_path(self.path)
 | 
					                module = import_path(self.path, root=self.config.rootpath)
 | 
				
			||||||
            except ImportError:
 | 
					            except ImportError:
 | 
				
			||||||
                if self.config.getvalue("doctest_ignore_import_errors"):
 | 
					                if self.config.getvalue("doctest_ignore_import_errors"):
 | 
				
			||||||
                    pytest.skip("unable to import module %r" % self.path)
 | 
					                    pytest.skip("unable to import module %r" % self.path)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -378,7 +378,9 @@ def _in_venv(path: Path) -> bool:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def pytest_ignore_collect(fspath: Path, config: Config) -> Optional[bool]:
 | 
					def pytest_ignore_collect(fspath: Path, config: Config) -> Optional[bool]:
 | 
				
			||||||
    ignore_paths = config._getconftest_pathlist("collect_ignore", path=fspath.parent)
 | 
					    ignore_paths = config._getconftest_pathlist(
 | 
				
			||||||
 | 
					        "collect_ignore", path=fspath.parent, rootpath=config.rootpath
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    ignore_paths = ignore_paths or []
 | 
					    ignore_paths = ignore_paths or []
 | 
				
			||||||
    excludeopt = config.getoption("ignore")
 | 
					    excludeopt = config.getoption("ignore")
 | 
				
			||||||
    if excludeopt:
 | 
					    if excludeopt:
 | 
				
			||||||
| 
						 | 
					@ -388,7 +390,7 @@ def pytest_ignore_collect(fspath: Path, config: Config) -> Optional[bool]:
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ignore_globs = config._getconftest_pathlist(
 | 
					    ignore_globs = config._getconftest_pathlist(
 | 
				
			||||||
        "collect_ignore_glob", path=fspath.parent
 | 
					        "collect_ignore_glob", path=fspath.parent, rootpath=config.rootpath
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    ignore_globs = ignore_globs or []
 | 
					    ignore_globs = ignore_globs or []
 | 
				
			||||||
    excludeglobopt = config.getoption("ignore_glob")
 | 
					    excludeglobopt = config.getoption("ignore_glob")
 | 
				
			||||||
| 
						 | 
					@ -546,7 +548,9 @@ class Session(nodes.FSCollector):
 | 
				
			||||||
        # hooks with all conftest.py files.
 | 
					        # hooks with all conftest.py files.
 | 
				
			||||||
        pm = self.config.pluginmanager
 | 
					        pm = self.config.pluginmanager
 | 
				
			||||||
        my_conftestmodules = pm._getconftestmodules(
 | 
					        my_conftestmodules = pm._getconftestmodules(
 | 
				
			||||||
            Path(fspath), self.config.getoption("importmode")
 | 
					            Path(fspath),
 | 
				
			||||||
 | 
					            self.config.getoption("importmode"),
 | 
				
			||||||
 | 
					            rootpath=self.config.rootpath,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
 | 
					        remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
 | 
				
			||||||
        if remove_mods:
 | 
					        if remove_mods:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,6 +23,7 @@ from pathlib import PurePath
 | 
				
			||||||
from posixpath import sep as posix_sep
 | 
					from posixpath import sep as posix_sep
 | 
				
			||||||
from types import ModuleType
 | 
					from types import ModuleType
 | 
				
			||||||
from typing import Callable
 | 
					from typing import Callable
 | 
				
			||||||
 | 
					from typing import Dict
 | 
				
			||||||
from typing import Iterable
 | 
					from typing import Iterable
 | 
				
			||||||
from typing import Iterator
 | 
					from typing import Iterator
 | 
				
			||||||
from typing import Optional
 | 
					from typing import Optional
 | 
				
			||||||
| 
						 | 
					@ -454,6 +455,7 @@ def import_path(
 | 
				
			||||||
    p: Union[str, "os.PathLike[str]"],
 | 
					    p: Union[str, "os.PathLike[str]"],
 | 
				
			||||||
    *,
 | 
					    *,
 | 
				
			||||||
    mode: Union[str, ImportMode] = ImportMode.prepend,
 | 
					    mode: Union[str, ImportMode] = ImportMode.prepend,
 | 
				
			||||||
 | 
					    root: Path,
 | 
				
			||||||
) -> ModuleType:
 | 
					) -> ModuleType:
 | 
				
			||||||
    """Import and return a module from the given path, which can be a file (a module) or
 | 
					    """Import and return a module from the given path, which can be a file (a module) or
 | 
				
			||||||
    a directory (a package).
 | 
					    a directory (a package).
 | 
				
			||||||
| 
						 | 
					@ -471,6 +473,11 @@ def import_path(
 | 
				
			||||||
      to import the module, which avoids having to use `__import__` and muck with `sys.path`
 | 
					      to import the module, which avoids having to use `__import__` and muck with `sys.path`
 | 
				
			||||||
      at all. It effectively allows having same-named test modules in different places.
 | 
					      at all. It effectively allows having same-named test modules in different places.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param root:
 | 
				
			||||||
 | 
					        Used as an anchor when mode == ImportMode.importlib to obtain
 | 
				
			||||||
 | 
					        a unique name for the module being imported so it can safely be stored
 | 
				
			||||||
 | 
					        into ``sys.modules``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :raises ImportPathMismatchError:
 | 
					    :raises ImportPathMismatchError:
 | 
				
			||||||
        If after importing the given `path` and the module `__file__`
 | 
					        If after importing the given `path` and the module `__file__`
 | 
				
			||||||
        are different. Only raised in `prepend` and `append` modes.
 | 
					        are different. Only raised in `prepend` and `append` modes.
 | 
				
			||||||
| 
						 | 
					@ -483,7 +490,7 @@ def import_path(
 | 
				
			||||||
        raise ImportError(path)
 | 
					        raise ImportError(path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if mode is ImportMode.importlib:
 | 
					    if mode is ImportMode.importlib:
 | 
				
			||||||
        module_name = path.stem
 | 
					        module_name = module_name_from_path(path, root)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for meta_importer in sys.meta_path:
 | 
					        for meta_importer in sys.meta_path:
 | 
				
			||||||
            spec = meta_importer.find_spec(module_name, [str(path.parent)])
 | 
					            spec = meta_importer.find_spec(module_name, [str(path.parent)])
 | 
				
			||||||
| 
						 | 
					@ -497,7 +504,9 @@ def import_path(
 | 
				
			||||||
                "Can't find module {} at location {}".format(module_name, str(path))
 | 
					                "Can't find module {} at location {}".format(module_name, str(path))
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        mod = importlib.util.module_from_spec(spec)
 | 
					        mod = importlib.util.module_from_spec(spec)
 | 
				
			||||||
 | 
					        sys.modules[module_name] = mod
 | 
				
			||||||
        spec.loader.exec_module(mod)  # type: ignore[union-attr]
 | 
					        spec.loader.exec_module(mod)  # type: ignore[union-attr]
 | 
				
			||||||
 | 
					        insert_missing_modules(sys.modules, module_name)
 | 
				
			||||||
        return mod
 | 
					        return mod
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pkg_path = resolve_package_path(path)
 | 
					    pkg_path = resolve_package_path(path)
 | 
				
			||||||
| 
						 | 
					@ -562,6 +571,47 @@ else:
 | 
				
			||||||
        return os.path.samefile(f1, f2)
 | 
					        return os.path.samefile(f1, f2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def module_name_from_path(path: Path, root: Path) -> str:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Return a dotted module name based on the given path, anchored on root.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    For example: path="projects/src/tests/test_foo.py" and root="/projects", the
 | 
				
			||||||
 | 
					    resulting module name will be "src.tests.test_foo".
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    path = path.with_suffix("")
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        relative_path = path.relative_to(root)
 | 
				
			||||||
 | 
					    except ValueError:
 | 
				
			||||||
 | 
					        # If we can't get a relative path to root, use the full path, except
 | 
				
			||||||
 | 
					        # for the first part ("d:\\" or "/" depending on the platform, for example).
 | 
				
			||||||
 | 
					        path_parts = path.parts[1:]
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        # Use the parts for the relative path to the root path.
 | 
				
			||||||
 | 
					        path_parts = relative_path.parts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return ".".join(path_parts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Used by ``import_path`` to create intermediate modules when using mode=importlib.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    When we want to import a module as "src.tests.test_foo" for example, we need
 | 
				
			||||||
 | 
					    to create empty modules "src" and "src.tests" after inserting "src.tests.test_foo",
 | 
				
			||||||
 | 
					    otherwise "src.tests.test_foo" is not importable by ``__import__``.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    module_parts = module_name.split(".")
 | 
				
			||||||
 | 
					    while module_name:
 | 
				
			||||||
 | 
					        if module_name not in modules:
 | 
				
			||||||
 | 
					            module = ModuleType(
 | 
				
			||||||
 | 
					                module_name,
 | 
				
			||||||
 | 
					                doc="Empty module created by pytest's importmode=importlib.",
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            modules[module_name] = module
 | 
				
			||||||
 | 
					        module_parts.pop(-1)
 | 
				
			||||||
 | 
					        module_name = ".".join(module_parts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def resolve_package_path(path: Path) -> Optional[Path]:
 | 
					def resolve_package_path(path: Path) -> Optional[Path]:
 | 
				
			||||||
    """Return the Python package path by looking for the last
 | 
					    """Return the Python package path by looking for the last
 | 
				
			||||||
    directory upwards which still contains an __init__.py.
 | 
					    directory upwards which still contains an __init__.py.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -577,7 +577,7 @@ class Module(nodes.File, PyCollector):
 | 
				
			||||||
        # We assume we are only called once per module.
 | 
					        # We assume we are only called once per module.
 | 
				
			||||||
        importmode = self.config.getoption("--import-mode")
 | 
					        importmode = self.config.getoption("--import-mode")
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            mod = import_path(self.path, mode=importmode)
 | 
					            mod = import_path(self.path, mode=importmode, root=self.config.rootpath)
 | 
				
			||||||
        except SyntaxError as e:
 | 
					        except SyntaxError as e:
 | 
				
			||||||
            raise self.CollectError(
 | 
					            raise self.CollectError(
 | 
				
			||||||
                ExceptionInfo.from_current().getrepr(style="short")
 | 
					                ExceptionInfo.from_current().getrepr(style="short")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -162,7 +162,7 @@ class TestTraceback_f_g_h:
 | 
				
			||||||
    def test_traceback_cut_excludepath(self, pytester: Pytester) -> None:
 | 
					    def test_traceback_cut_excludepath(self, pytester: Pytester) -> None:
 | 
				
			||||||
        p = pytester.makepyfile("def f(): raise ValueError")
 | 
					        p = pytester.makepyfile("def f(): raise ValueError")
 | 
				
			||||||
        with pytest.raises(ValueError) as excinfo:
 | 
					        with pytest.raises(ValueError) as excinfo:
 | 
				
			||||||
            import_path(p).f()  # type: ignore[attr-defined]
 | 
					            import_path(p, root=pytester.path).f()  # type: ignore[attr-defined]
 | 
				
			||||||
        basedir = Path(pytest.__file__).parent
 | 
					        basedir = Path(pytest.__file__).parent
 | 
				
			||||||
        newtraceback = excinfo.traceback.cut(excludepath=basedir)
 | 
					        newtraceback = excinfo.traceback.cut(excludepath=basedir)
 | 
				
			||||||
        for x in newtraceback:
 | 
					        for x in newtraceback:
 | 
				
			||||||
| 
						 | 
					@ -443,7 +443,7 @@ class TestFormattedExcinfo:
 | 
				
			||||||
            tmp_path.joinpath("__init__.py").touch()
 | 
					            tmp_path.joinpath("__init__.py").touch()
 | 
				
			||||||
            modpath.write_text(source)
 | 
					            modpath.write_text(source)
 | 
				
			||||||
            importlib.invalidate_caches()
 | 
					            importlib.invalidate_caches()
 | 
				
			||||||
            return import_path(modpath)
 | 
					            return import_path(modpath, root=tmp_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return importasmod
 | 
					        return importasmod
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -298,7 +298,7 @@ def test_source_of_class_at_eof_without_newline(_sys_snapshot, tmp_path: Path) -
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    path = tmp_path.joinpath("a.py")
 | 
					    path = tmp_path.joinpath("a.py")
 | 
				
			||||||
    path.write_text(str(source))
 | 
					    path.write_text(str(source))
 | 
				
			||||||
    mod: Any = import_path(path)
 | 
					    mod: Any = import_path(path, root=tmp_path)
 | 
				
			||||||
    s2 = Source(mod.A)
 | 
					    s2 = Source(mod.A)
 | 
				
			||||||
    assert str(source).strip() == str(s2).strip()
 | 
					    assert str(source).strip() == str(s2).strip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2069,9 +2069,9 @@ class TestAutouseManagement:
 | 
				
			||||||
        reprec = pytester.inline_run("-v", "-s", "--confcutdir", pytester.path)
 | 
					        reprec = pytester.inline_run("-v", "-s", "--confcutdir", pytester.path)
 | 
				
			||||||
        reprec.assertoutcome(passed=8)
 | 
					        reprec.assertoutcome(passed=8)
 | 
				
			||||||
        config = reprec.getcalls("pytest_unconfigure")[0].config
 | 
					        config = reprec.getcalls("pytest_unconfigure")[0].config
 | 
				
			||||||
        values = config.pluginmanager._getconftestmodules(p, importmode="prepend")[
 | 
					        values = config.pluginmanager._getconftestmodules(
 | 
				
			||||||
            0
 | 
					            p, importmode="prepend", rootpath=pytester.path
 | 
				
			||||||
        ].values
 | 
					        )[0].values
 | 
				
			||||||
        assert values == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2
 | 
					        assert values == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_scope_ordering(self, pytester: Pytester) -> None:
 | 
					    def test_scope_ordering(self, pytester: Pytester) -> None:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -597,8 +597,14 @@ class TestConfigAPI:
 | 
				
			||||||
        p = tmp_path.joinpath("conftest.py")
 | 
					        p = tmp_path.joinpath("conftest.py")
 | 
				
			||||||
        p.write_text(f"pathlist = ['.', {str(somepath)!r}]")
 | 
					        p.write_text(f"pathlist = ['.', {str(somepath)!r}]")
 | 
				
			||||||
        config = pytester.parseconfigure(p)
 | 
					        config = pytester.parseconfigure(p)
 | 
				
			||||||
        assert config._getconftest_pathlist("notexist", path=tmp_path) is None
 | 
					        assert (
 | 
				
			||||||
        pl = config._getconftest_pathlist("pathlist", path=tmp_path) or []
 | 
					            config._getconftest_pathlist("notexist", path=tmp_path, rootpath=tmp_path)
 | 
				
			||||||
 | 
					            is None
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        pl = (
 | 
				
			||||||
 | 
					            config._getconftest_pathlist("pathlist", path=tmp_path, rootpath=tmp_path)
 | 
				
			||||||
 | 
					            or []
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        print(pl)
 | 
					        print(pl)
 | 
				
			||||||
        assert len(pl) == 2
 | 
					        assert len(pl) == 2
 | 
				
			||||||
        assert pl[0] == tmp_path
 | 
					        assert pl[0] == tmp_path
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,7 +35,7 @@ def conftest_setinitial(
 | 
				
			||||||
            self.importmode = "prepend"
 | 
					            self.importmode = "prepend"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    namespace = cast(argparse.Namespace, Namespace())
 | 
					    namespace = cast(argparse.Namespace, Namespace())
 | 
				
			||||||
    conftest._set_initial_conftests(namespace)
 | 
					    conftest._set_initial_conftests(namespace, rootpath=Path(args[0]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.mark.usefixtures("_sys_snapshot")
 | 
					@pytest.mark.usefixtures("_sys_snapshot")
 | 
				
			||||||
| 
						 | 
					@ -57,39 +57,62 @@ class TestConftestValueAccessGlobal:
 | 
				
			||||||
    def test_basic_init(self, basedir: Path) -> None:
 | 
					    def test_basic_init(self, basedir: Path) -> None:
 | 
				
			||||||
        conftest = PytestPluginManager()
 | 
					        conftest = PytestPluginManager()
 | 
				
			||||||
        p = basedir / "adir"
 | 
					        p = basedir / "adir"
 | 
				
			||||||
        assert conftest._rget_with_confmod("a", p, importmode="prepend")[1] == 1
 | 
					        assert (
 | 
				
			||||||
 | 
					            conftest._rget_with_confmod("a", p, importmode="prepend", rootpath=basedir)[
 | 
				
			||||||
 | 
					                1
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					            == 1
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_immediate_initialiation_and_incremental_are_the_same(
 | 
					    def test_immediate_initialiation_and_incremental_are_the_same(
 | 
				
			||||||
        self, basedir: Path
 | 
					        self, basedir: Path
 | 
				
			||||||
    ) -> None:
 | 
					    ) -> None:
 | 
				
			||||||
        conftest = PytestPluginManager()
 | 
					        conftest = PytestPluginManager()
 | 
				
			||||||
        assert not len(conftest._dirpath2confmods)
 | 
					        assert not len(conftest._dirpath2confmods)
 | 
				
			||||||
        conftest._getconftestmodules(basedir, importmode="prepend")
 | 
					        conftest._getconftestmodules(
 | 
				
			||||||
 | 
					            basedir, importmode="prepend", rootpath=Path(basedir)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        snap1 = len(conftest._dirpath2confmods)
 | 
					        snap1 = len(conftest._dirpath2confmods)
 | 
				
			||||||
        assert snap1 == 1
 | 
					        assert snap1 == 1
 | 
				
			||||||
        conftest._getconftestmodules(basedir / "adir", importmode="prepend")
 | 
					        conftest._getconftestmodules(
 | 
				
			||||||
 | 
					            basedir / "adir", importmode="prepend", rootpath=basedir
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        assert len(conftest._dirpath2confmods) == snap1 + 1
 | 
					        assert len(conftest._dirpath2confmods) == snap1 + 1
 | 
				
			||||||
        conftest._getconftestmodules(basedir / "b", importmode="prepend")
 | 
					        conftest._getconftestmodules(
 | 
				
			||||||
 | 
					            basedir / "b", importmode="prepend", rootpath=basedir
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        assert len(conftest._dirpath2confmods) == snap1 + 2
 | 
					        assert len(conftest._dirpath2confmods) == snap1 + 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_value_access_not_existing(self, basedir: Path) -> None:
 | 
					    def test_value_access_not_existing(self, basedir: Path) -> None:
 | 
				
			||||||
        conftest = ConftestWithSetinitial(basedir)
 | 
					        conftest = ConftestWithSetinitial(basedir)
 | 
				
			||||||
        with pytest.raises(KeyError):
 | 
					        with pytest.raises(KeyError):
 | 
				
			||||||
            conftest._rget_with_confmod("a", basedir, importmode="prepend")
 | 
					            conftest._rget_with_confmod(
 | 
				
			||||||
 | 
					                "a", basedir, importmode="prepend", rootpath=Path(basedir)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_value_access_by_path(self, basedir: Path) -> None:
 | 
					    def test_value_access_by_path(self, basedir: Path) -> None:
 | 
				
			||||||
        conftest = ConftestWithSetinitial(basedir)
 | 
					        conftest = ConftestWithSetinitial(basedir)
 | 
				
			||||||
        adir = basedir / "adir"
 | 
					        adir = basedir / "adir"
 | 
				
			||||||
        assert conftest._rget_with_confmod("a", adir, importmode="prepend")[1] == 1
 | 
					 | 
				
			||||||
        assert (
 | 
					        assert (
 | 
				
			||||||
            conftest._rget_with_confmod("a", adir / "b", importmode="prepend")[1] == 1.5
 | 
					            conftest._rget_with_confmod(
 | 
				
			||||||
 | 
					                "a", adir, importmode="prepend", rootpath=basedir
 | 
				
			||||||
 | 
					            )[1]
 | 
				
			||||||
 | 
					            == 1
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        assert (
 | 
				
			||||||
 | 
					            conftest._rget_with_confmod(
 | 
				
			||||||
 | 
					                "a", adir / "b", importmode="prepend", rootpath=basedir
 | 
				
			||||||
 | 
					            )[1]
 | 
				
			||||||
 | 
					            == 1.5
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_value_access_with_confmod(self, basedir: Path) -> None:
 | 
					    def test_value_access_with_confmod(self, basedir: Path) -> None:
 | 
				
			||||||
        startdir = basedir / "adir" / "b"
 | 
					        startdir = basedir / "adir" / "b"
 | 
				
			||||||
        startdir.joinpath("xx").mkdir()
 | 
					        startdir.joinpath("xx").mkdir()
 | 
				
			||||||
        conftest = ConftestWithSetinitial(startdir)
 | 
					        conftest = ConftestWithSetinitial(startdir)
 | 
				
			||||||
        mod, value = conftest._rget_with_confmod("a", startdir, importmode="prepend")
 | 
					        mod, value = conftest._rget_with_confmod(
 | 
				
			||||||
 | 
					            "a", startdir, importmode="prepend", rootpath=Path(basedir)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        assert value == 1.5
 | 
					        assert value == 1.5
 | 
				
			||||||
        path = Path(mod.__file__)
 | 
					        path = Path(mod.__file__)
 | 
				
			||||||
        assert path.parent == basedir / "adir" / "b"
 | 
					        assert path.parent == basedir / "adir" / "b"
 | 
				
			||||||
| 
						 | 
					@ -110,7 +133,9 @@ def test_doubledash_considered(pytester: Pytester) -> None:
 | 
				
			||||||
    conf.joinpath("conftest.py").touch()
 | 
					    conf.joinpath("conftest.py").touch()
 | 
				
			||||||
    conftest = PytestPluginManager()
 | 
					    conftest = PytestPluginManager()
 | 
				
			||||||
    conftest_setinitial(conftest, [conf.name, conf.name])
 | 
					    conftest_setinitial(conftest, [conf.name, conf.name])
 | 
				
			||||||
    values = conftest._getconftestmodules(conf, importmode="prepend")
 | 
					    values = conftest._getconftestmodules(
 | 
				
			||||||
 | 
					        conf, importmode="prepend", rootpath=pytester.path
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    assert len(values) == 1
 | 
					    assert len(values) == 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -134,7 +159,7 @@ def test_conftest_global_import(pytester: Pytester) -> None:
 | 
				
			||||||
        import pytest
 | 
					        import pytest
 | 
				
			||||||
        from _pytest.config import PytestPluginManager
 | 
					        from _pytest.config import PytestPluginManager
 | 
				
			||||||
        conf = PytestPluginManager()
 | 
					        conf = PytestPluginManager()
 | 
				
			||||||
        mod = conf._importconftest(Path("conftest.py"), importmode="prepend")
 | 
					        mod = conf._importconftest(Path("conftest.py"), importmode="prepend", rootpath=Path.cwd())
 | 
				
			||||||
        assert mod.x == 3
 | 
					        assert mod.x == 3
 | 
				
			||||||
        import conftest
 | 
					        import conftest
 | 
				
			||||||
        assert conftest is mod, (conftest, mod)
 | 
					        assert conftest is mod, (conftest, mod)
 | 
				
			||||||
| 
						 | 
					@ -142,7 +167,7 @@ def test_conftest_global_import(pytester: Pytester) -> None:
 | 
				
			||||||
        sub.mkdir()
 | 
					        sub.mkdir()
 | 
				
			||||||
        subconf = sub / "conftest.py"
 | 
					        subconf = sub / "conftest.py"
 | 
				
			||||||
        subconf.write_text("y=4")
 | 
					        subconf.write_text("y=4")
 | 
				
			||||||
        mod2 = conf._importconftest(subconf, importmode="prepend")
 | 
					        mod2 = conf._importconftest(subconf, importmode="prepend", rootpath=Path.cwd())
 | 
				
			||||||
        assert mod != mod2
 | 
					        assert mod != mod2
 | 
				
			||||||
        assert mod2.y == 4
 | 
					        assert mod2.y == 4
 | 
				
			||||||
        import conftest
 | 
					        import conftest
 | 
				
			||||||
| 
						 | 
					@ -158,17 +183,25 @@ def test_conftestcutdir(pytester: Pytester) -> None:
 | 
				
			||||||
    p = pytester.mkdir("x")
 | 
					    p = pytester.mkdir("x")
 | 
				
			||||||
    conftest = PytestPluginManager()
 | 
					    conftest = PytestPluginManager()
 | 
				
			||||||
    conftest_setinitial(conftest, [pytester.path], confcutdir=p)
 | 
					    conftest_setinitial(conftest, [pytester.path], confcutdir=p)
 | 
				
			||||||
    values = conftest._getconftestmodules(p, importmode="prepend")
 | 
					    values = conftest._getconftestmodules(
 | 
				
			||||||
 | 
					        p, importmode="prepend", rootpath=pytester.path
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    assert len(values) == 0
 | 
					    assert len(values) == 0
 | 
				
			||||||
    values = conftest._getconftestmodules(conf.parent, importmode="prepend")
 | 
					    values = conftest._getconftestmodules(
 | 
				
			||||||
 | 
					        conf.parent, importmode="prepend", rootpath=pytester.path
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    assert len(values) == 0
 | 
					    assert len(values) == 0
 | 
				
			||||||
    assert Path(conf) not in conftest._conftestpath2mod
 | 
					    assert Path(conf) not in conftest._conftestpath2mod
 | 
				
			||||||
    # but we can still import a conftest directly
 | 
					    # but we can still import a conftest directly
 | 
				
			||||||
    conftest._importconftest(conf, importmode="prepend")
 | 
					    conftest._importconftest(conf, importmode="prepend", rootpath=pytester.path)
 | 
				
			||||||
    values = conftest._getconftestmodules(conf.parent, importmode="prepend")
 | 
					    values = conftest._getconftestmodules(
 | 
				
			||||||
 | 
					        conf.parent, importmode="prepend", rootpath=pytester.path
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    assert values[0].__file__.startswith(str(conf))
 | 
					    assert values[0].__file__.startswith(str(conf))
 | 
				
			||||||
    # and all sub paths get updated properly
 | 
					    # and all sub paths get updated properly
 | 
				
			||||||
    values = conftest._getconftestmodules(p, importmode="prepend")
 | 
					    values = conftest._getconftestmodules(
 | 
				
			||||||
 | 
					        p, importmode="prepend", rootpath=pytester.path
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    assert len(values) == 1
 | 
					    assert len(values) == 1
 | 
				
			||||||
    assert values[0].__file__.startswith(str(conf))
 | 
					    assert values[0].__file__.startswith(str(conf))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -177,7 +210,9 @@ def test_conftestcutdir_inplace_considered(pytester: Pytester) -> None:
 | 
				
			||||||
    conf = pytester.makeconftest("")
 | 
					    conf = pytester.makeconftest("")
 | 
				
			||||||
    conftest = PytestPluginManager()
 | 
					    conftest = PytestPluginManager()
 | 
				
			||||||
    conftest_setinitial(conftest, [conf.parent], confcutdir=conf.parent)
 | 
					    conftest_setinitial(conftest, [conf.parent], confcutdir=conf.parent)
 | 
				
			||||||
    values = conftest._getconftestmodules(conf.parent, importmode="prepend")
 | 
					    values = conftest._getconftestmodules(
 | 
				
			||||||
 | 
					        conf.parent, importmode="prepend", rootpath=pytester.path
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    assert len(values) == 1
 | 
					    assert len(values) == 1
 | 
				
			||||||
    assert values[0].__file__.startswith(str(conf))
 | 
					    assert values[0].__file__.startswith(str(conf))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -347,13 +382,16 @@ def test_conftest_import_order(pytester: Pytester, monkeypatch: MonkeyPatch) ->
 | 
				
			||||||
    ct2 = sub / "conftest.py"
 | 
					    ct2 = sub / "conftest.py"
 | 
				
			||||||
    ct2.write_text("")
 | 
					    ct2.write_text("")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def impct(p, importmode):
 | 
					    def impct(p, importmode, root):
 | 
				
			||||||
        return p
 | 
					        return p
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    conftest = PytestPluginManager()
 | 
					    conftest = PytestPluginManager()
 | 
				
			||||||
    conftest._confcutdir = pytester.path
 | 
					    conftest._confcutdir = pytester.path
 | 
				
			||||||
    monkeypatch.setattr(conftest, "_importconftest", impct)
 | 
					    monkeypatch.setattr(conftest, "_importconftest", impct)
 | 
				
			||||||
    mods = cast(List[Path], conftest._getconftestmodules(sub, importmode="prepend"))
 | 
					    mods = cast(
 | 
				
			||||||
 | 
					        List[Path],
 | 
				
			||||||
 | 
					        conftest._getconftestmodules(sub, importmode="prepend", rootpath=pytester.path),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    expected = [ct1, ct2]
 | 
					    expected = [ct1, ct2]
 | 
				
			||||||
    assert mods == expected
 | 
					    assert mods == expected
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,6 +5,7 @@ import unittest.mock
 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
from textwrap import dedent
 | 
					from textwrap import dedent
 | 
				
			||||||
from types import ModuleType
 | 
					from types import ModuleType
 | 
				
			||||||
 | 
					from typing import Any
 | 
				
			||||||
from typing import Generator
 | 
					from typing import Generator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
| 
						 | 
					@ -17,7 +18,9 @@ from _pytest.pathlib import get_extended_length_path_str
 | 
				
			||||||
from _pytest.pathlib import get_lock_path
 | 
					from _pytest.pathlib import get_lock_path
 | 
				
			||||||
from _pytest.pathlib import import_path
 | 
					from _pytest.pathlib import import_path
 | 
				
			||||||
from _pytest.pathlib import ImportPathMismatchError
 | 
					from _pytest.pathlib import ImportPathMismatchError
 | 
				
			||||||
 | 
					from _pytest.pathlib import insert_missing_modules
 | 
				
			||||||
from _pytest.pathlib import maybe_delete_a_numbered_dir
 | 
					from _pytest.pathlib import maybe_delete_a_numbered_dir
 | 
				
			||||||
 | 
					from _pytest.pathlib import module_name_from_path
 | 
				
			||||||
from _pytest.pathlib import resolve_package_path
 | 
					from _pytest.pathlib import resolve_package_path
 | 
				
			||||||
from _pytest.pathlib import symlink_or_skip
 | 
					from _pytest.pathlib import symlink_or_skip
 | 
				
			||||||
from _pytest.pathlib import visit
 | 
					from _pytest.pathlib import visit
 | 
				
			||||||
| 
						 | 
					@ -136,7 +139,7 @@ class TestImportPath:
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_smoke_test(self, path1: Path) -> None:
 | 
					    def test_smoke_test(self, path1: Path) -> None:
 | 
				
			||||||
        obj = import_path(path1 / "execfile.py")
 | 
					        obj = import_path(path1 / "execfile.py", root=path1)
 | 
				
			||||||
        assert obj.x == 42  # type: ignore[attr-defined]
 | 
					        assert obj.x == 42  # type: ignore[attr-defined]
 | 
				
			||||||
        assert obj.__name__ == "execfile"
 | 
					        assert obj.__name__ == "execfile"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -146,25 +149,25 @@ class TestImportPath:
 | 
				
			||||||
        tmp_path.joinpath("a").mkdir()
 | 
					        tmp_path.joinpath("a").mkdir()
 | 
				
			||||||
        p = tmp_path.joinpath("a", "test_x123.py")
 | 
					        p = tmp_path.joinpath("a", "test_x123.py")
 | 
				
			||||||
        p.touch()
 | 
					        p.touch()
 | 
				
			||||||
        import_path(p)
 | 
					        import_path(p, root=tmp_path)
 | 
				
			||||||
        tmp_path.joinpath("a").rename(tmp_path.joinpath("b"))
 | 
					        tmp_path.joinpath("a").rename(tmp_path.joinpath("b"))
 | 
				
			||||||
        with pytest.raises(ImportPathMismatchError):
 | 
					        with pytest.raises(ImportPathMismatchError):
 | 
				
			||||||
            import_path(tmp_path.joinpath("b", "test_x123.py"))
 | 
					            import_path(tmp_path.joinpath("b", "test_x123.py"), root=tmp_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Errors can be ignored.
 | 
					        # Errors can be ignored.
 | 
				
			||||||
        monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "1")
 | 
					        monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "1")
 | 
				
			||||||
        import_path(tmp_path.joinpath("b", "test_x123.py"))
 | 
					        import_path(tmp_path.joinpath("b", "test_x123.py"), root=tmp_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # PY_IGNORE_IMPORTMISMATCH=0 does not ignore error.
 | 
					        # PY_IGNORE_IMPORTMISMATCH=0 does not ignore error.
 | 
				
			||||||
        monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "0")
 | 
					        monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "0")
 | 
				
			||||||
        with pytest.raises(ImportPathMismatchError):
 | 
					        with pytest.raises(ImportPathMismatchError):
 | 
				
			||||||
            import_path(tmp_path.joinpath("b", "test_x123.py"))
 | 
					            import_path(tmp_path.joinpath("b", "test_x123.py"), root=tmp_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_messy_name(self, tmp_path: Path) -> None:
 | 
					    def test_messy_name(self, tmp_path: Path) -> None:
 | 
				
			||||||
        # http://bitbucket.org/hpk42/py-trunk/issue/129
 | 
					        # http://bitbucket.org/hpk42/py-trunk/issue/129
 | 
				
			||||||
        path = tmp_path / "foo__init__.py"
 | 
					        path = tmp_path / "foo__init__.py"
 | 
				
			||||||
        path.touch()
 | 
					        path.touch()
 | 
				
			||||||
        module = import_path(path)
 | 
					        module = import_path(path, root=tmp_path)
 | 
				
			||||||
        assert module.__name__ == "foo__init__"
 | 
					        assert module.__name__ == "foo__init__"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_dir(self, tmp_path: Path) -> None:
 | 
					    def test_dir(self, tmp_path: Path) -> None:
 | 
				
			||||||
| 
						 | 
					@ -172,31 +175,31 @@ class TestImportPath:
 | 
				
			||||||
        p.mkdir()
 | 
					        p.mkdir()
 | 
				
			||||||
        p_init = p / "__init__.py"
 | 
					        p_init = p / "__init__.py"
 | 
				
			||||||
        p_init.touch()
 | 
					        p_init.touch()
 | 
				
			||||||
        m = import_path(p)
 | 
					        m = import_path(p, root=tmp_path)
 | 
				
			||||||
        assert m.__name__ == "hello_123"
 | 
					        assert m.__name__ == "hello_123"
 | 
				
			||||||
        m = import_path(p_init)
 | 
					        m = import_path(p_init, root=tmp_path)
 | 
				
			||||||
        assert m.__name__ == "hello_123"
 | 
					        assert m.__name__ == "hello_123"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_a(self, path1: Path) -> None:
 | 
					    def test_a(self, path1: Path) -> None:
 | 
				
			||||||
        otherdir = path1 / "otherdir"
 | 
					        otherdir = path1 / "otherdir"
 | 
				
			||||||
        mod = import_path(otherdir / "a.py")
 | 
					        mod = import_path(otherdir / "a.py", root=path1)
 | 
				
			||||||
        assert mod.result == "got it"  # type: ignore[attr-defined]
 | 
					        assert mod.result == "got it"  # type: ignore[attr-defined]
 | 
				
			||||||
        assert mod.__name__ == "otherdir.a"
 | 
					        assert mod.__name__ == "otherdir.a"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_b(self, path1: Path) -> None:
 | 
					    def test_b(self, path1: Path) -> None:
 | 
				
			||||||
        otherdir = path1 / "otherdir"
 | 
					        otherdir = path1 / "otherdir"
 | 
				
			||||||
        mod = import_path(otherdir / "b.py")
 | 
					        mod = import_path(otherdir / "b.py", root=path1)
 | 
				
			||||||
        assert mod.stuff == "got it"  # type: ignore[attr-defined]
 | 
					        assert mod.stuff == "got it"  # type: ignore[attr-defined]
 | 
				
			||||||
        assert mod.__name__ == "otherdir.b"
 | 
					        assert mod.__name__ == "otherdir.b"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_c(self, path1: Path) -> None:
 | 
					    def test_c(self, path1: Path) -> None:
 | 
				
			||||||
        otherdir = path1 / "otherdir"
 | 
					        otherdir = path1 / "otherdir"
 | 
				
			||||||
        mod = import_path(otherdir / "c.py")
 | 
					        mod = import_path(otherdir / "c.py", root=path1)
 | 
				
			||||||
        assert mod.value == "got it"  # type: ignore[attr-defined]
 | 
					        assert mod.value == "got it"  # type: ignore[attr-defined]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_d(self, path1: Path) -> None:
 | 
					    def test_d(self, path1: Path) -> None:
 | 
				
			||||||
        otherdir = path1 / "otherdir"
 | 
					        otherdir = path1 / "otherdir"
 | 
				
			||||||
        mod = import_path(otherdir / "d.py")
 | 
					        mod = import_path(otherdir / "d.py", root=path1)
 | 
				
			||||||
        assert mod.value2 == "got it"  # type: ignore[attr-defined]
 | 
					        assert mod.value2 == "got it"  # type: ignore[attr-defined]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_import_after(self, tmp_path: Path) -> None:
 | 
					    def test_import_after(self, tmp_path: Path) -> None:
 | 
				
			||||||
| 
						 | 
					@ -204,7 +207,7 @@ class TestImportPath:
 | 
				
			||||||
        tmp_path.joinpath("xxxpackage", "__init__.py").touch()
 | 
					        tmp_path.joinpath("xxxpackage", "__init__.py").touch()
 | 
				
			||||||
        mod1path = tmp_path.joinpath("xxxpackage", "module1.py")
 | 
					        mod1path = tmp_path.joinpath("xxxpackage", "module1.py")
 | 
				
			||||||
        mod1path.touch()
 | 
					        mod1path.touch()
 | 
				
			||||||
        mod1 = import_path(mod1path)
 | 
					        mod1 = import_path(mod1path, root=tmp_path)
 | 
				
			||||||
        assert mod1.__name__ == "xxxpackage.module1"
 | 
					        assert mod1.__name__ == "xxxpackage.module1"
 | 
				
			||||||
        from xxxpackage import module1
 | 
					        from xxxpackage import module1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -222,7 +225,7 @@ class TestImportPath:
 | 
				
			||||||
            pseudopath.touch()
 | 
					            pseudopath.touch()
 | 
				
			||||||
            mod.__file__ = str(pseudopath)
 | 
					            mod.__file__ = str(pseudopath)
 | 
				
			||||||
            monkeypatch.setitem(sys.modules, name, mod)
 | 
					            monkeypatch.setitem(sys.modules, name, mod)
 | 
				
			||||||
            newmod = import_path(p)
 | 
					            newmod = import_path(p, root=tmp_path)
 | 
				
			||||||
            assert mod == newmod
 | 
					            assert mod == newmod
 | 
				
			||||||
        monkeypatch.undo()
 | 
					        monkeypatch.undo()
 | 
				
			||||||
        mod = ModuleType(name)
 | 
					        mod = ModuleType(name)
 | 
				
			||||||
| 
						 | 
					@ -231,7 +234,7 @@ class TestImportPath:
 | 
				
			||||||
        mod.__file__ = str(pseudopath)
 | 
					        mod.__file__ = str(pseudopath)
 | 
				
			||||||
        monkeypatch.setitem(sys.modules, name, mod)
 | 
					        monkeypatch.setitem(sys.modules, name, mod)
 | 
				
			||||||
        with pytest.raises(ImportPathMismatchError) as excinfo:
 | 
					        with pytest.raises(ImportPathMismatchError) as excinfo:
 | 
				
			||||||
            import_path(p)
 | 
					            import_path(p, root=tmp_path)
 | 
				
			||||||
        modname, modfile, orig = excinfo.value.args
 | 
					        modname, modfile, orig = excinfo.value.args
 | 
				
			||||||
        assert modname == name
 | 
					        assert modname == name
 | 
				
			||||||
        assert modfile == str(pseudopath)
 | 
					        assert modfile == str(pseudopath)
 | 
				
			||||||
| 
						 | 
					@ -248,8 +251,8 @@ class TestImportPath:
 | 
				
			||||||
        tmp_path.joinpath("sub", "proja").mkdir(parents=True)
 | 
					        tmp_path.joinpath("sub", "proja").mkdir(parents=True)
 | 
				
			||||||
        p2 = tmp_path.joinpath("sub", "proja", "__init__.py")
 | 
					        p2 = tmp_path.joinpath("sub", "proja", "__init__.py")
 | 
				
			||||||
        p2.touch()
 | 
					        p2.touch()
 | 
				
			||||||
        m1 = import_path(p1)
 | 
					        m1 = import_path(p1, root=tmp_path)
 | 
				
			||||||
        m2 = import_path(p2)
 | 
					        m2 = import_path(p2, root=tmp_path)
 | 
				
			||||||
        assert m1 == m2
 | 
					        assert m1 == m2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_ensuresyspath_append(self, tmp_path: Path) -> None:
 | 
					    def test_ensuresyspath_append(self, tmp_path: Path) -> None:
 | 
				
			||||||
| 
						 | 
					@ -258,44 +261,45 @@ class TestImportPath:
 | 
				
			||||||
        file1 = root1 / "x123.py"
 | 
					        file1 = root1 / "x123.py"
 | 
				
			||||||
        file1.touch()
 | 
					        file1.touch()
 | 
				
			||||||
        assert str(root1) not in sys.path
 | 
					        assert str(root1) not in sys.path
 | 
				
			||||||
        import_path(file1, mode="append")
 | 
					        import_path(file1, mode="append", root=tmp_path)
 | 
				
			||||||
        assert str(root1) == sys.path[-1]
 | 
					        assert str(root1) == sys.path[-1]
 | 
				
			||||||
        assert str(root1) not in sys.path[:-1]
 | 
					        assert str(root1) not in sys.path[:-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_invalid_path(self, tmp_path: Path) -> None:
 | 
					    def test_invalid_path(self, tmp_path: Path) -> None:
 | 
				
			||||||
        with pytest.raises(ImportError):
 | 
					        with pytest.raises(ImportError):
 | 
				
			||||||
            import_path(tmp_path / "invalid.py")
 | 
					            import_path(tmp_path / "invalid.py", root=tmp_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @pytest.fixture
 | 
					    @pytest.fixture
 | 
				
			||||||
    def simple_module(self, tmp_path: Path) -> Path:
 | 
					    def simple_module(self, tmp_path: Path) -> Path:
 | 
				
			||||||
        fn = tmp_path / "mymod.py"
 | 
					        fn = tmp_path / "_src/tests/mymod.py"
 | 
				
			||||||
        fn.write_text(
 | 
					        fn.parent.mkdir(parents=True)
 | 
				
			||||||
            dedent(
 | 
					        fn.write_text("def foo(x): return 40 + x")
 | 
				
			||||||
                """
 | 
					 | 
				
			||||||
            def foo(x): return 40 + x
 | 
					 | 
				
			||||||
            """
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        return fn
 | 
					        return fn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_importmode_importlib(self, simple_module: Path) -> None:
 | 
					    def test_importmode_importlib(self, simple_module: Path, tmp_path: Path) -> None:
 | 
				
			||||||
        """`importlib` mode does not change sys.path."""
 | 
					        """`importlib` mode does not change sys.path."""
 | 
				
			||||||
        module = import_path(simple_module, mode="importlib")
 | 
					        module = import_path(simple_module, mode="importlib", root=tmp_path)
 | 
				
			||||||
        assert module.foo(2) == 42  # type: ignore[attr-defined]
 | 
					        assert module.foo(2) == 42  # type: ignore[attr-defined]
 | 
				
			||||||
        assert str(simple_module.parent) not in sys.path
 | 
					        assert str(simple_module.parent) not in sys.path
 | 
				
			||||||
 | 
					        assert module.__name__ in sys.modules
 | 
				
			||||||
 | 
					        assert module.__name__ == "_src.tests.mymod"
 | 
				
			||||||
 | 
					        assert "_src" in sys.modules
 | 
				
			||||||
 | 
					        assert "_src.tests" in sys.modules
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_importmode_twice_is_different_module(self, simple_module: Path) -> None:
 | 
					    def test_importmode_twice_is_different_module(
 | 
				
			||||||
 | 
					        self, simple_module: Path, tmp_path: Path
 | 
				
			||||||
 | 
					    ) -> None:
 | 
				
			||||||
        """`importlib` mode always returns a new module."""
 | 
					        """`importlib` mode always returns a new module."""
 | 
				
			||||||
        module1 = import_path(simple_module, mode="importlib")
 | 
					        module1 = import_path(simple_module, mode="importlib", root=tmp_path)
 | 
				
			||||||
        module2 = import_path(simple_module, mode="importlib")
 | 
					        module2 = import_path(simple_module, mode="importlib", root=tmp_path)
 | 
				
			||||||
        assert module1 is not module2
 | 
					        assert module1 is not module2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_no_meta_path_found(
 | 
					    def test_no_meta_path_found(
 | 
				
			||||||
        self, simple_module: Path, monkeypatch: MonkeyPatch
 | 
					        self, simple_module: Path, monkeypatch: MonkeyPatch, tmp_path: Path
 | 
				
			||||||
    ) -> None:
 | 
					    ) -> None:
 | 
				
			||||||
        """Even without any meta_path should still import module."""
 | 
					        """Even without any meta_path should still import module."""
 | 
				
			||||||
        monkeypatch.setattr(sys, "meta_path", [])
 | 
					        monkeypatch.setattr(sys, "meta_path", [])
 | 
				
			||||||
        module = import_path(simple_module, mode="importlib")
 | 
					        module = import_path(simple_module, mode="importlib", root=tmp_path)
 | 
				
			||||||
        assert module.foo(2) == 42  # type: ignore[attr-defined]
 | 
					        assert module.foo(2) == 42  # type: ignore[attr-defined]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # mode='importlib' fails if no spec is found to load the module
 | 
					        # mode='importlib' fails if no spec is found to load the module
 | 
				
			||||||
| 
						 | 
					@ -305,7 +309,7 @@ class TestImportPath:
 | 
				
			||||||
            importlib.util, "spec_from_file_location", lambda *args: None
 | 
					            importlib.util, "spec_from_file_location", lambda *args: None
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        with pytest.raises(ImportError):
 | 
					        with pytest.raises(ImportError):
 | 
				
			||||||
            import_path(simple_module, mode="importlib")
 | 
					            import_path(simple_module, mode="importlib", root=tmp_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_resolve_package_path(tmp_path: Path) -> None:
 | 
					def test_resolve_package_path(tmp_path: Path) -> None:
 | 
				
			||||||
| 
						 | 
					@ -441,5 +445,130 @@ def test_samefile_false_negatives(tmp_path: Path, monkeypatch: MonkeyPatch) -> N
 | 
				
			||||||
        # the paths too. Using a context to narrow the patch as much as possible given
 | 
					        # the paths too. Using a context to narrow the patch as much as possible given
 | 
				
			||||||
        # this is an important system function.
 | 
					        # this is an important system function.
 | 
				
			||||||
        mp.setattr(os.path, "samefile", lambda x, y: False)
 | 
					        mp.setattr(os.path, "samefile", lambda x, y: False)
 | 
				
			||||||
        module = import_path(module_path)
 | 
					        module = import_path(module_path, root=tmp_path)
 | 
				
			||||||
    assert getattr(module, "foo")() == 42
 | 
					    assert getattr(module, "foo")() == 42
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestImportLibMode:
 | 
				
			||||||
 | 
					    @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
 | 
				
			||||||
 | 
					    def test_importmode_importlib_with_dataclass(self, tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					        """Ensure that importlib mode works with a module containing dataclasses (#7856)."""
 | 
				
			||||||
 | 
					        fn = tmp_path.joinpath("_src/tests/test_dataclass.py")
 | 
				
			||||||
 | 
					        fn.parent.mkdir(parents=True)
 | 
				
			||||||
 | 
					        fn.write_text(
 | 
				
			||||||
 | 
					            dedent(
 | 
				
			||||||
 | 
					                """
 | 
				
			||||||
 | 
					                from dataclasses import dataclass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                @dataclass
 | 
				
			||||||
 | 
					                class Data:
 | 
				
			||||||
 | 
					                    value: str
 | 
				
			||||||
 | 
					                """
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        module = import_path(fn, mode="importlib", root=tmp_path)
 | 
				
			||||||
 | 
					        Data: Any = getattr(module, "Data")
 | 
				
			||||||
 | 
					        data = Data(value="foo")
 | 
				
			||||||
 | 
					        assert data.value == "foo"
 | 
				
			||||||
 | 
					        assert data.__module__ == "_src.tests.test_dataclass"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_importmode_importlib_with_pickle(self, tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					        """Ensure that importlib mode works with pickle (#7859)."""
 | 
				
			||||||
 | 
					        fn = tmp_path.joinpath("_src/tests/test_pickle.py")
 | 
				
			||||||
 | 
					        fn.parent.mkdir(parents=True)
 | 
				
			||||||
 | 
					        fn.write_text(
 | 
				
			||||||
 | 
					            dedent(
 | 
				
			||||||
 | 
					                """
 | 
				
			||||||
 | 
					                import pickle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                def _action():
 | 
				
			||||||
 | 
					                    return 42
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                def round_trip():
 | 
				
			||||||
 | 
					                    s = pickle.dumps(_action)
 | 
				
			||||||
 | 
					                    return pickle.loads(s)
 | 
				
			||||||
 | 
					                """
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        module = import_path(fn, mode="importlib", root=tmp_path)
 | 
				
			||||||
 | 
					        round_trip = getattr(module, "round_trip")
 | 
				
			||||||
 | 
					        action = round_trip()
 | 
				
			||||||
 | 
					        assert action() == 42
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_importmode_importlib_with_pickle_separate_modules(
 | 
				
			||||||
 | 
					        self, tmp_path: Path
 | 
				
			||||||
 | 
					    ) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Ensure that importlib mode works can load pickles that look similar but are
 | 
				
			||||||
 | 
					        defined in separate modules.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        fn1 = tmp_path.joinpath("_src/m1/tests/test.py")
 | 
				
			||||||
 | 
					        fn1.parent.mkdir(parents=True)
 | 
				
			||||||
 | 
					        fn1.write_text(
 | 
				
			||||||
 | 
					            dedent(
 | 
				
			||||||
 | 
					                """
 | 
				
			||||||
 | 
					                import attr
 | 
				
			||||||
 | 
					                import pickle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                @attr.s(auto_attribs=True)
 | 
				
			||||||
 | 
					                class Data:
 | 
				
			||||||
 | 
					                    x: int = 42
 | 
				
			||||||
 | 
					                """
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fn2 = tmp_path.joinpath("_src/m2/tests/test.py")
 | 
				
			||||||
 | 
					        fn2.parent.mkdir(parents=True)
 | 
				
			||||||
 | 
					        fn2.write_text(
 | 
				
			||||||
 | 
					            dedent(
 | 
				
			||||||
 | 
					                """
 | 
				
			||||||
 | 
					                import attr
 | 
				
			||||||
 | 
					                import pickle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                @attr.s(auto_attribs=True)
 | 
				
			||||||
 | 
					                class Data:
 | 
				
			||||||
 | 
					                    x: str = ""
 | 
				
			||||||
 | 
					                """
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        import pickle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def round_trip(obj):
 | 
				
			||||||
 | 
					            s = pickle.dumps(obj)
 | 
				
			||||||
 | 
					            return pickle.loads(s)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        module = import_path(fn1, mode="importlib", root=tmp_path)
 | 
				
			||||||
 | 
					        Data1 = getattr(module, "Data")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        module = import_path(fn2, mode="importlib", root=tmp_path)
 | 
				
			||||||
 | 
					        Data2 = getattr(module, "Data")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert round_trip(Data1(20)) == Data1(20)
 | 
				
			||||||
 | 
					        assert round_trip(Data2("hello")) == Data2("hello")
 | 
				
			||||||
 | 
					        assert Data1.__module__ == "_src.m1.tests.test"
 | 
				
			||||||
 | 
					        assert Data2.__module__ == "_src.m2.tests.test"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_module_name_from_path(self, tmp_path: Path) -> None:
 | 
				
			||||||
 | 
					        result = module_name_from_path(tmp_path / "src/tests/test_foo.py", tmp_path)
 | 
				
			||||||
 | 
					        assert result == "src.tests.test_foo"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Path is not relative to root dir: use the full path to obtain the module name.
 | 
				
			||||||
 | 
					        result = module_name_from_path(Path("/home/foo/test_foo.py"), Path("/bar"))
 | 
				
			||||||
 | 
					        assert result == "home.foo.test_foo"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_insert_missing_modules(self) -> None:
 | 
				
			||||||
 | 
					        modules = {"src.tests.foo": ModuleType("src.tests.foo")}
 | 
				
			||||||
 | 
					        insert_missing_modules(modules, "src.tests.foo")
 | 
				
			||||||
 | 
					        assert sorted(modules) == ["src", "src.tests", "src.tests.foo"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mod = ModuleType("mod", doc="My Module")
 | 
				
			||||||
 | 
					        modules = {"src": mod}
 | 
				
			||||||
 | 
					        insert_missing_modules(modules, "src")
 | 
				
			||||||
 | 
					        assert modules == {"src": mod}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        modules = {}
 | 
				
			||||||
 | 
					        insert_missing_modules(modules, "")
 | 
				
			||||||
 | 
					        assert modules == {}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,7 +44,9 @@ class TestPytestPluginInteractions:
 | 
				
			||||||
        pm.hook.pytest_addhooks.call_historic(
 | 
					        pm.hook.pytest_addhooks.call_historic(
 | 
				
			||||||
            kwargs=dict(pluginmanager=config.pluginmanager)
 | 
					            kwargs=dict(pluginmanager=config.pluginmanager)
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        config.pluginmanager._importconftest(conf, importmode="prepend")
 | 
					        config.pluginmanager._importconftest(
 | 
				
			||||||
 | 
					            conf, importmode="prepend", rootpath=pytester.path
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        # print(config.pluginmanager.get_plugins())
 | 
					        # print(config.pluginmanager.get_plugins())
 | 
				
			||||||
        res = config.hook.pytest_myhook(xyz=10)
 | 
					        res = config.hook.pytest_myhook(xyz=10)
 | 
				
			||||||
        assert res == [11]
 | 
					        assert res == [11]
 | 
				
			||||||
| 
						 | 
					@ -71,7 +73,9 @@ class TestPytestPluginInteractions:
 | 
				
			||||||
                    default=True)
 | 
					                    default=True)
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        config.pluginmanager._importconftest(p, importmode="prepend")
 | 
					        config.pluginmanager._importconftest(
 | 
				
			||||||
 | 
					            p, importmode="prepend", rootpath=pytester.path
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        assert config.option.test123
 | 
					        assert config.option.test123
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_configure(self, pytester: Pytester) -> None:
 | 
					    def test_configure(self, pytester: Pytester) -> None:
 | 
				
			||||||
| 
						 | 
					@ -136,10 +140,14 @@ class TestPytestPluginInteractions:
 | 
				
			||||||
        conftest1 = pytester.path.joinpath("tests/conftest.py")
 | 
					        conftest1 = pytester.path.joinpath("tests/conftest.py")
 | 
				
			||||||
        conftest2 = pytester.path.joinpath("tests/subdir/conftest.py")
 | 
					        conftest2 = pytester.path.joinpath("tests/subdir/conftest.py")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        config.pluginmanager._importconftest(conftest1, importmode="prepend")
 | 
					        config.pluginmanager._importconftest(
 | 
				
			||||||
 | 
					            conftest1, importmode="prepend", rootpath=pytester.path
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        ihook_a = session.gethookproxy(pytester.path / "tests")
 | 
					        ihook_a = session.gethookproxy(pytester.path / "tests")
 | 
				
			||||||
        assert ihook_a is not None
 | 
					        assert ihook_a is not None
 | 
				
			||||||
        config.pluginmanager._importconftest(conftest2, importmode="prepend")
 | 
					        config.pluginmanager._importconftest(
 | 
				
			||||||
 | 
					            conftest2, importmode="prepend", rootpath=pytester.path
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        ihook_b = session.gethookproxy(pytester.path / "tests")
 | 
					        ihook_b = session.gethookproxy(pytester.path / "tests")
 | 
				
			||||||
        assert ihook_a is not ihook_b
 | 
					        assert ihook_a is not ihook_b
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -350,7 +358,9 @@ class TestPytestPluginManager:
 | 
				
			||||||
        pytester: Pytester,
 | 
					        pytester: Pytester,
 | 
				
			||||||
        pytestpm: PytestPluginManager,
 | 
					        pytestpm: PytestPluginManager,
 | 
				
			||||||
    ) -> None:
 | 
					    ) -> None:
 | 
				
			||||||
        mod = import_path(pytester.makepyfile("pytest_plugins='xyz'"))
 | 
					        mod = import_path(
 | 
				
			||||||
 | 
					            pytester.makepyfile("pytest_plugins='xyz'"), root=pytester.path
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        with pytest.raises(ImportError):
 | 
					        with pytest.raises(ImportError):
 | 
				
			||||||
            pytestpm.consider_conftest(mod)
 | 
					            pytestpm.consider_conftest(mod)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue