From 05c837dc3178814b581fd378cdda9363c2392ecd Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 14 Apr 2026 00:06:23 +0300 Subject: [PATCH 1/2] assertion: push `config` access up from `assertrepr_compare` to the hook Seems better to make the lower-level function more agnostic. --- src/_pytest/assertion/__init__.py | 9 ++++++++- src/_pytest/assertion/util.py | 10 ++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/_pytest/assertion/__init__.py b/src/_pytest/assertion/__init__.py index 22f3ca8e258..f274b9d579c 100644 --- a/src/_pytest/assertion/__init__.py +++ b/src/_pytest/assertion/__init__.py @@ -205,4 +205,11 @@ def pytest_sessionfinish(session: Session) -> None: def pytest_assertrepr_compare( config: Config, op: str, left: Any, right: Any ) -> list[str] | None: - return util.assertrepr_compare(config=config, op=op, left=left, right=right) + highlighter = config.get_terminal_writer()._highlight + return util.assertrepr_compare( + op=op, + left=left, + right=right, + verbose=config.get_verbosity(Config.VERBOSITY_ASSERTIONS), + highlighter=highlighter, + ) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 5d5e6d4777d..07918a66284 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -174,11 +174,14 @@ def has_default_eq(obj: object) -> bool: def assertrepr_compare( - config: Config, op: str, left: object, right: object + op: str, + left: object, + right: object, + *, + verbose: int, + highlighter: _HighlightFunc, ) -> list[str] | None: """Return specialised explanations for some operators/operands.""" - verbose = config.get_verbosity(Config.VERBOSITY_ASSERTIONS) - # Strings which normalize equal are often hard to distinguish when printed; use ascii() to make this easier. # See issue #3246. use_ascii = ( @@ -201,7 +204,6 @@ def assertrepr_compare( right_repr = saferepr(right, maxsize=maxsize, use_ascii=use_ascii) summary = f"{left_repr} {op} {right_repr}" - highlighter = config.get_terminal_writer()._highlight explanation = None try: From 4ee157d2da975f4c59f24ab99b2f885c3ba96d16 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 13 Apr 2026 22:43:54 +0300 Subject: [PATCH 2/2] assertion/rewrite: fix test crash on assert failure with `terminalreporter` disabled The `config.get_terminal_writer()` in `assertrepr_compare` (=> the function injected by assertion rewriting for every `assert`) requires the `terminalreporter` plugin, so it crashed when the plugin is disabled. Fix #14377. --- changelog/14377.bugfix.rst | 1 + src/_pytest/assertion/__init__.py | 6 +++++- testing/test_assertion.py | 6 ++++++ testing/test_assertrewrite.py | 19 +++++++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 changelog/14377.bugfix.rst diff --git a/changelog/14377.bugfix.rst b/changelog/14377.bugfix.rst new file mode 100644 index 00000000000..5d94fed0f54 --- /dev/null +++ b/changelog/14377.bugfix.rst @@ -0,0 +1 @@ +Fixed crash in `Config.get_terminal_writer` when an assertion fails with the ``terminalreporter`` plugin disabled. diff --git a/src/_pytest/assertion/__init__.py b/src/_pytest/assertion/__init__.py index f274b9d579c..4b946bc7074 100644 --- a/src/_pytest/assertion/__init__.py +++ b/src/_pytest/assertion/__init__.py @@ -205,7 +205,11 @@ def pytest_sessionfinish(session: Session) -> None: def pytest_assertrepr_compare( config: Config, op: str, left: Any, right: Any ) -> list[str] | None: - highlighter = config.get_terminal_writer()._highlight + if config.pluginmanager.has_plugin("terminalreporter"): + highlighter = config.get_terminal_writer()._highlight + else: + # Keep it plaintext when not using terminalrepoterer (#14377). + highlighter = util.dummy_highlighter return util.assertrepr_compare( op=op, left=left, diff --git a/testing/test_assertion.py b/testing/test_assertion.py index d68fd0b1fba..9a7305a2905 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -24,7 +24,13 @@ class TerminalWriter: def _highlight(self, source, lexer="python"): return source + class PluginManager: + def has_plugin(self, name: str) -> bool: + return True + class Config: + pluginmanager = PluginManager() + def get_terminal_writer(self): return TerminalWriter() diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 92664354470..2668001af65 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -2404,3 +2404,22 @@ def test_saferepr_unbounded(self): _saferepr(self.Help) == f"" ) + + +def test_assertion_failure_when_terminalreporter_is_disabled( + pytester: Pytester, +) -> None: + """Assertion rewriting doesn't crash when the terminalreporter plugin is + disabled (#14378).""" + pytester.makepyfile( + """ + import pytest + + def test(): + with pytest.raises(AssertionError) as excinfo: + assert 0 == 1 + assert excinfo.value.args[0] == 'assert 0 == 1' + """ + ) + reprec = pytester.inline_run("-p", "no:terminalreporter") + reprec.assertoutcome(passed=1)