Skip to content

Commit 30225f5

Browse files
committed
.
1 parent c8ba161 commit 30225f5

2 files changed

Lines changed: 85 additions & 0 deletions

File tree

‎MIGRATION_GUIDE.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh
3131
- The option `attach_stacktrace` is now `True` by default, meaning the SDK will attach stack traces to messages.
3232
- The Django integration now creates spans for cache operations by default. Pass `DjangoIntegration(cache_spans=False)` to `sentry_sdk.init()` to turn them off.
3333
- The Django integration no longer force-enables cache spans when Spotlight is active and `settings.DEBUG` is `True`. The `cache_spans` option is now always respected as given.
34+
- Exception groups in exception chains are now properly unfurled.
3435

3536
### Logging
3637

‎tests/test_exceptiongroup.py‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,90 @@ def test_simple_exception():
317317
assert exception_values == expected_exception_values
318318

319319

320+
@minimum_python_311
321+
def test_exception_group_chained_with_context():
322+
try:
323+
try:
324+
raise ExceptionGroup(
325+
"group",
326+
[
327+
ValueError("child1"),
328+
ExceptionGroup(
329+
"child2",
330+
[
331+
RuntimeError("grandchild1"),
332+
RuntimeError("grandchild2"),
333+
],
334+
),
335+
],
336+
)
337+
finally:
338+
raise TypeError("bar")
339+
except BaseException as e:
340+
exc = e
341+
342+
(event, _) = event_from_exception(
343+
exc,
344+
client_options={
345+
"include_local_variables": True,
346+
"include_source_context": True,
347+
"max_value_length": 1024,
348+
},
349+
mechanism={"type": "test_suite", "handled": False},
350+
)
351+
352+
exception_values = event["exception"]["values"]
353+
354+
# innermost (oldest) to outermost (newest)
355+
assert [(e["type"], e["value"]) for e in exception_values] == [
356+
("RuntimeError", "2"),
357+
("RuntimeError", "1"),
358+
("ExceptionGroup", "child2"),
359+
("ValueError", "child1"),
360+
("ExceptionGroup", "group"),
361+
("TypeError", "bar"),
362+
]
363+
364+
# TypeError("bar") is the outermost exception (exception_id=0)
365+
type_error = exception_values[-1]
366+
assert type_error["mechanism"]["type"] == "test_suite"
367+
assert type_error["mechanism"]["exception_id"] == 0
368+
369+
# ExceptionGroup("group") is the __context__ of TypeError
370+
group = exception_values[-2]
371+
assert group["mechanism"]["type"] == "chained"
372+
assert group["mechanism"]["source"] == "__context__"
373+
assert group["mechanism"]["parent_id"] == 0
374+
assert group["mechanism"]["is_exception_group"] is True
375+
376+
group_id = group["mechanism"]["exception_id"]
377+
378+
# ValueError("child1") and ExceptionGroup("child2") are children of "group"
379+
child1 = exception_values[-3]
380+
assert child1["type"] == "ValueError"
381+
assert child1["mechanism"]["source"] == "exceptions[0]"
382+
assert child1["mechanism"]["parent_id"] == group_id
383+
384+
child2 = exception_values[-4]
385+
assert child2["type"] == "ExceptionGroup"
386+
assert child2["mechanism"]["source"] == "exceptions[1]"
387+
assert child2["mechanism"]["parent_id"] == group_id
388+
assert child2["mechanism"]["is_exception_group"] is True
389+
390+
child2_id = child2["mechanism"]["exception_id"]
391+
392+
# RuntimeError("grandchild1") and RuntimeError("grandchild2") are children of "child2"
393+
rt1 = exception_values[-5]
394+
assert rt1["value"] == "grandchild1"
395+
assert rt1["mechanism"]["source"] == "exceptions[0]"
396+
assert rt1["mechanism"]["parent_id"] == child2_id
397+
398+
rt2 = exception_values[-6]
399+
assert rt2["value"] == "grandchild2"
400+
assert rt2["mechanism"]["source"] == "exceptions[1]"
401+
assert rt2["mechanism"]["parent_id"] == child2_id
402+
403+
320404
@minimum_python_311
321405
def test_exceptiongroup_starlette_collapse():
322406
"""

0 commit comments

Comments
 (0)