Revert "Correctly handle tracebackhide for chained exceptions (#10772)"

This reverts commit 431ec6d34e.

Fix #10903.
Reopen #1904.
This commit is contained in:
Ran Benita 2023-04-12 18:35:59 +03:00
parent 61f7c27ec0
commit 90412827c3
7 changed files with 34 additions and 49 deletions

View File

@ -128,7 +128,6 @@ Erik M. Bray
Evan Kepner Evan Kepner
Fabien Zarifian Fabien Zarifian
Fabio Zadrozny Fabio Zadrozny
Felix Hofstätter
Felix Nieuwenhuizen Felix Nieuwenhuizen
Feng Ma Feng Ma
Florian Bruhin Florian Bruhin

View File

@ -0,0 +1,2 @@
Fix crash ``INTERNALERROR IndexError: list index out of range`` which happens when displaying an exception where all entries are hidden.
This reverts the change "Correctly handle ``__tracebackhide__`` for chained exceptions." introduced in version 7.3.0.

View File

@ -82,6 +82,7 @@ Bug Fixes
- `#1904 <https://github.com/pytest-dev/pytest/issues/1904>`_: Correctly handle ``__tracebackhide__`` for chained exceptions. - `#1904 <https://github.com/pytest-dev/pytest/issues/1904>`_: Correctly handle ``__tracebackhide__`` for chained exceptions.
NOTE: This change was reverted in version 7.3.1.

View File

