1010import re
1111import sys
1212import textwrap
13+ import traceback
1314import types
1415import unittest
1516import weakref
@@ -57,6 +58,22 @@ def format_coroutine(qualname, state, src, source_traceback, generator=False):
5758 return 'coro=<%s() %s at %s>' % (qualname , state , src )
5859
5960
61+ def get_innermost_context (exc ):
62+ """
63+ Return information about the innermost exception context in the chain.
64+ """
65+ depth = 0
66+ while True :
67+ context = exc .__context__
68+ if context is None :
69+ break
70+
71+ exc = context
72+ depth += 1
73+
74+ return (type (exc ), exc .args , depth )
75+
76+
6077class Dummy :
6178
6279 def __repr__ (self ):
@@ -111,9 +128,10 @@ async def coro():
111128 self .assertEqual (t ._cancel_message , None )
112129
113130 t .cancel ('my message' )
131+ self .assertEqual (t ._cancel_message , 'my message' )
132+
114133 with self .assertRaises (asyncio .CancelledError ):
115134 self .loop .run_until_complete (t )
116- self .assertEqual (t ._cancel_message , 'my message' )
117135
118136 def test_task_cancel_message_setter (self ):
119137 async def coro ():
@@ -123,10 +141,8 @@ async def coro():
123141 t ._cancel_message = 'my new message'
124142 self .assertEqual (t ._cancel_message , 'my new message' )
125143
126- # Also check that the value is used for cancel().
127144 with self .assertRaises (asyncio .CancelledError ):
128145 self .loop .run_until_complete (t )
129- self .assertEqual (t ._cancel_message , 'my new message' )
130146
131147 def test_task_del_collect (self ):
132148 class Evil :
@@ -548,8 +564,8 @@ async def task():
548564 def test_cancel_with_message_then_future_result (self ):
549565 # Test Future.result() after calling cancel() with a message.
550566 cases = [
551- ((), ('' , )),
552- ((None ,), ('' , )),
567+ ((), ()),
568+ ((None ,), ()),
553569 (('my message' ,), ('my message' ,)),
554570 # Non-string values should roundtrip.
555571 ((5 ,), (5 ,)),
@@ -573,13 +589,17 @@ async def coro():
573589 with self .assertRaises (asyncio .CancelledError ) as cm :
574590 loop .run_until_complete (task )
575591 exc = cm .exception
576- self .assertEqual (exc .args , expected_args )
592+ self .assertEqual (exc .args , ())
593+
594+ actual = get_innermost_context (exc )
595+ self .assertEqual (actual ,
596+ (asyncio .CancelledError , expected_args , 2 ))
577597
578598 def test_cancel_with_message_then_future_exception (self ):
579599 # Test Future.exception() after calling cancel() with a message.
580600 cases = [
581- ((), ('' , )),
582- ((None ,), ('' , )),
601+ ((), ()),
602+ ((None ,), ()),
583603 (('my message' ,), ('my message' ,)),
584604 # Non-string values should roundtrip.
585605 ((5 ,), (5 ,)),
@@ -603,7 +623,11 @@ async def coro():
603623 with self .assertRaises (asyncio .CancelledError ) as cm :
604624 loop .run_until_complete (task )
605625 exc = cm .exception
606- self .assertEqual (exc .args , expected_args )
626+ self .assertEqual (exc .args , ())
627+
628+ actual = get_innermost_context (exc )
629+ self .assertEqual (actual ,
630+ (asyncio .CancelledError , expected_args , 2 ))
607631
608632 def test_cancel_with_message_before_starting_task (self ):
609633 loop = asyncio .new_event_loop ()
@@ -623,7 +647,11 @@ async def coro():
623647 with self .assertRaises (asyncio .CancelledError ) as cm :
624648 loop .run_until_complete (task )
625649 exc = cm .exception
626- self .assertEqual (exc .args , ('my message' ,))
650+ self .assertEqual (exc .args , ())
651+
652+ actual = get_innermost_context (exc )
653+ self .assertEqual (actual ,
654+ (asyncio .CancelledError , ('my message' ,), 2 ))
627655
628656 def test_cancel_yield (self ):
629657 with self .assertWarns (DeprecationWarning ):
@@ -805,6 +833,66 @@ async def coro():
805833 self .assertTrue (nested_task .cancelled ())
806834 self .assertTrue (fut .cancelled ())
807835
836+ def assert_text_contains (self , text , substr ):
837+ if substr not in text :
838+ raise RuntimeError (f'text { substr !r} not found in:\n >>>{ text } <<<' )
839+
840+ def test_cancel_traceback_for_future_result (self ):
841+ # When calling Future.result() on a cancelled task, check that the
842+ # line of code that was interrupted is included in the traceback.
843+ loop = asyncio .new_event_loop ()
844+ self .set_event_loop (loop )
845+
846+ async def nested ():
847+ # This will get cancelled immediately.
848+ await asyncio .sleep (10 )
849+
850+ async def coro ():
851+ task = self .new_task (loop , nested ())
852+ await asyncio .sleep (0 )
853+ task .cancel ()
854+ await task # search target
855+
856+ task = self .new_task (loop , coro ())
857+ try :
858+ loop .run_until_complete (task )
859+ except asyncio .CancelledError :
860+ tb = traceback .format_exc ()
861+ self .assert_text_contains (tb , "await asyncio.sleep(10)" )
862+ # The intermediate await should also be included.
863+ self .assert_text_contains (tb , "await task # search target" )
864+ else :
865+ self .fail ('CancelledError did not occur' )
866+
867+ def test_cancel_traceback_for_future_exception (self ):
868+ # When calling Future.exception() on a cancelled task, check that the
869+ # line of code that was interrupted is included in the traceback.
870+ loop = asyncio .new_event_loop ()
871+ self .set_event_loop (loop )
872+
873+ async def nested ():
874+ # This will get cancelled immediately.
875+ await asyncio .sleep (10 )
876+
877+ async def coro ():
878+ task = self .new_task (loop , nested ())
879+ await asyncio .sleep (0 )
880+ task .cancel ()
881+ done , pending = await asyncio .wait ([task ])
882+ task .exception () # search target
883+
884+ task = self .new_task (loop , coro ())
885+ try :
886+ loop .run_until_complete (task )
887+ except asyncio .CancelledError :
888+ tb = traceback .format_exc ()
889+ self .assert_text_contains (tb , "await asyncio.sleep(10)" )
890+ # The intermediate await should also be included.
891+ self .assert_text_contains (tb ,
892+ "task.exception() # search target" )
893+ else :
894+ self .fail ('CancelledError did not occur' )
895+
808896 def test_stop_while_run_in_complete (self ):
809897
810898 def gen ():
@@ -2391,15 +2479,14 @@ def cancelling_callback(_):
23912479
23922480 def test_cancel_gather_2 (self ):
23932481 cases = [
2394- ((), ('' , )),
2395- ((None ,), ('' , )),
2482+ ((), ()),
2483+ ((None ,), ()),
23962484 (('my message' ,), ('my message' ,)),
23972485 # Non-string values should roundtrip.
23982486 ((5 ,), (5 ,)),
23992487 ]
24002488 for cancel_args , expected_args in cases :
24012489 with self .subTest (cancel_args = cancel_args ):
2402-
24032490 loop = asyncio .new_event_loop ()
24042491 self .addCleanup (loop .close )
24052492
@@ -2417,15 +2504,20 @@ async def main():
24172504 qwe = self .new_task (loop , test ())
24182505 await asyncio .sleep (0.2 )
24192506 qwe .cancel (* cancel_args )
2420- try :
2421- await qwe
2422- except asyncio .CancelledError as exc :
2423- self .assertEqual (exc .args , expected_args )
2424- else :
2425- self .fail ('gather did not propagate the cancellation '
2426- 'request' )
2427-
2428- loop .run_until_complete (main ())
2507+ await qwe
2508+
2509+ try :
2510+ loop .run_until_complete (main ())
2511+ except asyncio .CancelledError as exc :
2512+ self .assertEqual (exc .args , ())
2513+ exc_type , exc_args , depth = get_innermost_context (exc )
2514+ self .assertEqual ((exc_type , exc_args ),
2515+ (asyncio .CancelledError , expected_args ))
2516+ # The exact traceback seems to vary in CI.
2517+ self .assertIn (depth , (2 , 3 ))
2518+ else :
2519+ self .fail ('gather did not propagate the cancellation '
2520+ 'request' )
24292521
24302522 def test_exception_traceback (self ):
24312523 # See http://bugs.python.org/issue28843
0 commit comments