[3.6] bpo-32650 Add support for async generators and more test for coroutines in pdb (GH-5403). (#5411)

(cherry picked from commit c7ab581db2)
This commit is contained in:
Andrew Svetlov 2018-01-29 08:51:07 +02:00 committed by GitHub
parent 543ec005a4
commit 3cfb84c657
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 51 additions and 7 deletions

View file

@ -3,10 +3,13 @@
import fnmatch import fnmatch
import sys import sys
import os import os
from inspect import CO_GENERATOR, CO_COROUTINE from inspect import CO_GENERATOR, CO_COROUTINE, CO_ASYNC_GENERATOR
__all__ = ["BdbQuit", "Bdb", "Breakpoint"] __all__ = ["BdbQuit", "Bdb", "Breakpoint"]
GENERATOR_AND_COROUTINE_FLAGS = CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR
class BdbQuit(Exception): class BdbQuit(Exception):
"""Exception to give up completely.""" """Exception to give up completely."""
@ -77,7 +80,7 @@ class Bdb:
# No need to trace this function # No need to trace this function
return # None return # None
# Ignore call events in generator except when stepping. # Ignore call events in generator except when stepping.
if self.stopframe and frame.f_code.co_flags & (CO_GENERATOR | CO_COROUTINE): if self.stopframe and frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS:
return self.trace_dispatch return self.trace_dispatch
self.user_call(frame, arg) self.user_call(frame, arg)
if self.quitting: raise BdbQuit if self.quitting: raise BdbQuit
@ -86,7 +89,7 @@ class Bdb:
def dispatch_return(self, frame, arg): def dispatch_return(self, frame, arg):
if self.stop_here(frame) or frame == self.returnframe: if self.stop_here(frame) or frame == self.returnframe:
# Ignore return events in generator except when stepping. # Ignore return events in generator except when stepping.
if self.stopframe and frame.f_code.co_flags & (CO_GENERATOR | CO_COROUTINE): if self.stopframe and frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS:
return self.trace_dispatch return self.trace_dispatch
try: try:
self.frame_returning = frame self.frame_returning = frame
@ -104,7 +107,7 @@ class Bdb:
# When stepping with next/until/return in a generator frame, skip # When stepping with next/until/return in a generator frame, skip
# the internal StopIteration exception (with no traceback) # the internal StopIteration exception (with no traceback)
# triggered by a subiterator run with the 'yield from' statement. # triggered by a subiterator run with the 'yield from' statement.
if not (frame.f_code.co_flags & (CO_GENERATOR | CO_COROUTINE) if not (frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
and arg[0] is StopIteration and arg[2] is None): and arg[0] is StopIteration and arg[2] is None):
self.user_exception(frame, arg) self.user_exception(frame, arg)
if self.quitting: raise BdbQuit if self.quitting: raise BdbQuit
@ -113,7 +116,7 @@ class Bdb:
# next/until command at the last statement in the generator before the # next/until command at the last statement in the generator before the
# exception. # exception.
elif (self.stopframe and frame is not self.stopframe elif (self.stopframe and frame is not self.stopframe
and self.stopframe.f_code.co_flags & (CO_GENERATOR | CO_COROUTINE) and self.stopframe.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
and arg[0] in (StopIteration, GeneratorExit)): and arg[0] in (StopIteration, GeneratorExit)):
self.user_exception(frame, arg) self.user_exception(frame, arg)
if self.quitting: raise BdbQuit if self.quitting: raise BdbQuit
@ -230,7 +233,7 @@ class Bdb:
def set_return(self, frame): def set_return(self, frame):
"""Stop when returning from the given frame.""" """Stop when returning from the given frame."""
if frame.f_code.co_flags & (CO_GENERATOR | CO_COROUTINE): if frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS:
self._set_stopinfo(frame, None, -1) self._set_stopinfo(frame, None, -1)
else: else:
self._set_stopinfo(frame.f_back, frame) self._set_stopinfo(frame.f_back, frame)

View file

@ -739,7 +739,7 @@ def test_pdb_next_command_for_coroutine():
... await test_coro() ... await test_coro()
>>> def test_function(): >>> def test_function():
... loop = asyncio.get_event_loop() ... loop = asyncio.new_event_loop()
... loop.run_until_complete(test_main()) ... loop.run_until_complete(test_main())
... loop.close() ... loop.close()
... print("finished") ... print("finished")
@ -834,6 +834,47 @@ def test_pdb_return_command_for_generator():
finished finished
""" """
def test_pdb_return_command_for_coroutine():
"""Testing no unwindng stack on yield for coroutines for "return" command
>>> import asyncio
>>> async def test_coro():
... await asyncio.sleep(0)
... await asyncio.sleep(0)
... await asyncio.sleep(0)
>>> async def test_main():
... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
... await test_coro()
>>> def test_function():
... loop = asyncio.new_event_loop()
... loop.run_until_complete(test_main())
... loop.close()
... print("finished")
>>> with PdbTestInput(['step',
... 'step',
... 'next',
... 'continue']):
... test_function()
> <doctest test.test_pdb.test_pdb_return_command_for_coroutine[2]>(3)test_main()
-> await test_coro()
(Pdb) step
--Call--
> <doctest test.test_pdb.test_pdb_return_command_for_coroutine[1]>(1)test_coro()
-> async def test_coro():
(Pdb) step
> <doctest test.test_pdb.test_pdb_return_command_for_coroutine[1]>(2)test_coro()
-> await asyncio.sleep(0)
(Pdb) next
> <doctest test.test_pdb.test_pdb_return_command_for_coroutine[1]>(3)test_coro()
-> await asyncio.sleep(0)
(Pdb) continue
finished
"""
def test_pdb_until_command_for_generator(): def test_pdb_until_command_for_generator():
"""Testing no unwindng stack on yield for generators """Testing no unwindng stack on yield for generators
for "until" command if target breakpoing is not reached for "until" command if target breakpoing is not reached