From 2001520610f3a31b1accb4ef41f1406a3f8dabd3 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 11 Aug 2026 12:26:24 +0200 Subject: [PATCH] fix: skipping an item without a line number no longer crashes TestReport.from_item_and_call asserted reportinfo() yields a line number. Items which cannot point at a line -- non-Python items legitimately report None -- crashed with an INTERNALERROR when skipped via a marker. Report the location without a line instead. --- changelog/14855.bugfix.rst | 3 +++ src/_pytest/reports.py | 6 ++++-- testing/test_skipping.py | 23 +++++++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 changelog/14855.bugfix.rst diff --git a/changelog/14855.bugfix.rst b/changelog/14855.bugfix.rst new file mode 100644 index 00000000000..3ce00fe29a4 --- /dev/null +++ b/changelog/14855.bugfix.rst @@ -0,0 +1,3 @@ +Skipping an item whose ``reportinfo()`` reports no line number no longer +crashes pytest with an ``INTERNALERROR``; the skip location is reported without +a line instead. Non-Python items may legitimately have no line to point at. diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index 35a212e8eab..021a4361529 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -443,8 +443,10 @@ def from_item_and_call(cls, item: Item, call: CallInfo[None]) -> TestReport: ) if excinfo.value._use_item_location: path, line = item.reportinfo()[:2] - assert line is not None - longrepr = (os.fspath(path), line + 1, r.message) + # Items which cannot point at a line -- non-Python items may + # legitimately report None -- get the location without one. + lineno = line + 1 if line is not None else -1 + longrepr = (os.fspath(path), lineno, r.message) else: longrepr = (str(r.path), r.lineno, r.message) elif isinstance(excinfo.value, BaseExceptionGroup) and ( diff --git a/testing/test_skipping.py b/testing/test_skipping.py index fbe196915e8..e719d47cf14 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -1463,6 +1463,29 @@ def pytest_collect_file(file_path, parent): assert xfailed +def test_mark_skip_item_without_line_number(pytester: Pytester) -> None: + # A non-Python Item may report no line number; skipping it used to + # crash with an INTERNALERROR on `assert line is not None`. + pytester.makeconftest( + """ + import pytest + + class MyItem(pytest.Item): + def runtest(self): + raise AssertionError("should not run") + + def pytest_collect_file(file_path, parent): + item = MyItem.from_parent(name="foo", parent=parent) + item.add_marker(pytest.mark.skip(reason="no line here")) + return item + """ + ) + result = pytester.runpytest("-rs") + assert "INTERNALERROR" not in result.stdout.str() + result.stdout.fnmatch_lines(["*no line here*"]) + result.assert_outcomes(skipped=1) + + def test_summary_list_after_errors(pytester: Pytester) -> None: """Ensure the list of errors/fails/xfails/skips appears after tracebacks in terminal reporting.""" pytester.makepyfile(