@ -411,13 +411,13 @@ class Traceback(List[TracebackEntry]):
""" """
return Traceback(filter(fn, self), self._excinfo) return Traceback(filter(fn, self), self._excinfo)
def getcrashentry(self) -> Optional[TracebackEntry]: def getcrashentry(self) -> TracebackEntry:
"""Return last non-hidden traceback entry that lead to the exception of a traceback.""" """Return last non-hidden traceback entry that lead to the exception of a traceback."""
for i in range(-1, -len(self) - 1, -1): for i in range(-1, -len(self) - 1, -1):
entry = self[i] entry = self[i]
if not entry.ishidden(): if not entry.ishidden():
return entry return entry
return None return self[-1]
def recursionindex(self) -> Optional[int]: def recursionindex(self) -> Optional[int]:
"""Return the index of the frame/TracebackEntry where recursion originates if """Return the index of the frame/TracebackEntry where recursion originates if
@ -602,13 +602,11 @@ class ExceptionInfo(Generic[E]):
""" """
return isinstance(self.value, exc) return isinstance(self.value, exc)
def _getreprcrash(self) -> Optional["ReprFileLocation"]: def _getreprcrash(self) -> "ReprFileLocation":
exconly = self.exconly(tryshort=True) exconly = self.exconly(tryshort=True)
entry = self.traceback.getcrashentry() entry = self.traceback.getcrashentry()
if entry:
path, lineno = entry.frame.code.raw.co_filename, entry.lineno path, lineno = entry.frame.code.raw.co_filename, entry.lineno
return ReprFileLocation(path, lineno + 1, exconly) return ReprFileLocation(path, lineno + 1, exconly)
return None
def getrepr( def getrepr(
self, self,
@ -946,14 +944,9 @@ class FormattedExcinfo:
) )
else: else:
reprtraceback = self.repr_traceback(excinfo_) reprtraceback = self.repr_traceback(excinfo_)
reprcrash: Optional[ReprFileLocation] = (
# will be None if all traceback entries are hidden excinfo_._getreprcrash() if self.style != "value" else None
reprcrash: Optional[ReprFileLocation] = excinfo_._getreprcrash() )
if reprcrash:
if self.style == "value":
repr_chain += [(reprtraceback, None, descr)]
else:
repr_chain += [(reprtraceback, reprcrash, descr)]
else: else:
# Fallback to native repr if the exception doesn't have a traceback: # Fallback to native repr if the exception doesn't have a traceback:
# ExceptionInfo objects require a full traceback to work. # ExceptionInfo objects require a full traceback to work.
@ -961,8 +954,8 @@ class FormattedExcinfo:
traceback.format_exception(type(e), e, None) traceback.format_exception(type(e), e, None)
) )
reprcrash = None reprcrash = None
repr_chain += [(reprtraceback, reprcrash, descr)]
repr_chain += [(reprtraceback, reprcrash, descr)]
if e.__cause__ is not None and self.chain: if e.__cause__ is not None and self.chain:
e = e.__cause__ e = e.__cause__
excinfo_ = ( excinfo_ = (
@ -1053,7 +1046,7 @@ class ExceptionChainRepr(ExceptionRepr):
@dataclasses.dataclass(eq=False) @dataclasses.dataclass(eq=False)
class ReprExceptionInfo(ExceptionRepr): class ReprExceptionInfo(ExceptionRepr):
reprtraceback: "ReprTraceback" reprtraceback: "ReprTraceback"
reprcrash: Optional["ReprFileLocation"] reprcrash: "ReprFileLocation"
def toterminal(self, tw: TerminalWriter) -> None: def toterminal(self, tw: TerminalWriter) -> None:
self.reprtraceback.toterminal(tw) self.reprtraceback.toterminal(tw)

View File

@ -347,10 +347,6 @@ class TestReport(BaseReport):
elif isinstance(excinfo.value, skip.Exception): elif isinstance(excinfo.value, skip.Exception):
outcome = "skipped" outcome = "skipped"
r = excinfo._getreprcrash() r = excinfo._getreprcrash()
if r is None:
raise ValueError(
"There should always be a traceback entry for skipping a test."
)
if excinfo.value._use_item_location: if excinfo.value._use_item_location:
path, line = item.reportinfo()[:2] path, line = item.reportinfo()[:2]
assert line is not None assert line is not None

View File

@ -294,7 +294,6 @@ class TestTraceback_f_g_h:
excinfo = pytest.raises(ValueError, f) excinfo = pytest.raises(ValueError, f)
tb = excinfo.traceback tb = excinfo.traceback
entry = tb.getcrashentry() entry = tb.getcrashentry()
assert entry is not None
co = _pytest._code.Code.from_function(h) co = _pytest._code.Code.from_function(h)
assert entry.frame.code.path == co.path assert entry.frame.code.path == co.path
assert entry.lineno == co.firstlineno + 1 assert entry.lineno == co.firstlineno + 1
@ -312,7 +311,10 @@ class TestTraceback_f_g_h:
excinfo = pytest.raises(ValueError, f) excinfo = pytest.raises(ValueError, f)
tb = excinfo.traceback tb = excinfo.traceback
entry = tb.getcrashentry() entry = tb.getcrashentry()
assert entry is None co = _pytest._code.Code.from_function(g)
assert entry.frame.code.path == co.path
assert entry.lineno == co.firstlineno + 2
assert entry.frame.code.name == "g"
def test_excinfo_exconly(): def test_excinfo_exconly():
@ -1573,3 +1575,20 @@ def test_exceptiongroup(pytester: Pytester, outer_chain, inner_chain) -> None:
# with py>=3.11 does not depend on exceptiongroup, though there is a toxenv for it # with py>=3.11 does not depend on exceptiongroup, though there is a toxenv for it
pytest.importorskip("exceptiongroup") pytest.importorskip("exceptiongroup")
_exceptiongroup_common(pytester, outer_chain, inner_chain, native=False) _exceptiongroup_common(pytester, outer_chain, inner_chain, native=False)
def test_all_entries_hidden_doesnt_crash(pytester: Pytester) -> None:
"""Regression test for #10903.
We're not really sure what should be *displayed* here, so this test
just verified that at least it doesn't crash.
"""
pytester.makepyfile(
"""
def test():
__tracebackhide__ = True
1 / 0
"""
)
result = pytester.runpytest()
assert result.ret == 1

View File

@ -1,25 +0,0 @@
def test_tbh_chained(testdir):
"""Ensure chained exceptions whose frames contain "__tracebackhide__" are not shown (#1904)."""
p = testdir.makepyfile(
"""
import pytest
def f1():
__tracebackhide__ = True
try:
return f1.meh
except AttributeError:
pytest.fail("fail")
@pytest.fixture
def fix():
f1()
def test(fix):
pass
"""
)
result = testdir.runpytest(str(p))
assert "'function' object has no attribute 'meh'" not in result.stdout.str()
assert result.ret == 1