From 38e139106b80ab71a6ba29a312cc3e64c7ea553c Mon Sep 17 00:00:00 2001 From: Alex Chen Date: Sun, 19 Jul 2026 15:00:28 +0000 Subject: [PATCH 1/2] fix: keep pytest_terminal_summary under --no-summary --no-summary was skipping the entire pytest_terminal_summary hook, which silenced third-party plugins (e.g. coverage) as well as pytest's own FAILURES/ERRORS sections. Still suppress TerminalReporter's built-in summary body, but always call the hook so plugins can report. Closes #14724 Signed-off-by: Alex Chen --- AUTHORS | 1 + changelog/14724.bugfix.rst | 4 ++++ src/_pytest/terminal.py | 27 +++++++++++++++++---------- testing/test_terminal.py | 15 +++++++++++++++ 4 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 changelog/14724.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 0d7b43ba711..229e4315078 100644 --- a/AUTHORS +++ b/AUTHORS @@ -19,6 +19,7 @@ Alejandro Villate Alessio Izzo Alex Jones Alex Lambson +Alex Chen Alexander Johnson Alexander King Alexei Kozlenok diff --git a/changelog/14724.bugfix.rst b/changelog/14724.bugfix.rst new file mode 100644 index 00000000000..fbd98a6c751 --- /dev/null +++ b/changelog/14724.bugfix.rst @@ -0,0 +1,4 @@ +``--no-summary`` no longer skips the ``pytest_terminal_summary`` hook. + +Built-in FAILURES/ERRORS/... sections stay suppressed, but third-party +plugins (for example coverage) can still write their terminal summaries. diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index b9a65ff191e..5ca28dfec5c 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -968,7 +968,10 @@ def pytest_sessionfinish( ExitCode.NO_TESTS_COLLECTED, ExitCode.MAX_WARNINGS_ERROR, ) - if exitstatus in summary_exit_codes and not self.no_summary: + # Always invoke pytest_terminal_summary so third-party plugins can report + # (e.g. coverage). --no-summary only suppresses TerminalReporter's own + # built-in summary sections; see TerminalReporter.pytest_terminal_summary. + if exitstatus in summary_exit_codes: self.config.hook.pytest_terminal_summary( terminalreporter=self, exitstatus=exitstatus, config=self.config ) @@ -995,18 +998,22 @@ def pytest_sessionfinish( @hookimpl(wrapper=True) def pytest_terminal_summary(self) -> Generator[None]: - self.summary_errors() - self.summary_failures() - self.summary_xfailures() - self.summary_warnings() - self.summary_passes() - self.summary_xpasses() + # With --no-summary, still yield so other plugins run their terminal + # summaries, but skip pytest's own FAILURES/ERRORS/... sections. + if not self.no_summary: + self.summary_errors() + self.summary_failures() + self.summary_xfailures() + self.summary_warnings() + self.summary_passes() + self.summary_xpasses() try: return (yield) finally: - self.short_test_summary() - # Display any extra warnings from teardown here (if any). - self.summary_warnings() + if not self.no_summary: + self.short_test_summary() + # Display any extra warnings from teardown here (if any). + self.summary_warnings() def pytest_keyboard_interrupt(self, excinfo: ExceptionInfo[BaseException]) -> None: self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 3053f5ef9a1..de1dfbbda3d 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1015,6 +1015,21 @@ def test_no_summary(): result = pytester.runpytest(p1, "--no-summary") result.stdout.no_fnmatch_line("*= FAILURES =*") + def test_no_summary_still_runs_terminal_summary_hook( + self, pytester: Pytester + ) -> None: + """--no-summary must not skip pytest_terminal_summary for plugins (#14724).""" + pytester.makeconftest( + """ + def pytest_terminal_summary(terminalreporter, exitstatus, config): + terminalreporter.write_line("PLUGIN_TERMINAL_SUMMARY_RAN") + """ + ) + p1 = pytester.makepyfile("def test_ok(): assert True") + result = pytester.runpytest(p1, "--no-summary") + result.stdout.fnmatch_lines(["PLUGIN_TERMINAL_SUMMARY_RAN"]) + result.stdout.no_fnmatch_line("*= FAILURES =*") + def test_showlocals(self, pytester: Pytester) -> None: p1 = pytester.makepyfile( """ From 92d8c78537c2acf0c667f73af64418428808dea6 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 23 Jul 2026 11:08:53 +0200 Subject: [PATCH 2/2] Adjust changelog --- changelog/14724.bugfix.rst | 4 ---- changelog/14724.improvement.rst | 4 ++++ src/_pytest/terminal.py | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 changelog/14724.bugfix.rst create mode 100644 changelog/14724.improvement.rst diff --git a/changelog/14724.bugfix.rst b/changelog/14724.bugfix.rst deleted file mode 100644 index fbd98a6c751..00000000000 --- a/changelog/14724.bugfix.rst +++ /dev/null @@ -1,4 +0,0 @@ -``--no-summary`` no longer skips the ``pytest_terminal_summary`` hook. - -Built-in FAILURES/ERRORS/... sections stay suppressed, but third-party -plugins (for example coverage) can still write their terminal summaries. diff --git a/changelog/14724.improvement.rst b/changelog/14724.improvement.rst new file mode 100644 index 00000000000..cc4bac6ea8c --- /dev/null +++ b/changelog/14724.improvement.rst @@ -0,0 +1,4 @@ +``--no-summary`` now only applies directly to pytest's own summary. + +The flag no longer skips the ``pytest_terminal_summary`` hook, so third-party +plugins (for example coverage) can still write their terminal summaries. diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 5ca28dfec5c..e46f8fb4c20 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -1000,7 +1000,8 @@ def pytest_sessionfinish( def pytest_terminal_summary(self) -> Generator[None]: # With --no-summary, still yield so other plugins run their terminal # summaries, but skip pytest's own FAILURES/ERRORS/... sections. - if not self.no_summary: + show_summary = not self.no_summary + if show_summary: self.summary_errors() self.summary_failures() self.summary_xfailures() @@ -1010,7 +1011,7 @@ def pytest_terminal_summary(self) -> Generator[None]: try: return (yield) finally: - if not self.no_summary: + if show_summary: self.short_test_summary() # Display any extra warnings from teardown here (if any). self.summary_warnings()