@@ -397,7 +397,61 @@ def test_timer_context_not_cancelled() -> None:
397
397
assert not m_asyncio .current_task .return_value .cancel .called
398
398
399
399
400
- def test_timer_context_no_task (loop ) -> None :
400
+ @pytest .mark .skipif (
401
+ sys .version_info < (3 , 11 ), reason = "Python 3.11+ is required for .cancelling()"
402
+ )
403
+ async def test_timer_context_timeout_does_not_leak_upward () -> None :
404
+ """Verify that the TimerContext does not leak cancellation outside the context manager."""
405
+ loop = asyncio .get_running_loop ()
406
+ ctx = helpers .TimerContext (loop )
407
+ current_task = asyncio .current_task ()
408
+ assert current_task is not None
409
+ with pytest .raises (asyncio .TimeoutError ):
410
+ with ctx :
411
+ assert current_task .cancelling () == 0
412
+ loop .call_soon (ctx .timeout )
413
+ await asyncio .sleep (1 )
414
+
415
+ # After the context manager exits, the task should no longer be cancelling
416
+ assert current_task .cancelling () == 0
417
+
418
+
419
+ @pytest .mark .skipif (
420
+ sys .version_info < (3 , 11 ), reason = "Python 3.11+ is required for .cancelling()"
421
+ )
422
+ async def test_timer_context_timeout_does_swallow_cancellation () -> None :
423
+ """Verify that the TimerContext does not swallow cancellation."""
424
+ loop = asyncio .get_running_loop ()
425
+ current_task = asyncio .current_task ()
426
+ assert current_task is not None
427
+ ctx = helpers .TimerContext (loop )
428
+
429
+ async def task_with_timeout () -> None :
430
+ nonlocal ctx
431
+ new_task = asyncio .current_task ()
432
+ assert new_task is not None
433
+ with pytest .raises (asyncio .TimeoutError ):
434
+ with ctx :
435
+ assert new_task .cancelling () == 0
436
+ await asyncio .sleep (1 )
437
+
438
+ task = asyncio .create_task (task_with_timeout ())
439
+ await asyncio .sleep (0 )
440
+ task .cancel ()
441
+ assert task .cancelling () == 1
442
+ ctx .timeout ()
443
+
444
+ # Cancellation should not leak into the current task
445
+ assert current_task .cancelling () == 0
446
+ # Cancellation should not be swallowed if the task is cancelled
447
+ # and it also times out
448
+ await asyncio .sleep (0 )
449
+ with pytest .raises (asyncio .CancelledError ):
450
+ await task
451
+ assert task .cancelling () == 1
452
+
453
+
454
+ def test_timer_context_no_task (loop : asyncio .AbstractEventLoop ) -> None :
401
455
with pytest .raises (RuntimeError ):
402
456
with helpers .TimerContext (loop ):
403
457
pass
0 commit comments