11import asyncio
22import io
33import sys
4+ import types
45import unittest
56from unittest import mock
67
@@ -11,6 +12,7 @@ def tearDownModule():
1112
1213
1314def capture_test_stack (* , fut = None , depth = 1 ):
15+ future_await_code = asyncio .futures ._PyFuture .__await__ .__code__
1416
1517 def walk (s ):
1618 ret = [
@@ -27,8 +29,12 @@ def walk(s):
2729 f"a { entry .frame .f_generator .cr_code .co_name } "
2830 if hasattr (entry .frame .f_generator , 'cr_code' ) else
2931 f"ag { entry .frame .f_generator .ag_code .co_name } "
32+ if hasattr (entry .frame .f_generator , 'ag_code' ) else
33+ f"g { entry .frame .f_generator .gi_code .co_name } "
3034 )
3135 ) for entry in s .call_stack
36+ # Skip the pure Python Future.__await__ generator frame
37+ if entry .frame .f_code is not future_await_code
3238 ]
3339 )
3440
@@ -305,6 +311,73 @@ async def main():
305311 []
306312 ])
307313
314+ async def test_stack_await_generator (self ):
315+ # The call graph of a suspended task sees through generators
316+ # returned by __await__() and the generators they yield from.
317+
318+ loop = asyncio .get_running_loop ()
319+ blocker = loop .create_future ()
320+
321+ def wait ():
322+ yield from blocker
323+
324+ class Awaitable :
325+ def __await__ (self ):
326+ return (yield from wait ())
327+
328+ async def main ():
329+ await Awaitable ()
330+
331+ task = asyncio .create_task (main (), name = 'await task' )
332+ await asyncio .sleep (0 )
333+ try :
334+ stack = capture_test_stack (fut = task )
335+ finally :
336+ blocker .set_result (None )
337+ await task
338+
339+ self .assertEqual (stack [0 ], [
340+ 'T<await task>' ,
341+ [
342+ 'g wait' ,
343+ 'g __await__' ,
344+ 'a main' ,
345+ ],
346+ []
347+ ])
348+
349+ async def test_stack_types_coroutine (self ):
350+ loop = asyncio .get_running_loop ()
351+ blocker = loop .create_future ()
352+
353+ async def inner ():
354+ await blocker
355+
356+ @types .coroutine
357+ def legacy ():
358+ yield from inner ()
359+
360+ async def main ():
361+ await legacy ()
362+
363+ task = asyncio .create_task (main (), name = 'legacy task' )
364+ await asyncio .sleep (0 )
365+ try :
366+ stack = capture_test_stack (fut = task )
367+ finally :
368+ blocker .set_result (None )
369+ await task
370+
371+ self .assertEqual (stack [0 ], [
372+ 'T<legacy task>' ,
373+ [
374+ 'a inner' ,
375+ 'g legacy' ,
376+ 'a main' ,
377+ ],
378+ []
379+ ])
380+
308381 def test_ag_frame_used_for_async_generator (self ):
309382 # Regression test for gh-148736: the ag_await branch of
310383 # _build_graph_for_future must read ag_frame, not cr_frame.
0 commit comments