diff --git a/AUTHORS b/AUTHORS index 972f39aa45e..cbb25c46222 100644 --- a/AUTHORS +++ b/AUTHORS @@ -189,6 +189,7 @@ George Kussumoto Georgy Dyuldin Gergely Kalmár Gleb Nikonorov +Goutam Adwant Graeme Smecher Graham Horler Greg Price diff --git a/changelog/14607.bugfix.rst b/changelog/14607.bugfix.rst new file mode 100644 index 00000000000..11b6b156e50 --- /dev/null +++ b/changelog/14607.bugfix.rst @@ -0,0 +1 @@ +Single skipped tests in the short test summary now include the test node id instead of only the file and line number. diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index b9a65ff191e..d7adabe1faa 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -1341,10 +1341,15 @@ def show_skipped_folded(lines: list[str]) -> None: ) markup_word = self._tw.markup(verbose_word, **verbose_markup) prefix = "Skipped: " - for num, fspath, lineno, reason in fskips: + for num, fspath, lineno, reason, single_report in fskips: if reason.startswith(prefix): reason = reason[len(prefix) :] - if lineno is not None: + if single_report is not None: + nodeid = _get_node_id_with_markup( + self._tw, self.config, single_report + ) + lines.append(f"{markup_word} [{num}] {nodeid} - {reason}") + elif lineno is not None: lines.append(f"{markup_word} [{num}] {fspath}:{lineno}: {reason}") else: lines.append(f"{markup_word} [{num}] {fspath}: {reason}") @@ -1577,9 +1582,9 @@ def _get_line_with_reprcrash_message( def _folded_skips( startpath: Path, - skipped: Sequence[CollectReport], -) -> list[tuple[int, str, int | None, str]]: - d: dict[tuple[str, int | None, str], list[CollectReport]] = {} + skipped: Sequence[BaseReport], +) -> list[tuple[int, str, int | None, str, BaseReport | None]]: + d: dict[tuple[str, int | None, str], list[BaseReport]] = {} for event in skipped: assert event.longrepr is not None assert isinstance(event.longrepr, tuple), (event, event.longrepr) @@ -1600,9 +1605,10 @@ def _folded_skips( else: key = (fspath, lineno, reason) d.setdefault(key, []).append(event) - values: list[tuple[int, str, int | None, str]] = [] + values: list[tuple[int, str, int | None, str, BaseReport | None]] = [] for key, events in d.items(): - values.append((len(events), *key)) + single_report = events[0] if len(events) == 1 else None + values.append((len(events), *key, single_report)) return values diff --git a/testing/test_mark.py b/testing/test_mark.py index 253cda94503..97860b220f2 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -1084,7 +1084,7 @@ def test(param): [ "* collected 1 item", "test_paramset_empty_no_idfunc* SKIPPED *", - "SKIPPED [1] test_paramset_empty_no_idfunc.py:5: got empty parameter set for (param)", + "SKIPPED [[]1[]] test_paramset_empty_no_idfunc.py::test[[]NOTSET[]] - got empty parameter set for (param)", "*= 1 skipped in *", ] ) diff --git a/testing/test_reports.py b/testing/test_reports.py index b81371587d9..9929d029e26 100644 --- a/testing/test_reports.py +++ b/testing/test_reports.py @@ -472,11 +472,11 @@ def test_skip(fixA, fixB): assert "ERROR at teardown" not in out @pytest.mark.parametrize( - "use_item_location, skip_file_location", - [(True, "test_it.py"), (False, "runner.py")], + "use_item_location", + [True, False], ) def test_exception_group_skips_use_item_location( - self, pytester: Pytester, use_item_location: bool, skip_file_location: str + self, pytester: Pytester, use_item_location: bool ): """ Regression for #13537: @@ -508,8 +508,8 @@ def test_both(fix_item1, fix_item2): out = result.stdout.str() # Both reasons should appear assert "A" and "B" in out - # Crucially, the skip should be attributed to the test item, not teardown - assert skip_file_location in out + # The short summary should identify the skipped test item. + assert "SKIPPED [1] test_it.py::test_both - A; B" in out class TestHooks: diff --git a/testing/test_skipping.py b/testing/test_skipping.py index 5bb641aed3c..c7f1a45aa68 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -378,11 +378,17 @@ def test_func2(): [ ( ["-rs"], - ["SKIPPED [1] test_sample.py:2: unconditional skip", "*1 skipped*"], + [ + "SKIPPED [[]1[]] test_sample.py::test_skip_location - unconditional skip", + "*1 skipped*", + ], ), ( ["-rs", "--runxfail"], - ["SKIPPED [1] test_sample.py:2: unconditional skip", "*1 skipped*"], + [ + "SKIPPED [[]1[]] test_sample.py::test_skip_location - unconditional skip", + "*1 skipped*", + ], ), ], ) @@ -1009,9 +1015,9 @@ def doskip(reason): result = pytester.runpytest("-rs") result.stdout.fnmatch_lines_random( [ - "SKIPPED [[]1[]] test_one.py:7: setup function", - "SKIPPED [[]1[]] helpers.py:4: test method", - "SKIPPED [[]1[]] test_one.py:14: via_decorator", + "SKIPPED [[]1[]] test_one.py::test_func - setup function", + "SKIPPED [[]1[]] test_one.py::TestClass::test_method - test method", + "SKIPPED [[]1[]] test_one.py::TestClass::test_deco - via_decorator", ] ) assert result.ret == 0 @@ -1426,7 +1432,7 @@ def test_pass(): ) result = pytester.runpytest("-rs", "tests/test_1.py", "--rootdir=tests") result.stdout.fnmatch_lines( - ["SKIPPED [[]1[]] tests/test_1.py:2: unconditional skip"] + ["SKIPPED [[]1[]] tests/test_1.py::test_pass - unconditional skip"] ) @@ -1447,7 +1453,7 @@ def arg(): ) result = pytester.runpytest("-rs", "tests/test_1.py", "--rootdir=tests") result.stdout.fnmatch_lines( - ["SKIPPED [[]1[]] tests/test_1.py:2: Fixture conditional skip"] + ["SKIPPED [[]1[]] tests/test_1.py::test_pass - Fixture conditional skip"] ) diff --git a/testing/test_subtests.py b/testing/test_subtests.py index 877e32b5204..58fcbdc3c19 100644 --- a/testing/test_subtests.py +++ b/testing/test_subtests.py @@ -166,7 +166,7 @@ def test_bar(subtests): [ "test_*.py .s * [[]100%[]]", "*=== short test summary info ===*", - "SKIPPED [[]1[]] test_skip.py:9: skip test_bar", + "SKIPPED [[]1[]] test_skip.py::test_bar - skip test_bar", "* 1 passed, 1 skipped in *", ] ) @@ -179,9 +179,9 @@ def test_bar(subtests): "*.py::test_bar SUBSKIPPED[[]bar subtest[]] (skip bar subtest) * [[]100%[]]", "*.py::test_bar SKIPPED (skip test_bar) * [[]100%[]]", "*=== short test summary info ===*", - "SUBSKIPPED[[]foo subtest[]] [[]1[]] *.py:*: skip foo subtest", - "SUBSKIPPED[[]foo subtest[]] [[]1[]] *.py:*: skip bar subtest", - "SUBSKIPPED[[]foo subtest[]] [[]1[]] *.py:*: skip test_bar", + "SUBSKIPPED[[]foo subtest[]] [[]1[]] *.py::test_foo - skip foo subtest", + "SUBSKIPPED[[]foo subtest[]] [[]1[]] *.py::test_bar - skip bar subtest", + "SUBSKIPPED[[]foo subtest[]] [[]1[]] *.py::test_bar - skip test_bar", "* 1 passed, 3 skipped in *", ] ) @@ -204,10 +204,10 @@ def test_bar(subtests): result.stdout.no_fnmatch_line("*.py::test_foo SUBPASSED[[]foo subtest[]]*") result.stdout.no_fnmatch_line("*.py::test_bar SUBPASSED[[]bar subtest[]]*") result.stdout.no_fnmatch_line( - "SUBSKIPPED[[]foo subtest[]] [[]1[]] *.py:*: skip foo subtest" + "SUBSKIPPED[[]foo subtest[]] [[]1[]] *.py::test_foo - skip foo subtest" ) result.stdout.no_fnmatch_line( - "SUBSKIPPED[[]foo subtest[]] [[]1[]] *.py:*: skip test_bar" + "SUBSKIPPED[[]foo subtest[]] [[]1[]] *.py::test_bar - skip test_bar" ) @@ -584,7 +584,9 @@ def test_foo(self): ) result = pytester.runpytest("-v", "-rsf") result.stdout.fnmatch_lines( - ["SKIPPED [1] test_only_original_skip_is_called.py:6: skip this test"] + [ + "SKIPPED [[]1[]] test_only_original_skip_is_called.py::T::test_foo - skip this test" + ] ) def test_skip_with_failure( @@ -623,7 +625,7 @@ def test_foo(self): "*.py::T::test_foo SUBSKIPPED[[]subtest 1[]] (skip subtest 1) * [[]100%[]]", "*.py::T::test_foo SUBFAILED[[]subtest 2[]] * [[]100%[]]", "*.py::T::test_foo PASSED * [[]100%[]]", - "SUBSKIPPED[[]subtest 1[]] [[]1[]] *.py:*: skip subtest 1", + "SUBSKIPPED[[]subtest 1[]] [[]1[]] *.py::T::test_foo - skip subtest 1", "SUBFAILED[[]subtest 2[]] *.py::T::test_foo - AssertionError: fail subtest 2", "* 1 failed, 1 passed, 1 skipped in *", ] @@ -649,7 +651,7 @@ def test_foo(self): "*.py::T::test_foo SUBSKIPPED[[]subtest 1[]] (skip subtest 1) * [[]100%[]]" ) result.stdout.no_fnmatch_line( - "SUBSKIPPED[[]subtest 1[]] [[]1[]] *.py:*: skip subtest 1" + "SUBSKIPPED[[]subtest 1[]] [[]1[]] *.py::T::test_foo - skip subtest 1" ) def test_msg_not_a_string( diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 3053f5ef9a1..586b387bcfa 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1180,7 +1180,7 @@ def test(): """ ) result = pytester.runpytest("-rsS") - expected = "SKIPPED [1] test_summary_s_alias.py:3: unconditional skip" + expected = "SKIPPED [1] test_summary_s_alias.py::test - unconditional skip" result.stdout.fnmatch_lines([expected]) assert result.stdout.lines.count(expected) == 1 @@ -2544,11 +2544,12 @@ class X: values = _folded_skips(Path.cwd(), [ev1, ev2, ev3]) assert len(values) == 1 - num, fspath, lineno_, reason = values[0] + num, fspath, lineno_, reason, single_report = values[0] assert num == 3 assert fspath == path assert lineno_ == lineno assert reason == message + assert single_report is None def test_line_with_reprcrash(monkeypatch: MonkeyPatch) -> None: