Skip to content

Commit 576b223

Browse files
gpsheadclaude
andcommitted
Add tests for BaseExceptionGroup rendering in IDLE's print_exception
Test nested groups, __cause__/__context__ chaining inside groups, and cycle detection for the exception group display code. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4ea0d84 commit 576b223

File tree

1 file changed

+63
-0
lines changed

1 file changed

+63
-0
lines changed

Lib/idlelib/idle_test/test_run.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,69 @@ def test_get_multiple_message(self, mock):
8282
subtests += 1
8383
self.assertEqual(subtests, len(data2)) # All subtests ran?
8484

85+
def _capture_exception(self):
86+
"""Call run.print_exception() and return its stderr output."""
87+
with captured_stderr() as output:
88+
with mock.patch.object(run, 'cleanup_traceback') as ct:
89+
ct.side_effect = lambda t, e: t
90+
run.print_exception()
91+
return output.getvalue()
92+
93+
@force_not_colorized
94+
def test_print_exception_group_nested(self):
95+
try:
96+
try:
97+
raise ExceptionGroup('inner', [ValueError('v1')])
98+
except ExceptionGroup as inner:
99+
raise ExceptionGroup('outer', [inner, TypeError('t1')])
100+
except ExceptionGroup:
101+
tb = self._capture_exception()
102+
103+
self.assertIn('ExceptionGroup: outer (2 sub-exceptions)', tb)
104+
self.assertIn('ExceptionGroup: inner', tb)
105+
self.assertIn('ValueError: v1', tb)
106+
self.assertIn('TypeError: t1', tb)
107+
# Verify tree structure characters.
108+
self.assertIn('+-+---------------- 1 ----------------', tb)
109+
self.assertIn('+---------------- 2 ----------------', tb)
110+
self.assertIn('+------------------------------------', tb)
111+
112+
@force_not_colorized
113+
def test_print_exception_group_chaining(self):
114+
# __cause__ on a sub-exception exercises the prefixed
115+
# chaining-message path (margin chars on separator lines).
116+
sub = TypeError('t1')
117+
sub.__cause__ = ValueError('original')
118+
try:
119+
raise ExceptionGroup('eg1', [sub])
120+
except ExceptionGroup:
121+
tb = self._capture_exception()
122+
self.assertIn('ValueError: original', tb)
123+
self.assertIn('| The above exception was the direct cause', tb)
124+
self.assertIn('ExceptionGroup: eg1', tb)
125+
126+
# __context__ (implicit chaining) on a sub-exception.
127+
sub = TypeError('t2')
128+
sub.__context__ = ValueError('first')
129+
try:
130+
raise ExceptionGroup('eg2', [sub])
131+
except ExceptionGroup:
132+
tb = self._capture_exception()
133+
self.assertIn('ValueError: first', tb)
134+
self.assertIn('| During handling of the above exception', tb)
135+
self.assertIn('ExceptionGroup: eg2', tb)
136+
137+
@force_not_colorized
138+
def test_print_exception_group_seen(self):
139+
shared = ValueError('shared')
140+
try:
141+
raise ExceptionGroup('eg', [shared, shared])
142+
except ExceptionGroup:
143+
tb = self._capture_exception()
144+
145+
self.assertIn('ValueError: shared', tb)
146+
self.assertIn('<exception ValueError has printed>', tb)
147+
85148
# StdioFile tests.
86149

87150
class S(str):

0 commit comments

Comments
 (0)