diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index f1fc1366341..4b4e7f01600 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -761,12 +761,13 @@ def resolve_pkg_root_and_module_name( """ pkg_path = resolve_package_path(path) if pkg_path is not None: - pkg_root = pkg_path.parent - # pkg_root.parent does not contain a __init__.py file, as per resolve_package_path, - # but if it is reachable from sys.argv, it should be considered a namespace package. # https://packaging.python.org/en/latest/guides/packaging-namespace-packages/ - if consider_ns_packages and str(pkg_root.parent) in sys.path: - pkg_root = pkg_root.parent + pkg_root = pkg_path.parent + if consider_ns_packages: + for parent in pkg_path.parents: + if str(parent) in sys.path: + pkg_root = parent + break names = list(path.with_suffix("").relative_to(pkg_root).parts) if names[-1] == "__init__": names.pop() diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py index edebf0d32d5..ec1ae480f49 100644 --- a/testing/test_pathlib.py +++ b/testing/test_pathlib.py @@ -647,6 +647,29 @@ def test_resolve_pkg_root_and_module_name( "src.app.core.models", ) + def test_resolve_pkg_root_and_module_name_ns_multiple_levels( + self, tmp_path: Path, monkeypatch: MonkeyPatch + ) -> None: + (tmp_path / "src/com/company/app/core").mkdir(parents=True) + (tmp_path / "src/com/company/app/__init__.py").touch() + (tmp_path / "src/com/company/app/core/__init__.py").touch() + models_py = tmp_path / "src/com/company/app/core/models.py" + models_py.touch() + + monkeypatch.syspath_prepend(tmp_path / "src") + + pkg_root, module_name = resolve_pkg_root_and_module_name( + models_py, consider_ns_packages=True + ) + assert (pkg_root, module_name) == ( + tmp_path / "src", + "com.company.app.core.models", + ) + + mod = import_path(models_py, mode="importlib", root=tmp_path) + assert mod.__name__ == "com.company.app.core.models" + assert mod.__file__ == str(models_py) + def test_insert_missing_modules( self, monkeypatch: MonkeyPatch, tmp_path: Path ) -> None: