Skip to content

fall back to native when handeling to exception groups #10071

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed
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 changelog/9159.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Showing inner exceptions by forcing native display in ``ExceptionGroups`` even when using display options other than ``--tb=native``. A temporary step before full implementation of pytest-native display for inner exceptions in ``ExceptionGroups``.
29 changes: 25 additions & 4 deletions src/_pytest/_code/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@

_TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"]

ExceptionGroupTypes: tuple = () # type: ignore
try:
ExceptionGroupTypes += (ExceptionGroup,) # type: ignore
except NameError:
pass # Is missing for `python<3.10`
try:
import exceptiongroup

ExceptionGroupTypes += (exceptiongroup.ExceptionGroup,)
except ModuleNotFoundError:
pass # No backport is installed


class Code:
"""Wrapper around Python code objects."""
Expand Down Expand Up @@ -923,11 +935,20 @@ def repr_excinfo(
seen: Set[int] = set()
while e is not None and id(e) not in seen:
seen.add(id(e))
if excinfo_:
reprtraceback = self.repr_traceback(excinfo_)
reprcrash: Optional[ReprFileLocation] = (
excinfo_._getreprcrash() if self.style != "value" else None
if isinstance(e, ExceptionGroupTypes):
reprtraceback: Union[
ReprTracebackNative, ReprTraceback
] = ReprTracebackNative(
traceback.format_exception(
type(excinfo.value),
excinfo.value,
excinfo.traceback[0]._rawentry,
)
)
reprcrash: Optional[ReprFileLocation] = None
elif excinfo_:
reprtraceback = self.repr_traceback(excinfo_)
reprcrash = excinfo_._getreprcrash() if self.style != "value" else None
else:
# Fallback to native repr if the exception doesn't have a traceback:
# ExceptionInfo objects require a full traceback to work.
Expand Down
31 changes: 31 additions & 0 deletions testing/code/test_excinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -1470,3 +1470,34 @@ def __getattr__(self, attr):
with pytest.raises(RuntimeError) as excinfo:
RecursionDepthError().trigger
assert "maximum recursion" in str(excinfo.getrepr())


def test_exceptiongroup(pytester: Pytester) -> None:
pytester.makepyfile(
"""
def f(): raise ValueError("From f()")
def g(): raise RuntimeError("From g()")

def main():
excs = []
for callback in [f, g]:
try:
callback()
except Exception as err:
excs.append(err)
if excs:
raise ExceptionGroup("Oops", excs)

def test():
main()
"""
)
result = pytester.runpytest()
assert result.ret != 0

match = [
r" | ExceptionGroup: Oops (2 sub-exceptions)",
r" | ValueError: From f()",
r" | RuntimeError: From g()",
]
result.stdout.re_match_lines(match)
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ setenv =
extras = testing
deps =
doctesting: PyYAML
exceptiongroup: exceptiongroup>=1.0.0
numpy: numpy>=1.19.4
pexpect: pexpect>=4.8.0
pluggymain: pluggy @ git+https://github.com/pytest-dev/pluggy.git
Expand Down