From 7c9cf56c8fe8522295666f11505f5eb38639cd3b Mon Sep 17 00:00:00 2001 From: AD Date: Sat, 28 Jun 2025 01:52:29 +0530 Subject: [PATCH 1/8] Handled CollectReport without duration attribute in terminal report --- src/_pytest/terminal.py | 2 +- testing/test_terminal_report.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 testing/test_terminal_report.py diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 5f27c46b41e..738661febae 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -734,7 +734,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) - return format_node_duration(sum(r.duration for r in not_reported)) + return format_node_duration(sum(r.duration for r in not_reported if isinstance(r, TestReport))) return "" if collected: return f" [{len(self._progress_nodeids_reported) * 100 // collected:3d}%]" diff --git a/testing/test_terminal_report.py b/testing/test_terminal_report.py new file mode 100644 index 00000000000..5aa931b4e35 --- /dev/null +++ b/testing/test_terminal_report.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +import pytest +from _pytest.pytester import Pytester + +def test_console_output_style_times_with_skipped_and_passed(pytester: Pytester) -> None: + pytester.makepyfile( + test_repro=""" + def test_hello(): + pass + """, + test_repro_skip=""" + import pytest + pytest.importorskip("fakepackage_does_not_exist") + """, + ) + + result = pytester.runpytest( + "test_repro.py", + "test_repro_skip.py", + "-o", "console_output_style=times", + ) + + print("Captured stdout:") + print(result.stdout.str()) + print("Captured stderr:") + print(result.stderr.str()) + + combined = result.stdout.lines + result.stderr.lines + assert any("'CollectReport' object has no attribute 'duration'" in line for line in combined) From 1ef62bcc46adda07be8878ba2c010381167996de Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 20:26:24 +0000 Subject: [PATCH 2/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_pytest/terminal.py | 4 +++- testing/test_terminal_report.py | 10 +++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 738661febae..a95f79ba6b6 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -734,7 +734,9 @@ 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) - return format_node_duration(sum(r.duration for r in not_reported if isinstance(r, TestReport))) + return format_node_duration( + sum(r.duration for r in not_reported if isinstance(r, TestReport)) + ) return "" if collected: return f" [{len(self._progress_nodeids_reported) * 100 // collected:3d}%]" diff --git a/testing/test_terminal_report.py b/testing/test_terminal_report.py index 5aa931b4e35..20170d2212e 100644 --- a/testing/test_terminal_report.py +++ b/testing/test_terminal_report.py @@ -1,8 +1,8 @@ from __future__ import annotations -import pytest from _pytest.pytester import Pytester + def test_console_output_style_times_with_skipped_and_passed(pytester: Pytester) -> None: pytester.makepyfile( test_repro=""" @@ -18,7 +18,8 @@ def test_hello(): result = pytester.runpytest( "test_repro.py", "test_repro_skip.py", - "-o", "console_output_style=times", + "-o", + "console_output_style=times", ) print("Captured stdout:") @@ -27,4 +28,7 @@ def test_hello(): print(result.stderr.str()) combined = result.stdout.lines + result.stderr.lines - assert any("'CollectReport' object has no attribute 'duration'" in line for line in combined) + assert any( + "'CollectReport' object has no attribute 'duration'" in line + for line in combined + ) From 6adf54e3a8101be65dad0a5a548eefc407169d28 Mon Sep 17 00:00:00 2001 From: Aditi De <92822822+coder-aditi@users.noreply.github.com> Date: Sun, 29 Jun 2025 19:40:36 +0530 Subject: [PATCH 3/8] Update testing/test_terminal_report.py Co-authored-by: Bruno Oliveira --- testing/test_terminal_report.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/testing/test_terminal_report.py b/testing/test_terminal_report.py index 20170d2212e..970b7d2e823 100644 --- a/testing/test_terminal_report.py +++ b/testing/test_terminal_report.py @@ -27,8 +27,4 @@ def test_hello(): print("Captured stderr:") print(result.stderr.str()) - combined = result.stdout.lines + result.stderr.lines - assert any( - "'CollectReport' object has no attribute 'duration'" in line - for line in combined - ) + result.stdout.fnmatch_lines(["* 1 passed, 1 skipped in *]) From aa02cabb1fd6aa133ac925cc94e73a9192c53a9a Mon Sep 17 00:00:00 2001 From: AD Date: Sun, 29 Jun 2025 20:26:23 +0530 Subject: [PATCH 4/8] Removed test_terminal_report.py from PR --- testing/test_terminal.py | 20 ++++++++++++++++++++ testing/test_terminal_report.py | 30 ------------------------------ 2 files changed, 20 insertions(+), 30 deletions(-) delete mode 100644 testing/test_terminal_report.py diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 3ea10195c6b..466a10c27c2 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -112,6 +112,26 @@ def test_func(): [" def test_func():", "> assert 0", "E assert 0"] ) + def test_console_output_style_times_with_skipped_and_passed(self, pytester: Pytester) -> None: + pytester.makepyfile( + test_repro=""" + def test_hello(): + pass + """, + test_repro_skip=""" + import pytest + pytest.importorskip("fakepackage_does_not_exist") + """, + ) + result = pytester.runpytest( + "test_repro.py", + "test_repro_skip.py", + "-o", "console_output_style=times", + ) + + combined = result.stdout.lines + result.stderr.lines + assert not any("'CollectReport' object has no attribute 'duration'" in line for line in combined) + def test_internalerror(self, pytester: Pytester, linecomp) -> None: modcol = pytester.getmodulecol("def test_one(): pass") rep = TerminalReporter(modcol.config, file=linecomp.stringio) diff --git a/testing/test_terminal_report.py b/testing/test_terminal_report.py deleted file mode 100644 index 970b7d2e823..00000000000 --- a/testing/test_terminal_report.py +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -from _pytest.pytester import Pytester - - -def test_console_output_style_times_with_skipped_and_passed(pytester: Pytester) -> None: - pytester.makepyfile( - test_repro=""" - def test_hello(): - pass - """, - test_repro_skip=""" - import pytest - pytest.importorskip("fakepackage_does_not_exist") - """, - ) - - result = pytester.runpytest( - "test_repro.py", - "test_repro_skip.py", - "-o", - "console_output_style=times", - ) - - print("Captured stdout:") - print(result.stdout.str()) - print("Captured stderr:") - print(result.stderr.str()) - - result.stdout.fnmatch_lines(["* 1 passed, 1 skipped in *]) From 351b3f4d8cfe1d19904b61ee04912fe837e2ded8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 14:56:56 +0000 Subject: [PATCH 5/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- testing/test_terminal.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 466a10c27c2..7744799e0a0 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -112,7 +112,9 @@ def test_func(): [" def test_func():", "> assert 0", "E assert 0"] ) - def test_console_output_style_times_with_skipped_and_passed(self, pytester: Pytester) -> None: + def test_console_output_style_times_with_skipped_and_passed( + self, pytester: Pytester + ) -> None: pytester.makepyfile( test_repro=""" def test_hello(): @@ -126,11 +128,15 @@ def test_hello(): result = pytester.runpytest( "test_repro.py", "test_repro_skip.py", - "-o", "console_output_style=times", + "-o", + "console_output_style=times", ) combined = result.stdout.lines + result.stderr.lines - assert not any("'CollectReport' object has no attribute 'duration'" in line for line in combined) + assert not any( + "'CollectReport' object has no attribute 'duration'" in line + for line in combined + ) def test_internalerror(self, pytester: Pytester, linecomp) -> None: modcol = pytester.getmodulecol("def test_one(): pass") From 9566c9fdcb799399c398f4ff7f1cb7f19f8d91b8 Mon Sep 17 00:00:00 2001 From: Aditi De <92822822+coder-aditi@users.noreply.github.com> Date: Sun, 29 Jun 2025 20:28:07 +0530 Subject: [PATCH 6/8] Updated test_terminal.py --- 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 7744799e0a0..78d61caf264 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -112,9 +112,7 @@ def test_func(): [" def test_func():", "> assert 0", "E assert 0"] ) - def test_console_output_style_times_with_skipped_and_passed( - self, pytester: Pytester - ) -> None: + def test_console_output_style_times_with_skipped_and_passed(self, pytester: Pytester) -> None: pytester.makepyfile( test_repro=""" def test_hello(): From bb03a4bb3a3c49cde0065cb4389750694fb5a28c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 14:58:29 +0000 Subject: [PATCH 7/8] [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, 3 insertions(+), 1 deletion(-) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 78d61caf264..7744799e0a0 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -112,7 +112,9 @@ def test_func(): [" def test_func():", "> assert 0", "E assert 0"] ) - def test_console_output_style_times_with_skipped_and_passed(self, pytester: Pytester) -> None: + def test_console_output_style_times_with_skipped_and_passed( + self, pytester: Pytester + ) -> None: pytester.makepyfile( test_repro=""" def test_hello(): From 25c8c0a1f1a849a89e84e11cbde3cf490a719e7b Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 29 Jun 2025 20:00:36 -0300 Subject: [PATCH 8/8] Improve test and add CHANGELOG --- changelog/13478.bugfix.rst | 1 + testing/test_terminal.py | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 changelog/13478.bugfix.rst diff --git a/changelog/13478.bugfix.rst b/changelog/13478.bugfix.rst new file mode 100644 index 00000000000..1147ee54c9e --- /dev/null +++ b/changelog/13478.bugfix.rst @@ -0,0 +1 @@ +Fixed a crash when using :confval:`console_output_style` with ``times`` and a module is skipped. diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 7744799e0a0..8c50311e9aa 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -132,11 +132,10 @@ def test_hello(): "console_output_style=times", ) - combined = result.stdout.lines + result.stderr.lines - assert not any( - "'CollectReport' object has no attribute 'duration'" in line - for line in combined - ) + result.stdout.fnmatch_lines("* 1 passed, 1 skipped in *") + + combined = "\n".join(result.stdout.lines + result.stderr.lines) + assert "INTERNALERROR" not in combined def test_internalerror(self, pytester: Pytester, linecomp) -> None: modcol = pytester.getmodulecol("def test_one(): pass")