@@ -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
87150class S (str ):
0 commit comments