From 9a444d113658be6ccb8dc9f57ed118a1ef17c94c Mon Sep 17 00:00:00 2001 From: dongfangtianyu <7629022+dongfangtianyu@users.noreply.github.com> Date: Fri, 30 Aug 2024 22:37:18 +0800 Subject: [PATCH] Fix assertion rewriting with importlib mode (#12716) Fixes #12659 --- AUTHORS | 1 + changelog/12659.bugfix.rst | 1 + src/_pytest/assertion/rewrite.py | 10 +++++++++ src/_pytest/pathlib.py | 4 +++- testing/test_pathlib.py | 36 ++++++++++++++++++++++++++++++++ 5 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 changelog/12659.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 8103a1d52a5..db993353689 100644 --- a/AUTHORS +++ b/AUTHORS @@ -412,6 +412,7 @@ Ted Xiao Terje Runde Thomas Grainger Thomas Hisch +Tianyu Dongfang Tim Hoffmann Tim Strazny TJ Bruno diff --git a/changelog/12659.bugfix.rst b/changelog/12659.bugfix.rst new file mode 100644 index 00000000000..297381b9602 --- /dev/null +++ b/changelog/12659.bugfix.rst @@ -0,0 +1 @@ +Fixed the issue of not displaying assertion failure differences when using the parameter ``--import-mode=importlib`` in pytest>=8.1. diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index f7ff4f6f7a2..a7a92c0f1fe 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -101,6 +101,16 @@ def find_spec( # Type ignored because mypy is confused about the `self` binding here. spec = self._find_spec(name, path) # type: ignore + + if spec is None and path is not None: + # With --import-mode=importlib, PathFinder cannot find spec without modifying `sys.path`, + # causing inability to assert rewriting (#12659). + # At this point, try using the file path to find the module spec. + for _path_str in path: + spec = importlib.util.spec_from_file_location(name, _path_str) + if spec is not None: + break + if ( # the import machinery could not find a file to import spec is None diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index e4dc4eddc9c..81e52ea729d 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -621,7 +621,9 @@ def _import_module_using_spec( # Checking with sys.meta_path first in case one of its hooks can import this module, # such as our own assertion-rewrite hook. for meta_importer in sys.meta_path: - spec = meta_importer.find_spec(module_name, [str(module_location)]) + spec = meta_importer.find_spec( + module_name, [str(module_location), str(module_path)] + ) if spec_matches_module_path(spec, module_path): break else: diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py index 377c30ca900..7c0a0e03d76 100644 --- a/testing/test_pathlib.py +++ b/testing/test_pathlib.py @@ -1372,6 +1372,42 @@ def test_resolve_pkg_root_and_module_name_ns_multiple_levels( ) assert mod is mod2 + def test_ns_multiple_levels_import_rewrite_assertions( + self, + tmp_path: Path, + monkeypatch: MonkeyPatch, + pytester: Pytester, + ) -> None: + """Check assert rewriting with `--import-mode=importlib` (#12659).""" + self.setup_directories(tmp_path, monkeypatch, pytester) + code = dedent(""" + def test(): + assert "four lights" == "five lights" + """) + + # A case is in a subdirectory with an `__init__.py` file. + test_py = tmp_path / "src/dist2/com/company/calc/algo/test_demo.py" + test_py.write_text(code, encoding="UTF-8") + + pkg_root, module_name = resolve_pkg_root_and_module_name( + test_py, consider_namespace_packages=True + ) + assert (pkg_root, module_name) == ( + tmp_path / "src/dist2", + "com.company.calc.algo.test_demo", + ) + + result = pytester.runpytest("--import-mode=importlib", test_py) + + result.stdout.fnmatch_lines( + [ + "E AssertionError: assert 'four lights' == 'five lights'", + "E *", + "E - five lights*", + "E + four lights", + ] + ) + @pytest.mark.parametrize("import_mode", ["prepend", "append", "importlib"]) def test_incorrect_namespace_package( self,