@@ -116,15 +116,68 @@ async def test_foreign_exception_passed(self):
116116 raise KeyError
117117 self .assertFalse (cm .expired ())
118118
119+ async def test_timeout_exception_context (self ):
120+ with self .assertRaises (TimeoutError ) as cm :
121+ async with asyncio .timeout (0.01 ):
122+ try :
123+ 1 / 0
124+ finally :
125+ await asyncio .sleep (1 )
126+ e = cm .exception
127+ # Expect TimeoutError caused by CancelledError raised during handling
128+ # of ZeroDivisionError.
129+ e2 = e .__cause__
130+ self .assertIsInstance (e2 , asyncio .CancelledError )
131+ self .assertIs (e .__context__ , e2 )
132+ self .assertIsNone (e2 .__cause__ )
133+ self .assertIsInstance (e2 .__context__ , ZeroDivisionError )
134+
119135 async def test_foreign_exception_on_timeout (self ):
120136 async def crash ():
121137 try :
122138 await asyncio .sleep (1 )
123139 finally :
124140 1 / 0
125- with self .assertRaises (ZeroDivisionError ):
141+ with self .assertRaises (ZeroDivisionError ) as cm :
126142 async with asyncio .timeout (0.01 ):
127143 await crash ()
144+ e = cm .exception
145+ # Expect ZeroDivisionError raised during handling of TimeoutError
146+ # caused by CancelledError.
147+ self .assertIsNone (e .__cause__ )
148+ e2 = e .__context__
149+ self .assertIsInstance (e2 , TimeoutError )
150+ e3 = e2 .__cause__
151+ self .assertIsInstance (e3 , asyncio .CancelledError )
152+ self .assertIs (e2 .__context__ , e3 )
153+
154+ async def test_foreign_exception_on_timeout_2 (self ):
155+ with self .assertRaises (ZeroDivisionError ) as cm :
156+ async with asyncio .timeout (0.01 ):
157+ try :
158+ try :
159+ raise ValueError
160+ finally :
161+ await asyncio .sleep (1 )
162+ finally :
163+ try :
164+ raise KeyError
165+ finally :
166+ 1 / 0
167+ e = cm .exception
168+ # Expect ZeroDivisionError raised during handling of KeyError
169+ # raised during handling of TimeoutError caused by CancelledError.
170+ self .assertIsNone (e .__cause__ )
171+ e2 = e .__context__
172+ self .assertIsInstance (e2 , KeyError )
173+ self .assertIsNone (e2 .__cause__ )
174+ e3 = e2 .__context__
175+ self .assertIsInstance (e3 , TimeoutError )
176+ e4 = e3 .__cause__
177+ self .assertIsInstance (e4 , asyncio .CancelledError )
178+ self .assertIsNone (e4 .__cause__ )
179+ self .assertIsInstance (e4 .__context__ , ValueError )
180+ self .assertIs (e3 .__context__ , e4 )
128181
129182 async def test_foreign_cancel_doesnt_timeout_if_not_expired (self ):
130183 with self .assertRaises (asyncio .CancelledError ):
@@ -219,14 +272,30 @@ async def test_repr_disabled(self):
219272 self .assertEqual (repr (cm ), r"<Timeout [active] when=None>" )
220273
221274 async def test_nested_timeout_in_finally (self ):
222- with self .assertRaises (TimeoutError ):
275+ with self .assertRaises (TimeoutError ) as cm1 :
223276 async with asyncio .timeout (0.01 ):
224277 try :
225278 await asyncio .sleep (1 )
226279 finally :
227- with self .assertRaises (TimeoutError ):
280+ with self .assertRaises (TimeoutError ) as cm2 :
228281 async with asyncio .timeout (0.01 ):
229282 await asyncio .sleep (10 )
283+ e1 = cm1 .exception
284+ # Expect TimeoutError caused by CancelledError.
285+ e12 = e1 .__cause__
286+ self .assertIsInstance (e12 , asyncio .CancelledError )
287+ self .assertIsNone (e12 .__cause__ )
288+ self .assertIsNone (e12 .__context__ )
289+ self .assertIs (e1 .__context__ , e12 )
290+ e2 = cm2 .exception
291+ # Expect TimeoutError caused by CancelledError raised during
292+ # handling of other CancelledError (which is the same as in
293+ # the above chain).
294+ e22 = e2 .__cause__
295+ self .assertIsInstance (e22 , asyncio .CancelledError )
296+ self .assertIsNone (e22 .__cause__ )
297+ self .assertIs (e22 .__context__ , e12 )
298+ self .assertIs (e2 .__context__ , e22 )
230299
231300 async def test_timeout_after_cancellation (self ):
232301 try :
@@ -235,7 +304,7 @@ async def test_timeout_after_cancellation(self):
235304 except asyncio .CancelledError :
236305 pass
237306 finally :
238- with self .assertRaises (TimeoutError ):
307+ with self .assertRaises (TimeoutError ) as cm :
239308 async with asyncio .timeout (0.0 ):
240309 await asyncio .sleep (1 ) # some cleanup
241310
@@ -251,13 +320,6 @@ async def test_cancel_in_timeout_after_cancellation(self):
251320 asyncio .current_task ().cancel ()
252321 await asyncio .sleep (2 ) # some cleanup
253322
254- async def test_timeout_exception_cause (self ):
255- with self .assertRaises (asyncio .TimeoutError ) as exc :
256- async with asyncio .timeout (0 ):
257- await asyncio .sleep (1 )
258- cause = exc .exception .__cause__
259- assert isinstance (cause , asyncio .CancelledError )
260-
261323 async def test_timeout_already_entered (self ):
262324 async with asyncio .timeout (0.01 ) as cm :
263325 with self .assertRaisesRegex (RuntimeError , "has already been entered" ):
@@ -303,6 +365,47 @@ async def test_timeout_without_task(self):
303365 with self .assertRaisesRegex (RuntimeError , "has not been entered" ):
304366 cm .reschedule (0.02 )
305367
368+ async def test_timeout_taskgroup (self ):
369+ async def task ():
370+ try :
371+ await asyncio .sleep (2 ) # Will be interrupted after 0.01 second
372+ finally :
373+ 1 / 0 # Crash in cleanup
374+
375+ with self .assertRaises (ExceptionGroup ) as cm :
376+ async with asyncio .timeout (0.01 ):
377+ async with asyncio .TaskGroup () as tg :
378+ tg .create_task (task ())
379+ try :
380+ raise ValueError
381+ finally :
382+ await asyncio .sleep (1 )
383+ eg = cm .exception
384+ # Expect ExceptionGroup raised during handling of TimeoutError caused
385+ # by CancelledError raised during handling of ValueError.
386+ self .assertIsNone (eg .__cause__ )
387+ e_1 = eg .__context__
388+ self .assertIsInstance (e_1 , TimeoutError )
389+ e_2 = e_1 .__cause__
390+ self .assertIsInstance (e_2 , asyncio .CancelledError )
391+ self .assertIsNone (e_2 .__cause__ )
392+ self .assertIsInstance (e_2 .__context__ , ValueError )
393+ self .assertIs (e_1 .__context__ , e_2 )
394+
395+ self .assertEqual (len (eg .exceptions ), 1 , eg )
396+ e1 = eg .exceptions [0 ]
397+ # Expect ZeroDivisionError raised during handling of TimeoutError
398+ # caused by CancelledError (it is a different CancelledError).
399+ self .assertIsInstance (e1 , ZeroDivisionError )
400+ self .assertIsNone (e1 .__cause__ )
401+ e2 = e1 .__context__
402+ self .assertIsInstance (e2 , TimeoutError )
403+ e3 = e2 .__cause__
404+ self .assertIsInstance (e3 , asyncio .CancelledError )
405+ self .assertIsNone (e3 .__context__ )
406+ self .assertIsNone (e3 .__cause__ )
407+ self .assertIs (e2 .__context__ , e3 )
408+
306409
307410if __name__ == '__main__' :
308411 unittest .main ()
0 commit comments