From 1c92e5193f4225375c1934390d7bfdf70c222e4d Mon Sep 17 00:00:00 2001 From: NIK-TIGER-BILL Date: Wed, 22 Apr 2026 23:14:08 +0000 Subject: [PATCH 1/2] fix(terminal): include subtests in timing progress reports When console_output_style=times is used with subtests, all subtests after the first one showed 0.00s because: 1. SubtestReport uses the same nodeid as the parent test, so _timing_nodeids_reported filtered out subsequent subtest reports. 2. Subtest reports were not included in all_reports at all. Fix by: - Tracking processed reports by id() instead of nodeid. - Including subtests passed/failed/skipped in all_reports. Fixes #14412 Signed-off-by: NIK-TIGER-BILL --- src/_pytest/terminal.py | 9 ++++++--- testing/test_terminal.py | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index b9a65ff191e..7a43fa43f09 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -401,7 +401,7 @@ def __init__(self, config: Config, file: TextIO | None = None) -> None: # We use CallableBool here to support both. self.isatty = compat.CallableBool(file.isatty()) self._progress_nodeids_reported: set[str] = set() - self._timing_nodeids_reported: set[str] = set() + self._timing_reports_reported: set[int] = set() self._show_progress_info = self._determine_show_progress_info() self._collect_report_last_write = timing.Instant() self._already_displayed_warnings: int | None = None @@ -737,11 +737,14 @@ def _get_progress_information_message(self) -> str: + self._get_reports_to_display("xfailed") + self._get_reports_to_display("skipped") + self._get_reports_to_display("error") + + self._get_reports_to_display("subtests passed") + + self._get_reports_to_display("subtests failed") + + self._get_reports_to_display("subtests skipped") + self._get_reports_to_display("") ) current_location = all_reports[-1].location[0] not_reported = [ - r for r in all_reports if r.nodeid not in self._timing_nodeids_reported + r for r in all_reports if id(r) not in self._timing_reports_reported ] tests_in_module = sum( i.location[0] == current_location for i in self._session.items @@ -753,7 +756,7 @@ def _get_progress_information_message(self) -> str: ) last_in_module = tests_completed == tests_in_module if self.showlongtestinfo or last_in_module: - self._timing_nodeids_reported.update(r.nodeid for r in not_reported) + self._timing_reports_reported.update(id(r) for r in not_reported) return format_node_duration( sum(r.duration for r in not_reported if isinstance(r, TestReport)) ) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 3053f5ef9a1..649a46fff37 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -140,6 +140,32 @@ def test_hello(): combined = "\n".join(result.stdout.lines + result.stderr.lines) assert "INTERNALERROR" not in combined + def test_console_output_style_times_subtests(self, pytester: Pytester) -> None: + pytester.makepyfile( + test_repro=""" + import time + def test_example(subtests): + for i in range(2): + with subtests.test(msg=str(i)): + time.sleep(0.05) + """, + ) + result = pytester.runpytest( + "test_repro.py", + "-v", + "-o", + "console_output_style=times", + ) + result.assert_outcomes(passed=1) + # Verify that subtest timing is non-zero (not 0.00s/0.000us). + output = result.stdout.str() + assert "0.000us" not in output + assert "0.00s" not in output + # Each subtest sleeps for 0.05s so we expect at least 40ms in the output. + result.stdout.re_match_lines( + [r".*SUBPASSED\[0\] .*[4-9][0-9]\.[0-9]+ms"] + ) + def test_internalerror(self, pytester: Pytester, linecomp) -> None: modcol = pytester.getmodulecol("def test_one(): pass") rep = TerminalReporter(modcol.config, file=linecomp.stringio) From baac1249f6fe5bd5ff6d7419c570137a24c0538c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:18:52 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- testing/test_terminal.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 649a46fff37..5bdaa7903cd 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -162,9 +162,7 @@ def test_example(subtests): assert "0.000us" not in output assert "0.00s" not in output # Each subtest sleeps for 0.05s so we expect at least 40ms in the output. - result.stdout.re_match_lines( - [r".*SUBPASSED\[0\] .*[4-9][0-9]\.[0-9]+ms"] - ) + result.stdout.re_match_lines([r".*SUBPASSED\[0\] .*[4-9][0-9]\.[0-9]+ms"]) def test_internalerror(self, pytester: Pytester, linecomp) -> None: modcol = pytester.getmodulecol("def test_one(): pass")