diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index 6c5dd4b7c4a..a4bec917884 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -934,7 +934,12 @@ def pytest_unconfigure(config: Config) -> None: def pytest_assertrepr_compare( config: Config, op: str, left: object, right: object ) -> list[str] | None: - """Return explanation for comparisons in failing assert expressions. + """Return explanation for comparisons in assert expressions. + + By default, this hook is used only for failing assertions. It can also + be called for passing assertions when the :confval:`enable_assertion_pass_hook` + option is enabled and a :hook:`pytest_assertion_pass` hook implementation is + active, because pytest needs an assertion explanation to pass to that hook. Return None for no custom explanation, otherwise return a list of strings. The strings will be joined by newlines but any newlines diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index e11863547ba..75e4a71551d 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -2072,6 +2072,40 @@ def test(): result = pytester.runpytest() result.stdout.fnmatch_lines("*Assertion Passed: f() 1") + def test_assertrepr_compare_called_for_pass_hook( + self, pytester: Pytester, flag_on + ) -> None: + pytester.makeconftest( + """\ + def pytest_assertrepr_compare(config, op, left, right): + config.rootpath.joinpath("reprcompare.txt").write_text( + f"{op}:{left}:{right}", encoding="utf-8" + ) + return ["custom pass comparison"] + + def pytest_assertion_pass(item, lineno, orig, expl): + item.config.rootpath.joinpath("assertion-pass.txt").write_text( + expl, encoding="utf-8" + ) + """ + ) + pytester.makepyfile( + """\ + def test_simple(): + assert 1 == 1 + """ + ) + result = pytester.runpytest() + result.assert_outcomes(passed=1) + assert ( + pytester.path.joinpath("reprcompare.txt").read_text(encoding="utf-8") + == "==:1:1" + ) + assert ( + pytester.path.joinpath("assertion-pass.txt").read_text(encoding="utf-8") + == "custom pass comparison" + ) + def test_hook_not_called_without_hookimpl( self, pytester: Pytester, monkeypatch, flag_on ) -> None: