Support for step in with asyncio coroutines. Fixes #1627

This commit is contained in:
Fabio Zadrozny 2019-12-16 11:58:20 -03:00 committed by Pavel Minaev
parent 161c4b5f28
commit a50e2602de
15 changed files with 4361 additions and 3125 deletions

View file

@ -666,6 +666,7 @@ def internal_step_in_thread(py_db, thread_id, cmd_id, set_additional_thread_info
info = set_additional_thread_info(thread_to_step)
info.pydev_original_step_cmd = cmd_id
info.pydev_step_cmd = cmd_id
info.pydev_step_stop = None
info.pydev_state = STATE_RUN
if py_db.stepping_resumes_all_threads:
@ -695,6 +696,7 @@ class InternalSetNextStatementThread(InternalThreadCommand):
if t:
t.additional_info.pydev_original_step_cmd = self.cmd_id
t.additional_info.pydev_step_cmd = self.cmd_id
t.additional_info.pydev_step_stop = None
t.additional_info.pydev_next_line = int(self.line)
t.additional_info.pydev_func_name = self.func_name
t.additional_info.pydev_state = STATE_RUN

View file

@ -87,6 +87,8 @@ CMD_PROCESS_EVENT = 204
CMD_AUTHENTICATE = 205
CMD_STEP_INTO_COROUTINE = 206
CMD_VERSION = 501
CMD_RETURN = 502
CMD_SET_PROTOCOL = 503
@ -175,6 +177,8 @@ ID_TO_MEANING = {
'205': 'CMD_AUTHENTICATE',
'206': 'CMD_STEP_INTO_COROUTINE',
'501': 'CMD_VERSION',
'502': 'CMD_RETURN',
'503': 'CMD_SET_PROTOCOL',

File diff suppressed because it is too large Load diff

View file

@ -183,6 +183,7 @@ from _pydevd_bundle.pydevd_comm_constants import constant_to_str
# 137 = 137
# 111 = 111
# 128 = 128
# 206 = 206
# 1 = 1
# 2 = 2
# ENDIF
@ -493,6 +494,21 @@ cdef class PyDBFrame:
finally:
f_locals_back = None
def _get_unfiltered_back_frame(self, main_debugger, frame):
f = frame.f_back
while f is not None:
if not main_debugger.is_files_filter_enabled:
return f
else:
if main_debugger.apply_files_filter(f, f.f_code.co_filename, False):
f = f.f_back
else:
return f
return f
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
cpdef trace_dispatch(self, frame, str event, arg):
cdef str filename;
@ -539,13 +555,12 @@ cdef class PyDBFrame:
return None if event == 'call' else NO_FTRACE
plugin_manager = main_debugger.plugin
is_coroutine_or_generator = frame.f_code.co_flags & 0xa0 # 0xa0 == CO_GENERATOR = 0x20 | CO_COROUTINE = 0x80
is_exception_event = event == 'exception'
has_exception_breakpoints = main_debugger.break_on_caught_exceptions or main_debugger.has_plugin_exception_breaks
stop_frame = info.pydev_step_stop
step_cmd = info.pydev_step_cmd
if is_coroutine_or_generator:
if frame.f_code.co_flags & 0xa0: # 0xa0 == CO_GENERATOR = 0x20 | CO_COROUTINE = 0x80
# Dealing with coroutines and generators:
# When in a coroutine we change the perceived event to the debugger because
# a call, StopIteration exception and return are usually just pausing/unpausing it.
@ -553,11 +568,13 @@ cdef class PyDBFrame:
is_line = True
is_call = False
is_return = False
is_exception_event = False
elif event == 'return':
is_line = False
is_call = False
is_return = True
is_exception_event = False
returns_cache_key = (frame_cache_key, 'returns')
return_lines = frame_skips_cache.get(returns_cache_key)
@ -589,10 +606,31 @@ cdef class PyDBFrame:
# in, but we may have to do it anyways to have a step in which doesn't end
# up in asyncio).
if stop_frame is frame:
if step_cmd in (108, 159):
info.pydev_step_stop = frame.f_back
if step_cmd in (108, 159, 107, 144):
f = self._get_unfiltered_back_frame(main_debugger, frame)
if f is not None:
info.pydev_step_cmd = 206
info.pydev_step_stop = f
else:
if step_cmd == 108:
info.pydev_step_cmd = 107
info.pydev_step_stop = None
elif is_exception_event:
elif step_cmd == 159:
info.pydev_step_cmd = 144
info.pydev_step_stop = None
elif step_cmd == 206:
# We're exiting this one, so, mark the new coroutine context.
f = self._get_unfiltered_back_frame(main_debugger, frame)
if f is not None:
info.pydev_step_stop = f
else:
info.pydev_step_cmd = 107
info.pydev_step_stop = None
elif event == 'exception':
breakpoints_for_file = None
if has_exception_breakpoints:
should_stop, frame = self.should_stop_on_exception(frame, event, arg)
if should_stop:
@ -605,7 +643,40 @@ cdef class PyDBFrame:
return self.trace_dispatch
else:
if is_exception_event:
if event == 'line':
is_line = True
is_call = False
is_return = False
is_exception_event = False
elif event == 'return':
is_line = False
is_return = True
is_call = False
is_exception_event = False
# If we are in single step mode and something causes us to exit the current frame, we need to make sure we break
# eventually. Force the step mode to step into and the step stop frame to None.
# I.e.: F6 in the end of a function should stop in the next possible position (instead of forcing the user
# to make a step in or step over at that location).
# Note: this is especially troublesome when we're skipping code with the
# @DontTrace comment.
if stop_frame is frame and is_return and step_cmd in (108, 109, 159, 160):
if step_cmd in (108, 109):
info.pydev_step_cmd = 107
else:
info.pydev_step_cmd = 144
info.pydev_step_stop = None
elif event == 'call':
is_line = False
is_call = True
is_return = False
is_exception_event = False
elif event == 'exception':
is_exception_event = True
breakpoints_for_file = None
if has_exception_breakpoints:
should_stop, frame = self.should_stop_on_exception(frame, event, arg)
if should_stop:
@ -614,37 +685,12 @@ cdef class PyDBFrame:
is_line = False
is_return = False
is_call = False
else:
if event == 'line':
is_line = True
is_call = False
is_return = False
else:
is_line = False
is_return = event == 'return'
is_call = event == 'call'
if not is_line and not is_return and not is_call:
# Unexpected: just keep the same trace func (i.e.: event == 'c_XXX').
return self.trace_dispatch
if is_exception_event:
breakpoints_for_file = None
else:
# If we are in single step mode and something causes us to exit the current frame, we need to make sure we break
# eventually. Force the step mode to step into and the step stop frame to None.
# I.e.: F6 in the end of a function should stop in the next possible position (instead of forcing the user
# to make a step in or step over at that location).
# Note: this is especially troublesome when we're skipping code with the
# @DontTrace comment.
if stop_frame is frame and is_return and step_cmd in (108, 109, 159, 160):
if not is_coroutine_or_generator: # i.e.: not a coroutine
if step_cmd in (108, 109):
info.pydev_step_cmd = 107
else:
info.pydev_step_cmd = 144
info.pydev_step_stop = None
# Unexpected: just keep the same trace func (i.e.: event == 'c_XXX').
return self.trace_dispatch
if not is_exception_event:
breakpoints_for_file = main_debugger.breakpoints.get(filename)
can_skip = False
@ -653,14 +699,28 @@ cdef class PyDBFrame:
# we can skip if:
# - we have no stop marked
# - we should make a step return/step over and we're not in the current frame
can_skip = step_cmd == -1 or (step_cmd in (108, 109, 159, 160) and stop_frame is not frame)
# - we're stepping into a coroutine context and we're not in that context
if step_cmd == -1:
can_skip = True
elif step_cmd in (108, 109, 159, 160) and stop_frame is not frame:
can_skip = True
elif step_cmd == 206:
f = frame
while f is not None:
if f is stop_frame:
break
f = f.f_back
else:
can_skip = True
if can_skip:
if plugin_manager is not None and (
main_debugger.has_plugin_line_breaks or main_debugger.has_plugin_exception_breaks):
can_skip = plugin_manager.can_skip(main_debugger, frame)
if can_skip and main_debugger.show_return_values and info.pydev_step_cmd in (108, 159) and frame.f_back is info.pydev_step_stop:
if can_skip and main_debugger.show_return_values and info.pydev_step_cmd in (108, 159) and frame.f_back is stop_frame:
# trace function for showing return values after step over
can_skip = False
@ -771,7 +831,7 @@ cdef class PyDBFrame:
return self.trace_dispatch
if main_debugger.show_return_values:
if is_return and info.pydev_step_cmd in (108, 159) and frame.f_back == info.pydev_step_stop:
if is_return and info.pydev_step_cmd in (108, 159) and frame.f_back == stop_frame:
self.show_return_values(frame, arg)
elif main_debugger.remove_return_values_flag:
@ -825,7 +885,7 @@ cdef class PyDBFrame:
if should_skip:
stop = False
elif step_cmd in (107, 144):
elif step_cmd in (107, 144, 206):
force_check_project_scope = step_cmd == 144
if is_line:
if force_check_project_scope or main_debugger.is_files_filter_enabled:
@ -841,6 +901,19 @@ cdef class PyDBFrame:
stop = not main_debugger.apply_files_filter(frame.f_back, frame.f_back.f_code.co_filename, force_check_project_scope)
else:
stop = True
else:
stop = False
if stop:
if step_cmd == 206:
# i.e.: Check if we're stepping into the proper context.
f = frame
while f is not None:
if f is stop_frame:
break
f = f.f_back
else:
stop = False
if plugin_manager is not None:
result = plugin_manager.cmd_step_into(main_debugger, frame, event, self._args, stop_info, stop)
@ -937,6 +1010,7 @@ cdef class PyDBFrame:
pydev_log.exception()
info.pydev_original_step_cmd = -1
info.pydev_step_cmd = -1
info.pydev_step_stop = None
except:
return None if is_call else NO_FTRACE

View file

@ -20,6 +20,7 @@ from _pydevd_bundle.pydevd_comm_constants import constant_to_str
# cython_inline_constant: CMD_STEP_CAUGHT_EXCEPTION = 137
# cython_inline_constant: CMD_SET_BREAK = 111
# cython_inline_constant: CMD_SMART_STEP_INTO = 128
# cython_inline_constant: CMD_STEP_INTO_COROUTINE = 206
# cython_inline_constant: STATE_RUN = 1
# cython_inline_constant: STATE_SUSPEND = 2
# ELSE
@ -33,6 +34,7 @@ CMD_STEP_OVER_MY_CODE = 159
CMD_STEP_CAUGHT_EXCEPTION = 137
CMD_SET_BREAK = 111
CMD_SMART_STEP_INTO = 128
CMD_STEP_INTO_COROUTINE = 206
STATE_RUN = 1
STATE_SUSPEND = 2
# ENDIF
@ -343,6 +345,21 @@ class PyDBFrame:
finally:
f_locals_back = None
def _get_unfiltered_back_frame(self, main_debugger, frame):
f = frame.f_back
while f is not None:
if not main_debugger.is_files_filter_enabled:
return f
else:
if main_debugger.apply_files_filter(f, f.f_code.co_filename, False):
f = f.f_back
else:
return f
return f
# IFDEF CYTHON
# cpdef trace_dispatch(self, frame, str event, arg):
# cdef str filename;
@ -389,13 +406,12 @@ class PyDBFrame:
return None if event == 'call' else NO_FTRACE
plugin_manager = main_debugger.plugin
is_coroutine_or_generator = frame.f_code.co_flags & 0xa0 # 0xa0 == CO_GENERATOR = 0x20 | CO_COROUTINE = 0x80
is_exception_event = event == 'exception'
has_exception_breakpoints = main_debugger.break_on_caught_exceptions or main_debugger.has_plugin_exception_breaks
stop_frame = info.pydev_step_stop
step_cmd = info.pydev_step_cmd
if is_coroutine_or_generator:
if frame.f_code.co_flags & 0xa0: # 0xa0 == CO_GENERATOR = 0x20 | CO_COROUTINE = 0x80
# Dealing with coroutines and generators:
# When in a coroutine we change the perceived event to the debugger because
# a call, StopIteration exception and return are usually just pausing/unpausing it.
@ -403,11 +419,13 @@ class PyDBFrame:
is_line = True
is_call = False
is_return = False
is_exception_event = False
elif event == 'return':
is_line = False
is_call = False
is_return = True
is_exception_event = False
returns_cache_key = (frame_cache_key, 'returns')
return_lines = frame_skips_cache.get(returns_cache_key)
@ -439,10 +457,31 @@ class PyDBFrame:
# in, but we may have to do it anyways to have a step in which doesn't end
# up in asyncio).
if stop_frame is frame:
if step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE):
info.pydev_step_stop = frame.f_back
if step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE, CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE):
f = self._get_unfiltered_back_frame(main_debugger, frame)
if f is not None:
info.pydev_step_cmd = CMD_STEP_INTO_COROUTINE
info.pydev_step_stop = f
else:
if step_cmd == CMD_STEP_OVER:
info.pydev_step_cmd = CMD_STEP_INTO
info.pydev_step_stop = None
elif is_exception_event:
elif step_cmd == CMD_STEP_OVER_MY_CODE:
info.pydev_step_cmd = CMD_STEP_INTO_MY_CODE
info.pydev_step_stop = None
elif step_cmd == CMD_STEP_INTO_COROUTINE:
# We're exiting this one, so, mark the new coroutine context.
f = self._get_unfiltered_back_frame(main_debugger, frame)
if f is not None:
info.pydev_step_stop = f
else:
info.pydev_step_cmd = CMD_STEP_INTO
info.pydev_step_stop = None
elif event == 'exception':
breakpoints_for_file = None
if has_exception_breakpoints:
should_stop, frame = self.should_stop_on_exception(frame, event, arg)
if should_stop:
@ -455,7 +494,40 @@ class PyDBFrame:
return self.trace_dispatch
else:
if is_exception_event:
if event == 'line':
is_line = True
is_call = False
is_return = False
is_exception_event = False
elif event == 'return':
is_line = False
is_return = True
is_call = False
is_exception_event = False
# If we are in single step mode and something causes us to exit the current frame, we need to make sure we break
# eventually. Force the step mode to step into and the step stop frame to None.
# I.e.: F6 in the end of a function should stop in the next possible position (instead of forcing the user
# to make a step in or step over at that location).
# Note: this is especially troublesome when we're skipping code with the
# @DontTrace comment.
if stop_frame is frame and is_return and step_cmd in (CMD_STEP_OVER, CMD_STEP_RETURN, CMD_STEP_OVER_MY_CODE, CMD_STEP_RETURN_MY_CODE):
if step_cmd in (CMD_STEP_OVER, CMD_STEP_RETURN):
info.pydev_step_cmd = CMD_STEP_INTO
else:
info.pydev_step_cmd = CMD_STEP_INTO_MY_CODE
info.pydev_step_stop = None
elif event == 'call':
is_line = False
is_call = True
is_return = False
is_exception_event = False
elif event == 'exception':
is_exception_event = True
breakpoints_for_file = None
if has_exception_breakpoints:
should_stop, frame = self.should_stop_on_exception(frame, event, arg)
if should_stop:
@ -464,37 +536,12 @@ class PyDBFrame:
is_line = False
is_return = False
is_call = False
else:
if event == 'line':
is_line = True
is_call = False
is_return = False
else:
is_line = False
is_return = event == 'return'
is_call = event == 'call'
if not is_line and not is_return and not is_call:
# Unexpected: just keep the same trace func (i.e.: event == 'c_XXX').
return self.trace_dispatch
if is_exception_event:
breakpoints_for_file = None
else:
# If we are in single step mode and something causes us to exit the current frame, we need to make sure we break
# eventually. Force the step mode to step into and the step stop frame to None.
# I.e.: F6 in the end of a function should stop in the next possible position (instead of forcing the user
# to make a step in or step over at that location).
# Note: this is especially troublesome when we're skipping code with the
# @DontTrace comment.
if stop_frame is frame and is_return and step_cmd in (CMD_STEP_OVER, CMD_STEP_RETURN, CMD_STEP_OVER_MY_CODE, CMD_STEP_RETURN_MY_CODE):
if not is_coroutine_or_generator: # i.e.: not a coroutine
if step_cmd in (CMD_STEP_OVER, CMD_STEP_RETURN):
info.pydev_step_cmd = CMD_STEP_INTO
else:
info.pydev_step_cmd = CMD_STEP_INTO_MY_CODE
info.pydev_step_stop = None
# Unexpected: just keep the same trace func (i.e.: event == 'c_XXX').
return self.trace_dispatch
if not is_exception_event:
breakpoints_for_file = main_debugger.breakpoints.get(filename)
can_skip = False
@ -503,14 +550,28 @@ class PyDBFrame:
# we can skip if:
# - we have no stop marked
# - we should make a step return/step over and we're not in the current frame
can_skip = step_cmd == -1 or (step_cmd in (CMD_STEP_OVER, CMD_STEP_RETURN, CMD_STEP_OVER_MY_CODE, CMD_STEP_RETURN_MY_CODE) and stop_frame is not frame)
# - we're stepping into a coroutine context and we're not in that context
if step_cmd == -1:
can_skip = True
elif step_cmd in (CMD_STEP_OVER, CMD_STEP_RETURN, CMD_STEP_OVER_MY_CODE, CMD_STEP_RETURN_MY_CODE) and stop_frame is not frame:
can_skip = True
elif step_cmd == CMD_STEP_INTO_COROUTINE:
f = frame
while f is not None:
if f is stop_frame:
break
f = f.f_back
else:
can_skip = True
if can_skip:
if plugin_manager is not None and (
main_debugger.has_plugin_line_breaks or main_debugger.has_plugin_exception_breaks):
can_skip = plugin_manager.can_skip(main_debugger, frame)
if can_skip and main_debugger.show_return_values and info.pydev_step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE) and frame.f_back is info.pydev_step_stop:
if can_skip and main_debugger.show_return_values and info.pydev_step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE) and frame.f_back is stop_frame:
# trace function for showing return values after step over
can_skip = False
@ -621,7 +682,7 @@ class PyDBFrame:
return self.trace_dispatch
if main_debugger.show_return_values:
if is_return and info.pydev_step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE) and frame.f_back == info.pydev_step_stop:
if is_return and info.pydev_step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE) and frame.f_back == stop_frame:
self.show_return_values(frame, arg)
elif main_debugger.remove_return_values_flag:
@ -675,7 +736,7 @@ class PyDBFrame:
if should_skip:
stop = False
elif step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE):
elif step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE, CMD_STEP_INTO_COROUTINE):
force_check_project_scope = step_cmd == CMD_STEP_INTO_MY_CODE
if is_line:
if force_check_project_scope or main_debugger.is_files_filter_enabled:
@ -691,6 +752,19 @@ class PyDBFrame:
stop = not main_debugger.apply_files_filter(frame.f_back, frame.f_back.f_code.co_filename, force_check_project_scope)
else:
stop = True
else:
stop = False
if stop:
if step_cmd == CMD_STEP_INTO_COROUTINE:
# i.e.: Check if we're stepping into the proper context.
f = frame
while f is not None:
if f is stop_frame:
break
f = f.f_back
else:
stop = False
if plugin_manager is not None:
result = plugin_manager.cmd_step_into(main_debugger, frame, event, self._args, stop_info, stop)
@ -787,6 +861,7 @@ class PyDBFrame:
pydev_log.exception()
info.pydev_original_step_cmd = -1
info.pydev_step_cmd = -1
info.pydev_step_stop = None
except:
return None if is_call else NO_FTRACE

View file

@ -14,7 +14,7 @@ from _pydevd_bundle.pydevd_comm_constants import CMD_THREAD_CREATE, CMD_RETURN,
CMD_STEP_RETURN, CMD_STEP_CAUGHT_EXCEPTION, CMD_ADD_EXCEPTION_BREAK, CMD_SET_BREAK, \
CMD_SET_NEXT_STATEMENT, CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION, \
CMD_THREAD_RESUME_SINGLE_NOTIFICATION, CMD_THREAD_KILL, CMD_STOP_ON_START, CMD_INPUT_REQUESTED, \
CMD_EXIT
CMD_EXIT, CMD_STEP_INTO_COROUTINE
from _pydevd_bundle.pydevd_constants import get_thread_id, dict_values, ForkSafeLock
from _pydevd_bundle.pydevd_net_command import NetCommand, NULL_NET_COMMAND
from _pydevd_bundle.pydevd_net_command_factory_xml import NetCommandFactory
@ -269,6 +269,7 @@ class NetCommandFactoryJson(NetCommandFactory):
CMD_STEP_RETURN,
CMD_STEP_INTO_MY_CODE,
CMD_STOP_ON_START,
CMD_STEP_INTO_COROUTINE,
])
_EXCEPTION_REASONS = set([
CMD_STEP_CAUGHT_EXCEPTION,

View file

@ -268,6 +268,7 @@ cdef PyObject * get_bytecode_while_frame_eval(PyFrameObject * frame_obj, int exc
cdef int CMD_STEP_OVER = 108
cdef int CMD_STEP_OVER_MY_CODE = 159
cdef int CMD_STEP_INTO_MY_CODE = 144
cdef int CMD_STEP_INTO_COROUTINE = 206
cdef bint can_skip = True
try:
thread_info = _thread_local_info.thread_info
@ -308,7 +309,7 @@ cdef PyObject * get_bytecode_while_frame_eval(PyFrameObject * frame_obj, int exc
if apply_to_global:
thread_info.thread_trace_func = trace_func
if additional_info.pydev_step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE) or \
if additional_info.pydev_step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE, CMD_STEP_INTO_COROUTINE) or \
main_debugger.break_on_caught_exceptions or \
main_debugger.has_plugin_exception_breaks or \
main_debugger.signature_factory or \

View file

@ -35,7 +35,7 @@ from _pydevd_bundle.pydevd_breakpoints import ExceptionBreakpoint, get_exception
from _pydevd_bundle.pydevd_comm_constants import (CMD_THREAD_SUSPEND, CMD_STEP_INTO, CMD_SET_BREAK,
CMD_STEP_INTO_MY_CODE, CMD_STEP_OVER, CMD_SMART_STEP_INTO, CMD_RUN_TO_LINE,
CMD_SET_NEXT_STATEMENT, CMD_STEP_RETURN, CMD_ADD_EXCEPTION_BREAK, CMD_STEP_RETURN_MY_CODE,
CMD_STEP_OVER_MY_CODE, constant_to_str)
CMD_STEP_OVER_MY_CODE, constant_to_str, CMD_STEP_INTO_COROUTINE)
from _pydevd_bundle.pydevd_constants import (IS_JYTH_LESS25, get_thread_id, get_current_thread_id,
dict_keys, dict_iter_items, DebugInfoHolder, PYTHON_SUSPEND, STATE_SUSPEND, STATE_RUN, get_frame,
clear_cached_thread_id, INTERACTIVE_MODE_AVAILABLE, SHOW_DEBUG_INFO_ENV, IS_PY34_OR_GREATER, IS_PY2, NULL,
@ -1572,6 +1572,7 @@ class PyDB(object):
# If the step command is not specified, set it to step into
# to make sure it'll break as soon as possible.
info.pydev_step_cmd = CMD_STEP_INTO
info.pydev_step_stop = None
# Mark as suspend as the last thing.
info.pydev_state = STATE_SUSPEND
@ -1811,9 +1812,16 @@ class PyDB(object):
# process any stepping instructions
if info.pydev_step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE):
info.pydev_step_stop = None
info.pydev_smart_step_stop = None
self.set_trace_for_frame_and_parents(frame)
if frame.f_code.co_flags & 0x80: # CO_COROUTINE = 0x80
# When in a coroutine we switch to CMD_STEP_INTO_COROUTINE.
info.pydev_step_cmd = CMD_STEP_INTO_COROUTINE
info.pydev_step_stop = frame
info.pydev_smart_step_stop = None
self.set_trace_for_frame_and_parents(frame)
else:
info.pydev_step_stop = None
info.pydev_smart_step_stop = None
self.set_trace_for_frame_and_parents(frame)
elif info.pydev_step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE):
info.pydev_step_stop = frame
@ -1826,6 +1834,7 @@ class PyDB(object):
self.set_trace_for_frame_and_parents(frame)
elif info.pydev_step_cmd == CMD_RUN_TO_LINE or info.pydev_step_cmd == CMD_SET_NEXT_STATEMENT:
info.pydev_step_stop = None
self.set_trace_for_frame_and_parents(frame)
stop = False
response_msg = ""

