Handle exception chaining and groups in Dev's traceback handling (#6178)

This commit is contained in:
Jakub Kuczys 2023-06-22 01:42:01 +02:00 committed by GitHub
parent fdcbe00143
commit 9c85917dad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 188 additions and 37 deletions

View File

@ -337,49 +337,74 @@ class DevOutput:
pass
else:
exc.lineno -= line_offset
if sys.version_info >= (3, 10) and exc.end_lineno is not None:
exc.end_lineno -= line_offset
else:
exc.lineno -= line_offset
if sys.version_info >= (3, 10) and exc.end_lineno is not None:
exc.end_lineno -= line_offset
traceback_exc = traceback.TracebackException(exc_type, exc, tb)
top_traceback_exc = traceback.TracebackException(exc_type, exc, tb)
py311_or_above = sys.version_info >= (3, 11)
stack_summary = traceback_exc.stack
for idx, frame_summary in enumerate(stack_summary):
try:
source_lines, line_offset = self.source_cache[frame_summary.filename]
except KeyError:
continue
lineno = frame_summary.lineno
if lineno is None:
continue
queue = [ # actually a stack but 'stack' is easy to confuse with actual traceback stack
top_traceback_exc,
]
seen = {id(top_traceback_exc)}
while queue:
traceback_exc = queue.pop()
try:
# line numbers are 1-based, the list indexes are 0-based
line = source_lines[lineno - 1]
except IndexError:
# the frame might be pointing at a different source code, ignore...
continue
lineno -= line_offset
# support for enhanced error locations in tracebacks
if py311_or_above:
end_lineno = frame_summary.end_lineno
if end_lineno is not None:
end_lineno -= line_offset
frame_summary = traceback.FrameSummary(
frame_summary.filename,
lineno,
frame_summary.name,
line=line,
end_lineno=end_lineno,
colno=frame_summary.colno,
end_colno=frame_summary.end_colno,
)
else:
frame_summary = traceback.FrameSummary(
frame_summary.filename, lineno, frame_summary.name, line=line
)
stack_summary[idx] = frame_summary
# handle exception groups; this uses getattr() to support `exceptiongroup` backport lib
exceptions: List[traceback.TracebackException] = (
getattr(traceback_exc, "exceptions", None) or []
)
# handle exception chaining
if traceback_exc.__cause__ is not None:
exceptions.append(traceback_exc.__cause__)
if traceback_exc.__context__ is not None:
exceptions.append(traceback_exc.__context__)
for te in exceptions:
if id(te) not in seen:
queue.append(te)
seen.add(id(te))
return "".join(traceback_exc.format())
stack_summary = traceback_exc.stack
for idx, frame_summary in enumerate(stack_summary):
try:
source_lines, line_offset = self.source_cache[frame_summary.filename]
except KeyError:
continue
lineno = frame_summary.lineno
if lineno is None:
continue
try:
# line numbers are 1-based, the list indexes are 0-based
line = source_lines[lineno - 1]
except IndexError:
# the frame might be pointing at a different source code, ignore...
continue
lineno -= line_offset
# support for enhanced error locations in tracebacks
if py311_or_above:
end_lineno = frame_summary.end_lineno
if end_lineno is not None:
end_lineno -= line_offset
frame_summary = traceback.FrameSummary(
frame_summary.filename,
lineno,
frame_summary.name,
line=line,
end_lineno=end_lineno,
colno=frame_summary.colno,
end_colno=frame_summary.end_colno,
)
else:
frame_summary = traceback.FrameSummary(
frame_summary.filename, lineno, frame_summary.name, line=line
)
stack_summary[idx] = frame_summary
return "".join(top_traceback_exc.format())
@cog_i18n(_)

View File

@ -330,6 +330,132 @@ STATEMENT_TESTS = {
""",
),
),
# exception chaining
"""\
try:
1 / 0
except ZeroDivisionError as exc:
try:
raise RuntimeError("direct cause") from exc
except RuntimeError:
raise ValueError("indirect cause")
""": (
(
lambda v: v < (3, 11),
"""\
Traceback (most recent call last):
File "<test run - snippet #0>", line 2, in <module>
1 / 0
ZeroDivisionError: division by zero
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<test run - snippet #0>", line 5, in <module>
raise RuntimeError("direct cause") from exc
RuntimeError: direct cause
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<test run - snippet #0>", line 7, in <module>
raise ValueError("indirect cause")
ValueError: indirect cause
""",
),
(
lambda v: v >= (3, 11),
"""\
Traceback (most recent call last):
File "<test run - snippet #0>", line 2, in <module>
1 / 0
~~^~~
ZeroDivisionError: division by zero
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<test run - snippet #0>", line 5, in <module>
raise RuntimeError("direct cause") from exc
RuntimeError: direct cause
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<test run - snippet #0>", line 7, in <module>
raise ValueError("indirect cause")
ValueError: indirect cause
""",
),
),
# exception groups
"""\
def f(v):
try:
1 / 0
except ZeroDivisionError:
try:
raise ValueError(v)
except ValueError as e:
return e
try:
raise ExceptionGroup("one", [f(1)])
except ExceptionGroup as e:
eg = e
try:
raise ExceptionGroup("two", [f(2), eg])
except ExceptionGroup as e:
raise RuntimeError("wrapping") from e
""": (
(
lambda v: v >= (3, 11),
"""\
+ Exception Group Traceback (most recent call last):
| File "<test run - snippet #0>", line 14, in <module>
| raise ExceptionGroup("two", [f(2), eg])
| ExceptionGroup: two (2 sub-exceptions)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "<test run - snippet #0>", line 3, in f
| 1 / 0
| ~~^~~
| ZeroDivisionError: division by zero
|
| During handling of the above exception, another exception occurred:
|
| Traceback (most recent call last):
| File "<test run - snippet #0>", line 6, in f
| raise ValueError(v)
| ValueError: 2
+---------------- 2 ----------------
| Exception Group Traceback (most recent call last):
| File "<test run - snippet #0>", line 10, in <module>
| raise ExceptionGroup("one", [f(1)])
| ExceptionGroup: one (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "<test run - snippet #0>", line 3, in f
| 1 / 0
| ~~^~~
| ZeroDivisionError: division by zero
|
| During handling of the above exception, another exception occurred:
|
| Traceback (most recent call last):
| File "<test run - snippet #0>", line 6, in f
| raise ValueError(v)
| ValueError: 1
+------------------------------------
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<test run - snippet #0>", line 16, in <module>
raise RuntimeError("wrapping") from e
RuntimeError: wrapping
""",
),
),
}