Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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 AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ algojogacor
Alice Purcell
Allan Feldman
Aly Sivji
Amine-LG
Amir Elkess
Ammar Askar
Anatoly Bubenkoff
Expand Down
1 change: 1 addition & 0 deletions changelog/14940.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed ``__tracebackhide__`` not hiding frames that share a source location in ``ExceptionGroup`` tracebacks.
15 changes: 8 additions & 7 deletions src/_pytest/_code/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -1658,18 +1658,19 @@ def _filter_tracebackexception(
objects. It recurses into exception group sub-exceptions and into
``__cause__`` / ``__context__`` chains.

Frames are matched by ``(filename, lineno)``: ``TracebackEntry._rawentry.tb_lineno``
is 1-based absolute, matching ``FrameSummary.lineno``.
Frames are filtered based on the identity of their corresponding raw
traceback entries.
"""
if e.__traceback__ is not None:
excinfo = ExceptionInfo.from_exception(e)
filtered = filter_excinfo_traceback(tbfilter, excinfo)
kept = {
(str(entry.frame.code.path), entry._rawentry.tb_lineno)
for entry in filtered
}
kept = {id(entry._rawentry) for entry in filtered}
tb_exc.stack = StackSummary.from_list(
[fs for fs in tb_exc.stack if (fs.filename, fs.lineno) in kept]
[
fs
for entry, fs in zip(excinfo.traceback, tb_exc.stack, strict=False)
if id(entry._rawentry) in kept
]
)
if isinstance(e, BaseExceptionGroup):
sub_tb_excs = getattr(tb_exc, "exceptions", None) or []
Expand Down
53 changes: 53 additions & 0 deletions testing/code/test_excinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -2220,6 +2220,59 @@ def test():
result.stdout.no_fnmatch_line("*in g1*")


def test_tracebackhide_in_exceptiongroup_distinguishes_same_line_frames(
pytester: Pytester,
) -> None:
p = pytester.makepyfile(
"""
import sys
if sys.version_info < (3, 11):
from exceptiongroup import ExceptionGroup

def fail(number):
__tracebackhide__ = number == 1
if number == 0:
raise ValueError("boom")
fail(number - 1)

def test():
try:
fail(2)
except ValueError as error:
raise ExceptionGroup("failure", [error]) from None
"""
)
result = pytester.runpytest(str(p), "--tb=short")
assert result.ret == 1
assert result.stdout.str().count("fail(number - 1)") == 1


def test_tracebackhide_in_exceptiongroup_with_tracebacklimit(
pytester: Pytester,
) -> None:
p = pytester.makepyfile(
"""
import sys
if sys.version_info < (3, 11):
from exceptiongroup import ExceptionGroup

def fail():
__tracebackhide__ = True
raise ValueError("boom")

def test(monkeypatch):
monkeypatch.setattr(sys, "tracebacklimit", 1, raising=False)
try:
fail()
except ValueError as error:
raise ExceptionGroup("failure", [error]) from None
"""
)
result = pytester.runpytest(str(p), "--tb=short")
assert result.ret == 1
result.stdout.fnmatch_lines(["*ValueError: boom*"])


def add_note(err: BaseException, msg: str) -> None:
"""Adds a note to an exception inplace."""
if sys.version_info < (3, 11):
Expand Down
Loading