10
10
import re
11
11
import sys
12
12
import textwrap
13
+ import traceback
13
14
import types
14
15
import unittest
15
16
import weakref
@@ -57,6 +58,22 @@ def format_coroutine(qualname, state, src, source_traceback, generator=False):
57
58
return 'coro=<%s() %s at %s>' % (qualname , state , src )
58
59
59
60
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
+
60
77
class Dummy :
61
78
62
79
def __repr__ (self ):
@@ -111,9 +128,10 @@ async def coro():
111
128
self .assertEqual (t ._cancel_message , None )
112
129
113
130
t .cancel ('my message' )
131
+ self .assertEqual (t ._cancel_message , 'my message' )
132
+
114
133
with self .assertRaises (asyncio .CancelledError ):
115
134
self .loop .run_until_complete (t )
116
- self .assertEqual (t ._cancel_message , 'my message' )
117
135
118
136
def test_task_cancel_message_setter (self ):
119
137
async def coro ():
@@ -123,10 +141,8 @@ async def coro():
123
141
t ._cancel_message = 'my new message'
124
142
self .assertEqual (t ._cancel_message , 'my new message' )
125
143
126
- # Also check that the value is used for cancel().
127
144
with self .assertRaises (asyncio .CancelledError ):
128
145
self .loop .run_until_complete (t )
129
- self .assertEqual (t ._cancel_message , 'my new message' )
130
146
131
147
def test_task_del_collect (self ):
132
148
class Evil :
@@ -548,8 +564,8 @@ async def task():
548
564
def test_cancel_with_message_then_future_result (self ):
549
565
# Test Future.result() after calling cancel() with a message.
550
566
cases = [
551
- ((), ('' , )),
552
- ((None ,), ('' , )),
567
+ ((), ()),
568
+ ((None ,), ()),
553
569
(('my message' ,), ('my message' ,)),
554
570
# Non-string values should roundtrip.
555
571
((5 ,), (5 ,)),
@@ -573,13 +589,17 @@ async def coro():
573
589
with self .assertRaises (asyncio .CancelledError ) as cm :
574
590
loop .run_until_complete (task )
575
591
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 ))
577
597
578
598
def test_cancel_with_message_then_future_exception (self ):
579
599
# Test Future.exception() after calling cancel() with a message.
580
600
cases = [
581
- ((), ('' , )),
582
- ((None ,), ('' , )),
601
+ ((), ()),
602
+ ((None ,), ()),
583
603
(('my message' ,), ('my message' ,)),
584
604
# Non-string values should roundtrip.
585
605
((5 ,), (5 ,)),
@@ -603,7 +623,11 @@ async def coro():
603
623
with self .assertRaises (asyncio .CancelledError ) as cm :
604
624
loop .run_until_complete (task )
605
625
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 ))
607
631
608
632
def test_cancel_with_message_before_starting_task (self ):
609
633
loop = asyncio .new_event_loop ()
@@ -623,7 +647,11 @@ async def coro():
623
647
with self .assertRaises (asyncio .CancelledError ) as cm :
624
648
loop .run_until_complete (task )
625
649
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 ))
627
655
628
656
def test_cancel_yield (self ):
629
657
with self .assertWarns (DeprecationWarning ):
@@ -805,6 +833,66 @@ async def coro():
805
833
self .assertTrue (nested_task .cancelled ())
806
834
self .assertTrue (fut .cancelled ())
807
835
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
+
808
896
def test_stop_while_run_in_complete (self ):
809
897
810
898
def gen ():
@@ -2391,15 +2479,14 @@ def cancelling_callback(_):
2391
2479
2392
2480
def test_cancel_gather_2 (self ):
2393
2481
cases = [
2394
- ((), ('' , )),
2395
- ((None ,), ('' , )),
2482
+ ((), ()),
2483
+ ((None ,), ()),
2396
2484
(('my message' ,), ('my message' ,)),
2397
2485
# Non-string values should roundtrip.
2398
2486
((5 ,), (5 ,)),
2399
2487
]
2400
2488
for cancel_args , expected_args in cases :
2401
2489
with self .subTest (cancel_args = cancel_args ):
2402
-
2403
2490
loop = asyncio .new_event_loop ()
2404
2491
self .addCleanup (loop .close )
2405
2492
@@ -2417,15 +2504,20 @@ async def main():
2417
2504
qwe = self .new_task (loop , test ())
2418
2505
await asyncio .sleep (0.2 )
2419
2506
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' )
2429
2521
2430
2522
def test_exception_traceback (self ):
2431
2523
# See http://bugs.python.org/issue28843
0 commit comments