From 74bd56ef4d60b0723763101208d16e236dfc41f1 Mon Sep 17 00:00:00 2001 From: dongfangtianyu <7629022+dongfangtianyu@users.noreply.github.com> Date: Thu, 5 Dec 2024 16:51:20 +0800 Subject: [PATCH 1/2] fix #13026 --- changelog/13026.bugfix.rst | 1 + src/_pytest/pathlib.py | 2 +- testing/test_pathlib.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 changelog/13026.bugfix.rst diff --git a/changelog/13026.bugfix.rst b/changelog/13026.bugfix.rst new file mode 100644 index 00000000000..a7a79234588 --- /dev/null +++ b/changelog/13026.bugfix.rst @@ -0,0 +1 @@ +Fixed ``AttributeError`` crash when using ``--import-mode=importlib`` when top-level directory same name as the standard library. diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index 55b27985d31..23bb4c29986 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -668,7 +668,7 @@ def _import_module_using_spec( parent_module: ModuleType | None = None if parent_module_name: parent_module = sys.modules.get(parent_module_name) - if parent_module is None: + if parent_module is None or not hasattr(parent_module, "__path__"): # Get parent_location based on location, get parent_path based on path. if module_path.name == "__init__.py": # If the current module is in a package, diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py index 436a6b9cae3..a21d1edf970 100644 --- a/testing/test_pathlib.py +++ b/testing/test_pathlib.py @@ -922,6 +922,37 @@ def test_my_test(): result = pytester.runpytest("--import-mode=importlib") result.stdout.fnmatch_lines("* 1 passed *") + @pytest.mark.parametrize("name", ["code", "time", "math"]) + def test_importlib_same_name_as_stl( + self, pytester, ns_param: bool, tmp_path: Path, name: str + ): + """Import a namespace package with the same name as the standard library (#13026).""" + file_path = pytester.path / f"{name}/foo/test_demo.py" + file_path.parent.mkdir(parents=True) + file_path.write_text( + dedent( + """ + def test_demo(): + pass + """ + ), + encoding="utf-8", + ) + + # unit test + __import__(name) # import standard library + + import_path( # import user files + file_path, + mode=ImportMode.importlib, + root=pytester.path, + consider_namespace_packages=ns_param, + ) + + # E2E test + result = pytester.runpytest("--import-mode=importlib") + result.stdout.fnmatch_lines("* 1 passed *") + def create_installed_doctests_and_tests_dir( self, path: Path, monkeypatch: MonkeyPatch ) -> tuple[Path, Path, Path]: From 2546d9d9a802a64663ae186a61182cb9ba24655a Mon Sep 17 00:00:00 2001 From: dongfangtianyu <7629022+dongfangtianyu@users.noreply.github.com> Date: Fri, 6 Dec 2024 13:29:37 +0800 Subject: [PATCH 2/2] chore: add comment --- changelog/13026.bugfix.rst | 2 +- src/_pytest/pathlib.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/changelog/13026.bugfix.rst b/changelog/13026.bugfix.rst index a7a79234588..d10edbd111a 100644 --- a/changelog/13026.bugfix.rst +++ b/changelog/13026.bugfix.rst @@ -1 +1 @@ -Fixed ``AttributeError`` crash when using ``--import-mode=importlib`` when top-level directory same name as the standard library. +Fixed :class:`AttributeError` crash when using ``--import-mode=importlib`` when top-level directory same name as another module of the standard library. diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index 23bb4c29986..7c368e0dcd0 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -668,7 +668,10 @@ def _import_module_using_spec( parent_module: ModuleType | None = None if parent_module_name: parent_module = sys.modules.get(parent_module_name) - if parent_module is None or not hasattr(parent_module, "__path__"): + # If the parent_module lacks the `__path__` attribute, AttributeError when finding a submodule's spec, + # requiring re-import according to the path. + need_reimport = not hasattr(parent_module, "__path__") + if parent_module is None or need_reimport: # Get parent_location based on location, get parent_path based on path. if module_path.name == "__init__.py": # If the current module is in a package,