mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
Support for step in with asyncio coroutines. Fixes #1627
This commit is contained in:
parent
161c4b5f28
commit
a50e2602de
15 changed files with 4361 additions and 3125 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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 = ""
|
||||
|
|
|
|||
|
|
@ -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,))
|
||||
|
||||
|
|
|
|||
|
|
@ -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!')
|
||||
|
|
@ -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!')
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
|
||||
async def call1(callback):
|
||||
await callback()
|
||||
|
||||
|
|
@ -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',
|
||||
[
|
||||
|
|
|
|||
|
|
@ -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; "
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue