From ab951af7c367cfd0c50ebadc79cb65df5f640db5 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 4 Mar 2024 12:44:56 -0300 Subject: [PATCH] Do not import duplicated modules with --importmode=importlib (#12074) Regression brought up by #11475. --- changelog/11475.bugfix.rst | 1 + src/_pytest/pathlib.py | 4 +++ testing/test_pathlib.py | 71 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 changelog/11475.bugfix.rst diff --git a/changelog/11475.bugfix.rst b/changelog/11475.bugfix.rst new file mode 100644 index 00000000000..bef8f4f76d3 --- /dev/null +++ b/changelog/11475.bugfix.rst @@ -0,0 +1 @@ +Fixed regression where ``--importmode=importlib`` would import non-test modules more than once. diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index a19e89aa116..e39f4772326 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -539,6 +539,10 @@ def import_path( except CouldNotResolvePathError: pass else: + # If the given module name is already in sys.modules, do not import it again. + with contextlib.suppress(KeyError): + return sys.modules[module_name] + mod = _import_module_using_spec( module_name, path, pkg_root, insert_modules=False ) diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py index 357860563a8..a4bccb1b2ac 100644 --- a/testing/test_pathlib.py +++ b/testing/test_pathlib.py @@ -587,6 +587,12 @@ class Data: assert data.value == "foo" assert data.__module__ == "_src.tests.test_dataclass" + # Ensure we do not import the same module again (#11475). + module2 = import_path( + fn, mode="importlib", root=tmp_path, consider_namespace_packages=ns_param + ) + assert module is module2 + def test_importmode_importlib_with_pickle( self, tmp_path: Path, ns_param: bool ) -> None: @@ -616,6 +622,12 @@ def round_trip(): action = round_trip() assert action() == 42 + # Ensure we do not import the same module again (#11475). + module2 = import_path( + fn, mode="importlib", root=tmp_path, consider_namespace_packages=ns_param + ) + assert module is module2 + def test_importmode_importlib_with_pickle_separate_modules( self, tmp_path: Path, ns_param: bool ) -> None: @@ -816,6 +828,14 @@ def __init__(self) -> None: consider_namespace_packages=ns_param, ) assert len(mod.instance.INSTANCES) == 1 + # Ensure we do not import the same module again (#11475). + mod2 = import_path( + init, + root=tmp_path, + mode=ImportMode.importlib, + consider_namespace_packages=ns_param, + ) + assert mod is mod2 def test_importlib_root_is_package(self, pytester: Pytester) -> None: """ @@ -942,6 +962,15 @@ def test_import_using_normal_mechanism_first( assert mod.__name__ == "app.core" assert mod.__file__ and Path(mod.__file__) == core_py + # Ensure we do not import the same module again (#11475). + mod2 = import_path( + core_py, + mode="importlib", + root=pytester.path, + consider_namespace_packages=ns_param, + ) + assert mod is mod2 + # tests are not reachable from sys.path, so they are imported as a standalone modules. # Instead of '.tests.a.test_core', we import as "_tests.a.test_core" because # importlib considers module names starting with '.' to be local imports. @@ -952,6 +981,16 @@ def test_import_using_normal_mechanism_first( consider_namespace_packages=ns_param, ) assert mod.__name__ == "_tests.a.test_core" + + # Ensure we do not import the same module again (#11475). + mod2 = import_path( + test_path1, + mode="importlib", + root=pytester.path, + consider_namespace_packages=ns_param, + ) + assert mod is mod2 + mod = import_path( test_path2, mode="importlib", @@ -960,6 +999,15 @@ def test_import_using_normal_mechanism_first( ) assert mod.__name__ == "_tests.b.test_core" + # Ensure we do not import the same module again (#11475). + mod2 = import_path( + test_path2, + mode="importlib", + root=pytester.path, + consider_namespace_packages=ns_param, + ) + assert mod is mod2 + def test_import_using_normal_mechanism_first_integration( self, monkeypatch: MonkeyPatch, pytester: Pytester, ns_param: bool ) -> None: @@ -1021,6 +1069,14 @@ def test_import_path_imports_correct_file( assert mod.__file__ and Path(mod.__file__) == x_in_sub_folder assert mod.X == "a/b/x" + mod2 = import_path( + x_in_sub_folder, + mode=ImportMode.importlib, + root=pytester.path, + consider_namespace_packages=ns_param, + ) + assert mod is mod2 + # Attempt to import root 'x.py'. with pytest.raises(AssertionError, match="x at root"): _ = import_path( @@ -1124,6 +1180,12 @@ def test_resolve_pkg_root_and_module_name_ns_multiple_levels( assert mod.__name__ == "com.company.app.core.models" assert mod.__file__ == str(models_py) + # Ensure we do not import the same module again (#11475). + mod2 = import_path( + models_py, mode=import_mode, root=tmp_path, consider_namespace_packages=True + ) + assert mod is mod2 + pkg_root, module_name = resolve_pkg_root_and_module_name( algorithms_py, consider_namespace_packages=True ) @@ -1141,6 +1203,15 @@ def test_resolve_pkg_root_and_module_name_ns_multiple_levels( assert mod.__name__ == "com.company.calc.algo.algorithms" assert mod.__file__ == str(algorithms_py) + # Ensure we do not import the same module again (#11475). + mod2 = import_path( + algorithms_py, + mode=import_mode, + root=tmp_path, + consider_namespace_packages=True, + ) + assert mod is mod2 + @pytest.mark.parametrize("import_mode", ["prepend", "append", "importlib"]) def test_incorrect_namespace_package( self,