mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
parent
4877164466
commit
944fdd9ab4
25 changed files with 784 additions and 346 deletions
|
|
@ -16,7 +16,7 @@ else
|
|||
fi
|
||||
|
||||
if [ "$PYDEVD_PYTHON_VERSION" = "2.7" ]; then
|
||||
conda install --yes pyqt=4
|
||||
conda install --yes pyqt=4 gevent
|
||||
pip install "django>=1.7,<1.8"
|
||||
pip install pathlib2
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ if [ "$PYDEVD_PYTHON_VERSION" = "3.5" ]; then
|
|||
fi
|
||||
|
||||
if [ "$PYDEVD_PYTHON_VERSION" = "3.6" ]; then
|
||||
conda install --yes pyqt=5
|
||||
conda install --yes pyqt=5 gevent
|
||||
pip install "django>=2.1,<2.2"
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -518,21 +518,6 @@ class BaseInterpreterInterface:
|
|||
# it to run in the main thread.
|
||||
self.exec_queue.put(do_change_variable)
|
||||
|
||||
def _findFrame(self, thread_id, frame_id):
|
||||
'''
|
||||
Used to show console with variables connection.
|
||||
Always return a frame where the locals map to our internal namespace.
|
||||
'''
|
||||
VIRTUAL_FRAME_ID = "1" # matches PyStackFrameConsole.java
|
||||
VIRTUAL_CONSOLE_ID = "console_main" # matches PyThreadConsole.java
|
||||
if thread_id == VIRTUAL_CONSOLE_ID and frame_id == VIRTUAL_FRAME_ID:
|
||||
f = FakeFrame()
|
||||
f.f_globals = {} # As globals=locals here, let's simply let it empty (and save a bit of network traffic).
|
||||
f.f_locals = self.get_namespace()
|
||||
return f
|
||||
else:
|
||||
return self.orig_find_frame(thread_id, frame_id)
|
||||
|
||||
def connectToDebugger(self, debuggerPort, debugger_options=None):
|
||||
'''
|
||||
Used to show console with variables connection.
|
||||
|
|
@ -569,10 +554,15 @@ class BaseInterpreterInterface:
|
|||
from _pydev_bundle import pydev_localhost
|
||||
set_thread_id(threading.currentThread(), "console_main")
|
||||
|
||||
self.orig_find_frame = pydevd_vars.find_frame
|
||||
pydevd_vars.find_frame = self._findFrame
|
||||
VIRTUAL_FRAME_ID = "1" # matches PyStackFrameConsole.java
|
||||
VIRTUAL_CONSOLE_ID = "console_main" # matches PyThreadConsole.java
|
||||
f = FakeFrame()
|
||||
f.f_back = None
|
||||
f.f_globals = {} # As globals=locals here, let's simply let it empty (and save a bit of network traffic).
|
||||
f.f_locals = self.get_namespace()
|
||||
|
||||
self.debugger = pydevd.PyDB()
|
||||
self.debugger.add_fake_frame(thread_id=VIRTUAL_CONSOLE_ID, frame_id=VIRTUAL_FRAME_ID, frame=f)
|
||||
try:
|
||||
pydevd.apply_debugger_options(debugger_options)
|
||||
self.debugger.connect(pydev_localhost.get_localhost(), debuggerPort)
|
||||
|
|
|
|||
|
|
@ -567,7 +567,7 @@ class InternalGetThreadStack(InternalThreadCommand):
|
|||
frame = additional_info.get_topmost_frame(t)
|
||||
try:
|
||||
self._cmd = py_db.cmd_factory.make_get_thread_stack_message(
|
||||
self.seq, self.thread_id, frame, must_be_suspended=not timed_out)
|
||||
py_db, self.seq, self.thread_id, frame, must_be_suspended=not timed_out)
|
||||
finally:
|
||||
frame = None
|
||||
t = None
|
||||
|
|
@ -644,7 +644,7 @@ class InternalGetVariable(InternalThreadCommand):
|
|||
try:
|
||||
xml = StringIO.StringIO()
|
||||
xml.write("<xml>")
|
||||
_typeName, val_dict = pydevd_vars.resolve_compound_variable_fields(self.thread_id, self.frame_id, self.scope, self.attributes)
|
||||
_typeName, val_dict = pydevd_vars.resolve_compound_variable_fields(dbg, self.thread_id, self.frame_id, self.scope, self.attributes)
|
||||
if val_dict is None:
|
||||
val_dict = {}
|
||||
|
||||
|
|
@ -686,7 +686,7 @@ class InternalGetArray(InternalThreadCommand):
|
|||
|
||||
def do_it(self, dbg):
|
||||
try:
|
||||
frame = pydevd_vars.find_frame(self.thread_id, self.frame_id)
|
||||
frame = dbg.find_frame(self.thread_id, self.frame_id)
|
||||
var = pydevd_vars.eval_in_context(self.name, frame.f_globals, frame.f_locals)
|
||||
xml = pydevd_vars.table_like_struct_to_xml(var, self.name, self.roffset, self.coffset, self.rows, self.cols, self.format)
|
||||
cmd = dbg.cmd_factory.make_get_array_message(self.sequence, xml)
|
||||
|
|
@ -724,7 +724,7 @@ class InternalChangeVariable(InternalThreadCommand):
|
|||
def internal_get_frame(dbg, seq, thread_id, frame_id):
|
||||
''' Converts request into python variable '''
|
||||
try:
|
||||
frame = pydevd_vars.find_frame(thread_id, frame_id)
|
||||
frame = dbg.find_frame(thread_id, frame_id)
|
||||
if frame is not None:
|
||||
hidden_ns = pydevconsole.get_ipython_hidden_vars()
|
||||
xml = "<xml>"
|
||||
|
|
@ -746,7 +746,7 @@ def internal_get_frame(dbg, seq, thread_id, frame_id):
|
|||
def internal_get_next_statement_targets(dbg, seq, thread_id, frame_id):
|
||||
''' gets the valid line numbers for use with set next statement '''
|
||||
try:
|
||||
frame = pydevd_vars.find_frame(thread_id, frame_id)
|
||||
frame = dbg.find_frame(thread_id, frame_id)
|
||||
if frame is not None:
|
||||
code = frame.f_code
|
||||
xml = "<xml>"
|
||||
|
|
@ -777,7 +777,7 @@ def internal_get_next_statement_targets(dbg, seq, thread_id, frame_id):
|
|||
def internal_evaluate_expression(dbg, seq, thread_id, frame_id, expression, is_exec, trim_if_too_big, attr_to_set_result):
|
||||
''' gets the value of a variable '''
|
||||
try:
|
||||
result = pydevd_vars.evaluate_expression(thread_id, frame_id, expression, is_exec)
|
||||
result = pydevd_vars.evaluate_expression(dbg, thread_id, frame_id, expression, is_exec)
|
||||
if attr_to_set_result != "":
|
||||
pydevd_vars.change_attr_expression(thread_id, frame_id, attr_to_set_result, expression, dbg, result)
|
||||
xml = "<xml>"
|
||||
|
|
@ -807,7 +807,7 @@ def internal_get_completions(dbg, seq, thread_id, frame_id, act_tok, line=-1, co
|
|||
act_tok += u'.'
|
||||
qualifier = token_and_qualifier[1]
|
||||
|
||||
frame = pydevd_vars.find_frame(thread_id, frame_id)
|
||||
frame = dbg.find_frame(thread_id, frame_id)
|
||||
if frame is not None:
|
||||
if IS_PY2:
|
||||
if not isinstance(act_tok, bytes):
|
||||
|
|
@ -842,7 +842,7 @@ def internal_get_description(dbg, seq, thread_id, frame_id, expression):
|
|||
''' Fetch the variable description stub from the debug console
|
||||
'''
|
||||
try:
|
||||
frame = pydevd_vars.find_frame(thread_id, frame_id)
|
||||
frame = dbg.find_frame(thread_id, frame_id)
|
||||
description = pydevd_console.get_description(frame, thread_id, frame_id, expression)
|
||||
description = pydevd_xml.make_valid_xml_value(quote(description, '/>_= \t'))
|
||||
description_xml = '<xml><var name="" type="" value="%s"/></xml>' % description
|
||||
|
|
@ -951,7 +951,7 @@ class InternalEvaluateConsoleExpression(InternalThreadCommand):
|
|||
</xml>
|
||||
'''
|
||||
try:
|
||||
frame = pydevd_vars.find_frame(self.thread_id, self.frame_id)
|
||||
frame = dbg.find_frame(self.thread_id, self.frame_id)
|
||||
if frame is not None:
|
||||
console_message = pydevd_console.execute_console_command(
|
||||
frame, self.thread_id, self.frame_id, self.line, self.buffer_output)
|
||||
|
|
@ -987,7 +987,7 @@ class InternalRunCustomOperation(InternalThreadCommand):
|
|||
|
||||
def do_it(self, dbg):
|
||||
try:
|
||||
res = pydevd_vars.custom_operation(self.thread_id, self.frame_id, self.scope, self.attrs,
|
||||
res = pydevd_vars.custom_operation(dbg, self.thread_id, self.frame_id, self.scope, self.attrs,
|
||||
self.style, self.code_or_file, self.fnname)
|
||||
resEncoded = quote_plus(res)
|
||||
cmd = dbg.cmd_factory.make_custom_operation_message(self.sequence, resEncoded)
|
||||
|
|
@ -1012,7 +1012,7 @@ class InternalConsoleGetCompletions(InternalThreadCommand):
|
|||
''' Get completions and write back to the client
|
||||
'''
|
||||
try:
|
||||
frame = pydevd_vars.find_frame(self.thread_id, self.frame_id)
|
||||
frame = dbg.find_frame(self.thread_id, self.frame_id)
|
||||
completions_xml = pydevd_console.get_completions(frame, self.act_tok)
|
||||
cmd = dbg.cmd_factory.make_send_console_message(self.sequence, completions_xml)
|
||||
dbg.writer.add_command(cmd)
|
||||
|
|
@ -1080,7 +1080,7 @@ class InternalLoadFullValue(InternalThreadCommand):
|
|||
else:
|
||||
scope, attrs = (variable, None)
|
||||
name = scope
|
||||
var_obj = pydevd_vars.getVariable(self.thread_id, self.frame_id, scope, attrs)
|
||||
var_obj = pydevd_vars.getVariable(dbg, self.thread_id, self.frame_id, scope, attrs)
|
||||
var_objects.append((var_obj, name))
|
||||
|
||||
t = GetValueAsyncThreadDebug(dbg, self.sequence, var_objects)
|
||||
|
|
|
|||
|
|
@ -5,13 +5,11 @@ import sys
|
|||
|
||||
DEBUG = False
|
||||
|
||||
#=======================================================================================================================
|
||||
# CustomFramesContainer
|
||||
#=======================================================================================================================
|
||||
|
||||
class CustomFramesContainer:
|
||||
|
||||
# Actual Values initialized later on.
|
||||
custom_frames_lock = None #: :type custom_frames_lock: threading.Lock
|
||||
custom_frames_lock = None # : :type custom_frames_lock: threading.Lock
|
||||
|
||||
custom_frames = None
|
||||
|
||||
|
|
@ -20,7 +18,7 @@ class CustomFramesContainer:
|
|||
_py_db_command_thread_event = None
|
||||
|
||||
|
||||
def custom_frames_container_init(): #Note: no staticmethod on jython 2.1 (so, use free-function)
|
||||
def custom_frames_container_init(): # Note: no staticmethod on jython 2.1 (so, use free-function)
|
||||
|
||||
CustomFramesContainer.custom_frames_lock = thread.allocate_lock()
|
||||
|
||||
|
|
@ -37,13 +35,11 @@ def custom_frames_container_init(): #Note: no staticmethod on jython 2.1 (so, us
|
|||
# when we do create the debugger.
|
||||
CustomFramesContainer._py_db_command_thread_event = Null()
|
||||
|
||||
#Initialize it the first time (it may be reinitialized later on when dealing with a fork).
|
||||
|
||||
# Initialize it the first time (it may be reinitialized later on when dealing with a fork).
|
||||
custom_frames_container_init()
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# CustomFrame
|
||||
#=======================================================================================================================
|
||||
class CustomFrame:
|
||||
|
||||
def __init__(self, name, frame, thread_id):
|
||||
|
|
@ -61,73 +57,59 @@ class CustomFrame:
|
|||
|
||||
|
||||
def add_custom_frame(frame, name, thread_id):
|
||||
CustomFramesContainer.custom_frames_lock.acquire()
|
||||
try:
|
||||
'''
|
||||
It's possible to show paused frames by adding a custom frame through this API (it's
|
||||
intended to be used for coroutines, but could potentially be used for generators too).
|
||||
|
||||
:param frame:
|
||||
The topmost frame to be shown paused when a thread with thread.ident == thread_id is paused.
|
||||
|
||||
:param name:
|
||||
The name to be shown for the custom thread in the UI.
|
||||
|
||||
:param thread_id:
|
||||
The thread id to which this frame is related (must match thread.ident).
|
||||
|
||||
:return: str
|
||||
Returns the custom thread id which will be used to show the given frame paused.
|
||||
'''
|
||||
with CustomFramesContainer.custom_frames_lock:
|
||||
curr_thread_id = get_current_thread_id(threading.currentThread())
|
||||
next_id = CustomFramesContainer._next_frame_id = CustomFramesContainer._next_frame_id + 1
|
||||
|
||||
# Note: the frame id kept contains an id and thread information on the thread where the frame was added
|
||||
# so that later on we can check if the frame is from the current thread by doing frame_id.endswith('|'+thread_id).
|
||||
frame_id = '__frame__:%s|%s' % (next_id, curr_thread_id)
|
||||
frame_custom_thread_id = '__frame__:%s|%s' % (next_id, curr_thread_id)
|
||||
if DEBUG:
|
||||
sys.stderr.write('add_custom_frame: %s (%s) %s %s\n' % (
|
||||
frame_id, get_abs_path_real_path_and_base_from_frame(frame)[-1], frame.f_lineno, frame.f_code.co_name))
|
||||
frame_custom_thread_id, get_abs_path_real_path_and_base_from_frame(frame)[-1], frame.f_lineno, frame.f_code.co_name))
|
||||
|
||||
CustomFramesContainer.custom_frames[frame_id] = CustomFrame(name, frame, thread_id)
|
||||
CustomFramesContainer.custom_frames[frame_custom_thread_id] = CustomFrame(name, frame, thread_id)
|
||||
CustomFramesContainer._py_db_command_thread_event.set()
|
||||
return frame_id
|
||||
finally:
|
||||
CustomFramesContainer.custom_frames_lock.release()
|
||||
return frame_custom_thread_id
|
||||
|
||||
addCustomFrame = add_custom_frame # Backward compatibility
|
||||
|
||||
def update_custom_frame(frame_id, frame, thread_id, name=None):
|
||||
CustomFramesContainer.custom_frames_lock.acquire()
|
||||
try:
|
||||
def update_custom_frame(frame_custom_thread_id, frame, thread_id, name=None):
|
||||
with CustomFramesContainer.custom_frames_lock:
|
||||
if DEBUG:
|
||||
sys.stderr.write('update_custom_frame: %s\n' % frame_id)
|
||||
sys.stderr.write('update_custom_frame: %s\n' % frame_custom_thread_id)
|
||||
try:
|
||||
old = CustomFramesContainer.custom_frames[frame_id]
|
||||
old = CustomFramesContainer.custom_frames[frame_custom_thread_id]
|
||||
if name is not None:
|
||||
old.name = name
|
||||
old.mod_time += 1
|
||||
old.thread_id = thread_id
|
||||
except:
|
||||
sys.stderr.write('Unable to get frame to replace: %s\n' % (frame_id,))
|
||||
sys.stderr.write('Unable to get frame to replace: %s\n' % (frame_custom_thread_id,))
|
||||
import traceback;traceback.print_exc()
|
||||
|
||||
CustomFramesContainer._py_db_command_thread_event.set()
|
||||
finally:
|
||||
CustomFramesContainer.custom_frames_lock.release()
|
||||
|
||||
|
||||
def get_custom_frame(thread_id, frame_id):
|
||||
'''
|
||||
:param thread_id: This should actually be the frame_id which is returned by add_custom_frame.
|
||||
:param frame_id: This is the actual id() of the frame
|
||||
'''
|
||||
|
||||
CustomFramesContainer.custom_frames_lock.acquire()
|
||||
try:
|
||||
frame_id = int(frame_id)
|
||||
f = CustomFramesContainer.custom_frames[thread_id].frame
|
||||
while f is not None:
|
||||
if id(f) == frame_id:
|
||||
return f
|
||||
f = f.f_back
|
||||
finally:
|
||||
f = None
|
||||
CustomFramesContainer.custom_frames_lock.release()
|
||||
|
||||
|
||||
def remove_custom_frame(frame_id):
|
||||
CustomFramesContainer.custom_frames_lock.acquire()
|
||||
try:
|
||||
def remove_custom_frame(frame_custom_thread_id):
|
||||
with CustomFramesContainer.custom_frames_lock:
|
||||
if DEBUG:
|
||||
sys.stderr.write('remove_custom_frame: %s\n' % frame_id)
|
||||
CustomFramesContainer.custom_frames.pop(frame_id, None)
|
||||
sys.stderr.write('remove_custom_frame: %s\n' % frame_custom_thread_id)
|
||||
CustomFramesContainer.custom_frames.pop(frame_custom_thread_id, None)
|
||||
CustomFramesContainer._py_db_command_thread_event.set()
|
||||
finally:
|
||||
CustomFramesContainer.custom_frames_lock.release()
|
||||
|
||||
removeCustomFrame = remove_custom_frame # Backward compatibility
|
||||
|
|
|
|||
|
|
@ -420,16 +420,10 @@ cdef class PyDBFrame:
|
|||
f = f.f_back
|
||||
f = None
|
||||
|
||||
thread_id = get_current_thread_id(thread)
|
||||
pydevd_vars.add_additional_frame_by_id(thread_id, frame_id_to_frame)
|
||||
try:
|
||||
main_debugger.send_caught_exception_stack(thread, arg, id(frame))
|
||||
self.set_suspend(thread, CMD_STEP_CAUGHT_EXCEPTION)
|
||||
self.do_wait_suspend(thread, frame, event, arg)
|
||||
main_debugger.send_caught_exception_stack_proceeded(thread)
|
||||
|
||||
finally:
|
||||
pydevd_vars.remove_additional_frame_by_id(thread_id)
|
||||
main_debugger.send_caught_exception_stack(thread, arg, id(frame))
|
||||
self.set_suspend(thread, CMD_STEP_CAUGHT_EXCEPTION)
|
||||
self.do_wait_suspend(thread, frame, event, arg)
|
||||
main_debugger.send_caught_exception_stack_proceeded(thread)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ DONT_TRACE = {
|
|||
'pydevd.py': PYDEV_FILE,
|
||||
'pydevd_additional_thread_info.py': PYDEV_FILE,
|
||||
'pydevd_additional_thread_info_regular.py': PYDEV_FILE,
|
||||
'pydevd_api.py': PYDEV_FILE,
|
||||
'pydevd_base_schema.py': PYDEV_FILE,
|
||||
'pydevd_breakpoints.py': PYDEV_FILE,
|
||||
'pydevd_collect_try_except_info.py': PYDEV_FILE,
|
||||
|
|
@ -90,6 +91,7 @@ DONT_TRACE = {
|
|||
'pydevd_helpers.py': PYDEV_FILE,
|
||||
'pydevd_import_class.py': PYDEV_FILE,
|
||||
'pydevd_io.py': PYDEV_FILE,
|
||||
'pydevd_json_debug_options.py': PYDEV_FILE,
|
||||
'pydevd_kill_all_pydevd_threads.py': PYDEV_FILE,
|
||||
'pydevd_modify_bytecode.py': PYDEV_FILE,
|
||||
'pydevd_net_command.py': PYDEV_FILE,
|
||||
|
|
@ -108,6 +110,7 @@ DONT_TRACE = {
|
|||
'pydevd_schema_log.py': PYDEV_FILE,
|
||||
'pydevd_signature.py': PYDEV_FILE,
|
||||
'pydevd_stackless.py': PYDEV_FILE,
|
||||
'pydevd_suspended_frames.py': PYDEV_FILE,
|
||||
'pydevd_thread_wrappers.py': PYDEV_FILE,
|
||||
'pydevd_trace_api.py': PYDEV_FILE,
|
||||
'pydevd_trace_dispatch.py': PYDEV_FILE,
|
||||
|
|
|
|||
|
|
@ -269,16 +269,10 @@ class PyDBFrame:
|
|||
f = f.f_back
|
||||
f = None
|
||||
|
||||
thread_id = get_current_thread_id(thread)
|
||||
pydevd_vars.add_additional_frame_by_id(thread_id, frame_id_to_frame)
|
||||
try:
|
||||
main_debugger.send_caught_exception_stack(thread, arg, id(frame))
|
||||
self.set_suspend(thread, CMD_STEP_CAUGHT_EXCEPTION)
|
||||
self.do_wait_suspend(thread, frame, event, arg)
|
||||
main_debugger.send_caught_exception_stack_proceeded(thread)
|
||||
|
||||
finally:
|
||||
pydevd_vars.remove_additional_frame_by_id(thread_id)
|
||||
main_debugger.send_caught_exception_stack(thread, arg, id(frame))
|
||||
self.set_suspend(thread, CMD_STEP_CAUGHT_EXCEPTION)
|
||||
self.do_wait_suspend(thread, frame, event, arg)
|
||||
main_debugger.send_caught_exception_stack_proceeded(thread)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class NetCommand:
|
|||
msg = '%s\t%s\t%s\n' % (cmd_id, seq, encoded)
|
||||
|
||||
else:
|
||||
msg = '%s\t%s\t%s\n' % (cmd_id, seq, text)
|
||||
msg = '%s\t%s\t%s' % (cmd_id, seq, text)
|
||||
|
||||
if IS_PY2:
|
||||
assert isinstance(msg, str) # i.e.: bytes
|
||||
|
|
|
|||
|
|
@ -69,10 +69,9 @@ class NetCommandFactory:
|
|||
except:
|
||||
return self.make_error_message(0, get_exception_traceback_str())
|
||||
|
||||
def make_custom_frame_created_message(self, frameId, frameDescription):
|
||||
frameDescription = pydevd_xml.make_valid_xml_value(frameDescription)
|
||||
cmdText = '<xml><thread name="%s" id="%s"/></xml>' % (frameDescription, frameId)
|
||||
return NetCommand(CMD_THREAD_CREATE, 0, cmdText)
|
||||
def make_custom_frame_created_message(self, frame_id, frame_description):
|
||||
frame_description = pydevd_xml.make_valid_xml_value(frame_description)
|
||||
return NetCommand(CMD_THREAD_CREATE, 0, '<xml><thread name="%s" id="%s"/></xml>' % (frame_description, frame_id))
|
||||
|
||||
def make_list_threads_message(self, seq):
|
||||
""" returns thread listing as XML """
|
||||
|
|
@ -88,7 +87,7 @@ class NetCommandFactory:
|
|||
except:
|
||||
return self.make_error_message(seq, get_exception_traceback_str())
|
||||
|
||||
def make_get_thread_stack_message(self, seq, thread_id, topmost_frame, must_be_suspended=False):
|
||||
def make_get_thread_stack_message(self, py_db, seq, thread_id, topmost_frame, must_be_suspended=False):
|
||||
"""
|
||||
Returns thread stack as XML.
|
||||
|
||||
|
|
@ -99,24 +98,21 @@ class NetCommandFactory:
|
|||
cmd_text = ['<xml><thread id="%s">' % (thread_id,)]
|
||||
|
||||
if topmost_frame is not None:
|
||||
frame_id_to_lineno = {}
|
||||
try:
|
||||
# Note: if we detect that we're already stopped in a given place within
|
||||
# the debugger, use that stack instead of creating a new one with the
|
||||
# current position (this is needed because when an uncaught exception
|
||||
# is reported for a given frame we are actually stopped in a different
|
||||
# place within the debugger).
|
||||
frame = topmost_frame
|
||||
thread_stack_str = ''
|
||||
while frame is not None:
|
||||
if frame.f_code.co_name == 'do_wait_suspend' and frame.f_code.co_filename.endswith('pydevd.py'):
|
||||
thread_stack_str = frame.f_locals.get('thread_stack_str')
|
||||
break
|
||||
frame = frame.f_back
|
||||
else:
|
||||
# : :type suspended_frames_manager: SuspendedFramesManager
|
||||
suspended_frames_manager = py_db.suspended_frames_manager
|
||||
info = suspended_frames_manager.get_topmost_frame_and_frame_id_to_line(thread_id)
|
||||
if info is None:
|
||||
# Could not find stack of suspended frame...
|
||||
if must_be_suspended:
|
||||
return None
|
||||
cmd_text.append(thread_stack_str or self.make_thread_stack_str(topmost_frame))
|
||||
else:
|
||||
# Note: we have to use the topmost frame where it was suspended (it may
|
||||
# be different if it was an exception).
|
||||
topmost_frame, frame_id_to_lineno = info
|
||||
|
||||
cmd_text.append(self.make_thread_stack_str(topmost_frame, frame_id_to_lineno))
|
||||
finally:
|
||||
topmost_frame = None
|
||||
cmd_text.append('</thread></xml>')
|
||||
|
|
@ -156,15 +152,15 @@ class NetCommandFactory:
|
|||
except:
|
||||
return self.make_error_message(0, get_exception_traceback_str())
|
||||
|
||||
def make_thread_stack_str(self, frame, frame_to_lineno=None):
|
||||
def make_thread_stack_str(self, frame, frame_id_to_lineno=None):
|
||||
'''
|
||||
:param frame_to_lineno:
|
||||
:param frame_id_to_lineno:
|
||||
If available, the line number for the frame will be gotten from this dict,
|
||||
otherwise frame.f_lineno will be used (needed for unhandled exceptions as
|
||||
the place where we report may be different from the place where it's raised).
|
||||
'''
|
||||
if frame_to_lineno is None:
|
||||
frame_to_lineno = {}
|
||||
if frame_id_to_lineno is None:
|
||||
frame_id_to_lineno = {}
|
||||
make_valid_xml_value = pydevd_xml.make_valid_xml_value
|
||||
cmd_text_list = []
|
||||
append = cmd_text_list.append
|
||||
|
|
@ -174,7 +170,7 @@ class NetCommandFactory:
|
|||
try:
|
||||
py_db = get_global_debugger()
|
||||
while curr_frame:
|
||||
my_id = id(curr_frame)
|
||||
frame_id = id(curr_frame)
|
||||
|
||||
if curr_frame.f_code is None:
|
||||
break # Iron Python sometimes does not have it!
|
||||
|
|
@ -197,11 +193,11 @@ class NetCommandFactory:
|
|||
|
||||
# print("file is ", filename_in_utf8)
|
||||
|
||||
lineno = frame_to_lineno.get(curr_frame, curr_frame.f_lineno)
|
||||
lineno = frame_id_to_lineno.get(frame_id, curr_frame.f_lineno)
|
||||
# print("line is ", lineno)
|
||||
|
||||
# Note: variables are all gotten 'on-demand'.
|
||||
append('<frame id="%s" name="%s" ' % (my_id , make_valid_xml_value(method_name)))
|
||||
append('<frame id="%s" name="%s" ' % (frame_id , make_valid_xml_value(method_name)))
|
||||
append('file="%s" line="%s">' % (quote(make_valid_xml_value(filename_in_utf8), '/>_= \t'), lineno))
|
||||
append("</frame>")
|
||||
curr_frame = curr_frame.f_back
|
||||
|
|
@ -218,7 +214,7 @@ class NetCommandFactory:
|
|||
stop_reason=None,
|
||||
message=None,
|
||||
suspend_type="trace",
|
||||
frame_to_lineno=None
|
||||
frame_id_to_lineno=None
|
||||
):
|
||||
"""
|
||||
:return tuple(str,str):
|
||||
|
|
@ -257,16 +253,16 @@ class NetCommandFactory:
|
|||
if suspend_type is not None:
|
||||
append(' suspend_type="%s"' % (suspend_type,))
|
||||
append('>')
|
||||
thread_stack_str = self.make_thread_stack_str(frame, frame_to_lineno)
|
||||
thread_stack_str = self.make_thread_stack_str(frame, frame_id_to_lineno)
|
||||
append(thread_stack_str)
|
||||
append("</thread></xml>")
|
||||
|
||||
return ''.join(cmd_text_list), thread_stack_str
|
||||
|
||||
def make_thread_suspend_message(self, thread_id, frame, stop_reason, message, suspend_type, frame_to_lineno=None):
|
||||
def make_thread_suspend_message(self, thread_id, frame, stop_reason, message, suspend_type, frame_id_to_lineno=None):
|
||||
try:
|
||||
thread_suspend_str, thread_stack_str = self.make_thread_suspend_str(
|
||||
thread_id, frame, stop_reason, message, suspend_type, frame_to_lineno=frame_to_lineno)
|
||||
thread_id, frame, stop_reason, message, suspend_type, frame_id_to_lineno=frame_id_to_lineno)
|
||||
cmd = NetCommand(CMD_THREAD_SUSPEND, 0, thread_suspend_str)
|
||||
cmd.thread_stack_str = thread_stack_str
|
||||
cmd.thread_suspend_str = thread_suspend_str
|
||||
|
|
|
|||
|
|
@ -0,0 +1,166 @@
|
|||
from contextlib import contextmanager
|
||||
import sys
|
||||
|
||||
from _pydev_imps._pydev_saved_modules import threading
|
||||
from _pydevd_bundle.pydevd_constants import get_frame
|
||||
import traceback
|
||||
|
||||
|
||||
class _FramesTracker(object):
|
||||
'''
|
||||
This is a helper class to be used to track frames when a thread becomes suspended.
|
||||
'''
|
||||
|
||||
def __init__(self, suspended_frames_manager, py_db):
|
||||
self._suspended_frames_manager = suspended_frames_manager
|
||||
self.py_db = py_db
|
||||
self._frame_id_to_frame = {}
|
||||
|
||||
# Note that a given frame may appear in multiple threads when we have custom
|
||||
# frames added, but as those are coroutines, this map will point to the actual
|
||||
# main thread (which is the one that needs to be suspended for us to get the
|
||||
# variables).
|
||||
self._frame_id_to_main_thread_id = {}
|
||||
|
||||
# A map of the suspended thread id -> list(frames ids) -- note that
|
||||
# frame ids are kept in order (the first one is the suspended frame).
|
||||
self._thread_id_to_frame_ids = {}
|
||||
|
||||
# A map of the lines where it's suspended (needed for exceptions where the frame
|
||||
# lineno is not correct).
|
||||
self._frame_id_to_lineno = {}
|
||||
|
||||
# The main suspended thread (if this is a coroutine this isn't the id of the
|
||||
# coroutine thread, it's the id of the actual suspended thread).
|
||||
self._main_thread_id = None
|
||||
|
||||
# Helper to know if it was already untracked.
|
||||
self._untracked = False
|
||||
|
||||
# We need to be thread-safe!
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def track(self, thread_id, frame, frame_id_to_lineno, frame_custom_thread_id=None):
|
||||
'''
|
||||
:param thread_id:
|
||||
The thread id to be used for this frame.
|
||||
|
||||
:param frame:
|
||||
The topmost frame which is suspended at the given thread.
|
||||
|
||||
:param frame_id_to_lineno:
|
||||
If available, the line number for the frame will be gotten from this dict,
|
||||
otherwise frame.f_lineno will be used (needed for unhandled exceptions as
|
||||
the place where we report may be different from the place where it's raised).
|
||||
|
||||
:param frame_custom_thread_id:
|
||||
If None this this is the id of the thread id for the custom frame (i.e.: coroutine).
|
||||
'''
|
||||
with self._lock:
|
||||
coroutine_or_main_thread_id = frame_custom_thread_id or thread_id
|
||||
|
||||
if coroutine_or_main_thread_id in self._suspended_frames_manager.thread_id_to_tracker:
|
||||
sys.stderr.write('pydevd: Something is wrong. Tracker being added twice to the same thread id.\n')
|
||||
|
||||
self._suspended_frames_manager.thread_id_to_tracker[coroutine_or_main_thread_id] = self
|
||||
self._main_thread_id = thread_id
|
||||
self._frame_id_to_lineno = frame_id_to_lineno
|
||||
|
||||
frame_ids_from_thread = self._thread_id_to_frame_ids.setdefault(
|
||||
coroutine_or_main_thread_id, [])
|
||||
|
||||
while frame is not None:
|
||||
frame_id = id(frame)
|
||||
self._frame_id_to_frame[frame_id] = frame
|
||||
frame_ids_from_thread.append(frame_id)
|
||||
|
||||
self._frame_id_to_main_thread_id[frame_id] = thread_id
|
||||
|
||||
frame = frame.f_back
|
||||
|
||||
def untrack_all(self):
|
||||
with self._lock:
|
||||
if self._untracked:
|
||||
# Calling multiple times is expected for the set next statement.
|
||||
return
|
||||
self._untracked = True
|
||||
for thread_id in self._thread_id_to_frame_ids:
|
||||
self._suspended_frames_manager.thread_id_to_tracker.pop(thread_id, None)
|
||||
|
||||
self._frame_id_to_frame.clear()
|
||||
self._frame_id_to_main_thread_id.clear()
|
||||
self._thread_id_to_frame_ids.clear()
|
||||
self._frame_id_to_lineno.clear()
|
||||
self._main_thread_id = None
|
||||
self._suspended_frames_manager = None
|
||||
|
||||
def get_topmost_frame_and_frame_id_to_line(self, thread_id):
|
||||
with self._lock:
|
||||
frame_ids = self._thread_id_to_frame_ids.get(thread_id)
|
||||
if frame_ids is not None:
|
||||
frame_id = frame_ids[0]
|
||||
return self._frame_id_to_frame[frame_id], self._frame_id_to_lineno
|
||||
|
||||
def find_frame(self, thread_id, frame_id):
|
||||
with self._lock:
|
||||
return self._frame_id_to_frame.get(frame_id)
|
||||
|
||||
def create_thread_suspend_command(self, thread_id, stop_reason, message, suspend_type):
|
||||
with self._lock:
|
||||
frame_ids = self._thread_id_to_frame_ids[thread_id]
|
||||
|
||||
# First one is topmost frame suspended.
|
||||
frame = self._frame_id_to_frame[frame_ids[0]]
|
||||
|
||||
cmd = self.py_db.cmd_factory.make_thread_suspend_message(
|
||||
thread_id, frame, stop_reason, message, suspend_type, frame_id_to_lineno=self._frame_id_to_lineno)
|
||||
|
||||
frame = None
|
||||
return cmd
|
||||
|
||||
|
||||
class SuspendedFramesManager(object):
|
||||
|
||||
def __init__(self):
|
||||
self._thread_id_to_fake_frames = {}
|
||||
self.thread_id_to_tracker = {}
|
||||
|
||||
def get_topmost_frame_and_frame_id_to_line(self, thread_id):
|
||||
tracker = self.thread_id_to_tracker.get(thread_id)
|
||||
if tracker is None:
|
||||
return None
|
||||
return tracker.get_topmost_frame_and_frame_id_to_line(thread_id)
|
||||
|
||||
@contextmanager
|
||||
def track_frames(self, py_db):
|
||||
tracker = _FramesTracker(self, py_db)
|
||||
try:
|
||||
yield tracker
|
||||
finally:
|
||||
tracker.untrack_all()
|
||||
|
||||
def add_fake_frame(self, thread_id, frame_id, frame):
|
||||
self._thread_id_to_fake_frames.setdefault(thread_id, {})[int(frame_id)] = frame
|
||||
|
||||
def find_frame(self, thread_id, frame_id):
|
||||
try:
|
||||
if frame_id == "*":
|
||||
return get_frame() # any frame is specified with "*"
|
||||
frame_id = int(frame_id)
|
||||
|
||||
fake_frames = self._thread_id_to_fake_frames.get(thread_id)
|
||||
if fake_frames is not None:
|
||||
frame = fake_frames.get(frame_id)
|
||||
if frame is not None:
|
||||
return frame
|
||||
|
||||
frames_tracker = self.thread_id_to_tracker.get(thread_id)
|
||||
if frames_tracker is not None:
|
||||
frame = frames_tracker.find_frame(thread_id, frame_id)
|
||||
if frame is not None:
|
||||
return frame
|
||||
|
||||
return None
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
|
@ -4,9 +4,7 @@
|
|||
import pickle
|
||||
from _pydevd_bundle.pydevd_constants import get_frame, get_current_thread_id, xrange
|
||||
|
||||
from _pydevd_bundle.pydevd_custom_frames import get_custom_frame
|
||||
from _pydevd_bundle.pydevd_xml import ExceptionOnEvaluate, get_type, var_to_xml
|
||||
from _pydev_imps._pydev_saved_modules import thread
|
||||
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
|
|
@ -17,20 +15,17 @@ import sys # @Reimport
|
|||
from _pydev_imps._pydev_saved_modules import threading
|
||||
import traceback
|
||||
from _pydevd_bundle import pydevd_save_locals
|
||||
from _pydev_bundle.pydev_imports import Exec, quote, execfile
|
||||
from _pydev_bundle.pydev_imports import Exec, execfile
|
||||
from _pydevd_bundle.pydevd_utils import to_string
|
||||
|
||||
SENTINEL_VALUE = []
|
||||
|
||||
# ------------------------------------------------------------------------------------------------------ class for errors
|
||||
|
||||
class VariableError(RuntimeError): pass
|
||||
class VariableError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
class FrameNotFoundError(RuntimeError): pass
|
||||
|
||||
|
||||
def _iter_frames(frame):
|
||||
def iter_frames(frame):
|
||||
while frame is not None:
|
||||
yield frame
|
||||
frame = frame.f_back
|
||||
|
|
@ -42,118 +37,12 @@ def dump_frames(thread_id):
|
|||
if thread_id != get_current_thread_id(threading.currentThread()):
|
||||
raise VariableError("find_frame: must execute on same thread")
|
||||
|
||||
curFrame = get_frame()
|
||||
for frame in _iter_frames(curFrame):
|
||||
frame = get_frame()
|
||||
for frame in iter_frames(frame):
|
||||
sys.stdout.write('%s\n' % pickle.dumps(frame))
|
||||
|
||||
|
||||
# ===============================================================================
|
||||
# AdditionalFramesContainer
|
||||
# ===============================================================================
|
||||
class AdditionalFramesContainer:
|
||||
lock = thread.allocate_lock()
|
||||
additional_frames = {} # dict of dicts
|
||||
|
||||
|
||||
def add_additional_frame_by_id(thread_id, frames_by_id):
|
||||
AdditionalFramesContainer.additional_frames[thread_id] = frames_by_id
|
||||
|
||||
|
||||
addAdditionalFrameById = add_additional_frame_by_id # Backward compatibility
|
||||
|
||||
|
||||
def remove_additional_frame_by_id(thread_id):
|
||||
del AdditionalFramesContainer.additional_frames[thread_id]
|
||||
|
||||
|
||||
removeAdditionalFrameById = remove_additional_frame_by_id # Backward compatibility
|
||||
|
||||
|
||||
def has_additional_frames_by_id(thread_id):
|
||||
return thread_id in AdditionalFramesContainer.additional_frames
|
||||
|
||||
|
||||
def get_additional_frames_by_id(thread_id):
|
||||
return AdditionalFramesContainer.additional_frames.get(thread_id)
|
||||
|
||||
|
||||
def find_frame(thread_id, frame_id):
|
||||
""" returns a frame on the thread that has a given frame_id """
|
||||
try:
|
||||
curr_thread_id = get_current_thread_id(threading.currentThread())
|
||||
if thread_id != curr_thread_id:
|
||||
try:
|
||||
return get_custom_frame(thread_id, frame_id) # I.e.: thread_id could be a stackless frame id + thread_id.
|
||||
except:
|
||||
pass
|
||||
|
||||
raise VariableError("find_frame: must execute on same thread (%s != %s)" % (thread_id, curr_thread_id))
|
||||
|
||||
lookingFor = int(frame_id)
|
||||
|
||||
if AdditionalFramesContainer.additional_frames:
|
||||
if thread_id in AdditionalFramesContainer.additional_frames:
|
||||
frame = AdditionalFramesContainer.additional_frames[thread_id].get(lookingFor)
|
||||
|
||||
if frame is not None:
|
||||
return frame
|
||||
|
||||
curFrame = get_frame()
|
||||
if frame_id == "*":
|
||||
return curFrame # any frame is specified with "*"
|
||||
|
||||
frameFound = None
|
||||
|
||||
for frame in _iter_frames(curFrame):
|
||||
if lookingFor == id(frame):
|
||||
frameFound = frame
|
||||
del frame
|
||||
break
|
||||
|
||||
del frame
|
||||
|
||||
# Important: python can hold a reference to the frame from the current context
|
||||
# if an exception is raised, so, if we don't explicitly add those deletes
|
||||
# we might have those variables living much more than we'd want to.
|
||||
|
||||
# I.e.: sys.exc_info holding reference to frame that raises exception (so, other places
|
||||
# need to call sys.exc_clear())
|
||||
del curFrame
|
||||
|
||||
if frameFound is None:
|
||||
msgFrames = ''
|
||||
i = 0
|
||||
|
||||
for frame in _iter_frames(get_frame()):
|
||||
i += 1
|
||||
msgFrames += str(id(frame))
|
||||
if i % 5 == 0:
|
||||
msgFrames += '\n'
|
||||
else:
|
||||
msgFrames += ' - '
|
||||
|
||||
# Note: commented this error message out (it may commonly happen
|
||||
# if a message asking for a frame is issued while a thread is paused
|
||||
# but the thread starts running before the message is actually
|
||||
# handled).
|
||||
# Leaving code to uncomment during tests.
|
||||
# err_msg = '''find_frame: frame not found.
|
||||
# Looking for thread_id:%s, frame_id:%s
|
||||
# Current thread_id:%s, available frames:
|
||||
# %s\n
|
||||
# ''' % (thread_id, lookingFor, curr_thread_id, msgFrames)
|
||||
#
|
||||
# sys.stderr.write(err_msg)
|
||||
return None
|
||||
|
||||
return frameFound
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
|
||||
def getVariable(thread_id, frame_id, scope, attrs):
|
||||
def getVariable(dbg, thread_id, frame_id, scope, attrs):
|
||||
"""
|
||||
returns the value of a variable
|
||||
|
||||
|
|
@ -192,7 +81,7 @@ def getVariable(thread_id, frame_id, scope, attrs):
|
|||
sys.stderr.write('Unable to find object with id: %s\n' % (frame_id,))
|
||||
return None
|
||||
|
||||
frame = find_frame(thread_id, frame_id)
|
||||
frame = dbg.find_frame(thread_id, frame_id)
|
||||
if frame is None:
|
||||
return {}
|
||||
|
||||
|
|
@ -208,7 +97,7 @@ def getVariable(thread_id, frame_id, scope, attrs):
|
|||
for count in xrange(len(attrList)):
|
||||
if count == 0:
|
||||
# An Expression can be in any scope (globals/locals), therefore it needs to evaluated as an expression
|
||||
var = evaluate_expression(thread_id, frame_id, attrList[count], False)
|
||||
var = evaluate_expression(dbg, thread_id, frame_id, attrList[count], False)
|
||||
else:
|
||||
_type, _typeName, resolver = get_type(var)
|
||||
var = resolver.resolve(var, attrList[count])
|
||||
|
|
@ -229,7 +118,7 @@ def getVariable(thread_id, frame_id, scope, attrs):
|
|||
return var
|
||||
|
||||
|
||||
def resolve_compound_variable_fields(thread_id, frame_id, scope, attrs):
|
||||
def resolve_compound_variable_fields(dbg, thread_id, frame_id, scope, attrs):
|
||||
"""
|
||||
Resolve compound variable in debugger scopes by its name and attributes
|
||||
|
||||
|
|
@ -241,7 +130,7 @@ def resolve_compound_variable_fields(thread_id, frame_id, scope, attrs):
|
|||
:return: a dictionary of variables's fields
|
||||
"""
|
||||
|
||||
var = getVariable(thread_id, frame_id, scope, attrs)
|
||||
var = getVariable(dbg, thread_id, frame_id, scope, attrs)
|
||||
|
||||
try:
|
||||
_type, _typeName, resolver = get_type(var)
|
||||
|
|
@ -291,14 +180,14 @@ def resolve_compound_var_object_fields(var, attrs):
|
|||
traceback.print_exc()
|
||||
|
||||
|
||||
def custom_operation(thread_id, frame_id, scope, attrs, style, code_or_file, operation_fn_name):
|
||||
def custom_operation(dbg, thread_id, frame_id, scope, attrs, style, code_or_file, operation_fn_name):
|
||||
"""
|
||||
We'll execute the code_or_file and then search in the namespace the operation_fn_name to execute with the given var.
|
||||
|
||||
code_or_file: either some code (i.e.: from pprint import pprint) or a file to be executed.
|
||||
operation_fn_name: the name of the operation to execute after the exec (i.e.: pprint)
|
||||
"""
|
||||
expressionValue = getVariable(thread_id, frame_id, scope, attrs)
|
||||
expressionValue = getVariable(dbg, thread_id, frame_id, scope, attrs)
|
||||
|
||||
try:
|
||||
namespace = {'__name__': '<custom_operation>'}
|
||||
|
|
@ -351,11 +240,11 @@ def eval_in_context(expression, globals, locals):
|
|||
return result
|
||||
|
||||
|
||||
def evaluate_expression(thread_id, frame_id, expression, is_exec):
|
||||
def evaluate_expression(dbg, thread_id, frame_id, expression, is_exec):
|
||||
'''returns the result of the evaluated expression
|
||||
@param is_exec: determines if we should do an exec or an eval
|
||||
'''
|
||||
frame = find_frame(thread_id, frame_id)
|
||||
frame = dbg.find_frame(thread_id, frame_id)
|
||||
if frame is None:
|
||||
return
|
||||
|
||||
|
|
@ -394,7 +283,7 @@ def evaluate_expression(thread_id, frame_id, expression, is_exec):
|
|||
def change_attr_expression(thread_id, frame_id, attr, expression, dbg, value=SENTINEL_VALUE):
|
||||
'''Changes some attribute in a given frame.
|
||||
'''
|
||||
frame = find_frame(thread_id, frame_id)
|
||||
frame = dbg.find_frame(thread_id, frame_id)
|
||||
if frame is None:
|
||||
return
|
||||
|
||||
|
|
@ -619,9 +508,9 @@ def dataframe_to_xml(df, name, roffset, coffset, rows, cols, format):
|
|||
elif dtype == 'f':
|
||||
fmt = '.5f'
|
||||
elif dtype == 'i' or dtype == 'u':
|
||||
fmt= 'd'
|
||||
fmt = 'd'
|
||||
else:
|
||||
fmt= 's'
|
||||
fmt = 's'
|
||||
col_formats.append('%' + fmt)
|
||||
bounds = col_bounds[col]
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ install:
|
|||
- cmd: IF "%TEST_IRONPYTHON%"=="" (%PYTHON_EXE% -m pip install pytest --no-warn-script-location)
|
||||
- cmd: IF "%TEST_IRONPYTHON%"=="" (%PYTHON_EXE% -m pip install pytest-xdist --no-warn-script-location)
|
||||
- cmd: IF "%TEST_IRONPYTHON%"=="" (%PYTHON_EXE% -m pip install psutil)
|
||||
- cmd: IF "%TEST_IRONPYTHON%"=="" (%PYTHON_EXE% -m pip install gevent --no-warn-script-location)
|
||||
- cmd: IF "%TEST_IRONPYTHON%"=="" (%PYTHON_EXE% -m pip install ipython --no-warn-script-location)
|
||||
- cmd: IF "%TEST_IRONPYTHON%"=="" (%PYTHON_EXE% -m pip install untangle --no-warn-script-location)
|
||||
- cmd: IF "%TEST_IRONPYTHON%"=="" IF %PYTHON_FOLDER%=="C:\\Python27" (%PYTHON_EXE% -m pip install django>=1.7,<1.8)
|
||||
|
|
|
|||
|
|
@ -283,7 +283,7 @@ def process_exec_queue(interpreter):
|
|||
# Note: it'll block here until return_control returns True.
|
||||
inputhook()
|
||||
except:
|
||||
import traceback;traceback.print_exc()
|
||||
traceback.print_exc()
|
||||
try:
|
||||
try:
|
||||
code_fragment = interpreter.exec_queue.get(block=True, timeout=1/20.) # 20 calls/second
|
||||
|
|
@ -541,7 +541,7 @@ class ConsoleWriter(InteractiveInterpreter):
|
|||
def console_exec(thread_id, frame_id, expression, dbg):
|
||||
"""returns 'False' in case expression is partially correct
|
||||
"""
|
||||
frame = pydevd_vars.find_frame(thread_id, frame_id)
|
||||
frame = dbg.find_frame(thread_id, frame_id)
|
||||
|
||||
is_multiline = expression.count('@LINE@') > 1
|
||||
expression = str(expression.replace('@LINE@', '\n'))
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ from _pydev_imps._pydev_saved_modules import time
|
|||
from _pydevd_bundle import pydevd_extension_utils
|
||||
from _pydevd_bundle import pydevd_io, pydevd_vm_type
|
||||
from _pydevd_bundle import pydevd_utils
|
||||
from _pydevd_bundle import pydevd_vars
|
||||
from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info
|
||||
from _pydevd_bundle.pydevd_breakpoints import ExceptionBreakpoint, get_exception_breakpoint
|
||||
from _pydevd_bundle.pydevd_comm_constants import (CMD_THREAD_SUSPEND, CMD_STEP_INTO, CMD_SET_BREAK,
|
||||
|
|
@ -64,6 +63,7 @@ from _pydevd_bundle.pydevd_comm import(InternalConsoleExec,
|
|||
|
||||
from _pydevd_bundle.pydevd_breakpoints import stop_on_unhandled_exception
|
||||
from _pydevd_bundle.pydevd_collect_try_except_info import collect_try_except_info
|
||||
from _pydevd_bundle.pydevd_suspended_frames import SuspendedFramesManager
|
||||
|
||||
__version_info__ = (1, 3, 3)
|
||||
__version_info_str__ = []
|
||||
|
|
@ -358,6 +358,7 @@ class PyDB(object):
|
|||
self.quitting = None
|
||||
self.cmd_factory = NetCommandFactory()
|
||||
self._cmd_queue = defaultdict(_queue.Queue) # Key is thread id or '*', value is Queue
|
||||
self.suspended_frames_manager = SuspendedFramesManager()
|
||||
|
||||
self.breakpoints = {}
|
||||
|
||||
|
|
@ -464,6 +465,9 @@ class PyDB(object):
|
|||
self._dont_trace_get_file_type = DONT_TRACE.get
|
||||
self.PYDEV_FILE = PYDEV_FILE
|
||||
|
||||
def add_fake_frame(self, thread_id, frame_id, frame):
|
||||
self.suspended_frames_manager.add_fake_frame(thread_id, frame_id, frame)
|
||||
|
||||
def handle_breakpoint_condition(self, info, breakpoint, new_frame):
|
||||
condition = breakpoint.condition
|
||||
try:
|
||||
|
|
@ -822,6 +826,8 @@ class PyDB(object):
|
|||
all_threads = threadingEnumerate()
|
||||
program_threads_dead = []
|
||||
with self._lock_running_thread_ids:
|
||||
reset_cache = not self._running_thread_ids
|
||||
|
||||
for t in all_threads:
|
||||
if getattr(t, 'is_pydev_daemon_thread', False):
|
||||
pass # I.e.: skip the DummyThreads created from pydev daemon threads
|
||||
|
|
@ -829,23 +835,13 @@ class PyDB(object):
|
|||
pydev_log.error_once('Error in debugger: Found PyDBDaemonThread not marked with is_pydev_daemon_thread=True.\n')
|
||||
|
||||
elif is_thread_alive(t):
|
||||
if not self._running_thread_ids:
|
||||
if reset_cache:
|
||||
# Fix multiprocessing debug with breakpoints in both main and child processes
|
||||
# (https://youtrack.jetbrains.com/issue/PY-17092) When the new process is created, the main
|
||||
# thread in the new process already has the attribute 'pydevd_id', so the new thread doesn't
|
||||
# get new id with its process number and the debugger loses access to both threads.
|
||||
# Therefore we should update thread_id for every main thread in the new process.
|
||||
|
||||
# Fix it for all existing threads.
|
||||
for existing_thread in all_threads:
|
||||
old_thread_id = get_thread_id(existing_thread)
|
||||
clear_cached_thread_id(t)
|
||||
|
||||
thread_id = get_thread_id(t)
|
||||
if thread_id != old_thread_id:
|
||||
if pydevd_vars.has_additional_frames_by_id(old_thread_id):
|
||||
frames_by_id = pydevd_vars.get_additional_frames_by_id(old_thread_id)
|
||||
pydevd_vars.add_additional_frame_by_id(thread_id, frames_by_id)
|
||||
clear_cached_thread_id(t)
|
||||
|
||||
thread_id = get_thread_id(t)
|
||||
program_threads_alive[thread_id] = t
|
||||
|
|
@ -1103,6 +1099,10 @@ class PyDB(object):
|
|||
finally:
|
||||
self._main_lock.release()
|
||||
|
||||
def find_frame(self, thread_id, frame_id):
|
||||
""" returns a frame on the thread that has a given frame_id """
|
||||
return self.suspended_frames_manager.find_frame(thread_id, frame_id)
|
||||
|
||||
def do_wait_suspend(self, thread, frame, event, arg, is_unhandled_exception=False): # @UnusedVariable
|
||||
""" busy waits until the thread state changes to RUN
|
||||
it expects thread's state as attributes of the thread.
|
||||
|
|
@ -1114,8 +1114,6 @@ class PyDB(object):
|
|||
"""
|
||||
# print('do_wait_suspend %s %s %s %s' % (frame.f_lineno, frame.f_code.co_name, frame.f_code.co_filename, event))
|
||||
self.process_internal_commands()
|
||||
thread_stack_str = '' # @UnusedVariable -- this is here so that `make_get_thread_stack_message`
|
||||
# can retrieve it later.
|
||||
|
||||
thread_id = get_current_thread_id(thread)
|
||||
|
||||
|
|
@ -1123,34 +1121,40 @@ class PyDB(object):
|
|||
message = thread.additional_info.pydev_message
|
||||
suspend_type = thread.additional_info.trace_suspend_type
|
||||
thread.additional_info.trace_suspend_type = 'trace' # Reset to trace mode for next call.
|
||||
frame_to_lineno = {}
|
||||
frame_id_to_lineno = {}
|
||||
stop_reason = thread.stop_reason
|
||||
if is_unhandled_exception:
|
||||
# arg must be the exception info (tuple(exc_type, exc, traceback))
|
||||
tb = arg[2]
|
||||
while tb is not None:
|
||||
frame_to_lineno[tb.tb_frame] = tb.tb_lineno
|
||||
frame_id_to_lineno[id(tb.tb_frame)] = tb.tb_lineno
|
||||
tb = tb.tb_next
|
||||
cmd = self.cmd_factory.make_thread_suspend_message(thread_id, frame, stop_reason, message, suspend_type, frame_to_lineno=frame_to_lineno)
|
||||
frame_to_lineno.clear()
|
||||
thread_stack_str = cmd.thread_stack_str # @UnusedVariable -- `make_get_thread_stack_message` uses it later.
|
||||
self.writer.add_command(cmd)
|
||||
|
||||
with CustomFramesContainer.custom_frames_lock: # @UndefinedVariable
|
||||
from_this_thread = []
|
||||
with self.suspended_frames_manager.track_frames(self) as frames_tracker:
|
||||
frames_tracker.track(thread_id, frame, frame_id_to_lineno)
|
||||
cmd = frames_tracker.create_thread_suspend_command(thread_id, stop_reason, message, suspend_type)
|
||||
self.writer.add_command(cmd)
|
||||
|
||||
for frame_id, custom_frame in dict_iter_items(CustomFramesContainer.custom_frames):
|
||||
if custom_frame.thread_id == thread.ident:
|
||||
# print >> sys.stderr, 'Frame created: ', frame_id
|
||||
self.writer.add_command(self.cmd_factory.make_custom_frame_created_message(frame_id, custom_frame.name))
|
||||
self.writer.add_command(self.cmd_factory.make_thread_suspend_message(frame_id, custom_frame.frame, CMD_THREAD_SUSPEND, "", suspend_type))
|
||||
with CustomFramesContainer.custom_frames_lock: # @UndefinedVariable
|
||||
from_this_thread = []
|
||||
|
||||
from_this_thread.append(frame_id)
|
||||
for frame_custom_thread_id, custom_frame in dict_iter_items(CustomFramesContainer.custom_frames):
|
||||
if custom_frame.thread_id == thread.ident:
|
||||
frames_tracker.track(thread_id, custom_frame.frame, frame_id_to_lineno, frame_custom_thread_id=frame_custom_thread_id)
|
||||
# print('Frame created as thread: %s' % (frame_custom_thread_id,))
|
||||
|
||||
with self._threads_suspended_single_notification.notify_thread_suspended(thread_id, stop_reason):
|
||||
self._do_wait_suspend(thread, frame, event, arg, suspend_type, from_this_thread)
|
||||
self.writer.add_command(self.cmd_factory.make_custom_frame_created_message(
|
||||
frame_custom_thread_id, custom_frame.name))
|
||||
|
||||
def _do_wait_suspend(self, thread, frame, event, arg, suspend_type, from_this_thread):
|
||||
self.writer.add_command(
|
||||
frames_tracker.create_thread_suspend_command(frame_custom_thread_id, CMD_THREAD_SUSPEND, "", suspend_type))
|
||||
|
||||
from_this_thread.append(frame_custom_thread_id)
|
||||
|
||||
with self._threads_suspended_single_notification.notify_thread_suspended(thread_id, stop_reason):
|
||||
self._do_wait_suspend(thread, frame, event, arg, suspend_type, from_this_thread, frames_tracker)
|
||||
|
||||
def _do_wait_suspend(self, thread, frame, event, arg, suspend_type, from_this_thread, frames_tracker):
|
||||
info = thread.additional_info
|
||||
|
||||
if info.pydev_state == STATE_SUSPEND and not self._finish_debugging_session:
|
||||
|
|
@ -1199,6 +1203,8 @@ class PyDB(object):
|
|||
info.pydev_message = ''
|
||||
|
||||
if stop:
|
||||
# Uninstall the current frames tracker before running it.
|
||||
frames_tracker.untrack_all()
|
||||
cmd = self.cmd_factory.make_thread_run_message(get_current_thread_id(thread), info.pydev_step_cmd)
|
||||
self.writer.add_command(cmd)
|
||||
info.pydev_state = STATE_SUSPEND
|
||||
|
|
@ -1212,7 +1218,7 @@ class PyDB(object):
|
|||
thread.stop_reason = CMD_THREAD_SUSPEND
|
||||
# return to the suspend state and wait for other command (without sending any
|
||||
# additional notification to the client).
|
||||
self._do_wait_suspend(thread, frame, event, arg, suspend_type, from_this_thread)
|
||||
self._do_wait_suspend(thread, frame, event, arg, suspend_type, from_this_thread, frames_tracker)
|
||||
return
|
||||
|
||||
elif info.pydev_step_cmd in (CMD_STEP_RETURN, CMD_STEP_RETURN_MY_CODE):
|
||||
|
|
@ -1244,23 +1250,19 @@ class PyDB(object):
|
|||
with CustomFramesContainer.custom_frames_lock:
|
||||
# The ones that remained on last_running must now be removed.
|
||||
for frame_id in from_this_thread:
|
||||
# print >> sys.stderr, 'Removing created frame: ', frame_id
|
||||
# print('Removing created frame: %s' % (frame_id,))
|
||||
self.writer.add_command(self.cmd_factory.make_thread_killed_message(frame_id))
|
||||
|
||||
def do_stop_on_unhandled_exception(self, thread, frame, frames_byid, arg):
|
||||
pydev_log.debug("We are stopping in post-mortem\n")
|
||||
thread_id = get_thread_id(thread)
|
||||
pydevd_vars.add_additional_frame_by_id(thread_id, frames_byid)
|
||||
try:
|
||||
try:
|
||||
add_exception_to_frame(frame, arg)
|
||||
self.set_suspend(thread, CMD_ADD_EXCEPTION_BREAK)
|
||||
self.do_wait_suspend(thread, frame, 'exception', arg, is_unhandled_exception=True)
|
||||
except:
|
||||
pydev_log.error("We've got an error while stopping in post-mortem: %s\n" % (arg[0],))
|
||||
add_exception_to_frame(frame, arg)
|
||||
self.set_suspend(thread, CMD_ADD_EXCEPTION_BREAK)
|
||||
self.do_wait_suspend(thread, frame, 'exception', arg, is_unhandled_exception=True)
|
||||
except:
|
||||
pydev_log.error("We've got an error while stopping in post-mortem: %s\n" % (arg[0],))
|
||||
finally:
|
||||
remove_exception_from_frame(frame)
|
||||
pydevd_vars.remove_additional_frame_by_id(thread_id)
|
||||
frame = None
|
||||
|
||||
def set_trace_for_frame_and_parents(self, frame, **kwargs):
|
||||
|
|
@ -1491,7 +1493,7 @@ class PyDB(object):
|
|||
frame = pydevd_frame_utils.Frame(None, -1, pydevd_frame_utils.FCode("Console",
|
||||
os.path.abspath(os.path.dirname(__file__))), globals, globals)
|
||||
thread_id = get_current_thread_id(thread)
|
||||
pydevd_vars.add_additional_frame_by_id(thread_id, {id(frame): frame})
|
||||
self.add_fake_frame(thread_id, id(frame), frame)
|
||||
|
||||
cmd = self.cmd_factory.make_show_console_message(thread_id, frame)
|
||||
self.writer.add_command(cmd)
|
||||
|
|
@ -2064,7 +2066,8 @@ def main():
|
|||
except:
|
||||
# It's ok not having stackless there...
|
||||
try:
|
||||
sys.exc_clear() # the exception information should be cleaned in Python 2
|
||||
if hasattr(sys, 'exc_clear'):
|
||||
sys.exc_clear() # the exception information should be cleaned in Python 2
|
||||
except:
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class DjangoLineBreakpoint(LineBreakpoint):
|
|||
return self.file == template_frame_file and self.line == template_frame_line
|
||||
|
||||
def __str__(self):
|
||||
return "DjangoLineBreakpoint: %s-%d" %(self.file, self.line)
|
||||
return "DjangoLineBreakpoint: %s-%d" % (self.file, self.line)
|
||||
|
||||
|
||||
def add_line_breakpoint(plugin, pydb, type, file, line, condition, expression, func_name, hit_condition=None, is_logpoint=False):
|
||||
|
|
@ -149,8 +149,6 @@ def suspend_django(main_debugger, thread, frame, cmd=CMD_SET_BREAK):
|
|||
if frame.f_lineno is None:
|
||||
return None
|
||||
|
||||
pydevd_vars.add_additional_frame_by_id(get_current_thread_id(thread), {id(frame): frame})
|
||||
|
||||
main_debugger.set_suspend(thread, cmd)
|
||||
thread.additional_info.suspend_type = DJANGO_SUSPEND
|
||||
|
||||
|
|
@ -356,7 +354,7 @@ def cmd_step_into(plugin, main_debugger, frame, event, args, stop_info, stop):
|
|||
plugin_stop = stop_info['django_stop']
|
||||
stop = stop and _is_django_resolve_call(frame.f_back) and not _is_django_context_get_call(frame)
|
||||
if stop:
|
||||
info.pydev_django_resolve_frame = True # we remember that we've go into python code from django rendering frame
|
||||
info.pydev_django_resolve_frame = True # we remember that we've go into python code from django rendering frame
|
||||
return stop, plugin_stop
|
||||
|
||||
|
||||
|
|
@ -371,7 +369,7 @@ def cmd_step_over(plugin, main_debugger, frame, event, args, stop_info, stop):
|
|||
return stop, plugin_stop
|
||||
else:
|
||||
if event == 'return' and info.pydev_django_resolve_frame and _is_django_resolve_call(frame.f_back):
|
||||
#we return to Django suspend mode and should not stop before django rendering frame
|
||||
# we return to Django suspend mode and should not stop before django rendering frame
|
||||
info.pydev_step_stop = frame.f_back
|
||||
info.pydev_django_resolve_frame = False
|
||||
thread.additional_info.suspend_type = DJANGO_SUSPEND
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class Jinja2LineBreakpoint(LineBreakpoint):
|
|||
return self.file == template_frame_file and self.line == template_frame_line
|
||||
|
||||
def __str__(self):
|
||||
return "Jinja2LineBreakpoint: %s-%d" %(self.file, self.line)
|
||||
return "Jinja2LineBreakpoint: %s-%d" % (self.file, self.line)
|
||||
|
||||
|
||||
def add_line_breakpoint(plugin, pydb, type, file, line, condition, expression, func_name, hit_condition=None, is_logpoint=False):
|
||||
|
|
@ -73,7 +73,6 @@ def _suspend_jinja2(pydb, thread, frame, cmd=CMD_SET_BREAK, message=None):
|
|||
if frame.f_lineno is None:
|
||||
return None
|
||||
|
||||
pydevd_vars.add_additional_frame_by_id(get_current_thread_id(thread), {id(frame): frame})
|
||||
pydb.set_suspend(thread, cmd)
|
||||
|
||||
thread.additional_info.suspend_type = JINJA2_SUSPEND
|
||||
|
|
@ -112,7 +111,7 @@ class Jinja2TemplateFrame:
|
|||
file_name = _get_jinja2_template_filename(frame)
|
||||
self.back_context = None
|
||||
if 'context' in frame.f_locals:
|
||||
#sometimes we don't have 'context', e.g. in macros
|
||||
# sometimes we don't have 'context', e.g. in macros
|
||||
self.back_context = frame.f_locals['context']
|
||||
self.f_code = FCode('template', file_name)
|
||||
self.f_lineno = _get_jinja2_template_line(frame)
|
||||
|
|
@ -172,7 +171,7 @@ def _is_missing(item):
|
|||
return False
|
||||
|
||||
def _find_render_function_frame(frame):
|
||||
#in order to hide internal rendering functions
|
||||
# in order to hide internal rendering functions
|
||||
old_frame = frame
|
||||
try:
|
||||
while not ('self' in frame.f_locals and frame.f_locals['self'].__class__.__name__ == 'Template' and \
|
||||
|
|
@ -189,7 +188,7 @@ def _get_jinja2_template_line(frame):
|
|||
if '__jinja_template__' in frame.f_globals:
|
||||
_debug_info = frame.f_globals['__jinja_template__']._debug_info
|
||||
if _debug_info != '':
|
||||
#sometimes template contains only plain text
|
||||
# sometimes template contains only plain text
|
||||
debug_info = frame.f_globals['__jinja_template__'].debug_info
|
||||
|
||||
if debug_info is None:
|
||||
|
|
@ -247,26 +246,26 @@ def cmd_step_into(plugin, pydb, frame, event, args, stop_info, stop):
|
|||
stop = False
|
||||
if info.pydev_call_from_jinja2 is not None:
|
||||
if _is_jinja2_internal_function(frame):
|
||||
#if internal Jinja2 function was called, we sould continue debugging inside template
|
||||
# if internal Jinja2 function was called, we sould continue debugging inside template
|
||||
info.pydev_call_from_jinja2 = None
|
||||
else:
|
||||
#we go into python code from Jinja2 rendering frame
|
||||
# we go into python code from Jinja2 rendering frame
|
||||
stop = True
|
||||
|
||||
if event == 'call' and _is_jinja2_context_call(frame.f_back):
|
||||
#we called function from context, the next step will be in function
|
||||
# we called function from context, the next step will be in function
|
||||
info.pydev_call_from_jinja2 = 1
|
||||
|
||||
if event == 'return' and _is_jinja2_context_call(frame.f_back):
|
||||
#we return from python code to Jinja2 rendering frame
|
||||
# we return from python code to Jinja2 rendering frame
|
||||
info.pydev_step_stop = info.pydev_call_from_jinja2
|
||||
info.pydev_call_from_jinja2 = None
|
||||
thread.additional_info.suspend_type = JINJA2_SUSPEND
|
||||
stop = False
|
||||
|
||||
#print "info.pydev_call_from_jinja2", info.pydev_call_from_jinja2, "stop_info", stop_info, \
|
||||
# print "info.pydev_call_from_jinja2", info.pydev_call_from_jinja2, "stop_info", stop_info, \
|
||||
# "thread.additional_info.suspend_type", thread.additional_info.suspend_type
|
||||
#print "event", event, "farme.locals", frame.f_locals
|
||||
# print "event", event, "farme.locals", frame.f_locals
|
||||
return stop, plugin_stop
|
||||
|
||||
|
||||
|
|
@ -295,19 +294,19 @@ def cmd_step_over(plugin, pydb, frame, event, args, stop_info, stop):
|
|||
return stop, plugin_stop
|
||||
else:
|
||||
if event == 'return' and _is_jinja2_context_call(frame.f_back):
|
||||
#we return from python code to Jinja2 rendering frame
|
||||
# we return from python code to Jinja2 rendering frame
|
||||
info.pydev_call_from_jinja2 = None
|
||||
info.pydev_call_inside_jinja2 = _find_jinja2_render_frame(frame)
|
||||
thread.additional_info.suspend_type = JINJA2_SUSPEND
|
||||
stop = False
|
||||
return stop, plugin_stop
|
||||
#print "info.pydev_call_from_jinja2", info.pydev_call_from_jinja2, "stop", stop, "jinja_stop", jinja2_stop, \
|
||||
# print "info.pydev_call_from_jinja2", info.pydev_call_from_jinja2, "stop", stop, "jinja_stop", jinja2_stop, \
|
||||
# "thread.additional_info.suspend_type", thread.additional_info.suspend_type
|
||||
#print "event", event, "info.pydev_call_inside_jinja2", info.pydev_call_inside_jinja2
|
||||
#print "frame", frame, "frame.f_back", frame.f_back, "step_stop", info.pydev_step_stop
|
||||
#print "is_context_call", _is_jinja2_context_call(frame)
|
||||
#print "render", _is_jinja2_render_call(frame)
|
||||
#print "-------------"
|
||||
# print "event", event, "info.pydev_call_inside_jinja2", info.pydev_call_inside_jinja2
|
||||
# print "frame", frame, "frame.f_back", frame.f_back, "step_stop", info.pydev_step_stop
|
||||
# print "is_context_call", _is_jinja2_context_call(frame)
|
||||
# print "render", _is_jinja2_render_call(frame)
|
||||
# print "-------------"
|
||||
return stop, plugin_stop
|
||||
|
||||
|
||||
|
|
@ -323,7 +322,7 @@ def stop(plugin, pydb, frame, event, args, stop_info, arg, step_cmd):
|
|||
|
||||
|
||||
def get_breakpoint(plugin, pydb, pydb_frame, frame, event, args):
|
||||
pydb= args[0]
|
||||
pydb = args[0]
|
||||
filename = args[1]
|
||||
info = args[2]
|
||||
new_frame = None
|
||||
|
|
@ -360,7 +359,7 @@ def exception_break(plugin, pydb, pydb_frame, frame, args, arg):
|
|||
if pydb.jinja2_exception_break and exception is not None:
|
||||
exception_type = dict_keys(pydb.jinja2_exception_break)[0]
|
||||
if exception.__name__ in ('UndefinedError', 'TemplateNotFound', 'TemplatesNotFound'):
|
||||
#errors in rendering
|
||||
# errors in rendering
|
||||
render_frame = _find_jinja2_render_frame(frame)
|
||||
if render_frame:
|
||||
suspend_frame = _suspend_jinja2(pydb, thread, render_frame, CMD_ADD_EXCEPTION_BREAK, message=exception_type)
|
||||
|
|
@ -371,10 +370,10 @@ def exception_break(plugin, pydb, pydb_frame, frame, args, arg):
|
|||
frame = suspend_frame
|
||||
return flag, frame
|
||||
elif exception.__name__ in ('TemplateSyntaxError', 'TemplateAssertionError'):
|
||||
#errors in compile time
|
||||
# errors in compile time
|
||||
name = frame.f_code.co_name
|
||||
if name in ('template', 'top-level template code', '<module>') or name.startswith('block '):
|
||||
#Jinja2 translates exception info and creates fake frame on his own
|
||||
# Jinja2 translates exception info and creates fake frame on his own
|
||||
pydb_frame.set_suspend(thread, CMD_ADD_EXCEPTION_BREAK)
|
||||
add_exception_to_frame(frame, (exception, value, trace))
|
||||
thread.additional_info.suspend_type = JINJA2_SUSPEND
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ if sys.version_info[0] == 2:
|
|||
IS_PY2 = True
|
||||
|
||||
IS_PY26 = sys.version_info[:2] == (2, 6)
|
||||
IS_PY27 = sys.version_info[:2] == (2, 7)
|
||||
IS_PY34 = sys.version_info[:2] == (3, 4)
|
||||
IS_PY36 = False
|
||||
if sys.version_info[0] == 3 and sys.version_info[1] == 6:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
from contextlib import contextmanager
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
|
|||
|
|
@ -179,6 +179,7 @@ class ReaderThread(threading.Thread):
|
|||
|
||||
def __init__(self, sock):
|
||||
threading.Thread.__init__(self)
|
||||
self.name = 'Test Reader Thread'
|
||||
try:
|
||||
from queue import Queue
|
||||
except ImportError:
|
||||
|
|
@ -234,7 +235,7 @@ class ReaderThread(threading.Thread):
|
|||
if IS_PY3K:
|
||||
show_line = line.decode('utf-8')
|
||||
|
||||
print('Test Reader Thread Received %s' % (show_line,))
|
||||
print('%s Received %s' % (self.name, show_line,))
|
||||
|
||||
if line.startswith(b'Content-Length:'):
|
||||
content_len = int(line.strip().split(b':', 1)[1])
|
||||
|
|
@ -331,7 +332,7 @@ class DebuggerRunner(object):
|
|||
args = self.add_command_line_args(args)
|
||||
|
||||
if SHOW_OTHER_DEBUG_INFO:
|
||||
print('executing', ' '.join(args))
|
||||
print('executing: %s' % (' '.join(args),))
|
||||
|
||||
with self.run_process(args, writer) as dct_with_stdout_stder:
|
||||
try:
|
||||
|
|
@ -562,6 +563,11 @@ class AbstractWriterThread(threading.Thread):
|
|||
dirname = os.path.dirname(dirname)
|
||||
return os.path.abspath(os.path.join(dirname, 'pydevd.py'))
|
||||
|
||||
def get_pydevconsole_file(self):
|
||||
dirname = os.path.dirname(__file__)
|
||||
dirname = os.path.dirname(dirname)
|
||||
return os.path.abspath(os.path.join(dirname, 'pydevconsole.py'))
|
||||
|
||||
def get_line_index_with_content(self, line_content):
|
||||
'''
|
||||
:return the line index which has the given content (1-based).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
import sys
|
||||
|
||||
from _pydevd_bundle.pydevd_custom_frames import add_custom_frame
|
||||
import threading
|
||||
|
||||
|
||||
def call1():
|
||||
add_custom_frame(sys._getframe(), 'call1', threading.current_thread().ident)
|
||||
|
||||
|
||||
def call2():
|
||||
add_custom_frame(sys._getframe(), 'call2', threading.current_thread().ident)
|
||||
|
||||
|
||||
def call3():
|
||||
add_custom_frame(sys._getframe(), 'call3', threading.current_thread().ident)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
call1() # break here
|
||||
call2()
|
||||
call3()
|
||||
print('TEST SUCEEDED')
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env python
|
||||
from gevent import monkey, sleep, threading as gevent_threading
|
||||
monkey.patch_all()
|
||||
import threading
|
||||
|
||||
called = []
|
||||
|
||||
|
||||
class MyGreenletThread(threading.Thread):
|
||||
|
||||
def run(self):
|
||||
for _i in range(5):
|
||||
called.append(self.name) # break here
|
||||
sleep()
|
||||
|
||||
if __name__ == '__main__':
|
||||
t1 = MyGreenletThread()
|
||||
t1.name = 't1'
|
||||
t2 = MyGreenletThread()
|
||||
t2.name = 't2'
|
||||
|
||||
if hasattr(gevent_threading, 'Thread'):
|
||||
# Only available in newer versions of gevent.
|
||||
assert isinstance(t1, gevent_threading.Thread)
|
||||
assert isinstance(t2, gevent_threading.Thread)
|
||||
|
||||
t1.start()
|
||||
t2.start()
|
||||
|
||||
for t1 in (t1, t2):
|
||||
t1.join()
|
||||
|
||||
# With gevent it's always the same (gevent coroutine support makes thread
|
||||
# switching serial).
|
||||
assert called == ['t1', 't1', 't2', 't1', 't2', 't1', 't2', 't1', 't2', 't2']
|
||||
print('TEST SUCEEDED')
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import time
|
||||
import multiprocessing
|
||||
import sys
|
||||
|
||||
|
||||
def run(name):
|
||||
|
|
@ -7,5 +7,7 @@ def run(name):
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if sys.version_info[0] >= 3 and sys.platform != 'win32':
|
||||
multiprocessing.set_start_method('fork')
|
||||
multiprocessing.Process(target=run, args=("argument to run method",)).start()
|
||||
print('TEST SUCEEDED!') # break 2 here
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
import time
|
||||
import multiprocessing
|
||||
import threading
|
||||
import sys
|
||||
import os
|
||||
|
||||
event = threading.Event()
|
||||
|
||||
|
||||
class MyThread(threading.Thread):
|
||||
|
||||
def run(self):
|
||||
_a = 10
|
||||
_b = 20 # break in thread here
|
||||
event.set()
|
||||
_c = 20
|
||||
|
||||
|
||||
def run_in_multiprocess():
|
||||
_a = 30
|
||||
_b = 40 # break in process here
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
MyThread().start()
|
||||
event.wait()
|
||||
if sys.version_info[0] >= 3 and sys.platform != 'win32':
|
||||
multiprocessing.set_start_method('fork')
|
||||
multiprocessing.Process(target=run_in_multiprocess, args=()).start()
|
||||
print('TEST SUCEEDED!') # break in main here
|
||||
205
src/ptvsd/_vendored/pydevd/tests_python/test_console.py
Normal file
205
src/ptvsd/_vendored/pydevd/tests_python/test_console.py
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
from contextlib import contextmanager
|
||||
|
||||
import pytest
|
||||
|
||||
from _pydev_bundle.pydev_override import overrides
|
||||
from tests_python.debugger_fixtures import DebuggerRunnerSimple
|
||||
from tests_python.debugger_unittest import AbstractWriterThread, SHOW_OTHER_DEBUG_INFO, \
|
||||
start_in_daemon_thread, wait_for_condition, IS_JYTHON
|
||||
from _pydev_bundle.pydev_localhost import get_socket_names, get_socket_name
|
||||
from _pydev_bundle.pydev_imports import xmlrpclib
|
||||
from _pydev_bundle.pydev_imports import _queue as queue
|
||||
from _pydev_bundle.pydev_imports import SimpleXMLRPCServer
|
||||
import time
|
||||
import socket
|
||||
from tests_python.debug_constants import IS_PY2
|
||||
|
||||
if IS_PY2:
|
||||
builtin_qualifier = "__builtin__"
|
||||
else:
|
||||
builtin_qualifier = "builtins"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def console_setup():
|
||||
|
||||
server_queue = queue.Queue()
|
||||
|
||||
def notify_finished(more):
|
||||
server_queue.put(('notify_finished', more))
|
||||
return ''
|
||||
|
||||
class ConsoleRunner(DebuggerRunnerSimple):
|
||||
|
||||
@overrides(DebuggerRunnerSimple.add_command_line_args)
|
||||
def add_command_line_args(self, args):
|
||||
port, client_port = get_socket_names(2, close=True)
|
||||
args.extend((
|
||||
writer.get_pydevconsole_file(),
|
||||
str(port[1]),
|
||||
str(client_port[1])
|
||||
))
|
||||
self.port = port
|
||||
self.client_port = client_port
|
||||
|
||||
server = SimpleXMLRPCServer(client_port)
|
||||
server.register_function(notify_finished, "NotifyFinished")
|
||||
start_in_daemon_thread(server.serve_forever, [])
|
||||
|
||||
self.proxy = xmlrpclib.ServerProxy("http://%s:%s/" % port)
|
||||
|
||||
return args
|
||||
|
||||
class WriterThread(AbstractWriterThread):
|
||||
|
||||
if IS_JYTHON:
|
||||
EXPECTED_RETURNCODE = 'any'
|
||||
|
||||
@overrides(AbstractWriterThread.additional_output_checks)
|
||||
def additional_output_checks(self, stdout, stderr):
|
||||
print('output found: %s - %s' % (stdout, stderr))
|
||||
|
||||
@overrides(AbstractWriterThread.write_dump_threads)
|
||||
def write_dump_threads(self):
|
||||
pass # no-op (may be called on timeout).
|
||||
|
||||
def execute_line(self, command, more=False):
|
||||
runner.proxy.execLine(command)
|
||||
assert server_queue.get(timeout=5.) == ('notify_finished', more)
|
||||
|
||||
def hello(self):
|
||||
|
||||
def _hello():
|
||||
try:
|
||||
msg = runner.proxy.hello('ignored')
|
||||
if msg is not None:
|
||||
if isinstance(msg, (list, tuple)):
|
||||
msg = next(iter(msg))
|
||||
if msg.lower().startswith('hello'):
|
||||
return True
|
||||
except:
|
||||
# That's ok, communication still not ready.
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
wait_for_condition(_hello)
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
runner.proxy.close()
|
||||
except:
|
||||
# Ignore any errors on close.
|
||||
pass
|
||||
|
||||
def connect_to_debugger(self, debugger_port):
|
||||
runner.proxy.connectToDebugger(debugger_port)
|
||||
|
||||
runner = ConsoleRunner()
|
||||
writer = WriterThread()
|
||||
|
||||
class CaseSetup(object):
|
||||
|
||||
@contextmanager
|
||||
def check_console(
|
||||
self,
|
||||
**kwargs
|
||||
):
|
||||
for key, value in kwargs.items():
|
||||
assert hasattr(WriterThread, key)
|
||||
setattr(WriterThread, key, value)
|
||||
|
||||
self.writer = writer
|
||||
|
||||
args = runner.get_command_line()
|
||||
|
||||
args = runner.add_command_line_args(args)
|
||||
|
||||
if SHOW_OTHER_DEBUG_INFO:
|
||||
print('executing: %s' % (' '.join(args),))
|
||||
try:
|
||||
with runner.run_process(args, writer) as dct_with_stdout_stder:
|
||||
writer.get_stdout = lambda: ''.join(dct_with_stdout_stder['stdout'])
|
||||
writer.get_stderr = lambda: ''.join(dct_with_stdout_stder['stderr'])
|
||||
|
||||
# Make sure communication is setup.
|
||||
writer.hello()
|
||||
yield writer
|
||||
finally:
|
||||
writer.log = []
|
||||
|
||||
stdout = dct_with_stdout_stder['stdout']
|
||||
stderr = dct_with_stdout_stder['stderr']
|
||||
writer.additional_output_checks(''.join(stdout), ''.join(stderr))
|
||||
|
||||
return CaseSetup()
|
||||
|
||||
|
||||
def test_console_simple(console_setup):
|
||||
with console_setup.check_console() as writer:
|
||||
writer.execute_line('a = 10')
|
||||
writer.execute_line('print("TEST SUCEEDED")')
|
||||
writer.close()
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
def test_console_debugger_connected(console_setup):
|
||||
|
||||
class _DebuggerWriterThread(AbstractWriterThread):
|
||||
|
||||
FORCE_KILL_PROCESS_WHEN_FINISHED_OK = True
|
||||
|
||||
def __init__(self):
|
||||
AbstractWriterThread.__init__(self)
|
||||
socket_name = get_socket_name(close=True)
|
||||
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
server_socket.bind(socket_name)
|
||||
server_socket.listen(1)
|
||||
self.port = socket_name[1]
|
||||
self.__server_socket = server_socket
|
||||
|
||||
def run(self):
|
||||
print('waiting for second process')
|
||||
self.sock, addr = self.__server_socket.accept()
|
||||
print('accepted second process')
|
||||
|
||||
from tests_python.debugger_unittest import ReaderThread
|
||||
self.reader_thread = ReaderThread(self.sock)
|
||||
self.reader_thread.start()
|
||||
|
||||
self._sequence = -1
|
||||
# initial command is always the version
|
||||
self.write_version()
|
||||
self.log.append('start_socket')
|
||||
self.write_make_initial_run()
|
||||
time.sleep(1)
|
||||
|
||||
seq = self.write_list_threads()
|
||||
msg = self.wait_for_list_threads(seq)
|
||||
assert msg.thread['name'] == 'MainThread'
|
||||
assert msg.thread['id'] == 'console_main'
|
||||
|
||||
self.write_get_frame('console_main', '1')
|
||||
self.wait_for_vars([
|
||||
[
|
||||
'<var name="a" type="int" qualifier="%s" value="int: 10"' % (builtin_qualifier,),
|
||||
'<var name="a" type="int" value="int', # jython
|
||||
],
|
||||
])
|
||||
|
||||
self.finished_ok = True
|
||||
|
||||
with console_setup.check_console() as writer:
|
||||
writer.execute_line('a = 10')
|
||||
|
||||
debugger_writer_thread = _DebuggerWriterThread()
|
||||
debugger_writer_thread.start()
|
||||
writer.connect_to_debugger(debugger_writer_thread.port)
|
||||
|
||||
wait_for_condition(lambda: debugger_writer_thread.finished_ok)
|
||||
writer.execute_line('print("TEST SUCEEDED")')
|
||||
|
||||
writer.close()
|
||||
writer.finished_ok = True
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ from tests_python.debugger_unittest import (CMD_SET_PROPERTY_TRACE, REASON_CAUGH
|
|||
IS_APPVEYOR, wait_for_condition, CMD_GET_FRAME, CMD_GET_BREAKPOINT_EXCEPTION,
|
||||
CMD_THREAD_SUSPEND, CMD_STEP_OVER, REASON_STEP_OVER, CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION,
|
||||
CMD_THREAD_RESUME_SINGLE_NOTIFICATION, REASON_STEP_RETURN, REASON_STEP_RETURN_MY_CODE,
|
||||
REASON_STEP_OVER_MY_CODE, REASON_STEP_INTO)
|
||||
REASON_STEP_OVER_MY_CODE, REASON_STEP_INTO, CMD_THREAD_KILL)
|
||||
from _pydevd_bundle.pydevd_constants import IS_WINDOWS
|
||||
from _pydevd_bundle.pydevd_comm_constants import CMD_RELOAD_CODE
|
||||
try:
|
||||
|
|
@ -2096,7 +2096,7 @@ def test_debug_zip_files(case_setup, tmpdir):
|
|||
|
||||
|
||||
@pytest.mark.skipif(not IS_CPYTHON, reason='CPython only test.')
|
||||
def test_multiprocessing(case_setup_multiprocessing):
|
||||
def test_multiprocessing_simple(case_setup_multiprocessing):
|
||||
import threading
|
||||
from tests_python.debugger_unittest import AbstractWriterThread
|
||||
with case_setup_multiprocessing.test_file('_debugger_case_multiprocessing.py') as writer:
|
||||
|
|
@ -2122,6 +2122,7 @@ def test_multiprocessing(case_setup_multiprocessing):
|
|||
new_sock, addr = server_socket.accept()
|
||||
|
||||
reader_thread = ReaderThread(new_sock)
|
||||
reader_thread.name = ' *** Multiprocess Reader Thread'
|
||||
reader_thread.start()
|
||||
|
||||
writer2 = SecondaryProcessWriterThread()
|
||||
|
|
@ -2147,6 +2148,76 @@ def test_multiprocessing(case_setup_multiprocessing):
|
|||
writer.finished_ok = True
|
||||
|
||||
|
||||
@pytest.mark.skipif(not IS_CPYTHON, reason='CPython only test.')
|
||||
def test_multiprocessing_with_stopped_breakpoints(case_setup_multiprocessing):
|
||||
import threading
|
||||
from tests_python.debugger_unittest import AbstractWriterThread
|
||||
with case_setup_multiprocessing.test_file('_debugger_case_multiprocessing_stopped_threads.py') as writer:
|
||||
break_main_line = writer.get_line_index_with_content('break in main here')
|
||||
break_thread_line = writer.get_line_index_with_content('break in thread here')
|
||||
break_process_line = writer.get_line_index_with_content('break in process here')
|
||||
|
||||
writer.write_add_breakpoint(break_main_line)
|
||||
writer.write_add_breakpoint(break_thread_line)
|
||||
writer.write_add_breakpoint(break_process_line)
|
||||
|
||||
server_socket = writer.server_socket
|
||||
|
||||
class SecondaryProcessWriterThread(AbstractWriterThread):
|
||||
|
||||
TEST_FILE = writer.get_main_filename()
|
||||
_sequence = -1
|
||||
|
||||
class SecondaryProcessThreadCommunication(threading.Thread):
|
||||
|
||||
def run(self):
|
||||
from tests_python.debugger_unittest import ReaderThread
|
||||
server_socket.listen(1)
|
||||
self.server_socket = server_socket
|
||||
new_sock, addr = server_socket.accept()
|
||||
|
||||
reader_thread = ReaderThread(new_sock)
|
||||
reader_thread.name = ' *** Multiprocess Reader Thread'
|
||||
reader_thread.start()
|
||||
|
||||
writer2 = SecondaryProcessWriterThread()
|
||||
|
||||
writer2.reader_thread = reader_thread
|
||||
writer2.sock = new_sock
|
||||
|
||||
writer2.write_version()
|
||||
writer2.write_add_breakpoint(break_main_line)
|
||||
writer2.write_add_breakpoint(break_thread_line)
|
||||
writer2.write_add_breakpoint(break_process_line)
|
||||
writer2.write_make_initial_run()
|
||||
hit = writer2.wait_for_breakpoint_hit()
|
||||
writer2.write_run_thread(hit.thread_id)
|
||||
|
||||
secondary_process_thread_communication = SecondaryProcessThreadCommunication()
|
||||
secondary_process_thread_communication.start()
|
||||
writer.write_make_initial_run()
|
||||
hit2 = writer.wait_for_breakpoint_hit() # Breaks in thread.
|
||||
writer.write_step_over(hit2.thread_id)
|
||||
|
||||
hit2 = writer.wait_for_breakpoint_hit(REASON_STEP_OVER) # line == event.set()
|
||||
|
||||
# paused on breakpoint, will start process and pause on main thread
|
||||
# in the main process too.
|
||||
writer.write_step_over(hit2.thread_id)
|
||||
|
||||
# Note: ignore the step over hit (go only for the breakpoint hit).
|
||||
main_hit = writer.wait_for_breakpoint_hit(REASON_STOP_ON_BREAKPOINT)
|
||||
|
||||
secondary_process_thread_communication.join(10)
|
||||
if secondary_process_thread_communication.isAlive():
|
||||
raise AssertionError('The SecondaryProcessThreadCommunication did not finish')
|
||||
|
||||
writer.write_run_thread(hit2.thread_id)
|
||||
writer.write_run_thread(main_hit.thread_id)
|
||||
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
@pytest.mark.skipif(not IS_CPYTHON, reason='CPython only test.')
|
||||
def test_remote_debugger_basic(case_setup_remote):
|
||||
with case_setup_remote.test_file('_debugger_case_remote.py') as writer:
|
||||
|
|
@ -2404,7 +2475,7 @@ a = 10 # break here
|
|||
assert my_temp2.call() == 2
|
||||
print('TEST SUCEEDED!')
|
||||
''')
|
||||
|
||||
|
||||
path2 = tmpdir.join('my_temp2.py')
|
||||
path2.write('''
|
||||
def call():
|
||||
|
|
@ -2415,7 +2486,7 @@ def call():
|
|||
writer.write_add_breakpoint(break_line, '')
|
||||
writer.write_make_initial_run()
|
||||
hit = writer.wait_for_breakpoint_hit()
|
||||
|
||||
|
||||
path2 = tmpdir.join('my_temp2.py')
|
||||
path2.write('''
|
||||
def call():
|
||||
|
|
@ -2428,6 +2499,56 @@ def call():
|
|||
writer.finished_ok = True
|
||||
|
||||
|
||||
@pytest.mark.skipif(IS_JYTHON, reason='Not working with Jython on ci (needs investigation).')
|
||||
def test_custom_frames(case_setup):
|
||||
with case_setup.test_file('_debugger_case_custom_frames.py') as writer:
|
||||
writer.write_add_breakpoint(writer.get_line_index_with_content('break here'))
|
||||
writer.write_make_initial_run()
|
||||
|
||||
hit = writer.wait_for_breakpoint_hit()
|
||||
|
||||
for i in range(3):
|
||||
writer.write_step_over(hit.thread_id)
|
||||
|
||||
# Check that the frame-related threads have been killed.
|
||||
for _ in range(i):
|
||||
writer.wait_for_message(CMD_THREAD_KILL, expect_xml=False)
|
||||
|
||||
# Main thread stopped
|
||||
writer.wait_for_breakpoint_hit(REASON_STEP_OVER)
|
||||
|
||||
# At each time we have an additional custom frame (which is shown as if it
|
||||
# was a thread which is created and then suspended).
|
||||
for _ in range(i):
|
||||
writer.wait_for_message(CMD_THREAD_CREATE)
|
||||
writer.wait_for_breakpoint_hit(REASON_THREAD_SUSPEND)
|
||||
|
||||
writer.write_run_thread(hit.thread_id)
|
||||
|
||||
# Check that the frame-related threads have been killed.
|
||||
for _ in range(i):
|
||||
writer.wait_for_message(CMD_THREAD_KILL, expect_xml=False)
|
||||
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
@pytest.mark.skipif((not (IS_PY36 or IS_PY27)) or IS_JYTHON, reason='Gevent only installed on Py36/Py27 for tests.')
|
||||
def test_gevent(case_setup):
|
||||
|
||||
def get_environ(writer):
|
||||
env = os.environ.copy()
|
||||
env['GEVENT_SUPPORT'] = 'True'
|
||||
return env
|
||||
|
||||
with case_setup.test_file('_debugger_case_gevent.py', get_environ=get_environ) as writer:
|
||||
writer.write_add_breakpoint(writer.get_line_index_with_content('break here'))
|
||||
writer.write_make_initial_run()
|
||||
for _i in range(10):
|
||||
hit = writer.wait_for_breakpoint_hit(name='run')
|
||||
writer.write_run_thread(hit.thread_id)
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
def test_return_value(case_setup):
|
||||
with case_setup.test_file('_debugger_case_return_value.py') as writer:
|
||||
break_line = writer.get_line_index_with_content('break here')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue