mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
Step in/step over support for IPython. Fixes #869
This commit is contained in:
parent
a294092d9c
commit
6b276e339c
16 changed files with 6358 additions and 4801 deletions
|
|
@ -53,6 +53,8 @@ class PyDBAdditionalThreadInfo(object):
|
|||
# of the last request for a given thread and pydev_smart_parent_offset/pydev_smart_child_offset relies on it).
|
||||
'pydev_smart_step_into_variants',
|
||||
'target_id_to_smart_step_into_variant',
|
||||
|
||||
'pydev_use_scoped_step_frame',
|
||||
]
|
||||
# ENDIF
|
||||
|
||||
|
|
@ -90,6 +92,18 @@ class PyDBAdditionalThreadInfo(object):
|
|||
self.pydev_smart_step_into_variants = ()
|
||||
self.target_id_to_smart_step_into_variant = {}
|
||||
|
||||
# Flag to indicate ipython use-case where each line will be executed as a call/line/return
|
||||
# in a new new frame but in practice we want to consider each new frame as if it was all
|
||||
# part of the same frame.
|
||||
#
|
||||
# In practice this means that a step over shouldn't revert to a step in and we need some
|
||||
# special logic to know when we should stop in a step over as we need to consider 2
|
||||
# different frames as being equal if they're logically the continuation of a frame
|
||||
# being executed by ipython line by line.
|
||||
#
|
||||
# See: https://github.com/microsoft/debugpy/issues/869#issuecomment-1132141003
|
||||
self.pydev_use_scoped_step_frame = False
|
||||
|
||||
def get_topmost_frame(self, thread):
|
||||
'''
|
||||
Gets the topmost frame for the given thread. Note that it may be None
|
||||
|
|
|
|||
|
|
@ -255,11 +255,6 @@ INTERACTIVE_MODE_AVAILABLE = sys.platform in ('darwin', 'win32') or os.getenv('D
|
|||
# If not specified, uses default heuristic to determine if it should be loaded.
|
||||
USE_CYTHON_FLAG = os.getenv('PYDEVD_USE_CYTHON')
|
||||
|
||||
# Use to disable loading the lib to set tracing to all threads (default is using heuristics based on where we're running).
|
||||
LOAD_NATIVE_LIB_FLAG = os.getenv('PYDEVD_LOAD_NATIVE_LIB', '').lower()
|
||||
|
||||
LOG_TIME = os.getenv('PYDEVD_LOG_TIME', 'true').lower() in ENV_TRUE_LOWER_VALUES
|
||||
|
||||
if USE_CYTHON_FLAG is not None:
|
||||
USE_CYTHON_FLAG = USE_CYTHON_FLAG.lower()
|
||||
if USE_CYTHON_FLAG not in ENV_TRUE_LOWER_VALUES and USE_CYTHON_FLAG not in ENV_FALSE_LOWER_VALUES:
|
||||
|
|
@ -270,6 +265,26 @@ else:
|
|||
if not CYTHON_SUPPORTED:
|
||||
USE_CYTHON_FLAG = 'no'
|
||||
|
||||
# If true in env, forces frame eval to be used (raises error if not available).
|
||||
# If false in env, disables it.
|
||||
# If not specified, uses default heuristic to determine if it should be loaded.
|
||||
PYDEVD_USE_FRAME_EVAL = os.getenv('PYDEVD_USE_FRAME_EVAL', '').lower()
|
||||
|
||||
PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING = is_true_in_env('PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING')
|
||||
|
||||
# If specified in PYDEVD_IPYTHON_CONTEXT it must be a string with the basename
|
||||
# and then the name of 2 methods in which the evaluate is done.
|
||||
PYDEVD_IPYTHON_CONTEXT = ('interactiveshell.py', 'run_code', 'run_ast_nodes')
|
||||
_ipython_ctx = os.getenv('PYDEVD_IPYTHON_CONTEXT')
|
||||
if _ipython_ctx:
|
||||
PYDEVD_IPYTHON_CONTEXT = tuple(x.strip() for x in _ipython_ctx.split(','))
|
||||
assert len(PYDEVD_IPYTHON_CONTEXT) == 3, 'Invalid PYDEVD_IPYTHON_CONTEXT: %s' % (_ipython_ctx,)
|
||||
|
||||
# Use to disable loading the lib to set tracing to all threads (default is using heuristics based on where we're running).
|
||||
LOAD_NATIVE_LIB_FLAG = os.getenv('PYDEVD_LOAD_NATIVE_LIB', '').lower()
|
||||
|
||||
LOG_TIME = os.getenv('PYDEVD_LOG_TIME', 'true').lower() in ENV_TRUE_LOWER_VALUES
|
||||
|
||||
SHOW_COMPILE_CYTHON_COMMAND_LINE = is_true_in_env('PYDEVD_SHOW_COMPILE_CYTHON_COMMAND_LINE')
|
||||
|
||||
LOAD_VALUES_ASYNC = is_true_in_env('PYDEVD_LOAD_VALUES_ASYNC')
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -24,3 +24,4 @@ cdef class PyDBAdditionalThreadInfo:
|
|||
cdef public int pydev_smart_child_offset
|
||||
cdef public tuple pydev_smart_step_into_variants
|
||||
cdef public dict target_id_to_smart_step_into_variant
|
||||
cdef public bint pydev_use_scoped_step_frame
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ cdef class PyDBAdditionalThreadInfo:
|
|||
# # of the last request for a given thread and pydev_smart_parent_offset/pydev_smart_child_offset relies on it).
|
||||
# 'pydev_smart_step_into_variants',
|
||||
# 'target_id_to_smart_step_into_variant',
|
||||
#
|
||||
# 'pydev_use_scoped_step_frame',
|
||||
# ]
|
||||
# ENDIF
|
||||
|
||||
|
|
@ -96,6 +98,18 @@ cdef class PyDBAdditionalThreadInfo:
|
|||
self.pydev_smart_step_into_variants = ()
|
||||
self.target_id_to_smart_step_into_variant = {}
|
||||
|
||||
# Flag to indicate ipython use-case where each line will be executed as a call/line/return
|
||||
# in a new new frame but in practice we want to consider each new frame as if it was all
|
||||
# part of the same frame.
|
||||
#
|
||||
# In practice this means that a step over shouldn't revert to a step in and we need some
|
||||
# special logic to know when we should stop in a step over as we need to consider 2
|
||||
# different frames as being equal if they're logically the continuation of a frame
|
||||
# being executed by ipython line by line.
|
||||
#
|
||||
# See: https://github.com/microsoft/debugpy/issues/869#issuecomment-1132141003
|
||||
self.pydev_use_scoped_step_frame = False
|
||||
|
||||
def get_topmost_frame(self, thread):
|
||||
'''
|
||||
Gets the topmost frame for the given thread. Note that it may be None
|
||||
|
|
@ -150,7 +164,7 @@ import re
|
|||
from _pydev_bundle import pydev_log
|
||||
from _pydevd_bundle import pydevd_dont_trace
|
||||
from _pydevd_bundle.pydevd_constants import (RETURN_VALUES_DICT, NO_FTRACE,
|
||||
EXCEPTION_TYPE_HANDLED, EXCEPTION_TYPE_USER_UNHANDLED)
|
||||
EXCEPTION_TYPE_HANDLED, EXCEPTION_TYPE_USER_UNHANDLED, PYDEVD_IPYTHON_CONTEXT)
|
||||
from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame, just_raised, remove_exception_from_frame, ignore_exception_trace
|
||||
from _pydevd_bundle.pydevd_utils import get_clsname_for_code
|
||||
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame
|
||||
|
|
@ -657,6 +671,31 @@ cdef class PyDBFrame:
|
|||
|
||||
return f
|
||||
|
||||
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
||||
cdef _is_same_frame(self, target_frame, current_frame):
|
||||
cdef PyDBAdditionalThreadInfo info;
|
||||
# ELSE
|
||||
# def _is_same_frame(self, target_frame, current_frame):
|
||||
# ENDIF
|
||||
if target_frame is current_frame:
|
||||
return True
|
||||
|
||||
info = self._args[2]
|
||||
if info.pydev_use_scoped_step_frame:
|
||||
# If using scoped step we don't check the target, we just need to check
|
||||
# if the current matches the same heuristic where the target was defined.
|
||||
if target_frame is not None and current_frame is not None:
|
||||
if target_frame.f_code.co_filename == current_frame.f_code.co_filename:
|
||||
# The co_name may be different (it may include the line number), but
|
||||
# the filename must still be the same.
|
||||
f = current_frame.f_back
|
||||
if f is not None and f.f_code.co_name == PYDEVD_IPYTHON_CONTEXT[1]:
|
||||
f = f.f_back
|
||||
if f is not None and f.f_code.co_name == PYDEVD_IPYTHON_CONTEXT[2]:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
||||
cpdef trace_dispatch(self, frame, str event, arg):
|
||||
cdef tuple abs_path_canonical_path_and_base;
|
||||
|
|
@ -772,7 +811,13 @@ cdef class PyDBFrame:
|
|||
# Solving this may not be trivial as we'd need to put a scope in the step
|
||||
# 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:
|
||||
#
|
||||
# Note2: we don't revert to a step in if we're doing scoped stepping
|
||||
# (because on scoped stepping we're always receiving a call/line/return
|
||||
# event for each line in ipython, so, we can't revert to step in on return
|
||||
# as the return shouldn't mean that we've actually completed executing a
|
||||
# frame in this case).
|
||||
if stop_frame is frame and not info.pydev_use_scoped_step_frame:
|
||||
if step_cmd in (108, 159, 107, 144):
|
||||
f = self._get_unfiltered_back_frame(main_debugger, frame)
|
||||
if f is not None:
|
||||
|
|
@ -809,7 +854,7 @@ cdef class PyDBFrame:
|
|||
# event == 'call' or event == 'c_XXX'
|
||||
return self.trace_dispatch
|
||||
|
||||
else:
|
||||
else: # Not coroutine nor generator
|
||||
if event == 'line':
|
||||
is_line = True
|
||||
is_call = False
|
||||
|
|
@ -828,7 +873,12 @@ cdef class PyDBFrame:
|
|||
# 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, 128):
|
||||
if (
|
||||
stop_frame is frame and
|
||||
not info.pydev_use_scoped_step_frame and is_return and
|
||||
step_cmd in (108, 109, 159, 160, 128)
|
||||
):
|
||||
|
||||
if step_cmd in (108, 109, 128):
|
||||
info.pydev_step_cmd = 107
|
||||
else:
|
||||
|
|
@ -876,7 +926,7 @@ cdef class PyDBFrame:
|
|||
if step_cmd == -1:
|
||||
can_skip = True
|
||||
|
||||
elif step_cmd in (108, 109, 159, 160) and stop_frame is not frame:
|
||||
elif step_cmd in (108, 109, 159, 160) and not self._is_same_frame(stop_frame, frame):
|
||||
can_skip = True
|
||||
|
||||
elif step_cmd == 128 and (
|
||||
|
|
@ -896,7 +946,7 @@ cdef class PyDBFrame:
|
|||
elif step_cmd == 206:
|
||||
f = frame
|
||||
while f is not None:
|
||||
if f is stop_frame:
|
||||
if self._is_same_frame(stop_frame, f):
|
||||
break
|
||||
f = f.f_back
|
||||
else:
|
||||
|
|
@ -907,7 +957,7 @@ cdef class PyDBFrame:
|
|||
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 stop_frame:
|
||||
if can_skip and main_debugger.show_return_values and info.pydev_step_cmd in (108, 159) and self._is_same_frame(stop_frame, frame.f_back):
|
||||
# trace function for showing return values after step over
|
||||
can_skip = False
|
||||
|
||||
|
|
@ -1006,7 +1056,7 @@ cdef class PyDBFrame:
|
|||
breakpoint = breakpoints_for_file[line]
|
||||
new_frame = frame
|
||||
stop = True
|
||||
if step_cmd in (108, 159) and (stop_frame is frame and is_line):
|
||||
if step_cmd in (108, 159) and (self._is_same_frame(stop_frame, frame) and is_line):
|
||||
stop = False # we don't stop on breakpoint if we have to stop by step-over (it will be processed later)
|
||||
elif plugin_manager is not None and main_debugger.has_plugin_line_breaks:
|
||||
result = plugin_manager.get_breakpoint(main_debugger, self, frame, event, self._args)
|
||||
|
|
@ -1050,8 +1100,8 @@ cdef class PyDBFrame:
|
|||
|
||||
if main_debugger.show_return_values:
|
||||
if is_return and (
|
||||
(info.pydev_step_cmd in (108, 159, 128) and (frame.f_back is stop_frame)) or
|
||||
(info.pydev_step_cmd in (109, 160) and (frame is stop_frame)) or
|
||||
(info.pydev_step_cmd in (108, 159, 128) and (self._is_same_frame(stop_frame, frame.f_back))) or
|
||||
(info.pydev_step_cmd in (109, 160) and (self._is_same_frame(stop_frame, frame))) or
|
||||
(info.pydev_step_cmd in (107, 206)) or
|
||||
(
|
||||
info.pydev_step_cmd == 144
|
||||
|
|
@ -1115,12 +1165,36 @@ cdef class PyDBFrame:
|
|||
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:
|
||||
stop = not main_debugger.apply_files_filter(frame, frame.f_code.co_filename, force_check_project_scope)
|
||||
if not info.pydev_use_scoped_step_frame:
|
||||
if force_check_project_scope or main_debugger.is_files_filter_enabled:
|
||||
stop = not main_debugger.apply_files_filter(frame, frame.f_code.co_filename, force_check_project_scope)
|
||||
else:
|
||||
stop = True
|
||||
else:
|
||||
stop = True
|
||||
# We can only stop inside the ipython call.
|
||||
filename = frame.f_code.co_filename
|
||||
if filename.endswith('.pyc'):
|
||||
filename = filename[:-1]
|
||||
|
||||
elif is_return and frame.f_back is not None:
|
||||
if not filename.endswith(PYDEVD_IPYTHON_CONTEXT[0]):
|
||||
f = frame.f_back
|
||||
while f is not None:
|
||||
if f.f_code.co_name == PYDEVD_IPYTHON_CONTEXT[1]:
|
||||
f2 = f.f_back
|
||||
if f2 is not None and f2.f_code.co_name == PYDEVD_IPYTHON_CONTEXT[2]:
|
||||
pydev_log.debug('Stop inside ipython call')
|
||||
stop = True
|
||||
break
|
||||
f = f.f_back
|
||||
|
||||
del f
|
||||
|
||||
if not stop:
|
||||
# In scoped mode if step in didn't work in this context it won't work
|
||||
# afterwards anyways.
|
||||
return None if is_call else NO_FTRACE
|
||||
|
||||
elif is_return and frame.f_back is not None and not info.pydev_use_scoped_step_frame:
|
||||
if main_debugger.get_file_type(frame.f_back) == main_debugger.PYDEV_FILE:
|
||||
stop = False
|
||||
else:
|
||||
|
|
@ -1141,7 +1215,7 @@ cdef class PyDBFrame:
|
|||
# i.e.: Check if we're stepping into the proper context.
|
||||
f = frame
|
||||
while f is not None:
|
||||
if f is stop_frame:
|
||||
if self._is_same_frame(stop_frame, f):
|
||||
break
|
||||
f = f.f_back
|
||||
else:
|
||||
|
|
@ -1156,7 +1230,7 @@ cdef class PyDBFrame:
|
|||
# Note: when dealing with a step over my code it's the same as a step over (the
|
||||
# difference is that when we return from a frame in one we go to regular step
|
||||
# into and in the other we go to a step into my code).
|
||||
stop = stop_frame is frame and is_line
|
||||
stop = self._is_same_frame(stop_frame, frame) and is_line
|
||||
# Note: don't stop on a return for step over, only for line events
|
||||
# i.e.: don't stop in: (stop_frame is frame.f_back and is_return) as we'd stop twice in that line.
|
||||
|
||||
|
|
@ -1168,11 +1242,11 @@ cdef class PyDBFrame:
|
|||
elif step_cmd == 128:
|
||||
stop = False
|
||||
back = frame.f_back
|
||||
if stop_frame is frame and is_return:
|
||||
if self._is_same_frame(stop_frame, frame) and is_return:
|
||||
# We're exiting the smart step into initial frame (so, we probably didn't find our target).
|
||||
stop = True
|
||||
|
||||
elif stop_frame is back and is_line:
|
||||
elif self._is_same_frame(stop_frame, back) and is_line:
|
||||
if info.pydev_smart_child_offset != -1:
|
||||
# i.e.: in this case, we're not interested in the pause in the parent, rather
|
||||
# we're interested in the pause in the child (when the parent is at the proper place).
|
||||
|
|
@ -1203,7 +1277,7 @@ cdef class PyDBFrame:
|
|||
# not be the case next time either, so, disable tracing for this frame.
|
||||
return None if is_call else NO_FTRACE
|
||||
|
||||
elif back is not None and stop_frame is back.f_back and is_line:
|
||||
elif back is not None and self._is_same_frame(stop_frame, back.f_back) and is_line:
|
||||
# Ok, we have to track 2 stops at this point, the parent and the child offset.
|
||||
# This happens when handling a step into which targets a function inside a list comprehension
|
||||
# or generator (in which case an intermediary frame is created due to an internal function call).
|
||||
|
|
@ -1237,7 +1311,7 @@ cdef class PyDBFrame:
|
|||
return None if is_call else NO_FTRACE
|
||||
|
||||
elif step_cmd in (109, 160):
|
||||
stop = is_return and stop_frame is frame
|
||||
stop = is_return and self._is_same_frame(stop_frame, frame)
|
||||
|
||||
else:
|
||||
stop = False
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import re
|
|||
from _pydev_bundle import pydev_log
|
||||
from _pydevd_bundle import pydevd_dont_trace
|
||||
from _pydevd_bundle.pydevd_constants import (RETURN_VALUES_DICT, NO_FTRACE,
|
||||
EXCEPTION_TYPE_HANDLED, EXCEPTION_TYPE_USER_UNHANDLED)
|
||||
EXCEPTION_TYPE_HANDLED, EXCEPTION_TYPE_USER_UNHANDLED, PYDEVD_IPYTHON_CONTEXT)
|
||||
from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame, just_raised, remove_exception_from_frame, ignore_exception_trace
|
||||
from _pydevd_bundle.pydevd_utils import get_clsname_for_code
|
||||
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame
|
||||
|
|
@ -524,6 +524,31 @@ class PyDBFrame:
|
|||
|
||||
return f
|
||||
|
||||
# IFDEF CYTHON
|
||||
# cdef _is_same_frame(self, target_frame, current_frame):
|
||||
# cdef PyDBAdditionalThreadInfo info;
|
||||
# ELSE
|
||||
def _is_same_frame(self, target_frame, current_frame):
|
||||
# ENDIF
|
||||
if target_frame is current_frame:
|
||||
return True
|
||||
|
||||
info = self._args[2]
|
||||
if info.pydev_use_scoped_step_frame:
|
||||
# If using scoped step we don't check the target, we just need to check
|
||||
# if the current matches the same heuristic where the target was defined.
|
||||
if target_frame is not None and current_frame is not None:
|
||||
if target_frame.f_code.co_filename == current_frame.f_code.co_filename:
|
||||
# The co_name may be different (it may include the line number), but
|
||||
# the filename must still be the same.
|
||||
f = current_frame.f_back
|
||||
if f is not None and f.f_code.co_name == PYDEVD_IPYTHON_CONTEXT[1]:
|
||||
f = f.f_back
|
||||
if f is not None and f.f_code.co_name == PYDEVD_IPYTHON_CONTEXT[2]:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
# IFDEF CYTHON
|
||||
# cpdef trace_dispatch(self, frame, str event, arg):
|
||||
# cdef tuple abs_path_canonical_path_and_base;
|
||||
|
|
@ -639,7 +664,13 @@ class PyDBFrame:
|
|||
# Solving this may not be trivial as we'd need to put a scope in the step
|
||||
# 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:
|
||||
#
|
||||
# Note2: we don't revert to a step in if we're doing scoped stepping
|
||||
# (because on scoped stepping we're always receiving a call/line/return
|
||||
# event for each line in ipython, so, we can't revert to step in on return
|
||||
# as the return shouldn't mean that we've actually completed executing a
|
||||
# frame in this case).
|
||||
if stop_frame is frame and not info.pydev_use_scoped_step_frame:
|
||||
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:
|
||||
|
|
@ -676,7 +707,7 @@ class PyDBFrame:
|
|||
# event == 'call' or event == 'c_XXX'
|
||||
return self.trace_dispatch
|
||||
|
||||
else:
|
||||
else: # Not coroutine nor generator
|
||||
if event == 'line':
|
||||
is_line = True
|
||||
is_call = False
|
||||
|
|
@ -695,7 +726,12 @@ class PyDBFrame:
|
|||
# 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, CMD_SMART_STEP_INTO):
|
||||
if (
|
||||
stop_frame is frame and
|
||||
not info.pydev_use_scoped_step_frame and is_return and
|
||||
step_cmd in (CMD_STEP_OVER, CMD_STEP_RETURN, CMD_STEP_OVER_MY_CODE, CMD_STEP_RETURN_MY_CODE, CMD_SMART_STEP_INTO)
|
||||
):
|
||||
|
||||
if step_cmd in (CMD_STEP_OVER, CMD_STEP_RETURN, CMD_SMART_STEP_INTO):
|
||||
info.pydev_step_cmd = CMD_STEP_INTO
|
||||
else:
|
||||
|
|
@ -743,7 +779,7 @@ class PyDBFrame:
|
|||
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:
|
||||
elif step_cmd in (CMD_STEP_OVER, CMD_STEP_RETURN, CMD_STEP_OVER_MY_CODE, CMD_STEP_RETURN_MY_CODE) and not self._is_same_frame(stop_frame, frame):
|
||||
can_skip = True
|
||||
|
||||
elif step_cmd == CMD_SMART_STEP_INTO and (
|
||||
|
|
@ -763,7 +799,7 @@ class PyDBFrame:
|
|||
elif step_cmd == CMD_STEP_INTO_COROUTINE:
|
||||
f = frame
|
||||
while f is not None:
|
||||
if f is stop_frame:
|
||||
if self._is_same_frame(stop_frame, f):
|
||||
break
|
||||
f = f.f_back
|
||||
else:
|
||||
|
|
@ -774,7 +810,7 @@ class PyDBFrame:
|
|||
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 stop_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 self._is_same_frame(stop_frame, frame.f_back):
|
||||
# trace function for showing return values after step over
|
||||
can_skip = False
|
||||
|
||||
|
|
@ -873,7 +909,7 @@ class PyDBFrame:
|
|||
breakpoint = breakpoints_for_file[line]
|
||||
new_frame = frame
|
||||
stop = True
|
||||
if step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE) and (stop_frame is frame and is_line):
|
||||
if step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE) and (self._is_same_frame(stop_frame, frame) and is_line):
|
||||
stop = False # we don't stop on breakpoint if we have to stop by step-over (it will be processed later)
|
||||
elif plugin_manager is not None and main_debugger.has_plugin_line_breaks:
|
||||
result = plugin_manager.get_breakpoint(main_debugger, self, frame, event, self._args)
|
||||
|
|
@ -917,8 +953,8 @@ class PyDBFrame:
|
|||
|
||||
if main_debugger.show_return_values:
|
||||
if is_return and (
|
||||
(info.pydev_step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE, CMD_SMART_STEP_INTO) and (frame.f_back is stop_frame)) or
|
||||
(info.pydev_step_cmd in (CMD_STEP_RETURN, CMD_STEP_RETURN_MY_CODE) and (frame is stop_frame)) or
|
||||
(info.pydev_step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE, CMD_SMART_STEP_INTO) and (self._is_same_frame(stop_frame, frame.f_back))) or
|
||||
(info.pydev_step_cmd in (CMD_STEP_RETURN, CMD_STEP_RETURN_MY_CODE) and (self._is_same_frame(stop_frame, frame))) or
|
||||
(info.pydev_step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_COROUTINE)) or
|
||||
(
|
||||
info.pydev_step_cmd == CMD_STEP_INTO_MY_CODE
|
||||
|
|
@ -982,12 +1018,36 @@ class PyDBFrame:
|
|||
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:
|
||||
stop = not main_debugger.apply_files_filter(frame, frame.f_code.co_filename, force_check_project_scope)
|
||||
if not info.pydev_use_scoped_step_frame:
|
||||
if force_check_project_scope or main_debugger.is_files_filter_enabled:
|
||||
stop = not main_debugger.apply_files_filter(frame, frame.f_code.co_filename, force_check_project_scope)
|
||||
else:
|
||||
stop = True
|
||||
else:
|
||||
stop = True
|
||||
# We can only stop inside the ipython call.
|
||||
filename = frame.f_code.co_filename
|
||||
if filename.endswith('.pyc'):
|
||||
filename = filename[:-1]
|
||||
|
||||
elif is_return and frame.f_back is not None:
|
||||
if not filename.endswith(PYDEVD_IPYTHON_CONTEXT[0]):
|
||||
f = frame.f_back
|
||||
while f is not None:
|
||||
if f.f_code.co_name == PYDEVD_IPYTHON_CONTEXT[1]:
|
||||
f2 = f.f_back
|
||||
if f2 is not None and f2.f_code.co_name == PYDEVD_IPYTHON_CONTEXT[2]:
|
||||
pydev_log.debug('Stop inside ipython call')
|
||||
stop = True
|
||||
break
|
||||
f = f.f_back
|
||||
|
||||
del f
|
||||
|
||||
if not stop:
|
||||
# In scoped mode if step in didn't work in this context it won't work
|
||||
# afterwards anyways.
|
||||
return None if is_call else NO_FTRACE
|
||||
|
||||
elif is_return and frame.f_back is not None and not info.pydev_use_scoped_step_frame:
|
||||
if main_debugger.get_file_type(frame.f_back) == main_debugger.PYDEV_FILE:
|
||||
stop = False
|
||||
else:
|
||||
|
|
@ -1008,7 +1068,7 @@ class PyDBFrame:
|
|||
# i.e.: Check if we're stepping into the proper context.
|
||||
f = frame
|
||||
while f is not None:
|
||||
if f is stop_frame:
|
||||
if self._is_same_frame(stop_frame, f):
|
||||
break
|
||||
f = f.f_back
|
||||
else:
|
||||
|
|
@ -1023,7 +1083,7 @@ class PyDBFrame:
|
|||
# Note: when dealing with a step over my code it's the same as a step over (the
|
||||
# difference is that when we return from a frame in one we go to regular step
|
||||
# into and in the other we go to a step into my code).
|
||||
stop = stop_frame is frame and is_line
|
||||
stop = self._is_same_frame(stop_frame, frame) and is_line
|
||||
# Note: don't stop on a return for step over, only for line events
|
||||
# i.e.: don't stop in: (stop_frame is frame.f_back and is_return) as we'd stop twice in that line.
|
||||
|
||||
|
|
@ -1035,11 +1095,11 @@ class PyDBFrame:
|
|||
elif step_cmd == CMD_SMART_STEP_INTO:
|
||||
stop = False
|
||||
back = frame.f_back
|
||||
if stop_frame is frame and is_return:
|
||||
if self._is_same_frame(stop_frame, frame) and is_return:
|
||||
# We're exiting the smart step into initial frame (so, we probably didn't find our target).
|
||||
stop = True
|
||||
|
||||
elif stop_frame is back and is_line:
|
||||
elif self._is_same_frame(stop_frame, back) and is_line:
|
||||
if info.pydev_smart_child_offset != -1:
|
||||
# i.e.: in this case, we're not interested in the pause in the parent, rather
|
||||
# we're interested in the pause in the child (when the parent is at the proper place).
|
||||
|
|
@ -1070,7 +1130,7 @@ class PyDBFrame:
|
|||
# not be the case next time either, so, disable tracing for this frame.
|
||||
return None if is_call else NO_FTRACE
|
||||
|
||||
elif back is not None and stop_frame is back.f_back and is_line:
|
||||
elif back is not None and self._is_same_frame(stop_frame, back.f_back) and is_line:
|
||||
# Ok, we have to track 2 stops at this point, the parent and the child offset.
|
||||
# This happens when handling a step into which targets a function inside a list comprehension
|
||||
# or generator (in which case an intermediary frame is created due to an internal function call).
|
||||
|
|
@ -1104,7 +1164,7 @@ class PyDBFrame:
|
|||
return None if is_call else NO_FTRACE
|
||||
|
||||
elif step_cmd in (CMD_STEP_RETURN, CMD_STEP_RETURN_MY_CODE):
|
||||
stop = is_return and stop_frame is frame
|
||||
stop = is_return and self._is_same_frame(stop_frame, frame)
|
||||
|
||||
else:
|
||||
stop = False
|
||||
|
|
|
|||
|
|
@ -3,34 +3,42 @@ import os
|
|||
from _pydev_bundle import pydev_log
|
||||
from _pydevd_bundle.pydevd_trace_dispatch import USING_CYTHON
|
||||
from _pydevd_bundle.pydevd_constants import USE_CYTHON_FLAG, ENV_FALSE_LOWER_VALUES, \
|
||||
ENV_TRUE_LOWER_VALUES, IS_PY36_OR_GREATER, IS_PY38_OR_GREATER, SUPPORT_GEVENT, IS_PYTHON_STACKLESS
|
||||
ENV_TRUE_LOWER_VALUES, IS_PY36_OR_GREATER, IS_PY38_OR_GREATER, SUPPORT_GEVENT, IS_PYTHON_STACKLESS, \
|
||||
PYDEVD_USE_FRAME_EVAL, PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING
|
||||
|
||||
frame_eval_func = None
|
||||
stop_frame_eval = None
|
||||
dummy_trace_dispatch = None
|
||||
clear_thread_local_info = None
|
||||
|
||||
USING_FRAME_EVAL = False
|
||||
|
||||
# "NO" means we should not use frame evaluation, 'YES' we should use it (and fail if not there) and unspecified uses if possible.
|
||||
use_frame_eval = os.environ.get('PYDEVD_USE_FRAME_EVAL', '').lower()
|
||||
if (
|
||||
PYDEVD_USE_FRAME_EVAL in ENV_FALSE_LOWER_VALUES or
|
||||
USE_CYTHON_FLAG in ENV_FALSE_LOWER_VALUES or
|
||||
not USING_CYTHON or
|
||||
|
||||
if use_frame_eval in ENV_FALSE_LOWER_VALUES or USE_CYTHON_FLAG in ENV_FALSE_LOWER_VALUES or not USING_CYTHON:
|
||||
pass
|
||||
# Frame eval mode does not work with ipython compatible debugging (this happens because the
|
||||
# way that frame eval works is run untraced and set tracing only for the frames with
|
||||
# breakpoints, but ipython compatible debugging creates separate frames for what's logically
|
||||
# the same frame).
|
||||
PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING
|
||||
):
|
||||
USING_FRAME_EVAL = False
|
||||
|
||||
elif SUPPORT_GEVENT or (IS_PYTHON_STACKLESS and not IS_PY38_OR_GREATER):
|
||||
pass
|
||||
USING_FRAME_EVAL = False
|
||||
# i.e gevent and frame eval mode don't get along very well.
|
||||
# https://github.com/microsoft/debugpy/issues/189
|
||||
# Same problem with Stackless.
|
||||
# https://github.com/stackless-dev/stackless/issues/240
|
||||
|
||||
elif use_frame_eval in ENV_TRUE_LOWER_VALUES:
|
||||
elif PYDEVD_USE_FRAME_EVAL in ENV_TRUE_LOWER_VALUES:
|
||||
# Fail if unable to use
|
||||
from _pydevd_frame_eval.pydevd_frame_eval_cython_wrapper import frame_eval_func, stop_frame_eval, dummy_trace_dispatch, clear_thread_local_info
|
||||
USING_FRAME_EVAL = True
|
||||
|
||||
else:
|
||||
USING_FRAME_EVAL = False
|
||||
# Try to use if possible
|
||||
if IS_PY36_OR_GREATER:
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Generated by Cython 0.29.30 */
|
||||
/* Generated by Cython 0.29.32 */
|
||||
|
||||
/* BEGIN: Cython Metadata
|
||||
{
|
||||
|
|
@ -32,8 +32,8 @@ END: Cython Metadata */
|
|||
#elif PY_VERSION_HEX < 0x02060000 || (0x03000000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x03030000)
|
||||
#error Cython requires Python 2.6+ or Python 3.3+.
|
||||
#else
|
||||
#define CYTHON_ABI "0_29_30"
|
||||
#define CYTHON_HEX_VERSION 0x001D1EF0
|
||||
#define CYTHON_ABI "0_29_32"
|
||||
#define CYTHON_HEX_VERSION 0x001D20F0
|
||||
#define CYTHON_FUTURE_DIVISION 0
|
||||
#include <stddef.h>
|
||||
#ifndef offsetof
|
||||
|
|
@ -72,6 +72,7 @@ END: Cython Metadata */
|
|||
#define CYTHON_COMPILING_IN_PYPY 1
|
||||
#define CYTHON_COMPILING_IN_PYSTON 0
|
||||
#define CYTHON_COMPILING_IN_CPYTHON 0
|
||||
#define CYTHON_COMPILING_IN_NOGIL 0
|
||||
#undef CYTHON_USE_TYPE_SLOTS
|
||||
#define CYTHON_USE_TYPE_SLOTS 0
|
||||
#undef CYTHON_USE_PYTYPE_LOOKUP
|
||||
|
|
@ -115,6 +116,7 @@ END: Cython Metadata */
|
|||
#define CYTHON_COMPILING_IN_PYPY 0
|
||||
#define CYTHON_COMPILING_IN_PYSTON 1
|
||||
#define CYTHON_COMPILING_IN_CPYTHON 0
|
||||
#define CYTHON_COMPILING_IN_NOGIL 0
|
||||
#ifndef CYTHON_USE_TYPE_SLOTS
|
||||
#define CYTHON_USE_TYPE_SLOTS 1
|
||||
#endif
|
||||
|
|
@ -155,10 +157,56 @@ END: Cython Metadata */
|
|||
#ifndef CYTHON_UPDATE_DESCRIPTOR_DOC
|
||||
#define CYTHON_UPDATE_DESCRIPTOR_DOC 0
|
||||
#endif
|
||||
#elif defined(PY_NOGIL)
|
||||
#define CYTHON_COMPILING_IN_PYPY 0
|
||||
#define CYTHON_COMPILING_IN_PYSTON 0
|
||||
#define CYTHON_COMPILING_IN_CPYTHON 0
|
||||
#define CYTHON_COMPILING_IN_NOGIL 1
|
||||
#ifndef CYTHON_USE_TYPE_SLOTS
|
||||
#define CYTHON_USE_TYPE_SLOTS 1
|
||||
#endif
|
||||
#undef CYTHON_USE_PYTYPE_LOOKUP
|
||||
#define CYTHON_USE_PYTYPE_LOOKUP 0
|
||||
#ifndef CYTHON_USE_ASYNC_SLOTS
|
||||
#define CYTHON_USE_ASYNC_SLOTS 1
|
||||
#endif
|
||||
#undef CYTHON_USE_PYLIST_INTERNALS
|
||||
#define CYTHON_USE_PYLIST_INTERNALS 0
|
||||
#ifndef CYTHON_USE_UNICODE_INTERNALS
|
||||
#define CYTHON_USE_UNICODE_INTERNALS 1
|
||||
#endif
|
||||
#undef CYTHON_USE_UNICODE_WRITER
|
||||
#define CYTHON_USE_UNICODE_WRITER 0
|
||||
#undef CYTHON_USE_PYLONG_INTERNALS
|
||||
#define CYTHON_USE_PYLONG_INTERNALS 0
|
||||
#ifndef CYTHON_AVOID_BORROWED_REFS
|
||||
#define CYTHON_AVOID_BORROWED_REFS 0
|
||||
#endif
|
||||
#ifndef CYTHON_ASSUME_SAFE_MACROS
|
||||
#define CYTHON_ASSUME_SAFE_MACROS 1
|
||||
#endif
|
||||
#ifndef CYTHON_UNPACK_METHODS
|
||||
#define CYTHON_UNPACK_METHODS 1
|
||||
#endif
|
||||
#undef CYTHON_FAST_THREAD_STATE
|
||||
#define CYTHON_FAST_THREAD_STATE 0
|
||||
#undef CYTHON_FAST_PYCALL
|
||||
#define CYTHON_FAST_PYCALL 0
|
||||
#ifndef CYTHON_PEP489_MULTI_PHASE_INIT
|
||||
#define CYTHON_PEP489_MULTI_PHASE_INIT 1
|
||||
#endif
|
||||
#ifndef CYTHON_USE_TP_FINALIZE
|
||||
#define CYTHON_USE_TP_FINALIZE 1
|
||||
#endif
|
||||
#undef CYTHON_USE_DICT_VERSIONS
|
||||
#define CYTHON_USE_DICT_VERSIONS 0
|
||||
#undef CYTHON_USE_EXC_INFO_STACK
|
||||
#define CYTHON_USE_EXC_INFO_STACK 0
|
||||
#else
|
||||
#define CYTHON_COMPILING_IN_PYPY 0
|
||||
#define CYTHON_COMPILING_IN_PYSTON 0
|
||||
#define CYTHON_COMPILING_IN_CPYTHON 1
|
||||
#define CYTHON_COMPILING_IN_NOGIL 0
|
||||
#ifndef CYTHON_USE_TYPE_SLOTS
|
||||
#define CYTHON_USE_TYPE_SLOTS 1
|
||||
#endif
|
||||
|
|
@ -993,6 +1041,7 @@ struct __pyx_obj_14_pydevd_bundle_13pydevd_cython_PyDBAdditionalThreadInfo {
|
|||
int pydev_smart_child_offset;
|
||||
PyObject *pydev_smart_step_into_variants;
|
||||
PyObject *target_id_to_smart_step_into_variant;
|
||||
int pydev_use_scoped_step_frame;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -133,12 +133,12 @@ conda deactivate
|
|||
|
||||
cd /D x:\pydev\plugins\org.python.pydev.core\pysrc
|
||||
set PYTHONPATH=x:\pydev\plugins\org.python.pydev.core\pysrc
|
||||
C:\bin\Python38-32\python build_tools\build.py
|
||||
python build_tools\build.py
|
||||
|
||||
${ptvsd_folder}
|
||||
cd /D X:\ptvsd_workspace\ptvsd\src\debugpy\_vendored\pydevd
|
||||
set PYTHONPATH=X:\ptvsd_workspace\ptvsd\src\debugpy\_vendored\pydevd
|
||||
C:\bin\Python38-32\python build_tools\build.py
|
||||
python build_tools\build.py
|
||||
|
||||
|
||||
cd ~/Desktop/Pydev/plugins/org.python.pydev.core/pysrc
|
||||
|
|
|
|||
|
|
@ -55,7 +55,8 @@ from _pydevd_bundle.pydevd_constants import (get_thread_id, get_current_thread_i
|
|||
DebugInfoHolder, PYTHON_SUSPEND, STATE_SUSPEND, STATE_RUN, get_frame,
|
||||
clear_cached_thread_id, INTERACTIVE_MODE_AVAILABLE, SHOW_DEBUG_INFO_ENV, NULL,
|
||||
NO_FTRACE, IS_IRONPYTHON, JSON_PROTOCOL, IS_CPYTHON, HTTP_JSON_PROTOCOL, USE_CUSTOM_SYS_CURRENT_FRAMES_MAP, call_only_once,
|
||||
ForkSafeLock, IGNORE_BASENAMES_STARTING_WITH, EXCEPTION_TYPE_UNHANDLED, SUPPORT_GEVENT)
|
||||
ForkSafeLock, IGNORE_BASENAMES_STARTING_WITH, EXCEPTION_TYPE_UNHANDLED, SUPPORT_GEVENT,
|
||||
PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING, PYDEVD_IPYTHON_CONTEXT)
|
||||
from _pydevd_bundle.pydevd_defaults import PydevdCustomization # Note: import alias used on pydev_monkey.
|
||||
from _pydevd_bundle.pydevd_custom_frames import CustomFramesContainer, custom_frames_container_init
|
||||
from _pydevd_bundle.pydevd_dont_trace_files import DONT_TRACE, PYDEV_FILE, LIB_FILE, DONT_TRACE_DIRS
|
||||
|
|
@ -181,6 +182,9 @@ _CACHE_FILE_TYPE = {}
|
|||
pydev_log.debug('Using GEVENT_SUPPORT: %s', pydevd_constants.SUPPORT_GEVENT)
|
||||
pydev_log.debug('Using GEVENT_SHOW_PAUSED_GREENLETS: %s', pydevd_constants.GEVENT_SHOW_PAUSED_GREENLETS)
|
||||
pydev_log.debug('pydevd __file__: %s', os.path.abspath(__file__))
|
||||
pydev_log.debug('Using PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING: %s', pydevd_constants.PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING)
|
||||
if pydevd_constants.PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING:
|
||||
pydev_log.debug('PYDEVD_IPYTHON_CONTEXT: %s', pydevd_constants.PYDEVD_IPYTHON_CONTEXT)
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
|
|
@ -2170,6 +2174,23 @@ class PyDB(object):
|
|||
info.pydev_step_cmd = -1
|
||||
info.pydev_state = STATE_RUN
|
||||
|
||||
if PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING:
|
||||
info.pydev_use_scoped_step_frame = False
|
||||
if info.pydev_step_cmd in (
|
||||
CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE,
|
||||
CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE
|
||||
):
|
||||
# i.e.: We're stepping: check if the stepping should be scoped (i.e.: in ipython
|
||||
# each line is executed separately in a new frame, in which case we need to consider
|
||||
# the next line as if it was still in the same frame).
|
||||
f = frame.f_back
|
||||
if f and f.f_code.co_name == PYDEVD_IPYTHON_CONTEXT[1]:
|
||||
f = f.f_back
|
||||
if f and f.f_code.co_name == PYDEVD_IPYTHON_CONTEXT[2]:
|
||||
info.pydev_use_scoped_step_frame = True
|
||||
pydev_log.info('Using (ipython) scoped stepping.')
|
||||
del f
|
||||
|
||||
del frame
|
||||
cmd = self.cmd_factory.make_thread_run_message(get_current_thread_id(thread), info.pydev_step_cmd)
|
||||
self.writer.add_command(cmd)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import asyncio
|
||||
import sys
|
||||
|
||||
|
||||
async def gen():
|
||||
f = sys._getframe()
|
||||
for i in range(10):
|
||||
await asyncio.sleep(.01)
|
||||
assert f is sys._getframe()
|
||||
yield i
|
||||
|
||||
|
||||
async def run():
|
||||
async for p in gen():
|
||||
print(p)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
loop = asyncio.get_event_loop_policy().get_event_loop()
|
||||
loop.run_until_complete(run())
|
||||
print('TEST SUCEEDED')
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
from ast import Module
|
||||
from ast import stmt
|
||||
from typing import List as ListType
|
||||
|
||||
import ast
|
||||
import inspect
|
||||
from ast import PyCF_ONLY_AST, PyCF_ALLOW_TOP_LEVEL_AWAIT
|
||||
import types
|
||||
import os
|
||||
from contextlib import contextmanager
|
||||
PyCF_DONT_IMPLY_DEDENT = 0x200 # Matches pythonrun.h
|
||||
|
||||
_assign_nodes = (ast.AugAssign, ast.AnnAssign, ast.Assign)
|
||||
_single_targets_nodes = (ast.AugAssign, ast.AnnAssign)
|
||||
|
||||
user_module = types.ModuleType("__main__",
|
||||
doc="Automatically created module for IPython interactive environment")
|
||||
|
||||
stored = []
|
||||
|
||||
|
||||
def tracefunc(frame, event, arg):
|
||||
if '_debugger_case_scoped_stepping_target' not in frame.f_code.co_filename:
|
||||
return None
|
||||
stored.append(frame)
|
||||
print('\n---')
|
||||
print(event, id(frame), os.path.basename(frame.f_code.co_filename), frame.f_lineno, arg, frame.f_code.co_name)
|
||||
assert frame.f_back.f_code.co_name == 'run_code'
|
||||
return None
|
||||
|
||||
|
||||
@contextmanager
|
||||
def tracing_info():
|
||||
import sys
|
||||
sys.settrace(tracefunc)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
sys.settrace(None)
|
||||
|
||||
|
||||
# Note: this is roughly what IPython itself does at:
|
||||
# https://github.com/ipython/ipython/blob/master/IPython/core/interactiveshell.py
|
||||
class Runner:
|
||||
|
||||
async def run_ast_nodes(
|
||||
self,
|
||||
nodelist: ListType[stmt],
|
||||
cell_name: str,
|
||||
interactivity="last_expr",
|
||||
compiler=compile,
|
||||
):
|
||||
if not nodelist:
|
||||
return
|
||||
|
||||
if interactivity == 'last_expr_or_assign':
|
||||
if isinstance(nodelist[-1], _assign_nodes):
|
||||
asg = nodelist[-1]
|
||||
if isinstance(asg, ast.Assign) and len(asg.targets) == 1:
|
||||
target = asg.targets[0]
|
||||
elif isinstance(asg, _single_targets_nodes):
|
||||
target = asg.target
|
||||
else:
|
||||
target = None
|
||||
if isinstance(target, ast.Name):
|
||||
nnode = ast.Expr(ast.Name(target.id, ast.Load()))
|
||||
ast.fix_missing_locations(nnode)
|
||||
nodelist.append(nnode)
|
||||
interactivity = 'last_expr'
|
||||
|
||||
_async = False
|
||||
if interactivity == 'last_expr':
|
||||
if isinstance(nodelist[-1], ast.Expr):
|
||||
interactivity = "last"
|
||||
else:
|
||||
interactivity = "none"
|
||||
|
||||
if interactivity == 'none':
|
||||
to_run_exec, to_run_interactive = nodelist, []
|
||||
elif interactivity == 'last':
|
||||
to_run_exec, to_run_interactive = nodelist[:-1], nodelist[-1:]
|
||||
elif interactivity == 'all':
|
||||
to_run_exec, to_run_interactive = [], nodelist
|
||||
else:
|
||||
raise ValueError("Interactivity was %r" % interactivity)
|
||||
|
||||
def compare(code):
|
||||
is_async = inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE
|
||||
return is_async
|
||||
|
||||
# Refactor that to just change the mod constructor.
|
||||
to_run = []
|
||||
for node in to_run_exec:
|
||||
to_run.append((node, "exec"))
|
||||
|
||||
for node in to_run_interactive:
|
||||
to_run.append((node, "single"))
|
||||
|
||||
for node, mode in to_run:
|
||||
if mode == "exec":
|
||||
mod = Module([node], [])
|
||||
elif mode == "single":
|
||||
mod = ast.Interactive([node])
|
||||
code = compiler(mod, cell_name, mode, PyCF_DONT_IMPLY_DEDENT |
|
||||
PyCF_ALLOW_TOP_LEVEL_AWAIT)
|
||||
asy = compare(code)
|
||||
if await self.run_code(code, async_=asy):
|
||||
return True
|
||||
|
||||
async def run_code(self, code_obj, *, async_=False):
|
||||
if async_:
|
||||
await eval(code_obj, self.user_global_ns, self.user_ns)
|
||||
else:
|
||||
exec(code_obj, self.user_global_ns, self.user_ns)
|
||||
|
||||
@property
|
||||
def user_global_ns(self):
|
||||
return user_module.__dict__
|
||||
|
||||
@property
|
||||
def user_ns(self):
|
||||
return user_module.__dict__
|
||||
|
||||
|
||||
async def main():
|
||||
SCOPED_STEPPING_TARGET = os.getenv('SCOPED_STEPPING_TARGET', '_debugger_case_scoped_stepping_target.py')
|
||||
filename = os.path.join(os.path.dirname(__file__), SCOPED_STEPPING_TARGET)
|
||||
assert os.path.exists(filename), '%s does not exist.' % (filename,)
|
||||
with open(filename, 'r') as stream:
|
||||
source = stream.read()
|
||||
code_ast = compile(
|
||||
source,
|
||||
filename,
|
||||
'exec',
|
||||
PyCF_DONT_IMPLY_DEDENT | PyCF_ONLY_AST | PyCF_ALLOW_TOP_LEVEL_AWAIT,
|
||||
1)
|
||||
|
||||
runner = Runner()
|
||||
await runner.run_ast_nodes(code_ast.body, filename)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import asyncio
|
||||
asyncio.run(main())
|
||||
print('TEST SUCEEDED!')
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
a = 1
|
||||
b = 2
|
||||
c = 3
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
a = 1
|
||||
|
||||
|
||||
def method():
|
||||
b = 2
|
||||
|
||||
|
||||
method() # break here
|
||||
c = 3
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# Note that await in the top-level isn't valid in general, but we compile
|
||||
# it specifically accepting it, so, that's ok.
|
||||
import asyncio
|
||||
|
||||
await asyncio.sleep(.01)
|
||||
a = 1 # Break here
|
||||
await asyncio.sleep(.01)
|
||||
b = 2
|
||||
await asyncio.sleep(.01)
|
||||
|
|
@ -6134,6 +6134,89 @@ print('TEST SUCEEDED')
|
|||
writer.finished_ok = True
|
||||
|
||||
|
||||
_TOP_LEVEL_AWAIT_AVAILABLE = False
|
||||
try:
|
||||
from ast import PyCF_ONLY_AST, PyCF_ALLOW_TOP_LEVEL_AWAIT
|
||||
_TOP_LEVEL_AWAIT_AVAILABLE = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skipif(not _TOP_LEVEL_AWAIT_AVAILABLE, reason="Top-level await required.")
|
||||
def test_ipython_stepping_basic(case_setup):
|
||||
|
||||
def get_environ(self):
|
||||
env = os.environ.copy()
|
||||
|
||||
# Test setup
|
||||
env["SCOPED_STEPPING_TARGET"] = '_debugger_case_scoped_stepping_target.py'
|
||||
|
||||
# Actually setup the debugging
|
||||
env["PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING"] = "1"
|
||||
env["PYDEVD_IPYTHON_CONTEXT"] = '_debugger_case_scoped_stepping.py, run_code, run_ast_nodes'
|
||||
return env
|
||||
|
||||
with case_setup.test_file('_debugger_case_scoped_stepping.py', get_environ=get_environ) as writer:
|
||||
json_facade = JsonFacade(writer)
|
||||
json_facade.write_launch(justMyCode=False)
|
||||
|
||||
target_file = debugger_unittest._get_debugger_test_file('_debugger_case_scoped_stepping_target.py')
|
||||
break_line = writer.get_line_index_with_content('a = 1', filename=target_file)
|
||||
assert break_line == 1
|
||||
json_facade.write_set_breakpoints(break_line, filename=target_file)
|
||||
json_facade.write_make_initial_run()
|
||||
json_hit = json_facade.wait_for_thread_stopped(line=break_line, file='_debugger_case_scoped_stepping_target.py')
|
||||
|
||||
json_facade.write_step_next(json_hit.thread_id)
|
||||
json_hit = json_facade.wait_for_thread_stopped('step', line=break_line + 1, file='_debugger_case_scoped_stepping_target.py')
|
||||
|
||||
json_facade.write_step_next(json_hit.thread_id)
|
||||
json_hit = json_facade.wait_for_thread_stopped('step', line=break_line + 2, file='_debugger_case_scoped_stepping_target.py')
|
||||
|
||||
json_facade.write_continue()
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
@pytest.mark.skipif(not _TOP_LEVEL_AWAIT_AVAILABLE, reason="Top-level await required.")
|
||||
def test_ipython_stepping_step_in(case_setup):
|
||||
|
||||
def get_environ(self):
|
||||
env = os.environ.copy()
|
||||
|
||||
# Test setup
|
||||
env["SCOPED_STEPPING_TARGET"] = '_debugger_case_scoped_stepping_target2.py'
|
||||
|
||||
# Actually setup the debugging
|
||||
env["PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING"] = "1"
|
||||
env["PYDEVD_IPYTHON_CONTEXT"] = '_debugger_case_scoped_stepping.py, run_code, run_ast_nodes'
|
||||
return env
|
||||
|
||||
with case_setup.test_file('_debugger_case_scoped_stepping.py', get_environ=get_environ) as writer:
|
||||
json_facade = JsonFacade(writer)
|
||||
json_facade.write_launch(justMyCode=False)
|
||||
|
||||
target_file = debugger_unittest._get_debugger_test_file('_debugger_case_scoped_stepping_target2.py')
|
||||
break_line = writer.get_line_index_with_content('break here', filename=target_file)
|
||||
json_facade.write_set_breakpoints(break_line, filename=target_file)
|
||||
json_facade.write_make_initial_run()
|
||||
json_hit = json_facade.wait_for_thread_stopped(line=break_line, file='_debugger_case_scoped_stepping_target2.py')
|
||||
|
||||
json_facade.write_step_in(json_hit.thread_id)
|
||||
stop_at = writer.get_line_index_with_content('b = 2', filename=target_file)
|
||||
json_hit = json_facade.wait_for_thread_stopped('step', line=stop_at, file='_debugger_case_scoped_stepping_target2.py')
|
||||
|
||||
json_facade.write_step_in(json_hit.thread_id)
|
||||
stop_at = writer.get_line_index_with_content('method() # break here', filename=target_file)
|
||||
json_hit = json_facade.wait_for_thread_stopped('step', line=stop_at, file='_debugger_case_scoped_stepping_target2.py')
|
||||
|
||||
json_facade.write_step_in(json_hit.thread_id)
|
||||
stop_at = writer.get_line_index_with_content('c = 3', filename=target_file)
|
||||
json_hit = json_facade.wait_for_thread_stopped('step', line=stop_at, file='_debugger_case_scoped_stepping_target2.py')
|
||||
|
||||
json_facade.write_continue()
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main(['-k', 'test_replace_process', '-s'])
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue