Skip to content

Commit a583c68

Browse files
committed
Correctly handle tracebackhide for chained exceptions
1 parent 572b565 commit a583c68

File tree

2 files changed

+40
-9
lines changed

2 files changed

+40
-9
lines changed

src/_pytest/_code/code.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -411,13 +411,13 @@ def filter(
411411
"""
412412
return Traceback(filter(fn, self), self._excinfo)
413413

414-
def getcrashentry(self) -> TracebackEntry:
414+
def getcrashentry(self) -> Optional[TracebackEntry]:
415415
"""Return last non-hidden traceback entry that lead to the exception of a traceback."""
416416
for i in range(-1, -len(self) - 1, -1):
417417
entry = self[i]
418418
if not entry.ishidden():
419419
return entry
420-
return self[-1]
420+
return None
421421

422422
def recursionindex(self) -> Optional[int]:
423423
"""Return the index of the frame/TracebackEntry where recursion originates if
@@ -602,11 +602,13 @@ def errisinstance(
602602
"""
603603
return isinstance(self.value, exc)
604604

605-
def _getreprcrash(self) -> "ReprFileLocation":
605+
def _getreprcrash(self) -> Optional["ReprFileLocation"]:
606606
exconly = self.exconly(tryshort=True)
607607
entry = self.traceback.getcrashentry()
608-
path, lineno = entry.frame.code.raw.co_filename, entry.lineno
609-
return ReprFileLocation(path, lineno + 1, exconly)
608+
if entry:
609+
path, lineno = entry.frame.code.raw.co_filename, entry.lineno
610+
return ReprFileLocation(path, lineno + 1, exconly)
611+
return None
610612

611613
def getrepr(
612614
self,
@@ -942,18 +944,23 @@ def repr_excinfo(
942944
)
943945
else:
944946
reprtraceback = self.repr_traceback(excinfo_)
945-
reprcrash: Optional[ReprFileLocation] = (
946-
excinfo_._getreprcrash() if self.style != "value" else None
947-
)
947+
948+
# will be None if all traceback entries are hidden
949+
reprcrash: Optional[ReprFileLocation] = excinfo_._getreprcrash()
950+
if reprcrash:
951+
if self.style == "value":
952+
repr_chain += [(reprtraceback, None, descr)]
953+
else:
954+
repr_chain += [(reprtraceback, reprcrash, descr)]
948955
else:
949956
# Fallback to native repr if the exception doesn't have a traceback:
950957
# ExceptionInfo objects require a full traceback to work.
951958
reprtraceback = ReprTracebackNative(
952959
traceback.format_exception(type(e), e, None)
953960
)
954961
reprcrash = None
962+
repr_chain += [(reprtraceback, reprcrash, descr)]
955963

956-
repr_chain += [(reprtraceback, reprcrash, descr)]
957964
if e.__cause__ is not None and self.chain:
958965
e = e.__cause__
959966
excinfo_ = (

testing/test_tracebackhide.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
def test_tbh_chained(testdir):
2+
p = testdir.makepyfile(
3+
"""
4+
import pytest
5+
6+
def f1():
7+
__tracebackhide__ = True
8+
try:
9+
return f1.meh
10+
except AttributeError:
11+
pytest.fail("fail")
12+
13+
@pytest.fixture
14+
def fix():
15+
f1()
16+
17+
18+
def test(fix):
19+
pass
20+
"""
21+
)
22+
result = testdir.runpytest(str(p))
23+
assert "'function' object has no attribute 'meh'" not in result.stdout.str()
24+
assert result.ret == 1

0 commit comments

Comments
 (0)