Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/12175.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed :meth:`ExceptionInfo.exconly(tryshort=True) <ExceptionInfo.exconly>` not stripping the ``AssertionError`` prefix when the :class:`ExceptionInfo` was created via :meth:`ExceptionInfo.for_later` (e.g. when using :func:`pytest.raises`).
6 changes: 6 additions & 0 deletions src/_pytest/_code/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,12 @@ def fill_unfilled(self, exc_info: tuple[type[E], E, TracebackType]) -> None:
"""Fill an unfilled ExceptionInfo created with ``for_later()``."""
assert self._excinfo is None, "ExceptionInfo was already filled"
self._excinfo = exc_info
if isinstance(exc_info[1], AssertionError):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm a bit torn, we don’t repeat this often enough for the rule of 3 to apply but i feels this logic should be there only once

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic is already centralized in _compute_striptext() (line 549) — both from_exc_info() and fill_unfilled() call that same classmethod. The two call sites are just one-liners invoking the shared implementation.

If you would prefer a different approach — like having fill_unfilled() delegate to from_exc_info() internally rather than having two separate call sites — I am happy to refactor.

exprinfo = getattr(exc_info[1], "msg", None)
if exprinfo is None:
exprinfo = saferepr(exc_info[1])
if exprinfo and exprinfo.startswith(self._assert_start_repr):
self._striptext = "AssertionError: "

@property
def type(self) -> type[E]:
Expand Down
21 changes: 21 additions & 0 deletions testing/code/test_excinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,27 @@ def test_excinfo_for_later() -> None:
assert "for raises" in str(e)


def test_excinfo_for_later_strips_assertion(pytester: Pytester) -> None:
"""ExceptionInfo created via for_later() should strip AssertionError prefix
with exconly(tryshort=True) for rewritten assertions (#12175)."""
pytester.makepyfile(
"""
import pytest

def test_tryshort():
with pytest.raises(AssertionError) as exc_info:
assert 1 == 2
# tryshort should strip 'AssertionError: ' from rewritten assertions
result = exc_info.exconly(tryshort=True)
assert not result.startswith("AssertionError"), (
f"Expected stripped prefix, got: {result!r}"
)
"""
)
result = pytester.runpytest()
result.assert_outcomes(passed=1)


def test_excinfo_errisinstance():
excinfo = pytest.raises(ValueError, h)
assert excinfo.errisinstance(ValueError)
Expand Down