@@ -41,7 +41,7 @@ def walk(s):
4141 return ret
4242
4343 buf = io .StringIO ()
44- asyncio .print_call_graph (fut , file = buf , depth = depth + 1 )
44+ asyncio .print_call_graph (fut , file = buf , depth = depth )
4545
4646 stack = asyncio .capture_call_graph (fut , depth = depth )
4747 return walk (stack ), buf .getvalue ()
@@ -298,6 +298,68 @@ async def main(t1, t2):
298298 ]
299299 ])
300300
301+ async def test_stack_as_completed (self ):
302+ # gh-156523: as_completed() must record the awaiting task
303+ stack_for_inner = None
304+
305+ async def inner ():
306+ await asyncio .sleep (0 )
307+ nonlocal stack_for_inner
308+ stack_for_inner = capture_test_stack ()
309+
310+ async def main (t ):
311+ for f in asyncio .as_completed ([t ]):
312+ await f
313+
314+ t = asyncio .create_task (inner (), name = 'inner' )
315+ await main (t )
316+ self .assertFalse (t ._asyncio_awaited_by )
317+
318+ self .assertEqual (stack_for_inner [0 ], [
319+ 'T<inner>' ,
320+ ['s capture_test_stack' , 'a inner' ],
321+ [
322+ ['T<anon>' ,
323+ ['a get' , 'a _wait_for_one' , 'a main' ,
324+ 'a test_stack_as_completed' ],
325+ []
326+ ]
327+ ]
328+ ])
329+
330+ async def test_stack_as_completed_timeout (self ):
331+ # gh-156523: the awaiting task must be dropped when as_completed() times out
332+ stack_for_inner = None
333+
334+ async def inner ():
335+ nonlocal stack_for_inner
336+ stack_for_inner = capture_test_stack ()
337+ await asyncio .sleep (3600 )
338+
339+ async def main (t ):
340+ with self .assertRaises (TimeoutError ):
341+ for f in asyncio .as_completed ([t ], timeout = 0.01 ):
342+ await f
343+
344+ t = asyncio .create_task (inner (), name = 'inner' )
345+ await main (t )
346+ self .assertFalse (t ._asyncio_awaited_by )
347+ t .cancel ()
348+ with self .assertRaises (asyncio .CancelledError ):
349+ await t
350+
351+ self .assertEqual (stack_for_inner [0 ], [
352+ 'T<inner>' ,
353+ ['s capture_test_stack' , 'a inner' ],
354+ [
355+ ['T<anon>' ,
356+ ['a get' , 'a _wait_for_one' , 'a main' ,
357+ 'a test_stack_as_completed_timeout' ],
358+ []
359+ ]
360+ ]
361+ ])
362+
301363 async def test_stack_task (self ):
302364
303365 stack_for_inner = None
@@ -422,6 +484,32 @@ def test_capture_call_graph_non_future(self):
422484 with self .assertRaises (TypeError ):
423485 asyncio .capture_call_graph ("not a future" )
424486
487+ async def test_print_call_graph_innermost_frame (self ):
488+ # gh-156327: print_call_graph() must not report its own frame
489+ buf = io .StringIO ()
490+ lineno = sys ._getframe ().f_lineno + 1
491+ asyncio .print_call_graph (file = buf )
492+ first_frame = buf .getvalue ().splitlines ()[2 ]
493+ self .assertIn (f'File { __file__ !r} , line { lineno } ,' , first_frame )
494+
495+ async def test_call_graph_finished_task (self ):
496+ # gh-156408: the call graph must not record a finished coroutine's None frame
497+ async def boom ():
498+ raise ValueError
499+
500+ done = asyncio .create_task (asyncio .sleep (0 ), name = 'done' )
501+ failed = asyncio .create_task (boom (), name = 'failed' )
502+ cancelled = asyncio .create_task (asyncio .Event ().wait (), name = 'cancelled' )
503+ cancelled .cancel ()
504+ await asyncio .gather (done , failed , cancelled , return_exceptions = True )
505+
506+ for task in (done , failed , cancelled ):
507+ with self .subTest (task = task .get_name ()):
508+ buf = io .StringIO ()
509+ asyncio .print_call_graph (task , file = buf )
510+ self .assertEqual (asyncio .capture_call_graph (task ).call_stack , ())
511+ self .assertIn (f"name={ task .get_name ()!r} " , buf .getvalue ())
512+
425513 async def test_capture_call_graph_no_current_task (self ):
426514 results = []
427515
0 commit comments