From e867798f1aa248b851f9bf2f0232df00ac82c9ef Mon Sep 17 00:00:00 2001 From: Phil Schaf Date: Mon, 5 Feb 2024 16:09:03 +0100 Subject: [PATCH] Use shortest module name --- src/_pytest/pathlib.py | 13 +++++++++++-- testing/test_pathlib.py | 27 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index 1e0891153..351223f76 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -607,11 +607,20 @@ else: def module_name_from_path(path: Path, root: Path) -> str: """ - Return a dotted module name based on the given path, anchored on root. + Return a dotted module name based on the given path, + anchored on root or the most likely entry in `sys.path`. For example: path="projects/src/tests/test_foo.py" and root="/projects", the resulting module name will be "src.tests.test_foo". """ + candidates = ( + _module_name_from_path(path, dir) + for dir in itertools.chain([root], map(Path, sys.path)) + ) + return ".".join(min(candidates, key=len)) + + +def _module_name_from_path(path: Path, root: Path) -> tuple[str, ...]: path = path.with_suffix("") try: relative_path = path.relative_to(root) @@ -628,7 +637,7 @@ def module_name_from_path(path: Path, root: Path) -> str: if len(path_parts) >= 2 and path_parts[-1] == "__init__": path_parts = path_parts[:-1] - return ".".join(path_parts) + return path_parts def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) -> None: diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py index 075259009..f22add229 100644 --- a/testing/test_pathlib.py +++ b/testing/test_pathlib.py @@ -669,6 +669,33 @@ class TestImportLibMode: mod = import_path(init, root=tmp_path, mode=ImportMode.importlib) assert len(mod.instance.INSTANCES) == 1 + def test_importlib_doctest(self, monkeypatch: MonkeyPatch, tmp_path: Path): + """ + Importing a package using --importmode=importlib should + import the package using the canonical name + """ + proj_dir = tmp_path / "proj" + proj_dir.mkdir() + pkgs_dir = tmp_path / "pkgs" + pkgs_dir.mkdir() + monkeypatch.chdir(proj_dir) + monkeypatch.syspath_prepend(pkgs_dir) + # this is also there, but shouldn’t be imported from + monkeypatch.syspath_prepend(proj_dir) + + package_name = "importlib_doctest" + # pkgs_dir is second to set `init` + for directory in [proj_dir / "src", pkgs_dir]: + pkgdir = directory / package_name + pkgdir.mkdir(parents=True) + init = pkgdir / "__init__.py" + init.write_text("", encoding="ascii") + + mod = import_path(init, root=proj_dir, mode=ImportMode.importlib) + # assert that it’s imported with the canonical name, not “path.to.package.” + mod_names = [n for n, m in sys.modules.items() if m is mod] + assert mod_names == ["importlib_doctest"] + def test_importlib_root_is_package(self, pytester: Pytester) -> None: """ Regression for importing a `__init__`.py file that is at the root