View file

@ -1003,7 +1003,10 @@ class AbstractWriterThread(threading.Thread):
assert line == frame_line, 'Expected hit to be in line %s, was: %s' % (line, frame_line)
if name is not None:
assert name == hit_name
if not isinstance(name, (list, tuple, set)):
assert name == hit_name
else:
assert hit_name in name
self.log.append('End(1): wait_for_breakpoint_hit: %s' % (msg.original_xml,))

View file

@ -0,0 +1,13 @@
def generator():
yield 1 # stop 1
yield 2 # stop 4
def main():
for i in generator(): # stop 3
print(i) # stop 2
if __name__ == '__main__':
main()
print('TEST SUCEEDED!')

View file

@ -0,0 +1,27 @@
import asyncio
async def main():
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from not_my_code import not_my_coroutine
await not_my_coroutine.call1(call2)
print('finish main')
async def call2():
print('on call2') # break here
if __name__ == '__main__':
if hasattr(asyncio, 'run'):
print('using asyncio.run')
asyncio.run(main())
else:
print('using loop.run_until_complete')
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
print('TEST SUCEEDED!')

View file

@ -0,0 +1,5 @@
async def call1(callback):
await callback()

View file

@ -3672,6 +3672,52 @@ def test_generator_step_return(case_setup, target_filename):
writer.finished_ok = True
@pytest.mark.skipif(not IS_PY36_OR_GREATER, reason='Only CPython 3.6 onwards')
def test_stepin_not_my_code_coroutine(case_setup):
def get_environ(writer):
environ = {'PYDEVD_FILTERS': '{"**/not_my_coroutine.py": true}'}
env = os.environ.copy()
env.update(environ)
return env
with case_setup.test_file('my_code/my_code_coroutine.py', get_environ=get_environ) as writer:
writer.write_set_project_roots([debugger_unittest._get_debugger_test_file('my_code')])
writer.write_add_breakpoint(writer.get_line_index_with_content('break here'))
writer.write_make_initial_run()
hit = writer.wait_for_breakpoint_hit()
writer.write_step_in(hit.thread_id)
hit = writer.wait_for_breakpoint_hit(reason=REASON_STEP_INTO)
assert hit.name == 'main'
writer.write_run_thread(hit.thread_id)
writer.finished_ok = True
def test_generator_step_in(case_setup):
with case_setup.test_file('_debugger_case_generator_step_in.py') as writer:
line = writer.get_line_index_with_content('stop 1')
writer.write_add_breakpoint(line)
writer.write_make_initial_run()
hit = writer.wait_for_breakpoint_hit()
for i in range(2, 5):
writer.write_step_in(hit.thread_id)
kwargs = {}
if not IS_JYTHON:
kwargs['line'] = writer.get_line_index_with_content('stop %s' % (i,))
hit = writer.wait_for_breakpoint_hit(
reason=REASON_STEP_INTO,
file='_debugger_case_generator_step_in.py',
**kwargs
)
writer.write_run_thread(hit.thread_id)
writer.finished_ok = True
@pytest.mark.parametrize(
'target_filename',
[
@ -3718,13 +3764,45 @@ def test_asyncio_step_over_end_of_function(case_setup, target_filename):
writer.write_step_over(hit.thread_id)
hit = writer.wait_for_breakpoint_hit(
reason=REASON_STEP_OVER,
file=target_filename,
line=writer.get_line_index_with_content('step main')
name=('sleep', 'wait_task_rescheduled'),
)
writer.write_run_thread(hit.thread_id)
writer.finished_ok = True
@pytest.mark.parametrize(
'target_filename',
[
'_debugger_case_asyncio.py',
'_debugger_case_trio.py',
]
)
@pytest.mark.skipif(not IS_CPYTHON or not IS_PY36_OR_GREATER, reason='Only CPython 3.6 onwards')
def test_asyncio_step_in(case_setup, target_filename):
with case_setup.test_file(target_filename) as writer:
line = writer.get_line_index_with_content('break count 1')
writer.write_add_breakpoint(line)
writer.write_make_initial_run()
hit = writer.wait_for_breakpoint_hit()
writer.write_step_return(hit.thread_id)
hit = writer.wait_for_breakpoint_hit(
reason=REASON_STEP_RETURN,
file=target_filename,
line=writer.get_line_index_with_content('break main')
)
writer.write_step_in(hit.thread_id)
hit = writer.wait_for_breakpoint_hit(
reason=REASON_STEP_INTO,
name=('sleep', 'wait_task_rescheduled'),
)
writer.write_run_thread(hit.thread_id)
writer.finished_ok = True
@pytest.mark.parametrize(
'target_filename',
[

View file

@ -21,10 +21,10 @@ class TestCase(unittest.TestCase):
check = '''C:\\bin\\python.exe -u -c connect(\\"127.0.0.1\\")'''
debug_command = (
'import sys; '
'sys.path.append(r\'%s\'); '
"import pydevd; pydevd.PydevdCustomization.DEFAULT_PROTOCOL='quoted-line';"
'sys.path.insert(0, r\'%s\'); '
"import pydevd; pydevd.PydevdCustomization.DEFAULT_PROTOCOL='quoted-line'; "
"pydevd.settrace(host='127.0.0.1', port=0, suspend=False, "
'trace_only_current_thread=False, patch_multiprocessing=True); '
'trace_only_current_thread=False, patch_multiprocessing=True, access_token=None, client_access_token=None); '
''
"from pydevd import SetupHolder; "
"SetupHolder.setup = %s; "
@ -50,8 +50,8 @@ class TestCase(unittest.TestCase):
SetupHolder.setup = {'client': '127.0.0.1', 'port': '0', 'protocol-quoted-line': True}
check = ['C:\\bin\\python.exe', '-u', '-c', 'connect("127.0.0.1")']
debug_command = (
"import sys; sys.path.append(r\'%s\'); import pydevd; pydevd.PydevdCustomization.DEFAULT_PROTOCOL='quoted-line';"
'pydevd.settrace(host=\'127.0.0.1\', port=0, suspend=False, trace_only_current_thread=False, patch_multiprocessing=True); '
"import sys; sys.path.insert(0, r\'%s\'); import pydevd; pydevd.PydevdCustomization.DEFAULT_PROTOCOL='quoted-line'; "
'pydevd.settrace(host=\'127.0.0.1\', port=0, suspend=False, trace_only_current_thread=False, patch_multiprocessing=True, access_token=None, client_access_token=None); '
''
"from pydevd import SetupHolder; "
"SetupHolder.setup = %s; "