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