mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
Merge branch '45_json_variables'
This commit is contained in:
commit
11ffd749be
33 changed files with 1953 additions and 1025 deletions
|
|
@ -91,7 +91,10 @@ def get_response_class(request):
|
|||
|
||||
def build_response(request, kwargs=None):
|
||||
if kwargs is None:
|
||||
kwargs = {}
|
||||
kwargs = {'success':True}
|
||||
else:
|
||||
if 'success' not in kwargs:
|
||||
kwargs['success'] = True
|
||||
response_class = _responses_to_types[request.command]
|
||||
kwargs.setdefault('seq', -1) # To be overwritten before sending
|
||||
return response_class(command=request.command, request_seq=request.seq, success=True, **kwargs)
|
||||
return response_class(command=request.command, request_seq=request.seq, **kwargs)
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ from _pydevd_bundle import pydevd_utils
|
|||
from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info
|
||||
from _pydevd_bundle.pydevd_comm import (InternalGetThreadStack, internal_get_completions,
|
||||
pydevd_find_thread_by_id, InternalStepThread, InternalSetNextStatementThread, internal_reload_code,
|
||||
InternalChangeVariable, InternalGetVariable, InternalGetArray, InternalLoadFullValue,
|
||||
internal_get_description, internal_get_frame, internal_evaluate_expression, InternalConsoleExec)
|
||||
InternalGetVariable, InternalGetArray, InternalLoadFullValue,
|
||||
internal_get_description, internal_get_frame, internal_evaluate_expression, InternalConsoleExec,
|
||||
internal_get_variable_json, internal_change_variable, internal_change_variable_json,
|
||||
internal_evaluate_expression_json, internal_set_expression_json)
|
||||
from _pydevd_bundle.pydevd_comm_constants import CMD_THREAD_SUSPEND, file_system_encoding
|
||||
from _pydevd_bundle.pydevd_constants import (get_current_thread_id, set_protocol, get_protocol,
|
||||
HTTP_JSON_PROTOCOL, JSON_PROTOCOL, STATE_RUN, IS_PY3K, DebugInfoHolder, dict_keys)
|
||||
|
|
@ -115,9 +117,10 @@ class PyDevdAPI(object):
|
|||
py_db.post_method_as_internal_command(
|
||||
thread_id, internal_get_completions, seq, thread_id, frame_id, act_tok, line=line, column=column)
|
||||
|
||||
def request_stack(self, py_db, seq, thread_id, timeout=.5):
|
||||
def request_stack(self, py_db, seq, thread_id, fmt=None, timeout=.5):
|
||||
# If it's already suspended, get it right away.
|
||||
internal_get_thread_stack = InternalGetThreadStack(seq, thread_id, py_db, set_additional_thread_info, timeout=timeout)
|
||||
internal_get_thread_stack = InternalGetThreadStack(
|
||||
seq, thread_id, py_db, set_additional_thread_info, fmt=fmt, timeout=timeout)
|
||||
if internal_get_thread_stack.can_be_executed_by(get_current_thread_id(threading.current_thread())):
|
||||
internal_get_thread_stack.do_it(py_db)
|
||||
else:
|
||||
|
|
@ -151,8 +154,8 @@ class PyDevdAPI(object):
|
|||
'''
|
||||
:param scope: 'FRAME' or 'GLOBAL'
|
||||
'''
|
||||
int_cmd = InternalChangeVariable(seq, thread_id, frame_id, scope, attr, value)
|
||||
py_db.post_internal_command(int_cmd, thread_id)
|
||||
py_db.post_method_as_internal_command(
|
||||
thread_id, internal_change_variable, seq, thread_id, frame_id, scope, attr, value)
|
||||
|
||||
def request_get_variable(self, py_db, seq, thread_id, frame_id, scope, attrs):
|
||||
'''
|
||||
|
|
@ -161,8 +164,8 @@ class PyDevdAPI(object):
|
|||
int_cmd = InternalGetVariable(seq, thread_id, frame_id, scope, attrs)
|
||||
py_db.post_internal_command(int_cmd, thread_id)
|
||||
|
||||
def request_get_array(self, py_db, seq, roffset, coffset, rows, cols, format, thread_id, frame_id, scope, attrs):
|
||||
int_cmd = InternalGetArray(seq, roffset, coffset, rows, cols, format, thread_id, frame_id, scope, attrs)
|
||||
def request_get_array(self, py_db, seq, roffset, coffset, rows, cols, fmt, thread_id, frame_id, scope, attrs):
|
||||
int_cmd = InternalGetArray(seq, roffset, coffset, rows, cols, fmt, thread_id, frame_id, scope, attrs)
|
||||
py_db.post_internal_command(int_cmd, thread_id)
|
||||
|
||||
def request_load_full_value(self, py_db, seq, thread_id, frame_id, vars):
|
||||
|
|
@ -366,6 +369,15 @@ class PyDevdAPI(object):
|
|||
thread_id, internal_evaluate_expression,
|
||||
seq, thread_id, frame_id, expression, is_exec, trim_if_too_big, attr_to_set_result)
|
||||
|
||||
def request_exec_or_evaluate_json(
|
||||
self, py_db, request, thread_id):
|
||||
py_db.post_method_as_internal_command(
|
||||
thread_id, internal_evaluate_expression_json, request, thread_id)
|
||||
|
||||
def request_set_expression_json(self, py_db, request, thread_id):
|
||||
py_db.post_method_as_internal_command(
|
||||
thread_id, internal_set_expression_json, request, thread_id)
|
||||
|
||||
def request_console_exec(self, py_db, seq, thread_id, frame_id, expression):
|
||||
int_cmd = InternalConsoleExec(seq, thread_id, frame_id, expression)
|
||||
py_db.post_internal_command(int_cmd, thread_id)
|
||||
|
|
@ -470,3 +482,16 @@ class PyDevdAPI(object):
|
|||
def set_use_libraries_filter(self, py_db, use_libraries_filter):
|
||||
py_db.set_use_libraries_filter(use_libraries_filter)
|
||||
|
||||
def request_get_variable_json(self, py_db, request, thread_id):
|
||||
'''
|
||||
:param VariablesRequest request:
|
||||
'''
|
||||
py_db.post_method_as_internal_command(
|
||||
thread_id, internal_get_variable_json, request)
|
||||
|
||||
def request_change_variable_json(self, py_db, request, thread_id):
|
||||
'''
|
||||
:param SetVariableRequest request:
|
||||
'''
|
||||
py_db.post_method_as_internal_command(
|
||||
thread_id, internal_change_variable_json, request)
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ each command has a format:
|
|||
'''
|
||||
|
||||
import itertools
|
||||
import os
|
||||
|
||||
from _pydev_bundle.pydev_imports import _queue
|
||||
from _pydev_imps._pydev_saved_modules import time
|
||||
|
|
@ -76,6 +77,11 @@ from _pydevd_bundle.pydevd_constants import (DebugInfoHolder, get_thread_id, IS_
|
|||
from _pydev_bundle.pydev_override import overrides
|
||||
import weakref
|
||||
from _pydev_bundle._pydev_completer import extract_token_and_qualifier
|
||||
from _pydevd_bundle._debug_adapter.pydevd_schema import VariablesResponseBody, \
|
||||
SetVariableResponseBody
|
||||
from _pydevd_bundle._debug_adapter import pydevd_base_schema, pydevd_schema
|
||||
from _pydevd_bundle.pydevd_net_command import NetCommand
|
||||
from _pydevd_bundle.pydevd_xml import ExceptionOnEvaluate
|
||||
try:
|
||||
from urllib import quote_plus, unquote_plus
|
||||
except:
|
||||
|
|
@ -435,7 +441,9 @@ def start_client(host, port):
|
|||
pass # May not be available everywhere.
|
||||
|
||||
try:
|
||||
s.settimeout(10) # 10 seconds timeout
|
||||
# 10 seconds default timeout
|
||||
timeout = int(os.environ.get('PYDEVD_CONNECT_TIMEOUT', 10))
|
||||
s.settimeout(timeout)
|
||||
s.connect((host, port))
|
||||
s.settimeout(None) # no timeout after connected
|
||||
pydevd_log(1, "Connected.")
|
||||
|
|
@ -541,12 +549,13 @@ class InternalGetThreadStack(InternalThreadCommand):
|
|||
stopped in a breakpoint).
|
||||
'''
|
||||
|
||||
def __init__(self, seq, thread_id, py_db, set_additional_thread_info, timeout=.5):
|
||||
def __init__(self, seq, thread_id, py_db, set_additional_thread_info, fmt, timeout=.5):
|
||||
InternalThreadCommand.__init__(self, thread_id)
|
||||
self._py_db = weakref.ref(py_db)
|
||||
self._timeout = time.time() + timeout
|
||||
self.seq = seq
|
||||
self._cmd = None
|
||||
self._fmt = fmt
|
||||
|
||||
# Note: receives set_additional_thread_info to avoid a circular import
|
||||
# in this module.
|
||||
|
|
@ -564,7 +573,7 @@ class InternalGetThreadStack(InternalThreadCommand):
|
|||
frame = additional_info.get_topmost_frame(t)
|
||||
try:
|
||||
self._cmd = py_db.cmd_factory.make_get_thread_stack_message(
|
||||
py_db, self.seq, self.thread_id, frame, must_be_suspended=not timed_out)
|
||||
py_db, self.seq, self.thread_id, frame, self._fmt, must_be_suspended=not timed_out)
|
||||
finally:
|
||||
frame = None
|
||||
t = None
|
||||
|
|
@ -626,6 +635,30 @@ class InternalSetNextStatementThread(InternalThreadCommand):
|
|||
t.additional_info.pydev_message = str(self.seq)
|
||||
|
||||
|
||||
def internal_get_variable_json(py_db, request):
|
||||
'''
|
||||
:param VariablesRequest request:
|
||||
'''
|
||||
arguments = request.arguments # : :type arguments: VariablesArguments
|
||||
variables_reference = arguments.variablesReference
|
||||
fmt = arguments.format
|
||||
if hasattr(fmt, 'to_dict'):
|
||||
fmt = fmt.to_dict()
|
||||
|
||||
variables = []
|
||||
try:
|
||||
variable = py_db.suspended_frames_manager.get_variable(variables_reference)
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
for child_var in variable.get_children_variables(fmt=fmt):
|
||||
variables.append(child_var.get_var_data(fmt=fmt))
|
||||
|
||||
body = VariablesResponseBody(variables)
|
||||
variables_response = pydevd_base_schema.build_response(request, kwargs={'body':body})
|
||||
py_db.writer.add_command(NetCommand(CMD_RETURN, 0, variables_response.to_dict(), is_json=True))
|
||||
|
||||
|
||||
class InternalGetVariable(InternalThreadCommand):
|
||||
''' gets the value of a variable '''
|
||||
|
||||
|
|
@ -641,7 +674,8 @@ class InternalGetVariable(InternalThreadCommand):
|
|||
try:
|
||||
xml = StringIO.StringIO()
|
||||
xml.write("<xml>")
|
||||
_typeName, val_dict = pydevd_vars.resolve_compound_variable_fields(dbg, 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 = {}
|
||||
|
||||
|
|
@ -693,29 +727,73 @@ class InternalGetArray(InternalThreadCommand):
|
|||
dbg.writer.add_command(cmd)
|
||||
|
||||
|
||||
class InternalChangeVariable(InternalThreadCommand):
|
||||
''' changes the value of a variable '''
|
||||
def internal_change_variable(dbg, seq, thread_id, frame_id, scope, attr, value):
|
||||
''' Changes the value of a variable '''
|
||||
try:
|
||||
frame = dbg.find_frame(thread_id, frame_id)
|
||||
if frame is not None:
|
||||
result = pydevd_vars.change_attr_expression(frame, attr, value, dbg)
|
||||
else:
|
||||
result = None
|
||||
xml = "<xml>"
|
||||
xml += pydevd_xml.var_to_xml(result, "")
|
||||
xml += "</xml>"
|
||||
cmd = dbg.cmd_factory.make_variable_changed_message(seq, xml)
|
||||
dbg.writer.add_command(cmd)
|
||||
except Exception:
|
||||
cmd = dbg.cmd_factory.make_error_message(seq, "Error changing variable attr:%s expression:%s traceback:%s" % (attr, value, get_exception_traceback_str()))
|
||||
dbg.writer.add_command(cmd)
|
||||
|
||||
def __init__(self, seq, thread_id, frame_id, scope, attr, expression):
|
||||
self.sequence = seq
|
||||
self.thread_id = thread_id
|
||||
self.frame_id = frame_id
|
||||
self.scope = scope
|
||||
self.attr = attr
|
||||
self.expression = expression
|
||||
|
||||
def do_it(self, dbg):
|
||||
''' Converts request into python variable '''
|
||||
try:
|
||||
result = pydevd_vars.change_attr_expression(self.thread_id, self.frame_id, self.attr, self.expression, dbg)
|
||||
xml = "<xml>"
|
||||
xml += pydevd_xml.var_to_xml(result, "")
|
||||
xml += "</xml>"
|
||||
cmd = dbg.cmd_factory.make_variable_changed_message(self.sequence, xml)
|
||||
dbg.writer.add_command(cmd)
|
||||
except Exception:
|
||||
cmd = dbg.cmd_factory.make_error_message(self.sequence, "Error changing variable attr:%s expression:%s traceback:%s" % (self.attr, self.expression, get_exception_traceback_str()))
|
||||
dbg.writer.add_command(cmd)
|
||||
def internal_change_variable_json(py_db, request):
|
||||
'''
|
||||
The pydevd_vars.change_attr_expression(thread_id, frame_id, attr, value, dbg) can only
|
||||
deal with changing at a frame level, so, currently changing the contents of something
|
||||
in a different scope is currently not supported.
|
||||
|
||||
TODO: make the resolvers structure resolve the name and change accordingly -- for instance, the
|
||||
list resolver should change the value considering the index.
|
||||
|
||||
:param SetVariableRequest request:
|
||||
'''
|
||||
# : :type arguments: SetVariableArguments
|
||||
arguments = request.arguments
|
||||
variables_reference = arguments.variablesReference
|
||||
fmt = arguments.format
|
||||
if hasattr(fmt, 'to_dict'):
|
||||
fmt = fmt.to_dict()
|
||||
|
||||
# : :type frame: _FrameVariable
|
||||
frame_variable = py_db.suspended_frames_manager.get_variable(variables_reference)
|
||||
if hasattr(frame_variable, 'frame'):
|
||||
frame = frame_variable.frame
|
||||
|
||||
pydevd_vars.change_attr_expression(frame, arguments.name, arguments.value, py_db)
|
||||
|
||||
for child_var in frame_variable.get_children_variables(fmt=fmt):
|
||||
if child_var.get_name() == arguments.name:
|
||||
var_data = child_var.get_var_data(fmt=fmt)
|
||||
body = SetVariableResponseBody(
|
||||
value=var_data['value'],
|
||||
type=var_data['type'],
|
||||
variablesReference=var_data.get('variablesReference'),
|
||||
namedVariables=var_data.get('namedVariables'),
|
||||
indexedVariables=var_data.get('indexedVariables'),
|
||||
)
|
||||
variables_response = pydevd_base_schema.build_response(request, kwargs={'body':body})
|
||||
py_db.writer.add_command(NetCommand(CMD_RETURN, 0, variables_response.to_dict(), is_json=True))
|
||||
break
|
||||
|
||||
# If it's gotten here we haven't been able to evaluate it properly. Let the client know.
|
||||
body = SetVariableResponseBody('')
|
||||
variables_response = pydevd_base_schema.build_response(
|
||||
request,
|
||||
kwargs={
|
||||
'body':body,
|
||||
'success': False,
|
||||
'message': 'Unable to change: %s.' % (arguments.name,)
|
||||
})
|
||||
return NetCommand(CMD_RETURN, 0, variables_response.to_dict(), is_json=True)
|
||||
|
||||
|
||||
def internal_get_frame(dbg, seq, thread_id, frame_id):
|
||||
|
|
@ -771,12 +849,93 @@ def internal_get_next_statement_targets(dbg, seq, thread_id, frame_id):
|
|||
dbg.writer.add_command(cmd)
|
||||
|
||||
|
||||
def _evaluate_response(py_db, request, result, error_message=''):
|
||||
is_error = isinstance(result, ExceptionOnEvaluate)
|
||||
if is_error:
|
||||
result = result.result
|
||||
if not error_message:
|
||||
body = pydevd_schema.EvaluateResponseBody(result=result, variablesReference=0)
|
||||
variables_response = pydevd_base_schema.build_response(request, kwargs={'body':body})
|
||||
py_db.writer.add_command(NetCommand(CMD_RETURN, 0, variables_response.to_dict(), is_json=True))
|
||||
else:
|
||||
body = pydevd_schema.EvaluateResponseBody(result='', variablesReference=0)
|
||||
variables_response = pydevd_base_schema.build_response(request, kwargs={
|
||||
'body':body, 'success':False, 'message': error_message})
|
||||
py_db.writer.add_command(NetCommand(CMD_RETURN, 0, variables_response.to_dict(), is_json=True))
|
||||
|
||||
|
||||
def internal_evaluate_expression_json(py_db, request, thread_id):
|
||||
'''
|
||||
:param EvaluateRequest request:
|
||||
'''
|
||||
# : :type arguments: EvaluateArguments
|
||||
|
||||
arguments = request.arguments
|
||||
expression = arguments.expression
|
||||
frame_id = arguments.frameId
|
||||
context = arguments.context
|
||||
fmt = arguments.format
|
||||
if hasattr(fmt, 'to_dict'):
|
||||
fmt = fmt.to_dict()
|
||||
|
||||
if IS_PY2 and isinstance(expression, unicode):
|
||||
try:
|
||||
expression = expression.encode('utf-8')
|
||||
except:
|
||||
_evaluate_response(py_db, request, '', error_message='Expression is not valid utf-8.')
|
||||
raise
|
||||
|
||||
frame = py_db.find_frame(thread_id, frame_id)
|
||||
result = pydevd_vars.evaluate_expression(py_db, frame, expression, is_exec=False)
|
||||
is_error = isinstance(result, ExceptionOnEvaluate)
|
||||
|
||||
if is_error:
|
||||
if context == 'hover':
|
||||
_evaluate_response(py_db, request, result='')
|
||||
return
|
||||
|
||||
elif context == 'repl':
|
||||
try:
|
||||
pydevd_vars.evaluate_expression(py_db, frame, expression, is_exec=True)
|
||||
except:
|
||||
pass
|
||||
# No result on exec.
|
||||
_evaluate_response(py_db, request, result='')
|
||||
return
|
||||
|
||||
# Ok, we have the result (could be an error), let's put it into the saved variables.
|
||||
frame_tracker = py_db.suspended_frames_manager.get_frame_tracker(thread_id)
|
||||
if frame_tracker is None:
|
||||
# This is not really expected.
|
||||
_evaluate_response(py_db, request, result, error_message='Thread id: %s is not current thread id.' % (thread_id,))
|
||||
return
|
||||
|
||||
variable = frame_tracker.obtain_as_variable(expression, result)
|
||||
var_data = variable.get_var_data(fmt=fmt)
|
||||
|
||||
body = pydevd_schema.EvaluateResponseBody(
|
||||
result=var_data['value'],
|
||||
variablesReference=var_data.get('variableReference', 0),
|
||||
type=var_data.get('type'),
|
||||
presentationHint=var_data.get('presentationHint'),
|
||||
namedVariables=var_data.get('namedVariables'),
|
||||
indexedVariables=var_data.get('indexedVariables'),
|
||||
)
|
||||
variables_response = pydevd_base_schema.build_response(request, kwargs={'body':body})
|
||||
py_db.writer.add_command(NetCommand(CMD_RETURN, 0, variables_response.to_dict(), is_json=True))
|
||||
|
||||
|
||||
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(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)
|
||||
frame = dbg.find_frame(thread_id, frame_id)
|
||||
if frame is not None:
|
||||
result = pydevd_vars.evaluate_expression(dbg, frame, expression, is_exec)
|
||||
if attr_to_set_result != "":
|
||||
pydevd_vars.change_attr_expression(frame, attr_to_set_result, expression, dbg, result)
|
||||
else:
|
||||
result = None
|
||||
|
||||
xml = "<xml>"
|
||||
xml += pydevd_xml.var_to_xml(result, expression, trim_if_too_big)
|
||||
xml += "</xml>"
|
||||
|
|
@ -788,6 +947,70 @@ def internal_evaluate_expression(dbg, seq, thread_id, frame_id, expression, is_e
|
|||
dbg.writer.add_command(cmd)
|
||||
|
||||
|
||||
def _set_expression_response(py_db, request, result, error_message):
|
||||
body = pydevd_schema.SetExpressionResponseBody(result='', variablesReference=0)
|
||||
variables_response = pydevd_base_schema.build_response(request, kwargs={
|
||||
'body':body, 'success':False, 'message': error_message})
|
||||
py_db.writer.add_command(NetCommand(CMD_RETURN, 0, variables_response.to_dict(), is_json=True))
|
||||
|
||||
|
||||
def internal_set_expression_json(py_db, request, thread_id):
|
||||
# : :type arguments: SetExpressionArguments
|
||||
|
||||
arguments = request.arguments
|
||||
expression = arguments.expression
|
||||
frame_id = arguments.frameId
|
||||
value = arguments.value
|
||||
fmt = arguments.format
|
||||
if hasattr(fmt, 'to_dict'):
|
||||
fmt = fmt.to_dict()
|
||||
|
||||
if IS_PY2 and isinstance(expression, unicode):
|
||||
try:
|
||||
expression = expression.encode('utf-8')
|
||||
except:
|
||||
_evaluate_response(py_db, request, '', error_message='Expression is not valid utf-8.')
|
||||
raise
|
||||
if IS_PY2 and isinstance(value, unicode):
|
||||
try:
|
||||
value = value.encode('utf-8')
|
||||
except:
|
||||
_evaluate_response(py_db, request, '', error_message='Value is not valid utf-8.')
|
||||
raise
|
||||
|
||||
frame = py_db.find_frame(thread_id, frame_id)
|
||||
exec_code = '%s = (%s)' % (expression, value)
|
||||
result = pydevd_vars.evaluate_expression(py_db, frame, exec_code, is_exec=True)
|
||||
is_error = isinstance(result, ExceptionOnEvaluate)
|
||||
|
||||
if is_error:
|
||||
_set_expression_response(py_db, request, result, error_message='Error executing: %s' % (exec_code,))
|
||||
return
|
||||
|
||||
# Ok, we have the result (could be an error), let's put it into the saved variables.
|
||||
frame_tracker = py_db.suspended_frames_manager.get_frame_tracker(thread_id)
|
||||
if frame_tracker is None:
|
||||
# This is not really expected.
|
||||
_set_expression_response(py_db, request, result, error_message='Thread id: %s is not current thread id.' % (thread_id,))
|
||||
return
|
||||
|
||||
# Now that the exec is done, get the actual value changed to return.
|
||||
result = pydevd_vars.evaluate_expression(py_db, frame, expression, is_exec=False)
|
||||
variable = frame_tracker.obtain_as_variable(expression, result)
|
||||
var_data = variable.get_var_data(fmt=fmt)
|
||||
|
||||
body = pydevd_schema.SetExpressionResponseBody(
|
||||
value=var_data['value'],
|
||||
variablesReference=var_data.get('variableReference', 0),
|
||||
type=var_data.get('type'),
|
||||
presentationHint=var_data.get('presentationHint'),
|
||||
namedVariables=var_data.get('namedVariables'),
|
||||
indexedVariables=var_data.get('indexedVariables'),
|
||||
)
|
||||
variables_response = pydevd_base_schema.build_response(request, kwargs={'body':body})
|
||||
py_db.writer.add_command(NetCommand(CMD_RETURN, 0, variables_response.to_dict(), is_json=True))
|
||||
|
||||
|
||||
def internal_get_completions(dbg, seq, thread_id, frame_id, act_tok, line=-1, column=-1):
|
||||
'''
|
||||
Note that if the column is >= 0, the act_tok is considered text and the actual
|
||||
|
|
|
|||
|
|
@ -82,6 +82,8 @@ CMD_REDIRECT_OUTPUT = 200
|
|||
CMD_GET_NEXT_STATEMENT_TARGETS = 201
|
||||
CMD_SET_PROJECT_ROOTS = 202
|
||||
|
||||
CMD_MODULE_EVENT = 203
|
||||
|
||||
CMD_VERSION = 501
|
||||
CMD_RETURN = 502
|
||||
CMD_SET_PROTOCOL = 503
|
||||
|
|
@ -165,6 +167,7 @@ ID_TO_MEANING = {
|
|||
'200': 'CMD_REDIRECT_OUTPUT',
|
||||
'201': 'CMD_GET_NEXT_STATEMENT_TARGETS',
|
||||
'202': 'CMD_SET_PROJECT_ROOTS',
|
||||
'203': 'CMD_MODULE_EVENT',
|
||||
|
||||
'501': 'CMD_VERSION',
|
||||
'502': 'CMD_RETURN',
|
||||
|
|
|
|||
|
|
@ -10,6 +10,11 @@ PYTHON_SUSPEND = 1
|
|||
DJANGO_SUSPEND = 2
|
||||
JINJA2_SUSPEND = 3
|
||||
|
||||
try:
|
||||
int_types = (int, long)
|
||||
except NameError:
|
||||
int_types = (int,)
|
||||
|
||||
|
||||
class DebugInfoHolder:
|
||||
# we have to put it here because it can be set through the command line (so, the
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ DONT_TRACE = {
|
|||
'pydevd_referrers.py': PYDEV_FILE,
|
||||
'pydevd_reload.py': PYDEV_FILE,
|
||||
'pydevd_resolver.py': PYDEV_FILE,
|
||||
'pydevd_safe_repr.py': PYDEV_FILE,
|
||||
'pydevd_save_locals.py': PYDEV_FILE,
|
||||
'pydevd_schema.py': PYDEV_FILE,
|
||||
'pydevd_schema_log.py': PYDEV_FILE,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,68 @@
|
|||
import os
|
||||
|
||||
from _pydev_bundle._pydev_imports_tipper import TYPE_IMPORT, TYPE_CLASS, TYPE_FUNCTION, TYPE_ATTR, \
|
||||
TYPE_BUILTIN, TYPE_PARAM
|
||||
from _pydev_bundle.pydev_is_thread_alive import is_thread_alive
|
||||
from _pydev_bundle.pydev_override import overrides
|
||||
from _pydevd_bundle._debug_adapter import pydevd_schema
|
||||
from _pydevd_bundle.pydevd_comm_constants import CMD_THREAD_CREATE, CMD_RETURN
|
||||
from _pydevd_bundle.pydevd_constants import get_thread_id
|
||||
from _pydevd_bundle.pydevd_comm_constants import CMD_THREAD_CREATE, CMD_RETURN, CMD_MODULE_EVENT
|
||||
from _pydevd_bundle.pydevd_constants import get_thread_id, dict_values
|
||||
from _pydevd_bundle.pydevd_net_command import NetCommand
|
||||
from _pydevd_bundle.pydevd_net_command_factory_xml import NetCommandFactory
|
||||
from _pydevd_bundle.pydevd_utils import get_non_pydevd_threads
|
||||
from _pydev_imps._pydev_saved_modules import threading
|
||||
from _pydevd_bundle._debug_adapter.pydevd_schema import ModuleEvent, ModuleEventBody, Module
|
||||
from functools import partial
|
||||
import itertools
|
||||
import pydevd_file_utils
|
||||
|
||||
|
||||
class ModulesManager(object):
|
||||
|
||||
def __init__(self):
|
||||
self._lock = threading.Lock()
|
||||
self._modules = {}
|
||||
self._next_id = partial(next, itertools.count(0))
|
||||
|
||||
def track_module(self, filename_in_utf8, module_name, frame):
|
||||
'''
|
||||
:return list(NetCommand):
|
||||
Returns a list with the module events to be sent.
|
||||
'''
|
||||
if filename_in_utf8 in self._modules:
|
||||
return []
|
||||
|
||||
module_events = []
|
||||
with self._lock:
|
||||
# Must check again after getting the lock.
|
||||
if filename_in_utf8 in self._modules:
|
||||
return
|
||||
|
||||
version = frame.f_globals.get('__version__', '')
|
||||
package_name = frame.f_globals.get('__package__', '')
|
||||
module_id = self._next_id()
|
||||
|
||||
module = Module(module_id, module_name, filename_in_utf8)
|
||||
if version:
|
||||
module.version = version
|
||||
|
||||
if package_name:
|
||||
# Note: package doesn't appear in the docs but seems to be expected?
|
||||
module.kwargs['package'] = package_name
|
||||
|
||||
module_event = ModuleEvent(ModuleEventBody('new', module))
|
||||
|
||||
module_events.append(NetCommand(CMD_MODULE_EVENT, 0, module_event.to_dict(), is_json=True))
|
||||
|
||||
self._modules[filename_in_utf8] = module.to_dict()
|
||||
return module_events
|
||||
|
||||
def get_modules_info(self):
|
||||
'''
|
||||
:return list(Module)
|
||||
'''
|
||||
with self._lock:
|
||||
return dict_values(self._modules)
|
||||
|
||||
|
||||
class NetCommandFactoryJson(NetCommandFactory):
|
||||
|
|
@ -22,6 +77,10 @@ class NetCommandFactoryJson(NetCommandFactory):
|
|||
no longer use NetCommandFactory as the base class.
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
NetCommandFactory.__init__(self)
|
||||
self.modules_manager = ModulesManager()
|
||||
|
||||
@overrides(NetCommandFactory.make_thread_created_message)
|
||||
def make_thread_created_message(self, thread):
|
||||
|
||||
|
|
@ -73,3 +132,74 @@ class NetCommandFactoryJson(NetCommandFactory):
|
|||
request_seq=seq, success=True, command='completions', body=body)
|
||||
return NetCommand(CMD_RETURN, 0, response.to_dict(), is_json=True)
|
||||
|
||||
def _format_frame_name(self, fmt, initial_name, module_name, line, path):
|
||||
if fmt is None:
|
||||
return initial_name
|
||||
frame_name = initial_name
|
||||
if fmt.get('module', False):
|
||||
if module_name:
|
||||
if initial_name == '<module>':
|
||||
frame_name = module_name
|
||||
else:
|
||||
frame_name = '%s.%s' % (module_name, initial_name)
|
||||
else:
|
||||
basename = os.path.basename(path)
|
||||
basename = basename[0:-3] if basename.lower().endswith('.py') else basename
|
||||
if initial_name == '<module>':
|
||||
frame_name = '%s in %s' % (initial_name, basename)
|
||||
else:
|
||||
frame_name = '%s.%s' % (basename, initial_name)
|
||||
|
||||
if fmt.get('line', False):
|
||||
frame_name = '%s : %d' % (frame_name, line)
|
||||
|
||||
return frame_name
|
||||
|
||||
@overrides(NetCommandFactory.make_get_thread_stack_message)
|
||||
def make_get_thread_stack_message(self, py_db, seq, thread_id, topmost_frame, fmt, must_be_suspended=False):
|
||||
frames = []
|
||||
module_events = []
|
||||
if topmost_frame is not None:
|
||||
frame_id_to_lineno = {}
|
||||
try:
|
||||
# : :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
|
||||
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
|
||||
|
||||
for frame_id, frame, method_name, filename_in_utf8, lineno in self._iter_visible_frames_info(
|
||||
py_db, topmost_frame, frame_id_to_lineno
|
||||
):
|
||||
|
||||
module_name = frame.f_globals.get('__qualname__', '')
|
||||
if not module_name:
|
||||
module_name = frame.f_globals.get('__name__', '')
|
||||
|
||||
module_events.extend(self.modules_manager.track_module(filename_in_utf8, module_name, frame))
|
||||
|
||||
# TODO: presentationHint should be subtle if it's a filtered method.
|
||||
formatted_name = self._format_frame_name(fmt, method_name, module_name, lineno, filename_in_utf8)
|
||||
frames.append(pydevd_schema.StackFrame(
|
||||
frame_id, formatted_name, lineno, column=1, source={
|
||||
'path': filename_in_utf8,
|
||||
'sourceReference': pydevd_file_utils.get_client_filename_source_reference(filename_in_utf8),
|
||||
}).to_dict())
|
||||
finally:
|
||||
topmost_frame = None
|
||||
|
||||
for module_event in module_events:
|
||||
py_db.writer.add_command(module_event)
|
||||
|
||||
response = pydevd_schema.StackTraceResponse(
|
||||
request_seq=seq,
|
||||
success=True,
|
||||
command='stackTrace',
|
||||
body=pydevd_schema.StackTraceResponseBody(stackFrames=frames, totalFrames=len(frames)))
|
||||
return NetCommand(CMD_RETURN, 0, response.to_dict(), is_json=True)
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ if IS_IRONPYTHON:
|
|||
#=======================================================================================================================
|
||||
# NetCommandFactory
|
||||
#=======================================================================================================================
|
||||
class NetCommandFactory:
|
||||
class NetCommandFactory(object):
|
||||
|
||||
def _thread_to_xml(self, thread):
|
||||
""" thread information as XML """
|
||||
|
|
@ -87,11 +87,11 @@ class NetCommandFactory:
|
|||
except:
|
||||
return self.make_error_message(seq, get_exception_traceback_str())
|
||||
|
||||
def make_get_thread_stack_message(self, py_db, seq, thread_id, topmost_frame, must_be_suspended=False):
|
||||
def make_get_thread_stack_message(self, py_db, seq, thread_id, topmost_frame, fmt, must_be_suspended=False):
|
||||
"""
|
||||
Returns thread stack as XML.
|
||||
|
||||
:param be_suspended: If True and the thread is not suspended, returns None.
|
||||
:param must_be_suspended: If True and the thread is not suspended, returns None.
|
||||
"""
|
||||
try:
|
||||
# If frame is None, the return is an empty frame list.
|
||||
|
|
@ -152,6 +152,30 @@ class NetCommandFactory:
|
|||
except:
|
||||
return self.make_error_message(0, get_exception_traceback_str())
|
||||
|
||||
def _iter_visible_frames_info(self, py_db, frame, frame_id_to_lineno):
|
||||
while frame is not None:
|
||||
if frame.f_code is None:
|
||||
continue # IronPython sometimes does not have it!
|
||||
|
||||
method_name = frame.f_code.co_name # method name (if in method) or ? if global
|
||||
if method_name is None:
|
||||
continue # IronPython sometimes does not have it!
|
||||
|
||||
abs_path_real_path_and_base = get_abs_path_real_path_and_base_from_frame(frame)
|
||||
if py_db.get_file_type(abs_path_real_path_and_base) == py_db.PYDEV_FILE:
|
||||
# Skip pydevd files.
|
||||
frame = frame.f_back
|
||||
continue
|
||||
|
||||
filename_in_utf8 = pydevd_file_utils.norm_file_to_client(abs_path_real_path_and_base[0])
|
||||
|
||||
frame_id = id(frame)
|
||||
lineno = frame_id_to_lineno.get(frame_id, frame.f_lineno)
|
||||
|
||||
yield frame_id, frame, method_name, filename_in_utf8, lineno
|
||||
|
||||
frame = frame.f_back
|
||||
|
||||
def make_thread_stack_str(self, frame, frame_id_to_lineno=None):
|
||||
'''
|
||||
:param frame_id_to_lineno:
|
||||
|
|
@ -169,38 +193,17 @@ class NetCommandFactory:
|
|||
frame = None # Clear frame reference
|
||||
try:
|
||||
py_db = get_global_debugger()
|
||||
while curr_frame:
|
||||
frame_id = id(curr_frame)
|
||||
|
||||
if curr_frame.f_code is None:
|
||||
break # Iron Python sometimes does not have it!
|
||||
|
||||
method_name = curr_frame.f_code.co_name # method name (if in method) or ? if global
|
||||
if method_name is None:
|
||||
break # Iron Python sometimes does not have it!
|
||||
|
||||
abs_path_real_path_and_base = get_abs_path_real_path_and_base_from_frame(curr_frame)
|
||||
if py_db.get_file_type(abs_path_real_path_and_base) == py_db.PYDEV_FILE:
|
||||
# Skip pydevd files.
|
||||
curr_frame = curr_frame.f_back
|
||||
continue
|
||||
|
||||
filename_in_utf8 = pydevd_file_utils.norm_file_to_client(abs_path_real_path_and_base[0])
|
||||
if not filesystem_encoding_is_utf8 and hasattr(filename_in_utf8, "decode"):
|
||||
# filename_in_utf8 is a byte string encoded using the file system encoding
|
||||
# convert it to utf8
|
||||
filename_in_utf8 = filename_in_utf8.decode(file_system_encoding).encode("utf-8")
|
||||
for frame_id, frame, method_name, filename_in_utf8, lineno in self._iter_visible_frames_info(
|
||||
py_db, curr_frame, frame_id_to_lineno
|
||||
):
|
||||
|
||||
# print("file is ", filename_in_utf8)
|
||||
|
||||
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" ' % (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
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ class _PyDevCommandProcessor(object):
|
|||
thread_id = text
|
||||
timeout = .5 # Default timeout is .5 seconds
|
||||
|
||||
return self.api.request_stack(py_db, seq, thread_id, timeout)
|
||||
return self.api.request_stack(py_db, seq, thread_id, fmt={}, timeout=timeout)
|
||||
|
||||
def cmd_set_protocol(self, py_db, cmd_id, seq, text):
|
||||
return self.api.set_protocol(py_db, seq, text.strip())
|
||||
|
|
|
|||
|
|
@ -4,13 +4,15 @@ import json
|
|||
import os
|
||||
|
||||
from _pydevd_bundle._debug_adapter import pydevd_base_schema
|
||||
from _pydevd_bundle._debug_adapter.pydevd_schema import SourceBreakpoint
|
||||
from _pydevd_bundle._debug_adapter.pydevd_schema import (SourceBreakpoint, ScopesResponseBody, Scope,
|
||||
VariablesResponseBody, SetVariableResponseBody, ModulesResponseBody, SourceResponseBody)
|
||||
from _pydevd_bundle.pydevd_api import PyDevdAPI
|
||||
from _pydevd_bundle.pydevd_comm_constants import CMD_RETURN
|
||||
from _pydevd_bundle.pydevd_filtering import ExcludeFilter
|
||||
from _pydevd_bundle.pydevd_json_debug_options import _extract_debug_options
|
||||
from _pydevd_bundle.pydevd_net_command import NetCommand
|
||||
from _pydevd_bundle.pydevd_utils import convert_dap_log_message_to_expression
|
||||
import pydevd_file_utils
|
||||
|
||||
|
||||
def _convert_rules_to_exclude_filters(rules, filename_to_server, on_error):
|
||||
|
|
@ -134,7 +136,9 @@ class _PyDevJsonCommandProcessor(object):
|
|||
arguments = request.arguments # : :type arguments: CompletionsArguments
|
||||
seq = request.seq
|
||||
text = arguments.text
|
||||
thread_id, frame_id = arguments.frameId
|
||||
frame_id = arguments.frameId
|
||||
thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
|
||||
frame_id)
|
||||
|
||||
# Note: line and column are 1-based (convert to 0-based for pydevd).
|
||||
column = arguments.column - 1
|
||||
|
|
@ -268,5 +272,126 @@ class _PyDevJsonCommandProcessor(object):
|
|||
set_breakpoints_response = pydevd_base_schema.build_response(request, kwargs={'body':body})
|
||||
return NetCommand(CMD_RETURN, 0, set_breakpoints_response.to_dict(), is_json=True)
|
||||
|
||||
def on_stacktrace_request(self, py_db, request):
|
||||
'''
|
||||
:param StackTraceRequest request:
|
||||
'''
|
||||
# : :type stack_trace_arguments: StackTraceArguments
|
||||
stack_trace_arguments = request.arguments
|
||||
thread_id = stack_trace_arguments.threadId
|
||||
|
||||
fmt = stack_trace_arguments.format
|
||||
if hasattr(fmt, 'to_dict'):
|
||||
fmt = fmt.to_dict()
|
||||
self.api.request_stack(py_db, request.seq, thread_id, fmt)
|
||||
|
||||
def on_scopes_request(self, py_db, request):
|
||||
'''
|
||||
Scopes are the top-level items which appear for a frame (so, we receive the frame id
|
||||
and provide the scopes it has).
|
||||
|
||||
:param ScopesRequest request:
|
||||
'''
|
||||
frame_id = request.arguments.frameId
|
||||
|
||||
variables_reference = frame_id
|
||||
scopes = [Scope('Locals', int(variables_reference), False).to_dict()]
|
||||
body = ScopesResponseBody(scopes)
|
||||
scopes_response = pydevd_base_schema.build_response(request, kwargs={'body':body})
|
||||
return NetCommand(CMD_RETURN, 0, scopes_response.to_dict(), is_json=True)
|
||||
|
||||
def on_evaluate_request(self, py_db, request):
|
||||
'''
|
||||
:param EvaluateRequest request:
|
||||
'''
|
||||
# : :type arguments: EvaluateArguments
|
||||
arguments = request.arguments
|
||||
|
||||
thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
|
||||
arguments.frameId)
|
||||
|
||||
self.api.request_exec_or_evaluate_json(
|
||||
py_db, request, thread_id)
|
||||
|
||||
def on_setexpression_request(self, py_db, request):
|
||||
# : :type arguments: SetExpressionArguments
|
||||
arguments = request.arguments
|
||||
|
||||
thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
|
||||
arguments.frameId)
|
||||
|
||||
self.api.request_set_expression_json(
|
||||
py_db, request, thread_id)
|
||||
|
||||
def on_variables_request(self, py_db, request):
|
||||
'''
|
||||
Variables can be asked whenever some place returned a variables reference (so, it
|
||||
can be a scope gotten from on_scopes_request, the result of some evaluation, etc.).
|
||||
|
||||
Note that in the DAP the variables reference requires a unique int... the way this works for
|
||||
pydevd is that an instance is generated for that specific variable reference and we use its
|
||||
id(instance) to identify it to make sure all items are unique (and the actual {id->instance}
|
||||
is added to a dict which is only valid while the thread is suspended and later cleared when
|
||||
the related thread resumes execution).
|
||||
|
||||
see: SuspendedFramesManager
|
||||
|
||||
:param VariablesRequest request:
|
||||
'''
|
||||
arguments = request.arguments # : :type arguments: VariablesArguments
|
||||
variables_reference = arguments.variablesReference
|
||||
|
||||
thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
|
||||
variables_reference)
|
||||
if thread_id is not None:
|
||||
self.api.request_get_variable_json(py_db, request, thread_id)
|
||||
else:
|
||||
variables = []
|
||||
body = VariablesResponseBody(variables)
|
||||
variables_response = pydevd_base_schema.build_response(request, kwargs={'body':body})
|
||||
return NetCommand(CMD_RETURN, 0, variables_response.to_dict(), is_json=True)
|
||||
|
||||
def on_setvariable_request(self, py_db, request):
|
||||
arguments = request.arguments # : :type arguments: SetVariableArguments
|
||||
variables_reference = arguments.variablesReference
|
||||
|
||||
thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
|
||||
variables_reference)
|
||||
if thread_id is not None:
|
||||
self.api.request_change_variable_json(py_db, request, thread_id)
|
||||
else:
|
||||
body = SetVariableResponseBody('')
|
||||
variables_response = pydevd_base_schema.build_response(
|
||||
request,
|
||||
kwargs={
|
||||
'body':body,
|
||||
'success': False,
|
||||
'message': 'Unable to find thread to evaluate variable reference.'
|
||||
})
|
||||
return NetCommand(CMD_RETURN, 0, variables_response.to_dict(), is_json=True)
|
||||
|
||||
def on_modules_request(self, py_db, request):
|
||||
modules_manager = py_db.cmd_factory.modules_manager # : :type modules_manager: ModulesManager
|
||||
modules_info = modules_manager.get_modules_info()
|
||||
body = ModulesResponseBody(modules_info)
|
||||
variables_response = pydevd_base_schema.build_response(request, kwargs={'body':body})
|
||||
return NetCommand(CMD_RETURN, 0, variables_response.to_dict(), is_json=True)
|
||||
|
||||
def on_source_request(self, py_db, request):
|
||||
'''
|
||||
:param SourceRequest request:
|
||||
'''
|
||||
source_reference = request.arguments.sourceReference
|
||||
content = ''
|
||||
if source_reference != 0:
|
||||
server_filename = pydevd_file_utils.get_server_filename_from_source_reference(source_reference)
|
||||
if os.path.exists(server_filename):
|
||||
with open(server_filename, 'r') as stream:
|
||||
content = stream.read()
|
||||
|
||||
body = SourceResponseBody(content)
|
||||
response = pydevd_base_schema.build_response(request, kwargs={'body':body})
|
||||
return NetCommand(CMD_RETURN, 0, response.to_dict(), is_json=True)
|
||||
|
||||
|
||||
process_net_command_json = _PyDevJsonCommandProcessor(pydevd_base_schema.from_json).process_net_command_json
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@ from os.path import basename
|
|||
from _pydevd_bundle import pydevd_constants
|
||||
from _pydevd_bundle.pydevd_constants import dict_iter_items, dict_keys, xrange
|
||||
|
||||
|
||||
# Note: 300 is already a lot to see in the outline (after that the user should really use the shell to get things)
|
||||
# and this also means we'll pass less information to the client side (which makes debugging faster).
|
||||
MAX_ITEMS_TO_HANDLE = 300
|
||||
MAX_ITEMS_TO_HANDLE = 300
|
||||
|
||||
TOO_LARGE_MSG = 'Too large to show contents. Max items to show: ' + str(MAX_ITEMS_TO_HANDLE)
|
||||
TOO_LARGE_ATTR = 'Unable to handle:'
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# UnableToResolveVariableException
|
||||
#=======================================================================================================================
|
||||
|
|
@ -27,11 +27,14 @@ class UnableToResolveVariableException(Exception):
|
|||
# InspectStub
|
||||
#=======================================================================================================================
|
||||
class InspectStub:
|
||||
|
||||
def isbuiltin(self, _args):
|
||||
return False
|
||||
|
||||
def isroutine(self, object):
|
||||
return False
|
||||
|
||||
|
||||
try:
|
||||
import inspect
|
||||
except:
|
||||
|
|
@ -43,22 +46,37 @@ except:
|
|||
OrderedDict = dict
|
||||
|
||||
try:
|
||||
import java.lang #@UnresolvedImport
|
||||
import java.lang # @UnresolvedImport
|
||||
except:
|
||||
pass
|
||||
|
||||
#types does not include a MethodWrapperType
|
||||
# types does not include a MethodWrapperType
|
||||
try:
|
||||
MethodWrapperType = type([].__str__)
|
||||
except:
|
||||
MethodWrapperType = None
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# See: pydevd_extension_api module for resolver interface
|
||||
#=======================================================================================================================
|
||||
|
||||
|
||||
def sorted_attributes_key(attr_name):
|
||||
if attr_name.startswith('__'):
|
||||
if attr_name.endswith('__'):
|
||||
# __ double under before and after __
|
||||
return (3, attr_name)
|
||||
else:
|
||||
# __ double under before
|
||||
return (2, attr_name)
|
||||
elif attr_name.startswith('_'):
|
||||
# _ single under
|
||||
return (1, attr_name)
|
||||
else:
|
||||
# Regular (Before anything)
|
||||
return (0, attr_name)
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# DefaultResolver
|
||||
#=======================================================================================================================
|
||||
|
|
@ -70,6 +88,11 @@ class DefaultResolver:
|
|||
def resolve(self, var, attribute):
|
||||
return getattr(var, attribute)
|
||||
|
||||
def get_contents_debug_adapter_protocol(self, obj):
|
||||
dct = self.get_dictionary(obj)
|
||||
lst = sorted(dict_iter_items(dct), key=sorted_attributes_key)
|
||||
return [(attr_name, attr_value, '.%s' % attr_name) for (attr_name, attr_value) in lst]
|
||||
|
||||
def get_dictionary(self, var, names=None):
|
||||
if MethodWrapperType:
|
||||
return self._getPyDictionary(var, names)
|
||||
|
|
@ -83,7 +106,7 @@ class DefaultResolver:
|
|||
original = obj
|
||||
if hasattr(obj, '__class__') and obj.__class__ == java.lang.Class:
|
||||
|
||||
#get info about superclasses
|
||||
# get info about superclasses
|
||||
classes = []
|
||||
classes.append(obj)
|
||||
c = obj.getSuperclass()
|
||||
|
|
@ -91,13 +114,13 @@ class DefaultResolver:
|
|||
classes.append(c)
|
||||
c = c.getSuperclass()
|
||||
|
||||
#get info about interfaces
|
||||
# get info about interfaces
|
||||
interfs = []
|
||||
for obj in classes:
|
||||
interfs.extend(obj.getInterfaces())
|
||||
classes.extend(interfs)
|
||||
|
||||
#now is the time when we actually get info on the declared methods and fields
|
||||
# now is the time when we actually get info on the declared methods and fields
|
||||
for obj in classes:
|
||||
|
||||
declaredMethods = obj.getDeclaredMethods()
|
||||
|
|
@ -110,24 +133,24 @@ class DefaultResolver:
|
|||
for i in xrange(len(declaredFields)):
|
||||
name = declaredFields[i].getName()
|
||||
found.put(name, 1)
|
||||
#if declaredFields[i].isAccessible():
|
||||
# if declaredFields[i].isAccessible():
|
||||
declaredFields[i].setAccessible(True)
|
||||
#ret[name] = declaredFields[i].get( declaredFields[i] )
|
||||
# ret[name] = declaredFields[i].get( declaredFields[i] )
|
||||
try:
|
||||
ret[name] = declaredFields[i].get(original)
|
||||
except:
|
||||
ret[name] = declaredFields[i].toString()
|
||||
|
||||
#this simple dir does not always get all the info, that's why we have the part before
|
||||
#(e.g.: if we do a dir on String, some methods that are from other interfaces such as
|
||||
#charAt don't appear)
|
||||
# this simple dir does not always get all the info, that's why we have the part before
|
||||
# (e.g.: if we do a dir on String, some methods that are from other interfaces such as
|
||||
# charAt don't appear)
|
||||
try:
|
||||
d = dir(original)
|
||||
for name in d:
|
||||
if found.get(name) is not 1:
|
||||
ret[name] = getattr(original, name)
|
||||
except:
|
||||
#sometimes we're unable to do a dir
|
||||
# sometimes we're unable to do a dir
|
||||
pass
|
||||
|
||||
return ret
|
||||
|
|
@ -148,9 +171,9 @@ class DefaultResolver:
|
|||
names = self.get_names(var)
|
||||
d = {}
|
||||
|
||||
#Be aware that the order in which the filters are applied attempts to
|
||||
#optimize the operation by removing as many items as possible in the
|
||||
#first filters, leaving fewer items for later filters
|
||||
# Be aware that the order in which the filters are applied attempts to
|
||||
# optimize the operation by removing as many items as possible in the
|
||||
# first filters, leaving fewer items for later filters
|
||||
|
||||
if filterBuiltIn or filterFunction:
|
||||
for n in names:
|
||||
|
|
@ -165,17 +188,17 @@ class DefaultResolver:
|
|||
try:
|
||||
attr = getattr(var, n)
|
||||
|
||||
#filter builtins?
|
||||
# filter builtins?
|
||||
if filterBuiltIn:
|
||||
if inspect.isbuiltin(attr):
|
||||
continue
|
||||
|
||||
#filter functions?
|
||||
# filter functions?
|
||||
if filterFunction:
|
||||
if inspect.isroutine(attr) or isinstance(attr, MethodWrapperType):
|
||||
continue
|
||||
except:
|
||||
#if some error occurs getting it, let's put it to the user.
|
||||
# if some error occurs getting it, let's put it to the user.
|
||||
strIO = StringIO.StringIO()
|
||||
traceback.print_exc(file=strIO)
|
||||
attr = strIO.getvalue()
|
||||
|
|
@ -195,15 +218,15 @@ class DictResolver:
|
|||
return None
|
||||
|
||||
if '(' not in key:
|
||||
#we have to treat that because the dict resolver is also used to directly resolve the global and local
|
||||
#scopes (which already have the items directly)
|
||||
# we have to treat that because the dict resolver is also used to directly resolve the global and local
|
||||
# scopes (which already have the items directly)
|
||||
try:
|
||||
return dict[key]
|
||||
except:
|
||||
return getattr(dict, key)
|
||||
|
||||
#ok, we have to iterate over the items to find the one that matches the id, because that's the only way
|
||||
#to actually find the reference from the string we have before.
|
||||
# ok, we have to iterate over the items to find the one that matches the id, because that's the only way
|
||||
# to actually find the reference from the string we have before.
|
||||
expected_id = int(key.split('(')[-1][:-1])
|
||||
for key, val in dict_iter_items(dict):
|
||||
if id(key) == expected_id:
|
||||
|
|
@ -212,24 +235,48 @@ class DictResolver:
|
|||
raise UnableToResolveVariableException()
|
||||
|
||||
def key_to_str(self, key):
|
||||
if isinstance(key, str):
|
||||
return '%r' % key
|
||||
else:
|
||||
if not pydevd_constants.IS_PY3K:
|
||||
if isinstance(key, unicode):
|
||||
return "u'%s'" % key
|
||||
return key
|
||||
return '%r' % (key,)
|
||||
|
||||
def init_dict(self):
|
||||
return {}
|
||||
|
||||
def get_contents_debug_adapter_protocol(self, dct):
|
||||
'''
|
||||
This method is to be used in the case where the variables are all saved by its id (and as
|
||||
such don't need to have the `resolve` method called later on, so, keys don't need to
|
||||
embed the reference in the key).
|
||||
|
||||
Note that the return should be ordered.
|
||||
|
||||
:return list(tuple(name:str, value:object, evaluateName:str))
|
||||
'''
|
||||
ret = []
|
||||
|
||||
i = 0
|
||||
for key, val in dict_iter_items(dct):
|
||||
i += 1
|
||||
key_as_str = self.key_to_str(key)
|
||||
ret.append((key_as_str, val, '[%s]' % (key_as_str,)))
|
||||
if i > MAX_ITEMS_TO_HANDLE:
|
||||
ret.append((TOO_LARGE_ATTR, TOO_LARGE_MSG))
|
||||
break
|
||||
|
||||
ret.append(('__len__', len(dct), '.__len__()'))
|
||||
# in case the class extends built-in type and has some additional fields
|
||||
from_default_resolver = defaultResolver.get_contents_debug_adapter_protocol(dct)
|
||||
|
||||
if from_default_resolver:
|
||||
ret = from_default_resolver + ret
|
||||
|
||||
return sorted(ret, key=lambda tup: sorted_attributes_key(tup[0]))
|
||||
|
||||
def get_dictionary(self, dict):
|
||||
ret = self.init_dict()
|
||||
|
||||
i = 0
|
||||
for key, val in dict_iter_items(dict):
|
||||
i += 1
|
||||
#we need to add the id because otherwise we cannot find the real object to get its contents later on.
|
||||
# we need to add the id because otherwise we cannot find the real object to get its contents later on.
|
||||
key = '%s (%s)' % (self.key_to_str(key), id(key))
|
||||
ret[key] = val
|
||||
if i > MAX_ITEMS_TO_HANDLE:
|
||||
|
|
@ -246,7 +293,7 @@ class DictResolver:
|
|||
#=======================================================================================================================
|
||||
# TupleResolver
|
||||
#=======================================================================================================================
|
||||
class TupleResolver: #to enumerate tuples and lists
|
||||
class TupleResolver: # to enumerate tuples and lists
|
||||
|
||||
def resolve(self, var, attribute):
|
||||
'''
|
||||
|
|
@ -260,21 +307,48 @@ class TupleResolver: #to enumerate tuples and lists
|
|||
except:
|
||||
return getattr(var, attribute)
|
||||
|
||||
def get_contents_debug_adapter_protocol(self, lst):
|
||||
'''
|
||||
This method is to be used in the case where the variables are all saved by its id (and as
|
||||
such don't need to have the `resolve` method called later on, so, keys don't need to
|
||||
embed the reference in the key).
|
||||
|
||||
Note that the return should be ordered.
|
||||
|
||||
:return list(tuple(name:str, value:object, evaluateName:str))
|
||||
'''
|
||||
l = len(lst)
|
||||
ret = []
|
||||
|
||||
format_str = '%0' + str(int(len(str(l - 1)))) + 'd'
|
||||
|
||||
for i, item in enumerate(lst):
|
||||
ret.append((format_str % i, item, '[%s]' % i))
|
||||
|
||||
if i > MAX_ITEMS_TO_HANDLE:
|
||||
ret.append((TOO_LARGE_ATTR, TOO_LARGE_MSG, None))
|
||||
break
|
||||
|
||||
ret.append(('__len__', len(lst), '.__len__()'))
|
||||
# Needed in case the class extends the built-in type and has some additional fields.
|
||||
from_default_resolver = defaultResolver.get_dictionary(lst)
|
||||
if from_default_resolver:
|
||||
ret = from_default_resolver + ret
|
||||
return ret
|
||||
|
||||
def get_dictionary(self, var):
|
||||
l = len(var)
|
||||
d = {}
|
||||
|
||||
format_str = '%0' + str(int(len(str(l)))) + 'd'
|
||||
format_str = '%0' + str(int(len(str(l - 1)))) + 'd'
|
||||
|
||||
i = 0
|
||||
for item in var:
|
||||
for i, item in enumerate(var):
|
||||
d[format_str % i] = item
|
||||
i += 1
|
||||
|
||||
|
||||
if i > MAX_ITEMS_TO_HANDLE:
|
||||
d[TOO_LARGE_ATTR] = TOO_LARGE_MSG
|
||||
break
|
||||
|
||||
|
||||
d['__len__'] = len(var)
|
||||
# in case if the class extends built-in type and has some additional fields
|
||||
additional_fields = defaultResolver.get_dictionary(var)
|
||||
|
|
@ -282,7 +356,6 @@ class TupleResolver: #to enumerate tuples and lists
|
|||
return d
|
||||
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# SetResolver
|
||||
#=======================================================================================================================
|
||||
|
|
@ -312,12 +385,11 @@ class SetResolver:
|
|||
for item in var:
|
||||
i += 1
|
||||
d[str(id(item))] = item
|
||||
|
||||
|
||||
if i > MAX_ITEMS_TO_HANDLE:
|
||||
d[TOO_LARGE_ATTR] = TOO_LARGE_MSG
|
||||
break
|
||||
|
||||
|
||||
d['__len__'] = len(var)
|
||||
# in case if the class extends built-in type and has some additional fields
|
||||
additional_fields = defaultResolver.get_dictionary(var)
|
||||
|
|
@ -373,8 +445,6 @@ class JyArrayResolver:
|
|||
return ret
|
||||
|
||||
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# MultiValueDictResolver
|
||||
#=======================================================================================================================
|
||||
|
|
@ -384,8 +454,8 @@ class MultiValueDictResolver(DictResolver):
|
|||
if key in ('__len__', TOO_LARGE_ATTR):
|
||||
return None
|
||||
|
||||
#ok, we have to iterate over the items to find the one that matches the id, because that's the only way
|
||||
#to actually find the reference from the string we have before.
|
||||
# ok, we have to iterate over the items to find the one that matches the id, because that's the only way
|
||||
# to actually find the reference from the string we have before.
|
||||
expected_id = int(key.split('(')[-1][:-1])
|
||||
for key in dict_keys(dict):
|
||||
val = dict.getlist(key)
|
||||
|
|
@ -395,7 +465,6 @@ class MultiValueDictResolver(DictResolver):
|
|||
raise UnableToResolveVariableException()
|
||||
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# DjangoFormResolver
|
||||
#=======================================================================================================================
|
||||
|
|
@ -428,6 +497,7 @@ class DjangoFormResolver(DefaultResolver):
|
|||
# DequeResolver
|
||||
#=======================================================================================================================
|
||||
class DequeResolver(TupleResolver):
|
||||
|
||||
def get_dictionary(self, var):
|
||||
d = TupleResolver.get_dictionary(self, var)
|
||||
d['maxlen'] = getattr(var, 'maxlen', None)
|
||||
|
|
@ -438,6 +508,7 @@ class DequeResolver(TupleResolver):
|
|||
# OrderedDictResolver
|
||||
#=======================================================================================================================
|
||||
class OrderedDictResolver(DictResolver):
|
||||
|
||||
def init_dict(self):
|
||||
return OrderedDict()
|
||||
|
||||
|
|
@ -462,7 +533,6 @@ class FrameResolver:
|
|||
|
||||
return None
|
||||
|
||||
|
||||
def get_dictionary(self, obj):
|
||||
ret = {}
|
||||
ret['__internals__'] = defaultResolver.get_dictionary(obj)
|
||||
|
|
@ -470,7 +540,6 @@ class FrameResolver:
|
|||
ret['f_locals'] = obj.f_locals
|
||||
return ret
|
||||
|
||||
|
||||
def get_frame_stack(self, frame):
|
||||
ret = []
|
||||
if frame is not None:
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
# Gotten from ptvsd for supporting the format expected there.
|
||||
import sys
|
||||
|
||||
|
||||
# Py3 compat - alias unicode to str, and xrange to range
|
||||
try:
|
||||
unicode # noqa
|
||||
|
|
@ -2,8 +2,158 @@ from contextlib import contextmanager
|
|||
import sys
|
||||
|
||||
from _pydev_imps._pydev_saved_modules import threading
|
||||
from _pydevd_bundle.pydevd_constants import get_frame
|
||||
from _pydevd_bundle.pydevd_constants import get_frame, dict_items, RETURN_VALUES_DICT, \
|
||||
dict_iter_items
|
||||
import traceback
|
||||
from _pydevd_bundle.pydevd_xml import get_variable_details, get_type
|
||||
from _pydev_bundle.pydev_override import overrides
|
||||
from _pydevd_bundle.pydevd_resolver import sorted_attributes_key
|
||||
from _pydevd_bundle.pydevd_safe_repr import SafeRepr
|
||||
|
||||
|
||||
class _AbstractVariable(object):
|
||||
|
||||
# Default attributes in class, set in instance.
|
||||
|
||||
name = None
|
||||
value = None
|
||||
evaluate_name = None
|
||||
|
||||
def get_name(self):
|
||||
return self.name
|
||||
|
||||
def get_value(self):
|
||||
return self.value
|
||||
|
||||
def get_variable_reference(self):
|
||||
return id(self.value)
|
||||
|
||||
def get_var_data(self, fmt=None):
|
||||
'''
|
||||
:param dict fmt:
|
||||
Format expected by the DAP (keys: 'hex': bool, 'rawString': bool)
|
||||
'''
|
||||
safe_repr = SafeRepr()
|
||||
if fmt is not None:
|
||||
safe_repr.convert_to_hex = fmt.get('hex', False)
|
||||
safe_repr.raw_value = fmt.get('rawString', False)
|
||||
|
||||
type_name, _type_qualifier, _is_exception_on_eval, resolver, value = get_variable_details(
|
||||
self.value, to_string=safe_repr)
|
||||
|
||||
is_raw_string = type_name in ('str', 'unicode', 'bytes', 'bytearray')
|
||||
|
||||
attributes = []
|
||||
|
||||
if is_raw_string:
|
||||
attributes.append('rawString')
|
||||
|
||||
name = self.name
|
||||
|
||||
if self._is_return_value:
|
||||
attributes.append('readOnly')
|
||||
name = '(return) %s' % (name,)
|
||||
|
||||
var_data = {
|
||||
'name': name,
|
||||
'value': value,
|
||||
'type': type_name,
|
||||
}
|
||||
|
||||
if self.evaluate_name is not None:
|
||||
var_data['evaluateName'] = self.evaluate_name
|
||||
|
||||
if resolver is not None: # I.e.: it's a container
|
||||
var_data['variablesReference'] = self.get_variable_reference()
|
||||
|
||||
if len(attributes) > 0:
|
||||
var_data['presentationHint'] = {'attributes': attributes}
|
||||
|
||||
return var_data
|
||||
|
||||
def get_children_variables(self, fmt=None):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class _ObjectVariable(_AbstractVariable):
|
||||
|
||||
def __init__(self, name, value, register_variable, is_return_value=False, evaluate_name=None):
|
||||
_AbstractVariable.__init__(self)
|
||||
self.name = name
|
||||
self.value = value
|
||||
self._register_variable = register_variable
|
||||
self._register_variable(self)
|
||||
self._is_return_value = is_return_value
|
||||
self.evaluate_name = evaluate_name
|
||||
|
||||
@overrides(_AbstractVariable.get_children_variables)
|
||||
def get_children_variables(self, fmt=None):
|
||||
_type, _type_name, resolver = get_type(self.value)
|
||||
|
||||
children_variables = []
|
||||
if resolver is not None: # i.e.: it's a container.
|
||||
if hasattr(resolver, 'get_contents_debug_adapter_protocol'):
|
||||
# The get_contents_debug_adapter_protocol needs to return sorted.
|
||||
lst = resolver.get_contents_debug_adapter_protocol(self.value)
|
||||
else:
|
||||
# If there's no special implementation, the default is sorting the keys.
|
||||
dct = resolver.get_dictionary(self.value)
|
||||
lst = dict_items(dct)
|
||||
lst.sort(key=lambda tup: sorted_attributes_key(tup[0]))
|
||||
# No evaluate name in this case.
|
||||
lst = [(key, value, None) for (key, value) in lst]
|
||||
|
||||
parent_evaluate_name = self.evaluate_name
|
||||
if parent_evaluate_name:
|
||||
for key, val, evaluate_name in lst:
|
||||
if evaluate_name is not None:
|
||||
evaluate_name = parent_evaluate_name + evaluate_name
|
||||
variable = _ObjectVariable(
|
||||
key, val, self._register_variable, evaluate_name=evaluate_name)
|
||||
children_variables.append(variable)
|
||||
else:
|
||||
for key, val, evaluate_name in lst:
|
||||
# No evaluate name
|
||||
variable = _ObjectVariable(key, val, self._register_variable)
|
||||
children_variables.append(variable)
|
||||
|
||||
return children_variables
|
||||
|
||||
|
||||
def sorted_variables_key(obj):
|
||||
return sorted_attributes_key(obj.name)
|
||||
|
||||
|
||||
class _FrameVariable(_AbstractVariable):
|
||||
|
||||
def __init__(self, frame, register_variable):
|
||||
_AbstractVariable.__init__(self)
|
||||
self.frame = frame
|
||||
|
||||
self.name = self.frame.f_code.co_name
|
||||
self.value = frame
|
||||
|
||||
self._register_variable = register_variable
|
||||
self._register_variable(self)
|
||||
|
||||
@overrides(_AbstractVariable.get_children_variables)
|
||||
def get_children_variables(self, fmt=None):
|
||||
children_variables = []
|
||||
for key, val in dict_items(self.frame.f_locals):
|
||||
is_return_value = key == RETURN_VALUES_DICT
|
||||
if is_return_value:
|
||||
for return_key, return_value in dict_iter_items(val):
|
||||
variable = _ObjectVariable(
|
||||
return_key, return_value, self._register_variable, is_return_value, '%s[%r]' % (key, return_key))
|
||||
children_variables.append(variable)
|
||||
else:
|
||||
variable = _ObjectVariable(key, val, self._register_variable, is_return_value, key)
|
||||
children_variables.append(variable)
|
||||
|
||||
# Frame variables always sorted.
|
||||
children_variables.sort(key=sorted_variables_key)
|
||||
|
||||
return children_variables
|
||||
|
||||
|
||||
class _FramesTracker(object):
|
||||
|
|
@ -40,6 +190,31 @@ class _FramesTracker(object):
|
|||
# We need to be thread-safe!
|
||||
self._lock = threading.Lock()
|
||||
|
||||
self._variable_reference_to_variable = {}
|
||||
|
||||
def _register_variable(self, variable):
|
||||
variable_reference = variable.get_variable_reference()
|
||||
self._variable_reference_to_variable[variable_reference] = variable
|
||||
|
||||
def obtain_as_variable(self, name, value, evaluate_name=None):
|
||||
if evaluate_name is None:
|
||||
evaluate_name = name
|
||||
|
||||
variable_reference = id(value)
|
||||
variable = self._variable_reference_to_variable.get(variable_reference)
|
||||
if variable is not None:
|
||||
return variable
|
||||
|
||||
# Still not created, let's do it now.
|
||||
return _ObjectVariable(
|
||||
name, value, self._register_variable, is_return_value=False, evaluate_name=evaluate_name)
|
||||
|
||||
def get_main_thread_id(self):
|
||||
return self._main_thread_id
|
||||
|
||||
def get_variable(self, variable_reference):
|
||||
return self._variable_reference_to_variable[variable_reference]
|
||||
|
||||
def track(self, thread_id, frame, frame_id_to_lineno, frame_custom_thread_id=None):
|
||||
'''
|
||||
:param thread_id:
|
||||
|
|
@ -59,10 +234,10 @@ class _FramesTracker(object):
|
|||
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:
|
||||
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._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
|
||||
|
||||
|
|
@ -72,6 +247,8 @@ class _FramesTracker(object):
|
|||
while frame is not None:
|
||||
frame_id = id(frame)
|
||||
self._frame_id_to_frame[frame_id] = frame
|
||||
_FrameVariable(frame, self._register_variable) # Instancing is enough to register.
|
||||
self._suspended_frames_manager._variable_reference_to_frames_tracker[frame_id] = self
|
||||
frame_ids_from_thread.append(frame_id)
|
||||
|
||||
self._frame_id_to_main_thread_id[frame_id] = thread_id
|
||||
|
|
@ -85,7 +262,10 @@ class _FramesTracker(object):
|
|||
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._suspended_frames_manager._thread_id_to_tracker.pop(thread_id, None)
|
||||
|
||||
for frame_id in self._frame_id_to_frame:
|
||||
del self._suspended_frames_manager._variable_reference_to_frames_tracker[frame_id]
|
||||
|
||||
self._frame_id_to_frame.clear()
|
||||
self._frame_id_to_main_thread_id.clear()
|
||||
|
|
@ -93,6 +273,7 @@ class _FramesTracker(object):
|
|||
self._frame_id_to_lineno.clear()
|
||||
self._main_thread_id = None
|
||||
self._suspended_frames_manager = None
|
||||
self._variable_reference_to_variable.clear()
|
||||
|
||||
def get_topmost_frame_and_frame_id_to_line(self, thread_id):
|
||||
with self._lock:
|
||||
|
|
@ -123,10 +304,59 @@ class SuspendedFramesManager(object):
|
|||
|
||||
def __init__(self):
|
||||
self._thread_id_to_fake_frames = {}
|
||||
self.thread_id_to_tracker = {}
|
||||
self._thread_id_to_tracker = {}
|
||||
|
||||
# Mappings
|
||||
self._variable_reference_to_frames_tracker = {}
|
||||
|
||||
def _get_tracker_for_variable_reference(self, variable_reference):
|
||||
tracker = self._variable_reference_to_frames_tracker.get(variable_reference)
|
||||
if tracker is not None:
|
||||
return tracker
|
||||
|
||||
for _thread_id, tracker in dict_iter_items(self._thread_id_to_tracker):
|
||||
try:
|
||||
tracker.get_variable(variable_reference)
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return tracker
|
||||
|
||||
return None
|
||||
|
||||
def get_thread_id_for_variable_reference(self, variable_reference):
|
||||
'''
|
||||
We can't evaluate variable references values on any thread, only in the suspended
|
||||
thread (the main reason for this is that in UI frameworks inspecting a UI object
|
||||
from a different thread can potentially crash the application).
|
||||
|
||||
:param int variable_reference:
|
||||
The variable reference (can be either a frame id or a reference to a previously
|
||||
gotten variable).
|
||||
|
||||
:return str:
|
||||
The thread id for the thread to be used to inspect the given variable reference or
|
||||
None if the thread was already resumed.
|
||||
'''
|
||||
frames_tracker = self._get_tracker_for_variable_reference(variable_reference)
|
||||
if frames_tracker is not None:
|
||||
return frames_tracker.get_main_thread_id()
|
||||
return None
|
||||
|
||||
def get_frame_tracker(self, thread_id):
|
||||
return self._thread_id_to_tracker.get(thread_id)
|
||||
|
||||
def get_variable(self, variable_reference):
|
||||
'''
|
||||
:raises KeyError
|
||||
'''
|
||||
frames_tracker = self._get_tracker_for_variable_reference(variable_reference)
|
||||
if frames_tracker is None:
|
||||
raise KeyError()
|
||||
return frames_tracker.get_variable(variable_reference)
|
||||
|
||||
def get_topmost_frame_and_frame_id_to_line(self, thread_id):
|
||||
tracker = self.thread_id_to_tracker.get(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)
|
||||
|
|
@ -154,7 +384,7 @@ class SuspendedFramesManager(object):
|
|||
if frame is not None:
|
||||
return frame
|
||||
|
||||
frames_tracker = self.thread_id_to_tracker.get(thread_id)
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ def getVariable(dbg, 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(dbg, thread_id, frame_id, attrList[count], False)
|
||||
var = evaluate_expression(dbg, frame, attrList[count], False)
|
||||
else:
|
||||
_type, _typeName, resolver = get_type(var)
|
||||
var = resolver.resolve(var, attrList[count])
|
||||
|
|
@ -240,11 +240,10 @@ def eval_in_context(expression, globals, locals):
|
|||
return result
|
||||
|
||||
|
||||
def evaluate_expression(dbg, thread_id, frame_id, expression, is_exec):
|
||||
def evaluate_expression(dbg, frame, expression, is_exec):
|
||||
'''returns the result of the evaluated expression
|
||||
@param is_exec: determines if we should do an exec or an eval
|
||||
'''
|
||||
frame = dbg.find_frame(thread_id, frame_id)
|
||||
if frame is None:
|
||||
return
|
||||
|
||||
|
|
@ -280,10 +279,9 @@ def evaluate_expression(dbg, thread_id, frame_id, expression, is_exec):
|
|||
del frame
|
||||
|
||||
|
||||
def change_attr_expression(thread_id, frame_id, attr, expression, dbg, value=SENTINEL_VALUE):
|
||||
def change_attr_expression(frame, attr, expression, dbg, value=SENTINEL_VALUE):
|
||||
'''Changes some attribute in a given frame.
|
||||
'''
|
||||
frame = dbg.find_frame(thread_id, frame_id)
|
||||
if frame is None:
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -16,12 +16,14 @@ try:
|
|||
except:
|
||||
frame_type = None
|
||||
|
||||
|
||||
def make_valid_xml_value(s):
|
||||
# Same thing as xml.sax.saxutils.escape but also escaping double quotes.
|
||||
return s.replace("&", "&").replace('<', '<').replace('>', '>').replace('"', '"')
|
||||
|
||||
|
||||
class ExceptionOnEvaluate:
|
||||
|
||||
def __init__(self, result):
|
||||
self.result = result
|
||||
|
||||
|
|
@ -204,14 +206,14 @@ class TypeResolveHandler(object):
|
|||
|
||||
_TYPE_RESOLVE_HANDLER = TypeResolveHandler()
|
||||
|
||||
"""
|
||||
"""
|
||||
def get_type(o):
|
||||
Receives object and returns a triple (typeObject, typeString, resolver).
|
||||
|
||||
|
||||
resolver != None means that variable is a container, and should be displayed as a hierarchy.
|
||||
|
||||
|
||||
Use the resolver to get its attributes.
|
||||
|
||||
|
||||
All container objects should have a resolver.
|
||||
"""
|
||||
get_type = _TYPE_RESOLVE_HANDLER.get_type
|
||||
|
|
@ -271,9 +273,7 @@ def frame_vars_to_xml(frame_f_locals, hidden_ns=None):
|
|||
return return_values_xml + xml
|
||||
|
||||
|
||||
def var_to_xml(val, name, trim_if_too_big=True, additional_in_xml='', evaluate_full_value=True):
|
||||
""" single variable or dictionary to xml representation """
|
||||
|
||||
def get_variable_details(val, evaluate_full_value=True, to_string=None):
|
||||
try:
|
||||
# This should be faster than isinstance (but we have to protect against not having a '__class__' attribute).
|
||||
is_exception_on_eval = val.__class__ == ExceptionOnEvaluate
|
||||
|
|
@ -285,15 +285,19 @@ def var_to_xml(val, name, trim_if_too_big=True, additional_in_xml='', evaluate_f
|
|||
else:
|
||||
v = val
|
||||
|
||||
_type, typeName, resolver = get_type(v)
|
||||
_type, type_name, resolver = get_type(v)
|
||||
type_qualifier = getattr(_type, "__module__", "")
|
||||
if not evaluate_full_value:
|
||||
value = DEFAULT_VALUE
|
||||
else:
|
||||
try:
|
||||
str_from_provider = _str_from_providers(v, _type, typeName)
|
||||
str_from_provider = _str_from_providers(v, _type, type_name)
|
||||
if str_from_provider is not None:
|
||||
value = str_from_provider
|
||||
|
||||
elif to_string is not None:
|
||||
value = to_string(v)
|
||||
|
||||
elif hasattr(v, '__class__'):
|
||||
if v.__class__ == frame_type:
|
||||
value = pydevd_resolver.frameResolver.get_frame_name(v)
|
||||
|
|
@ -326,12 +330,32 @@ def var_to_xml(val, name, trim_if_too_big=True, additional_in_xml='', evaluate_f
|
|||
except:
|
||||
value = 'Unable to get repr for %s' % v.__class__
|
||||
|
||||
# fix to work with unicode values
|
||||
try:
|
||||
if not IS_PY3K:
|
||||
if value.__class__ == unicode: # @UndefinedVariable
|
||||
value = value.encode('utf-8', 'replace')
|
||||
else:
|
||||
if value.__class__ == bytes:
|
||||
value = value.decode('utf-8', 'replace')
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
return type_name, type_qualifier, is_exception_on_eval, resolver, value
|
||||
|
||||
|
||||
def var_to_xml(val, name, trim_if_too_big=True, additional_in_xml='', evaluate_full_value=True):
|
||||
""" single variable or dictionary to xml representation """
|
||||
|
||||
type_name, type_qualifier, is_exception_on_eval, resolver, value = get_variable_details(
|
||||
val, evaluate_full_value)
|
||||
|
||||
try:
|
||||
name = quote(name, '/>_= ') # TODO: Fix PY-5834 without using quote
|
||||
except:
|
||||
pass
|
||||
|
||||
xml = '<var name="%s" type="%s" ' % (make_valid_xml_value(name), make_valid_xml_value(typeName))
|
||||
xml = '<var name="%s" type="%s" ' % (make_valid_xml_value(name), make_valid_xml_value(type_name))
|
||||
|
||||
if type_qualifier:
|
||||
xml_qualifier = 'qualifier="%s"' % make_valid_xml_value(type_qualifier)
|
||||
|
|
@ -344,17 +368,6 @@ def var_to_xml(val, name, trim_if_too_big=True, additional_in_xml='', evaluate_f
|
|||
value = value[0:MAXIMUM_VARIABLE_REPRESENTATION_SIZE]
|
||||
value += '...'
|
||||
|
||||
# fix to work with unicode values
|
||||
try:
|
||||
if not IS_PY3K:
|
||||
if value.__class__ == unicode: # @UndefinedVariable
|
||||
value = value.encode('utf-8')
|
||||
else:
|
||||
if value.__class__ == bytes:
|
||||
value = value.encode('utf-8')
|
||||
except TypeError: # in java, unicode is a function
|
||||
pass
|
||||
|
||||
xml_value = ' value="%s"' % (make_valid_xml_value(quote(value, '/>_= ')))
|
||||
else:
|
||||
xml_value = ''
|
||||
|
|
|
|||
|
|
@ -166,7 +166,13 @@ class PyDBCommandThread(PyDBDaemonThread):
|
|||
self._py_db_command_thread_event.clear()
|
||||
self._py_db_command_thread_event.wait(0.3)
|
||||
except:
|
||||
pydev_log.debug(sys.exc_info()[0])
|
||||
try:
|
||||
pydev_log.debug(sys.exc_info()[0])
|
||||
except:
|
||||
# In interpreter shutdown many things can go wrong (any module variables may
|
||||
# be None, streams can be closed, etc).
|
||||
pass
|
||||
|
||||
# only got this error in interpreter shutdown
|
||||
# pydevd_log(0, 'Finishing debug communication...(3)')
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ except:
|
|||
|
||||
threadingCurrentThread = threading.currentThread
|
||||
|
||||
|
||||
DONT_TRACE_THREADING = ['threading.py', 'pydevd.py']
|
||||
INNER_METHODS = ['_stop']
|
||||
INNER_FILES = ['threading.py']
|
||||
|
|
@ -27,11 +26,9 @@ THREAD_METHODS = ['start', '_stop', 'join']
|
|||
LOCK_METHODS = ['__init__', 'acquire', 'release', '__enter__', '__exit__']
|
||||
QUEUE_METHODS = ['put', 'get']
|
||||
|
||||
|
||||
# return time since epoch in milliseconds
|
||||
cur_time = lambda: int(round(time.time() * 1000000))
|
||||
|
||||
|
||||
try:
|
||||
import asyncio # @UnresolvedImport
|
||||
except:
|
||||
|
|
@ -44,35 +41,31 @@ def get_text_list_for_frame(frame):
|
|||
cmdTextList = []
|
||||
try:
|
||||
while curFrame:
|
||||
#print cmdText
|
||||
# print cmdText
|
||||
myId = str(id(curFrame))
|
||||
#print "id is ", myId
|
||||
# print "id is ", myId
|
||||
|
||||
if curFrame.f_code is None:
|
||||
break #Iron Python sometimes does not have it!
|
||||
break # Iron Python sometimes does not have it!
|
||||
|
||||
myName = curFrame.f_code.co_name #method name (if in method) or ? if global
|
||||
myName = curFrame.f_code.co_name # method name (if in method) or ? if global
|
||||
if myName is None:
|
||||
break #Iron Python sometimes does not have it!
|
||||
break # Iron Python sometimes does not have it!
|
||||
|
||||
#print "name is ", myName
|
||||
# print "name is ", myName
|
||||
|
||||
filename = pydevd_file_utils.get_abs_path_real_path_and_base_from_frame(curFrame)[1]
|
||||
|
||||
myFile = pydevd_file_utils.norm_file_to_client(filename)
|
||||
if file_system_encoding.lower() != "utf-8" and hasattr(myFile, "decode"):
|
||||
# myFile is a byte string encoded using the file system encoding
|
||||
# convert it to utf8
|
||||
myFile = myFile.decode(file_system_encoding).encode("utf-8")
|
||||
|
||||
#print "file is ", myFile
|
||||
#myFile = inspect.getsourcefile(curFrame) or inspect.getfile(frame)
|
||||
# print "file is ", myFile
|
||||
# myFile = inspect.getsourcefile(curFrame) or inspect.getfile(frame)
|
||||
|
||||
myLine = str(curFrame.f_lineno)
|
||||
#print "line is ", myLine
|
||||
# print "line is ", myLine
|
||||
|
||||
#the variables are all gotten 'on-demand'
|
||||
#variables = pydevd_xml.frame_vars_to_xml(curFrame.f_locals)
|
||||
# the variables are all gotten 'on-demand'
|
||||
# variables = pydevd_xml.frame_vars_to_xml(curFrame.f_locals)
|
||||
|
||||
variables = ''
|
||||
cmdTextList.append('<frame id="%s" name="%s" ' % (myId , pydevd_xml.make_valid_xml_value(myName)))
|
||||
|
|
@ -121,6 +114,7 @@ def log_new_thread(global_debugger, t):
|
|||
|
||||
|
||||
class ThreadingLogger:
|
||||
|
||||
def __init__(self):
|
||||
self.start_time = cur_time()
|
||||
|
||||
|
|
@ -235,12 +229,12 @@ class ThreadingLogger:
|
|||
# print(event_time, t.getName(), get_thread_id(t), "lock",
|
||||
# real_method, back.f_code.co_filename, back.f_lineno)
|
||||
|
||||
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
class NameManager:
|
||||
|
||||
def __init__(self, name_prefix):
|
||||
self.tasks = {}
|
||||
self.last = 0
|
||||
|
|
@ -254,6 +248,7 @@ class NameManager:
|
|||
|
||||
|
||||
class AsyncioLogger:
|
||||
|
||||
def __init__(self):
|
||||
self.task_mgr = NameManager("Task")
|
||||
self.coro_mgr = NameManager("Coro")
|
||||
|
|
@ -263,7 +258,7 @@ class AsyncioLogger:
|
|||
while frame is not None:
|
||||
if "self" in frame.f_locals:
|
||||
self_obj = frame.f_locals["self"]
|
||||
if isinstance(self_obj, asyncio.Task):
|
||||
if isinstance(self_obj, asyncio.Task):
|
||||
method_name = frame.f_code.co_name
|
||||
if method_name == "_step":
|
||||
return id(self_obj)
|
||||
|
|
@ -308,7 +303,7 @@ class AsyncioLogger:
|
|||
if method_name == "acquire":
|
||||
if not self_obj._waiters and not self_obj.locked():
|
||||
send_message("asyncio_event", event_time, task_name, task_name, "lock",
|
||||
method_name+"_begin", frame.f_code.co_filename, frame.f_lineno, frame, lock_id=str(id(self_obj)))
|
||||
method_name + "_begin", frame.f_code.co_filename, frame.f_lineno, frame, lock_id=str(id(self_obj)))
|
||||
if self_obj.locked():
|
||||
method_name += "_begin"
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -43,10 +43,14 @@ r'''
|
|||
|
||||
from _pydevd_bundle.pydevd_constants import IS_PY2, IS_PY3K, DebugInfoHolder, IS_WINDOWS, IS_JYTHON
|
||||
from _pydev_bundle._pydev_filesystem_encoding import getfilesystemencoding
|
||||
from _pydevd_bundle.pydevd_comm_constants import file_system_encoding, filesystem_encoding_is_utf8
|
||||
|
||||
import json
|
||||
import os.path
|
||||
import sys
|
||||
import traceback
|
||||
import itertools
|
||||
from functools import partial
|
||||
|
||||
_os_normcase = os.path.normcase
|
||||
basename = os.path.basename
|
||||
|
|
@ -363,11 +367,29 @@ except:
|
|||
# instead of importing any of those names to a given scope.
|
||||
|
||||
|
||||
def _path_to_expected_str(filename):
|
||||
if IS_PY2:
|
||||
if not filesystem_encoding_is_utf8 and hasattr(filename, "decode"):
|
||||
# filename_in_utf8 is a byte string encoded using the file system encoding
|
||||
# convert it to utf8
|
||||
filename = filename.decode(file_system_encoding)
|
||||
|
||||
if not isinstance(filename, bytes):
|
||||
filename = filename.encode('utf-8')
|
||||
|
||||
else: # py3
|
||||
if isinstance(filename, bytes):
|
||||
filename = filename.decode(file_system_encoding)
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
def _original_file_to_client(filename, cache={}):
|
||||
try:
|
||||
return cache[filename]
|
||||
except KeyError:
|
||||
cache[filename] = get_path_with_real_case(_AbsFile(filename))
|
||||
translated = _path_to_expected_str(get_path_with_real_case(_AbsFile(filename)))
|
||||
cache[filename] = translated
|
||||
return cache[filename]
|
||||
|
||||
|
||||
|
|
@ -388,6 +410,18 @@ def _fix_path(path, sep):
|
|||
|
||||
_last_client_server_paths_set = []
|
||||
|
||||
_source_reference_to_server_filename = {}
|
||||
_client_filename_in_utf8_to_source_reference = {}
|
||||
_next_source_reference = partial(next, itertools.count(1))
|
||||
|
||||
|
||||
def get_client_filename_source_reference(client_filename):
|
||||
return _client_filename_in_utf8_to_source_reference.get(client_filename, 0)
|
||||
|
||||
|
||||
def get_server_filename_from_source_reference(source_reference):
|
||||
return _source_reference_to_server_filename.get(source_reference, '')
|
||||
|
||||
|
||||
def setup_client_server_paths(paths):
|
||||
'''paths is the same format as PATHS_FROM_ECLIPSE_TO_PYTHON'''
|
||||
|
|
@ -414,7 +448,7 @@ def setup_client_server_paths(paths):
|
|||
path0 = path0.encode(sys.getfilesystemencoding())
|
||||
if isinstance(path1, unicode):
|
||||
path1 = path1.encode(sys.getfilesystemencoding())
|
||||
|
||||
|
||||
path0 = _fix_path(path0, eclipse_sep)
|
||||
path1 = _fix_path(path1, python_sep)
|
||||
initial_paths[i] = (path0, path1)
|
||||
|
|
@ -504,9 +538,17 @@ def setup_client_server_paths(paths):
|
|||
if eclipse_sep != python_sep:
|
||||
translated = translated.replace(python_sep, eclipse_sep)
|
||||
|
||||
translated = _path_to_expected_str(translated)
|
||||
|
||||
# The resulting path is not in the python process, so, we cannot do a _NormFile here,
|
||||
# only at the beginning of this method.
|
||||
cache[filename] = translated
|
||||
|
||||
if translated not in _client_filename_in_utf8_to_source_reference:
|
||||
source_reference = _next_source_reference()
|
||||
_client_filename_in_utf8_to_source_reference[translated] = source_reference
|
||||
_source_reference_to_server_filename[source_reference] = filename
|
||||
|
||||
return translated
|
||||
|
||||
norm_file_to_server = _norm_file_to_server
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ args = dict(
|
|||
'_pydev_imps',
|
||||
'_pydev_runfiles',
|
||||
'_pydevd_bundle',
|
||||
'_pydevd_bundle._debug_adapter',
|
||||
'_pydevd_frame_eval',
|
||||
'pydev_ipython',
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
def Call():
|
||||
variable_for_test_1 = 10
|
||||
variable_for_test_2 = 20
|
||||
variable_for_test_3 = 30
|
||||
|
||||
if __name__ == '__main__':
|
||||
Call()
|
||||
print('TEST SUCEEDED!')
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
|
||||
def Call():
|
||||
variable_for_test_1 = 10 # Break here
|
||||
variable_for_test_2 = 20
|
||||
variable_for_test_3 = {'a':30, 'b':20}
|
||||
locals()[u'\u16A0'] = u'\u16A1' # unicode variable (would be syntax error on py2).
|
||||
|
||||
all_vars_set = True # Break 2 here
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
Call()
|
||||
print('TEST SUCEEDED!')
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
|
||||
def Call():
|
||||
variable_for_test_1 = ['a', 'b']
|
||||
variable_for_test_2 = set(['a'])
|
||||
|
||||
all_vars_set = True # Break here
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
Call()
|
||||
print('TEST SUCEEDED!')
|
||||
|
|
@ -58,7 +58,7 @@ else:
|
|||
builtin_qualifier = "builtins"
|
||||
|
||||
|
||||
@pytest.mark.skipif(IS_IRONPYTHON, reason='Test needs gc.get_referrers to really check anything.')
|
||||
@pytest.mark.skipif(IS_IRONPYTHON or IS_JYTHON, reason='Test needs gc.get_referrers to really check anything.')
|
||||
def test_case_referrers(case_setup):
|
||||
with case_setup.test_file('_debugger_case1.py') as writer:
|
||||
writer.log.append('writing add breakpoint')
|
||||
|
|
@ -389,8 +389,8 @@ def test_case_6(case_setup):
|
|||
def test_case_7(case_setup):
|
||||
# This test checks that we start without variables and at each step a new var is created, but on ironpython,
|
||||
# the variables exist all at once (with None values), so, we can't test it properly.
|
||||
with case_setup.test_file('_debugger_case7.py') as writer:
|
||||
writer.write_add_breakpoint(2, 'Call')
|
||||
with case_setup.test_file('_debugger_case_local_variables.py') as writer:
|
||||
writer.write_add_breakpoint(writer.get_line_index_with_content('Break here'), 'Call')
|
||||
writer.write_make_initial_run()
|
||||
|
||||
hit = writer.wait_for_breakpoint_hit('111')
|
||||
|
|
@ -1804,8 +1804,8 @@ def test_path_translation(case_setup):
|
|||
|
||||
|
||||
def test_evaluate_errors(case_setup):
|
||||
with case_setup.test_file('_debugger_case7.py') as writer:
|
||||
writer.write_add_breakpoint(4, 'Call')
|
||||
with case_setup.test_file('_debugger_case_local_variables.py') as writer:
|
||||
writer.write_add_breakpoint(writer.get_line_index_with_content('Break here'), 'Call')
|
||||
writer.write_make_initial_run()
|
||||
|
||||
hit = writer.wait_for_breakpoint_hit()
|
||||
|
|
@ -1819,8 +1819,8 @@ def test_evaluate_errors(case_setup):
|
|||
|
||||
|
||||
def test_list_threads(case_setup):
|
||||
with case_setup.test_file('_debugger_case7.py') as writer:
|
||||
writer.write_add_breakpoint(4, 'Call')
|
||||
with case_setup.test_file('_debugger_case_local_variables.py') as writer:
|
||||
writer.write_add_breakpoint(writer.get_line_index_with_content('Break here'), 'Call')
|
||||
writer.write_make_initial_run()
|
||||
|
||||
hit = writer.wait_for_breakpoint_hit()
|
||||
|
|
|
|||
|
|
@ -2,15 +2,22 @@ import pytest
|
|||
|
||||
from _pydevd_bundle._debug_adapter import pydevd_schema, pydevd_base_schema
|
||||
from _pydevd_bundle._debug_adapter.pydevd_base_schema import from_json
|
||||
from _pydevd_bundle._debug_adapter.pydevd_schema import ThreadEvent
|
||||
from _pydevd_bundle._debug_adapter.pydevd_schema import ThreadEvent, ModuleEvent
|
||||
from tests_python import debugger_unittest
|
||||
from tests_python.debugger_unittest import IS_JYTHON, REASON_STEP_INTO, REASON_STEP_OVER
|
||||
import json
|
||||
from collections import namedtuple
|
||||
from _pydevd_bundle.pydevd_constants import int_types, IS_WINDOWS
|
||||
from tests_python.debug_constants import IS_PY2
|
||||
from os.path import os
|
||||
from tests_python.debugger_unittest import CMD_LOAD_SOURCE
|
||||
|
||||
pytest_plugins = [
|
||||
str('tests_python.debugger_fixtures'),
|
||||
]
|
||||
|
||||
_JsonHit = namedtuple('_JsonHit', 'frameId')
|
||||
|
||||
|
||||
class JsonFacade(object):
|
||||
|
||||
|
|
@ -112,6 +119,46 @@ class JsonFacade(object):
|
|||
request = pydevd_schema.DisconnectRequest(arguments)
|
||||
self.wait_for_response(self.write_request(request))
|
||||
|
||||
def get_stack_as_json_hit(self, thread_id):
|
||||
stack_trace_request = self.write_request(
|
||||
pydevd_schema.StackTraceRequest(pydevd_schema.StackTraceArguments(threadId=thread_id)))
|
||||
|
||||
# : :type stack_trace_response: StackTraceResponse
|
||||
# : :type stack_trace_response_body: StackTraceResponseBody
|
||||
# : :type stack_frame: StackFrame
|
||||
stack_trace_response = self.wait_for_response(stack_trace_request)
|
||||
stack_trace_response_body = stack_trace_response.body
|
||||
assert len(stack_trace_response_body.stackFrames) > 0
|
||||
stack_frame = next(iter(stack_trace_response_body.stackFrames))
|
||||
|
||||
return _JsonHit(frameId=stack_frame['id'])
|
||||
|
||||
def get_variables_response(self, variables_reference):
|
||||
variables_request = self.write_request(
|
||||
pydevd_schema.VariablesRequest(pydevd_schema.VariablesArguments(variables_reference)))
|
||||
variables_response = self.wait_for_response(variables_request)
|
||||
return variables_response
|
||||
|
||||
def filter_return_variables(self, variables):
|
||||
ret = []
|
||||
for variable in variables:
|
||||
if variable['name'].startswith('(return)'):
|
||||
ret.append(variable)
|
||||
return ret
|
||||
|
||||
def pop_variables_reference(self, lst):
|
||||
'''
|
||||
Modifies dicts in-place to remove the variablesReference and returns those (in the same order
|
||||
in which they were received).
|
||||
'''
|
||||
references = []
|
||||
for dct in lst:
|
||||
reference = dct.pop('variablesReference', None)
|
||||
if reference is not None:
|
||||
assert isinstance(reference, int_types)
|
||||
references.append(reference)
|
||||
return references
|
||||
|
||||
|
||||
def test_case_json_logpoints(case_setup):
|
||||
with case_setup.test_file('_debugger_case_change_breaks.py') as writer:
|
||||
|
|
@ -304,11 +351,11 @@ def test_case_completions_json(case_setup):
|
|||
json_facade.write_make_initial_run()
|
||||
|
||||
hit = writer.wait_for_breakpoint_hit()
|
||||
thread_id = hit.thread_id
|
||||
frame_id = hit.frame_id
|
||||
|
||||
json_hit = json_facade.get_stack_as_json_hit(hit.thread_id)
|
||||
|
||||
completions_arguments = pydevd_schema.CompletionsArguments(
|
||||
'dict.', 6, frameId=(thread_id, frame_id), line=0)
|
||||
'dict.', 6, frameId=json_hit.frameId, line=0)
|
||||
completions_request = json_facade.write_request(
|
||||
pydevd_schema.CompletionsRequest(completions_arguments))
|
||||
|
||||
|
|
@ -317,7 +364,7 @@ def test_case_completions_json(case_setup):
|
|||
assert set(labels).issuperset(set(['__contains__', 'items', 'keys', 'values']))
|
||||
|
||||
completions_arguments = pydevd_schema.CompletionsArguments(
|
||||
'dict.item', 10, frameId=(thread_id, frame_id))
|
||||
'dict.item', 10, frameId=json_hit.frameId)
|
||||
completions_request = json_facade.write_request(
|
||||
pydevd_schema.CompletionsRequest(completions_arguments))
|
||||
|
||||
|
|
@ -329,7 +376,425 @@ def test_case_completions_json(case_setup):
|
|||
assert response.body.targets == [
|
||||
{'start': 5, 'length': 4, 'type': 'function', 'label': 'items'}]
|
||||
|
||||
writer.write_run_thread(thread_id)
|
||||
writer.write_run_thread(hit.thread_id)
|
||||
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
def test_modules(case_setup):
|
||||
with case_setup.test_file('_debugger_case_local_variables.py') as writer:
|
||||
json_facade = JsonFacade(writer)
|
||||
|
||||
writer.write_set_protocol('http_json')
|
||||
|
||||
writer.write_add_breakpoint(writer.get_line_index_with_content('Break 2 here'))
|
||||
json_facade.write_make_initial_run()
|
||||
|
||||
hit = writer.wait_for_breakpoint_hit()
|
||||
|
||||
json_facade.write_request(
|
||||
pydevd_schema.StackTraceRequest(pydevd_schema.StackTraceArguments(threadId=hit.thread_id)))
|
||||
|
||||
json_facade.wait_for_json_message(ModuleEvent)
|
||||
|
||||
# : :type response: ModulesResponse
|
||||
# : :type modules_response_body: ModulesResponseBody
|
||||
response = json_facade.wait_for_response(json_facade.write_request(
|
||||
pydevd_schema.ModulesRequest(pydevd_schema.ModulesArguments())))
|
||||
modules_response_body = response.body
|
||||
assert len(modules_response_body.modules) == 1
|
||||
module = next(iter(modules_response_body.modules))
|
||||
assert module['name'] == '__main__'
|
||||
assert module['path'].endswith('_debugger_case_local_variables.py')
|
||||
|
||||
writer.write_run_thread(hit.thread_id)
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
@pytest.mark.skipif(IS_JYTHON, reason='Putting unicode on frame vars does not work on Jython.')
|
||||
def test_stack_and_variables_dict(case_setup):
|
||||
with case_setup.test_file('_debugger_case_local_variables.py') as writer:
|
||||
json_facade = JsonFacade(writer)
|
||||
|
||||
writer.write_set_protocol('http_json')
|
||||
|
||||
writer.write_add_breakpoint(writer.get_line_index_with_content('Break 2 here'))
|
||||
json_facade.write_make_initial_run()
|
||||
|
||||
hit = writer.wait_for_breakpoint_hit()
|
||||
json_hit = json_facade.get_stack_as_json_hit(hit.thread_id)
|
||||
variables_response = json_facade.get_variables_response(json_hit.frameId)
|
||||
|
||||
variables_references = json_facade.pop_variables_reference(variables_response.body.variables)
|
||||
dict_variable_reference = variables_references[2]
|
||||
assert isinstance(dict_variable_reference, int_types)
|
||||
# : :type variables_response: VariablesResponse
|
||||
|
||||
if IS_PY2:
|
||||
print(repr(variables_response.body.variables[-1]))
|
||||
expected_unicode = {
|
||||
u'name': u'\u16a0',
|
||||
u'value': u"u'\\u16a1'",
|
||||
u'type': u'unicode',
|
||||
u'presentationHint': {u'attributes': [u'rawString']},
|
||||
u'evaluateName': u'\u16a0',
|
||||
}
|
||||
else:
|
||||
expected_unicode = {
|
||||
'name': u'\u16A0',
|
||||
'value': "'\u16a1'",
|
||||
'type': 'str',
|
||||
'presentationHint': {'attributes': ['rawString']},
|
||||
'evaluateName': u'\u16A0',
|
||||
}
|
||||
assert variables_response.body.variables == [
|
||||
{'name': 'variable_for_test_1', 'value': '10', 'type': 'int', 'evaluateName': 'variable_for_test_1'},
|
||||
{'name': 'variable_for_test_2', 'value': '20', 'type': 'int', 'evaluateName': 'variable_for_test_2'},
|
||||
{'name': 'variable_for_test_3', 'value': "{'a': 30, 'b': 20}", 'type': 'dict', 'evaluateName': 'variable_for_test_3'},
|
||||
expected_unicode
|
||||
]
|
||||
|
||||
variables_response = json_facade.get_variables_response(dict_variable_reference)
|
||||
assert variables_response.body.variables == [
|
||||
{'name': "'a'", 'value': '30', 'type': 'int', 'evaluateName': "variable_for_test_3['a']" },
|
||||
{'name': "'b'", 'value': '20', 'type': 'int', 'evaluateName': "variable_for_test_3['b']"},
|
||||
{'name': '__len__', 'value': '2', 'type': 'int', 'evaluateName': 'variable_for_test_3.__len__()'}
|
||||
]
|
||||
|
||||
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:
|
||||
json_facade = JsonFacade(writer)
|
||||
writer.write_set_protocol('http_json')
|
||||
|
||||
break_line = writer.get_line_index_with_content('break here')
|
||||
writer.write_add_breakpoint(break_line)
|
||||
writer.write_show_return_vars()
|
||||
json_facade.write_make_initial_run()
|
||||
|
||||
hit = writer.wait_for_breakpoint_hit()
|
||||
writer.write_step_over(hit.thread_id)
|
||||
hit = writer.wait_for_breakpoint_hit(REASON_STEP_OVER, name='<module>', line=break_line + 1)
|
||||
|
||||
json_hit = json_facade.get_stack_as_json_hit(hit.thread_id)
|
||||
variables_response = json_facade.get_variables_response(json_hit.frameId)
|
||||
return_variables = json_facade.filter_return_variables(variables_response.body.variables)
|
||||
assert return_variables == [{
|
||||
'name': '(return) method1',
|
||||
'value': '1',
|
||||
'type': 'int',
|
||||
'evaluateName': "__pydevd_ret_val_dict['method1']",
|
||||
'presentationHint': {'attributes': ['readOnly']}
|
||||
}]
|
||||
|
||||
writer.write_run_thread(hit.thread_id)
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
def test_stack_and_variables_set_and_list(case_setup):
|
||||
with case_setup.test_file('_debugger_case_local_variables2.py') as writer:
|
||||
json_facade = JsonFacade(writer)
|
||||
|
||||
writer.write_set_protocol('http_json')
|
||||
|
||||
writer.write_add_breakpoint(writer.get_line_index_with_content('Break here'))
|
||||
json_facade.write_make_initial_run()
|
||||
|
||||
hit = writer.wait_for_breakpoint_hit()
|
||||
json_hit = json_facade.get_stack_as_json_hit(hit.thread_id)
|
||||
variables_response = json_facade.get_variables_response(json_hit.frameId)
|
||||
|
||||
variables_references = json_facade.pop_variables_reference(variables_response.body.variables)
|
||||
if IS_PY2:
|
||||
expected_set = "set(['a'])"
|
||||
else:
|
||||
expected_set = "{'a'}"
|
||||
assert variables_response.body.variables == [
|
||||
{'type': 'list', 'evaluateName': 'variable_for_test_1', 'name': 'variable_for_test_1', 'value': "['a', 'b']"},
|
||||
{'type': 'set', 'evaluateName': 'variable_for_test_2', 'name': 'variable_for_test_2', 'value': expected_set}
|
||||
]
|
||||
|
||||
variables_response = json_facade.get_variables_response(variables_references[0])
|
||||
assert variables_response.body.variables == [{
|
||||
u'name': u'0',
|
||||
u'type': u'str',
|
||||
u'value': u"'a'",
|
||||
u'presentationHint': {u'attributes': [u'rawString']},
|
||||
u'evaluateName': u'variable_for_test_1[0]',
|
||||
},
|
||||
{
|
||||
u'name': u'1',
|
||||
u'type': u'str',
|
||||
u'value': u"'b'",
|
||||
u'presentationHint': {u'attributes': [u'rawString']},
|
||||
u'evaluateName': u'variable_for_test_1[1]',
|
||||
},
|
||||
{
|
||||
u'name': u'__len__',
|
||||
u'type': u'int',
|
||||
u'value': u'2',
|
||||
u'evaluateName': u'variable_for_test_1.__len__()',
|
||||
}]
|
||||
|
||||
writer.write_run_thread(hit.thread_id)
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
@pytest.mark.skipif(IS_JYTHON, reason='Putting unicode on frame vars does not work on Jython.')
|
||||
def test_evaluate_unicode(case_setup):
|
||||
from _pydevd_bundle._debug_adapter.pydevd_schema import EvaluateRequest
|
||||
from _pydevd_bundle._debug_adapter.pydevd_schema import EvaluateArguments
|
||||
with case_setup.test_file('_debugger_case_local_variables.py') as writer:
|
||||
json_facade = JsonFacade(writer)
|
||||
|
||||
writer.write_set_protocol('http_json')
|
||||
|
||||
writer.write_add_breakpoint(writer.get_line_index_with_content('Break 2 here'))
|
||||
json_facade.write_make_initial_run()
|
||||
|
||||
hit = writer.wait_for_breakpoint_hit()
|
||||
json_hit = json_facade.get_stack_as_json_hit(hit.thread_id)
|
||||
|
||||
evaluate_response = json_facade.wait_for_response(
|
||||
json_facade.write_request(EvaluateRequest(EvaluateArguments(u'\u16A0', json_hit.frameId))))
|
||||
|
||||
if IS_PY2:
|
||||
assert evaluate_response.body.to_dict() == {
|
||||
'result': u"SyntaxError('invalid syntax', ('<string>', 1, 1, '\\xe1\\x9a\\xa0'))",
|
||||
'type': u'SyntaxError',
|
||||
'variablesReference': 0,
|
||||
'presentationHint': {},
|
||||
}
|
||||
else:
|
||||
assert evaluate_response.body.to_dict() == {
|
||||
'result': "'\u16a1'",
|
||||
'type': 'str',
|
||||
'variablesReference': 0,
|
||||
'presentationHint': {'attributes': ['rawString']},
|
||||
}
|
||||
|
||||
writer.write_run_thread(hit.thread_id)
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
def test_set_expression(case_setup):
|
||||
from _pydevd_bundle._debug_adapter.pydevd_schema import SetExpressionRequest
|
||||
from _pydevd_bundle._debug_adapter.pydevd_schema import SetExpressionArguments
|
||||
with case_setup.test_file('_debugger_case_local_variables2.py') as writer:
|
||||
json_facade = JsonFacade(writer)
|
||||
|
||||
writer.write_set_protocol('http_json')
|
||||
|
||||
writer.write_add_breakpoint(writer.get_line_index_with_content('Break here'))
|
||||
json_facade.write_make_initial_run()
|
||||
|
||||
hit = writer.wait_for_breakpoint_hit()
|
||||
json_hit = json_facade.get_stack_as_json_hit(hit.thread_id)
|
||||
|
||||
set_expression_response = json_facade.wait_for_response(
|
||||
json_facade.write_request(SetExpressionRequest(
|
||||
SetExpressionArguments('bb', '20', frameId=json_hit.frameId))))
|
||||
assert set_expression_response.to_dict()['body'] == {
|
||||
'value': '20', 'type': 'int', 'presentationHint': {}, 'variablesReference': 0}
|
||||
|
||||
variables_response = json_facade.get_variables_response(json_hit.frameId)
|
||||
assert {'name': 'bb', 'value': '20', 'type': 'int', 'evaluateName': 'bb'} in \
|
||||
variables_response.to_dict()['body']['variables']
|
||||
|
||||
writer.write_run_thread(hit.thread_id)
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
@pytest.mark.skipif(IS_JYTHON, reason='Putting unicode on frame vars does not work on Jython.')
|
||||
def test_stack_and_variables(case_setup):
|
||||
|
||||
with case_setup.test_file('_debugger_case_local_variables.py') as writer:
|
||||
json_facade = JsonFacade(writer)
|
||||
|
||||
writer.write_set_protocol('http_json')
|
||||
writer.write_add_breakpoint(writer.get_line_index_with_content('Break here'))
|
||||
|
||||
json_facade.write_make_initial_run()
|
||||
|
||||
hit = writer.wait_for_breakpoint_hit()
|
||||
|
||||
# : :type stack_trace_response: StackTraceResponse
|
||||
# : :type stack_trace_response_body: StackTraceResponseBody
|
||||
# : :type stack_frame: StackFrame
|
||||
|
||||
# Check stack trace format.
|
||||
stack_trace_request = json_facade.write_request(
|
||||
pydevd_schema.StackTraceRequest(pydevd_schema.StackTraceArguments(
|
||||
threadId=hit.thread_id,
|
||||
format={'module': True, 'line': True}
|
||||
)))
|
||||
stack_trace_response = json_facade.wait_for_response(stack_trace_request)
|
||||
stack_trace_response_body = stack_trace_response.body
|
||||
stack_frame = next(iter(stack_trace_response_body.stackFrames))
|
||||
assert stack_frame['name'] == '__main__.Call : 4'
|
||||
|
||||
# Regular stack trace request (no format).
|
||||
stack_trace_request = json_facade.write_request(
|
||||
pydevd_schema.StackTraceRequest(pydevd_schema.StackTraceArguments(threadId=hit.thread_id)))
|
||||
stack_trace_response = json_facade.wait_for_response(stack_trace_request)
|
||||
stack_trace_response_body = stack_trace_response.body
|
||||
assert len(stack_trace_response_body.stackFrames) == 2
|
||||
stack_frame = next(iter(stack_trace_response_body.stackFrames))
|
||||
assert stack_frame['name'] == 'Call'
|
||||
assert stack_frame['source']['path'].endswith('_debugger_case_local_variables.py')
|
||||
|
||||
scopes_request = json_facade.write_request(pydevd_schema.ScopesRequest(
|
||||
pydevd_schema.ScopesArguments(stack_frame['id'])))
|
||||
|
||||
scopes_response = json_facade.wait_for_response(scopes_request)
|
||||
scopes = scopes_response.body.scopes
|
||||
assert len(scopes) == 1
|
||||
scope = pydevd_schema.Scope(**next(iter(scopes)))
|
||||
assert scope.name == 'Locals'
|
||||
assert not scope.expensive
|
||||
frame_variables_reference = scope.variablesReference
|
||||
assert isinstance(frame_variables_reference, int)
|
||||
|
||||
variables_request = json_facade.write_request(
|
||||
pydevd_schema.VariablesRequest(pydevd_schema.VariablesArguments(frame_variables_reference)))
|
||||
variables_response = json_facade.wait_for_response(variables_request)
|
||||
# : :type variables_response: VariablesResponse
|
||||
assert len(variables_response.body.variables) == 0 # No variables expected here
|
||||
|
||||
writer.write_step_over(hit.thread_id)
|
||||
hit = writer.wait_for_breakpoint_hit(REASON_STEP_OVER)
|
||||
|
||||
variables_request = json_facade.write_request(
|
||||
pydevd_schema.VariablesRequest(pydevd_schema.VariablesArguments(frame_variables_reference)))
|
||||
variables_response = json_facade.wait_for_response(variables_request)
|
||||
# : :type variables_response: VariablesResponse
|
||||
assert variables_response.body.variables == [{
|
||||
'name': 'variable_for_test_1',
|
||||
'value': '10',
|
||||
'type': 'int',
|
||||
'evaluateName': 'variable_for_test_1'
|
||||
}]
|
||||
|
||||
# Same thing with hex format
|
||||
variables_request = json_facade.write_request(
|
||||
pydevd_schema.VariablesRequest(pydevd_schema.VariablesArguments(
|
||||
frame_variables_reference,
|
||||
format={'hex': True}
|
||||
)))
|
||||
variables_response = json_facade.wait_for_response(variables_request)
|
||||
# : :type variables_response: VariablesResponse
|
||||
assert variables_response.body.variables == [{
|
||||
'name': 'variable_for_test_1',
|
||||
'value': '0xa',
|
||||
'type': 'int',
|
||||
'evaluateName': 'variable_for_test_1'
|
||||
}]
|
||||
|
||||
# Note: besides the scope/stack/variables we can also have references when:
|
||||
# - setting variable
|
||||
# * If the variable was changed to a container, the new reference should be returned.
|
||||
# - evaluate expression
|
||||
# * Currently ptvsd returns a None value in on_setExpression, so, skip this for now.
|
||||
# - output
|
||||
# * Currently not handled by ptvsd, so, skip for now.
|
||||
|
||||
# Reference is for parent (in this case the frame).
|
||||
# We'll change `variable_for_test_1` from 10 to [1].
|
||||
set_variable_request = json_facade.write_request(
|
||||
pydevd_schema.SetVariableRequest(pydevd_schema.SetVariableArguments(
|
||||
frame_variables_reference, 'variable_for_test_1', '[1]'
|
||||
)))
|
||||
set_variable_response = json_facade.wait_for_response(set_variable_request)
|
||||
set_variable_response_as_dict = set_variable_response.to_dict()['body']
|
||||
if not IS_JYTHON:
|
||||
# Not properly changing var on Jython.
|
||||
assert isinstance(set_variable_response_as_dict.pop('variablesReference'), int)
|
||||
assert set_variable_response_as_dict == {'value': "[1]", 'type': 'list'}
|
||||
|
||||
variables_request = json_facade.write_request(
|
||||
pydevd_schema.VariablesRequest(pydevd_schema.VariablesArguments(frame_variables_reference)))
|
||||
variables_response = json_facade.wait_for_response(variables_request)
|
||||
# : :type variables_response: VariablesResponse
|
||||
variables = variables_response.body.variables
|
||||
assert len(variables) == 1
|
||||
var_as_dict = next(iter(variables))
|
||||
if not IS_JYTHON:
|
||||
# Not properly changing var on Jython.
|
||||
assert isinstance(var_as_dict.pop('variablesReference'), int)
|
||||
assert var_as_dict == {
|
||||
'name': 'variable_for_test_1',
|
||||
'value': "[1]",
|
||||
'type': 'list',
|
||||
'evaluateName': 'variable_for_test_1',
|
||||
}
|
||||
|
||||
writer.write_run_thread(hit.thread_id)
|
||||
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
def test_path_translation_and_source_reference(case_setup):
|
||||
|
||||
def get_file_in_client(writer):
|
||||
# Instead of using: test_python/_debugger_case_path_translation.py
|
||||
# we'll set the breakpoints at foo/_debugger_case_path_translation.py
|
||||
file_in_client = os.path.dirname(os.path.dirname(writer.TEST_FILE))
|
||||
return os.path.join(os.path.dirname(file_in_client), 'foo', '_debugger_case_path_translation.py')
|
||||
|
||||
def get_environ(writer):
|
||||
env = os.environ.copy()
|
||||
|
||||
env["PYTHONIOENCODING"] = 'utf-8'
|
||||
|
||||
assert writer.TEST_FILE.endswith('_debugger_case_path_translation.py')
|
||||
env["PATHS_FROM_ECLIPSE_TO_PYTHON"] = json.dumps([
|
||||
(
|
||||
os.path.dirname(get_file_in_client(writer)),
|
||||
os.path.dirname(writer.TEST_FILE)
|
||||
)
|
||||
])
|
||||
return env
|
||||
|
||||
with case_setup.test_file('_debugger_case_path_translation.py', get_environ=get_environ) as writer:
|
||||
file_in_client = get_file_in_client(writer)
|
||||
assert 'tests_python' not in file_in_client
|
||||
assert 'foo' in file_in_client
|
||||
|
||||
json_facade = JsonFacade(writer)
|
||||
|
||||
writer.write_set_protocol('http_json')
|
||||
writer.write_add_breakpoint(2, 'main', filename=file_in_client)
|
||||
|
||||
json_facade.write_make_initial_run()
|
||||
|
||||
hit = writer.wait_for_breakpoint_hit()
|
||||
|
||||
# : :type stack_trace_response: StackTraceResponse
|
||||
# : :type stack_trace_response_body: StackTraceResponseBody
|
||||
# : :type stack_frame: StackFrame
|
||||
|
||||
# Check stack trace format.
|
||||
stack_trace_request = json_facade.write_request(
|
||||
pydevd_schema.StackTraceRequest(pydevd_schema.StackTraceArguments(
|
||||
threadId=hit.thread_id,
|
||||
format={'module': True, 'line': True}
|
||||
)))
|
||||
stack_trace_response = json_facade.wait_for_response(stack_trace_request)
|
||||
stack_trace_response_body = stack_trace_response.body
|
||||
stack_frame = next(iter(stack_trace_response_body.stackFrames))
|
||||
assert stack_frame['name'] == '__main__.main : 2'
|
||||
assert stack_frame['source']['path'] == file_in_client
|
||||
source_reference = stack_frame['source']['sourceReference']
|
||||
assert source_reference != 0
|
||||
|
||||
response = json_facade.wait_for_response(json_facade.write_request(
|
||||
pydevd_schema.SourceRequest(pydevd_schema.SourceArguments(source_reference))))
|
||||
assert "print('TEST SUCEEDED!')" in response.body.content
|
||||
|
||||
writer.write_run_thread(hit.thread_id)
|
||||
|
||||
writer.finished_ok = True
|
||||
|
||||
|
|
|
|||
81
src/ptvsd/_vendored/pydevd/tests_python/test_resolvers.py
Normal file
81
src/ptvsd/_vendored/pydevd/tests_python/test_resolvers.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
from tests_python.debug_constants import IS_PY2
|
||||
|
||||
|
||||
def test_dict_resolver():
|
||||
from _pydevd_bundle.pydevd_resolver import DictResolver
|
||||
dict_resolver = DictResolver()
|
||||
dct = {(1, 2): 2, u'22': 22}
|
||||
contents_debug_adapter_protocol = dict_resolver.get_contents_debug_adapter_protocol(dct)
|
||||
if IS_PY2:
|
||||
assert contents_debug_adapter_protocol == [
|
||||
('(1, 2)', 2, '[(1, 2)]'), (u"u'22'", 22, u"[u'22']"), ('__len__', 2, '.__len__()')]
|
||||
else:
|
||||
assert contents_debug_adapter_protocol == [
|
||||
("'22'", 22, "['22']"), ('(1, 2)', 2, '[(1, 2)]'), ('__len__', 2, '.__len__()')]
|
||||
|
||||
|
||||
def test_tuple_resolver():
|
||||
from _pydevd_bundle.pydevd_resolver import TupleResolver
|
||||
tuple_resolver = TupleResolver()
|
||||
lst = tuple(range(11))
|
||||
contents_debug_adapter_protocol = tuple_resolver.get_contents_debug_adapter_protocol(lst)
|
||||
assert contents_debug_adapter_protocol == [
|
||||
('00', 0, '[0]'),
|
||||
('01', 1, '[1]'),
|
||||
('02', 2, '[2]'),
|
||||
('03', 3, '[3]'),
|
||||
('04', 4, '[4]'),
|
||||
('05', 5, '[5]'),
|
||||
('06', 6, '[6]'),
|
||||
('07', 7, '[7]'),
|
||||
('08', 8, '[8]'),
|
||||
('09', 9, '[9]'),
|
||||
('10', 10, '[10]'),
|
||||
('__len__', 11, '.__len__()')
|
||||
]
|
||||
|
||||
assert tuple_resolver.get_dictionary(lst) == {
|
||||
'00': 0,
|
||||
'01': 1,
|
||||
'02': 2,
|
||||
'03': 3,
|
||||
'04': 4,
|
||||
'05': 5,
|
||||
'06': 6,
|
||||
'07': 7,
|
||||
'08': 8,
|
||||
'09': 9,
|
||||
'10': 10,
|
||||
'__len__': 11
|
||||
}
|
||||
|
||||
lst = tuple(range(10))
|
||||
contents_debug_adapter_protocol = tuple_resolver.get_contents_debug_adapter_protocol(lst)
|
||||
assert contents_debug_adapter_protocol == [
|
||||
('0', 0, '[0]'),
|
||||
('1', 1, '[1]'),
|
||||
('2', 2, '[2]'),
|
||||
('3', 3, '[3]'),
|
||||
('4', 4, '[4]'),
|
||||
('5', 5, '[5]'),
|
||||
('6', 6, '[6]'),
|
||||
('7', 7, '[7]'),
|
||||
('8', 8, '[8]'),
|
||||
('9', 9, '[9]'),
|
||||
('__len__', 10, '.__len__()')
|
||||
]
|
||||
|
||||
assert tuple_resolver.get_dictionary(lst) == {
|
||||
'0': 0,
|
||||
'1': 1,
|
||||
'2': 2,
|
||||
'3': 3,
|
||||
'4': 4,
|
||||
'5': 5,
|
||||
'6': 6,
|
||||
'7': 7,
|
||||
'8': 8,
|
||||
'9': 9,
|
||||
'__len__': 10
|
||||
}
|
||||
|
||||
|
|
@ -2,15 +2,13 @@ import collections
|
|||
import sys
|
||||
import re
|
||||
import pytest
|
||||
from _pydevd_bundle.pydevd_safe_repr import SafeRepr
|
||||
|
||||
try:
|
||||
import numpy as np
|
||||
except ImportError:
|
||||
np = None
|
||||
|
||||
from ptvsd.safe_repr import SafeRepr
|
||||
|
||||
|
||||
PY_VER = sys.version_info[0]
|
||||
assert PY_VER <= 3 # Fix the code when Python 4 comes around.
|
||||
PY3K = PY_VER == 3
|
||||
|
|
@ -84,12 +82,12 @@ class TestSafeRepr(SafeReprTestBase):
|
|||
for i in range(SafeRepr.maxcollection[0]):
|
||||
dcoll[str(i) * SafeRepr.maxstring_outer] = coll
|
||||
text = self.saferepr(dcoll)
|
||||
#try:
|
||||
# try:
|
||||
# text_repr = repr(dcoll)
|
||||
#except MemoryError:
|
||||
# except MemoryError:
|
||||
# print('Memory error raised while creating repr of test data')
|
||||
# text_repr = ''
|
||||
#print('len(SafeRepr()(dcoll)) = ' + str(len(text)) +
|
||||
# print('len(SafeRepr()(dcoll)) = ' + str(len(text)) +
|
||||
# ', len(repr(coll)) = ' + str(len(text_repr)))
|
||||
|
||||
assert len(text) < 8192
|
||||
|
|
@ -161,13 +159,13 @@ class TestStrings(SafeReprTestBase):
|
|||
"b'" + 'A' * 43688 + "..." + 'A' * 21844 + "'")
|
||||
self.assert_shortened([value], "[b'AAAAAAAAAAAAAAAAAA...AAAAAAAAA']")
|
||||
|
||||
@pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
def test_bytearray_small(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
def test_bytearray_large(self):
|
||||
raise NotImplementedError
|
||||
# @pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
# def test_bytearray_small(self):
|
||||
# raise NotImplementedError
|
||||
#
|
||||
# @pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
# def test_bytearray_large(self):
|
||||
# raise NotImplementedError
|
||||
|
||||
|
||||
class RawValueTests(SafeReprTestBase):
|
||||
|
|
@ -188,20 +186,19 @@ class RawValueTests(SafeReprTestBase):
|
|||
value = bytearray(b'A' * 5)
|
||||
self.assert_saferepr(value, value.decode('ascii'))
|
||||
|
||||
|
||||
class TestNumbers(SafeReprTestBase):
|
||||
|
||||
@pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
def test_int(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
def test_float(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
def test_complex(self):
|
||||
raise NotImplementedError
|
||||
# class TestNumbers(SafeReprTestBase):
|
||||
#
|
||||
# @pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
# def test_int(self):
|
||||
# raise NotImplementedError
|
||||
#
|
||||
# @pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
# def test_float(self):
|
||||
# raise NotImplementedError
|
||||
#
|
||||
# @pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
# def test_complex(self):
|
||||
# raise NotImplementedError
|
||||
|
||||
|
||||
class ContainerBase(object):
|
||||
|
|
@ -241,7 +238,7 @@ class ContainerBase(object):
|
|||
suffix = _suffix + ("," + _suffix) * depth
|
||||
else:
|
||||
suffix = _suffix * (depth + 1)
|
||||
#print("ctype = " + ctype.__name__ + ", maxcollection[" +
|
||||
# print("ctype = " + ctype.__name__ + ", maxcollection[" +
|
||||
# str(i) + "] == " + str(SafeRepr.maxcollection[i]))
|
||||
return self._combine(items, prefix, suffix, large=large)
|
||||
|
||||
|
|
@ -263,13 +260,13 @@ class ContainerBase(object):
|
|||
|
||||
self.assert_shortened(c2, c2_expect)
|
||||
|
||||
@pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
def test_empty(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
def test_subclass(self):
|
||||
raise NotImplementedError
|
||||
# @pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
# def test_empty(self):
|
||||
# raise NotImplementedError
|
||||
#
|
||||
# @pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
# def test_subclass(self):
|
||||
# raise NotImplementedError
|
||||
|
||||
def test_boundary(self):
|
||||
items1 = range(SafeRepr.maxcollection[0] - 1)
|
||||
|
|
@ -295,7 +292,7 @@ class ContainerBase(object):
|
|||
c1 = self.CLASS(items1)
|
||||
c2 = self.CLASS(items2)
|
||||
c3 = self.CLASS(items3)
|
||||
for j in range(i):
|
||||
for _j in range(i):
|
||||
c1, c2, c3 = ctype((c1,)), ctype((c2,)), ctype((c3,))
|
||||
expected1 = self.combine_nested(i, items1)
|
||||
expected2 = self.combine_nested(i, items2[:-1], large=True)
|
||||
|
|
@ -438,15 +435,15 @@ class TestOtherPythonTypes(SafeReprTestBase):
|
|||
# type
|
||||
# super
|
||||
|
||||
@pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
def test_file(self):
|
||||
raise NotImplementedError
|
||||
# @pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
# def test_file(self):
|
||||
# raise NotImplementedError
|
||||
|
||||
def test_range_small(self):
|
||||
range_name = xrange.__name__
|
||||
value = xrange(1, 42)
|
||||
|
||||
self.assert_unchanged(value, '{}(1, 42)'.format(range_name))
|
||||
self.assert_unchanged(value, '%s(1, 42)' % (range_name,))
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 0), reason='Py3 specific test')
|
||||
def test_range_large_stop_only(self):
|
||||
|
|
@ -455,7 +452,7 @@ class TestOtherPythonTypes(SafeReprTestBase):
|
|||
value = xrange(stop)
|
||||
|
||||
self.assert_unchanged(value,
|
||||
'{}(0, {})'.format(range_name, stop))
|
||||
'%s(0, %s)' % (range_name, stop))
|
||||
|
||||
def test_range_large_with_start(self):
|
||||
range_name = xrange.__name__
|
||||
|
|
@ -463,29 +460,32 @@ class TestOtherPythonTypes(SafeReprTestBase):
|
|||
value = xrange(1, stop)
|
||||
|
||||
self.assert_unchanged(value,
|
||||
'{}(1, {})'.format(range_name, stop))
|
||||
'%s(1, %s)' % (range_name, stop))
|
||||
|
||||
@pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
def test_named_struct(self):
|
||||
# e.g. sys.version_info
|
||||
raise NotImplementedError
|
||||
|
||||
@pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
def test_namedtuple(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
@pytest.mark.skipif(sys.version_info < (3, 0), reason='Py3 specific test')
|
||||
def test_SimpleNamespace(self):
|
||||
raise NotImplementedError
|
||||
# @pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
# def test_named_struct(self):
|
||||
# # e.g. sys.version_info
|
||||
# raise NotImplementedError
|
||||
#
|
||||
# @pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
# def test_namedtuple(self):
|
||||
# raise NotImplementedError
|
||||
#
|
||||
# @pytest.mark.skip(reason='not written') # TODO: finish!
|
||||
# @pytest.mark.skipif(sys.version_info < (3, 0), reason='Py3 specific test')
|
||||
# def test_SimpleNamespace(self):
|
||||
# raise NotImplementedError
|
||||
|
||||
|
||||
class TestUserDefinedObjects(SafeReprTestBase):
|
||||
|
||||
def test_broken_repr(self):
|
||||
|
||||
class TestClass(object):
|
||||
def __repr__(_):
|
||||
|
||||
def __repr__(self):
|
||||
raise NameError
|
||||
|
||||
value = TestClass()
|
||||
|
||||
with pytest.raises(NameError):
|
||||
|
|
@ -493,46 +493,60 @@ class TestUserDefinedObjects(SafeReprTestBase):
|
|||
self.assert_saferepr(value, object.__repr__(value))
|
||||
|
||||
def test_large(self):
|
||||
|
||||
class TestClass(object):
|
||||
|
||||
def __repr__(self):
|
||||
return '<' + 'A' * SafeRepr.maxother_outer * 2 + '>'
|
||||
|
||||
value = TestClass()
|
||||
|
||||
self.assert_shortened_regex(value, r'\<A+\.\.\.A+\>')
|
||||
|
||||
def test_inherit_repr(self):
|
||||
|
||||
class TestClass(dict):
|
||||
pass
|
||||
|
||||
value_dict = TestClass()
|
||||
|
||||
class TestClass(list):
|
||||
class TestClass2(list):
|
||||
pass
|
||||
value_list = TestClass()
|
||||
|
||||
value_list = TestClass2()
|
||||
|
||||
self.assert_unchanged(value_dict, '{}')
|
||||
self.assert_unchanged(value_list, '[]')
|
||||
|
||||
def test_custom_repr(self):
|
||||
|
||||
class TestClass(dict):
|
||||
def __repr__(_):
|
||||
|
||||
def __repr__(self):
|
||||
return 'MyRepr'
|
||||
|
||||
value1 = TestClass()
|
||||
|
||||
class TestClass(list):
|
||||
def __repr__(_):
|
||||
class TestClass2(list):
|
||||
|
||||
def __repr__(self):
|
||||
return 'MyRepr'
|
||||
value2 = TestClass()
|
||||
|
||||
value2 = TestClass2()
|
||||
|
||||
self.assert_unchanged(value1, 'MyRepr')
|
||||
self.assert_unchanged(value2, 'MyRepr')
|
||||
|
||||
def test_custom_repr_many_items(self):
|
||||
|
||||
class TestClass(list):
|
||||
|
||||
def __init__(self, it=()):
|
||||
list.__init__(self, it)
|
||||
|
||||
def __repr__(_):
|
||||
def __repr__(self):
|
||||
return 'MyRepr'
|
||||
|
||||
value1 = TestClass(xrange(0, 15))
|
||||
value2 = TestClass(xrange(0, 16))
|
||||
value3 = TestClass([TestClass(xrange(0, 10))])
|
||||
|
|
@ -544,12 +558,15 @@ class TestUserDefinedObjects(SafeReprTestBase):
|
|||
self.assert_shortened(value4, '<TestClass, len() = 1>')
|
||||
|
||||
def test_custom_repr_large_item(self):
|
||||
|
||||
class TestClass(list):
|
||||
|
||||
def __init__(self, it=()):
|
||||
list.__init__(self, it)
|
||||
|
||||
def __repr__(_):
|
||||
def __repr__(self):
|
||||
return 'MyRepr'
|
||||
|
||||
value1 = TestClass(['a' * (SafeRepr.maxcollection[1] + 1)])
|
||||
value2 = TestClass(['a' * (SafeRepr.maxstring_inner + 1)])
|
||||
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
import sys
|
||||
from _pydevd_bundle.pydevd_constants import int_types
|
||||
|
||||
|
||||
def get_frame():
|
||||
var1 = 1
|
||||
var2 = [var1]
|
||||
var3 = {33: [var1]}
|
||||
return sys._getframe()
|
||||
|
||||
|
||||
def check_vars_dict_expected(as_dict, expected):
|
||||
assert as_dict == expected
|
||||
|
||||
|
||||
def test_suspended_frames_manager():
|
||||
from _pydevd_bundle.pydevd_suspended_frames import SuspendedFramesManager
|
||||
suspended_frames_manager = SuspendedFramesManager()
|
||||
py_db = None
|
||||
with suspended_frames_manager.track_frames(py_db) as tracker:
|
||||
# : :type tracker: _FramesTracker
|
||||
thread_id = 'thread1'
|
||||
frame = get_frame()
|
||||
tracker.track(thread_id, frame, frame_id_to_lineno={})
|
||||
|
||||
assert suspended_frames_manager.get_thread_id_for_variable_reference(id(frame)) == thread_id
|
||||
|
||||
variable = suspended_frames_manager.get_variable(id(frame))
|
||||
|
||||
# Should be properly sorted.
|
||||
assert ['var1', 'var2', 'var3'] == [x.get_name()for x in variable.get_children_variables()]
|
||||
|
||||
as_dict = dict((x.get_name(), x.get_var_data()) for x in variable.get_children_variables())
|
||||
var_reference = as_dict['var2'].pop('variablesReference')
|
||||
assert isinstance(var_reference, int_types) # The variable reference is always a new int.
|
||||
assert isinstance(as_dict['var3'].pop('variablesReference'), int_types) # The variable reference is always a new int.
|
||||
|
||||
check_vars_dict_expected(as_dict, {
|
||||
'var1': {'name': 'var1', 'value': '1', 'type': 'int', 'evaluateName': 'var1'},
|
||||
'var2': {'name': 'var2', 'value': '[1]', 'type': 'list', 'evaluateName': 'var2'},
|
||||
'var3': {'name': 'var3', 'value': '{33: [1]}', 'type': 'dict', 'evaluateName': 'var3'}
|
||||
})
|
||||
|
||||
# Now, same thing with a different format.
|
||||
as_dict = dict((x.get_name(), x.get_var_data(fmt={'hex': True})) for x in variable.get_children_variables())
|
||||
var_reference = as_dict['var2'].pop('variablesReference')
|
||||
assert isinstance(var_reference, int_types) # The variable reference is always a new int.
|
||||
assert isinstance(as_dict['var3'].pop('variablesReference'), int_types) # The variable reference is always a new int.
|
||||
|
||||
check_vars_dict_expected(as_dict, {
|
||||
'var1': {'name': 'var1', 'value': '0x1', 'type': 'int', 'evaluateName': 'var1'},
|
||||
'var2': {'name': 'var2', 'value': '[0x1]', 'type': 'list', 'evaluateName': 'var2'},
|
||||
'var3': {'name': 'var3', 'value': '{0x21: [0x1]}', 'type': 'dict', 'evaluateName': 'var3'}
|
||||
})
|
||||
|
||||
var2 = dict((x.get_name(), x) for x in variable.get_children_variables())['var2']
|
||||
children_vars = var2.get_children_variables()
|
||||
as_dict = (dict([x.get_name(), x.get_var_data()] for x in children_vars))
|
||||
assert as_dict == {
|
||||
'0': {'name': '0', 'value': '1', 'type': 'int', 'evaluateName': 'var2[0]' },
|
||||
'__len__': {'name': '__len__', 'value': '1', 'type': 'int', 'evaluateName': 'var2.__len__()'},
|
||||
}
|
||||
|
||||
var3 = dict((x.get_name(), x) for x in variable.get_children_variables())['var3']
|
||||
children_vars = var3.get_children_variables()
|
||||
as_dict = (dict([x.get_name(), x.get_var_data()] for x in children_vars))
|
||||
assert isinstance(as_dict['33'].pop('variablesReference'), int_types) # The variable reference is always a new int.
|
||||
|
||||
check_vars_dict_expected(as_dict, {
|
||||
'33': {'name': '33', 'value': "[1]", 'type': 'list', 'evaluateName': 'var3[33]'},
|
||||
'__len__': {'name': '__len__', 'value': '1', 'type': 'int', 'evaluateName': 'var3.__len__()'}
|
||||
})
|
||||
|
|
@ -5,7 +5,6 @@
|
|||
from __future__ import print_function, absolute_import, unicode_literals
|
||||
|
||||
import bisect
|
||||
import contextlib
|
||||
import copy
|
||||
import errno
|
||||
import io
|
||||
|
|
@ -30,7 +29,6 @@ try:
|
|||
except ImportError:
|
||||
import Queue as queue
|
||||
import warnings
|
||||
from xml.sax import SAXParseException
|
||||
from xml.sax.saxutils import unescape as xml_unescape
|
||||
|
||||
import pydevd # noqa
|
||||
|
|
@ -53,10 +51,11 @@ import ptvsd.ipcjson as ipcjson # noqa
|
|||
import ptvsd.futures as futures # noqa
|
||||
import ptvsd.untangle as untangle # noqa
|
||||
from ptvsd.pathutils import PathUnNormcase # noqa
|
||||
from ptvsd.safe_repr import SafeRepr # noqa
|
||||
from ptvsd.version import __version__ # noqa
|
||||
from ptvsd.socket import TimeoutError # noqa
|
||||
|
||||
LOG_FILENAME = 'pydevd.log'
|
||||
|
||||
WAIT_FOR_THREAD_FINISH_TIMEOUT = 1 # seconds
|
||||
|
||||
STEP_REASONS = {
|
||||
|
|
@ -79,22 +78,6 @@ debugger_attached = threading.Event()
|
|||
# print(s)
|
||||
# ipcjson._TRACE = ipcjson_trace
|
||||
|
||||
# completion types.
|
||||
TYPE_IMPORT = '0'
|
||||
TYPE_CLASS = '1'
|
||||
TYPE_FUNCTION = '2'
|
||||
TYPE_ATTR = '3'
|
||||
TYPE_BUILTIN = '4'
|
||||
TYPE_PARAM = '5'
|
||||
TYPE_LOOK_UP = {
|
||||
TYPE_IMPORT: 'module',
|
||||
TYPE_CLASS: 'class',
|
||||
TYPE_FUNCTION: 'function',
|
||||
TYPE_ATTR: 'field',
|
||||
TYPE_BUILTIN: 'keyword',
|
||||
TYPE_PARAM: 'variable',
|
||||
}
|
||||
|
||||
|
||||
def NOOP(*args, **kwargs):
|
||||
pass
|
||||
|
|
@ -104,54 +87,6 @@ def path_to_unicode(s):
|
|||
return s if isinstance(s, unicode) else s.decode(sys.getfilesystemencoding())
|
||||
|
||||
|
||||
class SafeReprPresentationProvider(pydevd_extapi.StrPresentationProvider):
|
||||
"""
|
||||
Computes string representation of Python values by delegating them
|
||||
to SafeRepr.
|
||||
"""
|
||||
|
||||
_lock = threading.Lock()
|
||||
|
||||
def __init__(self):
|
||||
self.set_format({})
|
||||
|
||||
def can_provide(self, type_object, type_name):
|
||||
"""Implements StrPresentationProvider."""
|
||||
return True
|
||||
|
||||
def get_str(self, val):
|
||||
"""Implements StrPresentationProvider."""
|
||||
return self._repr(val)
|
||||
|
||||
def set_format(self, fmt):
|
||||
"""
|
||||
Use fmt for all future formatting operations done by this provider.
|
||||
"""
|
||||
safe_repr = SafeRepr()
|
||||
safe_repr.convert_to_hex = fmt.get('hex', False)
|
||||
safe_repr.raw_value = fmt.get('rawString', False)
|
||||
self._repr = safe_repr
|
||||
|
||||
@contextlib.contextmanager
|
||||
def using_format(self, fmt):
|
||||
"""
|
||||
Returns a context manager that invokes set_format(fmt) on enter,
|
||||
and restores the old format on exit.
|
||||
"""
|
||||
old_repr = self._repr
|
||||
self.set_format(fmt)
|
||||
yield
|
||||
self._repr = old_repr
|
||||
|
||||
|
||||
# Do not access directly - use safe_repr_provider() instead!
|
||||
SafeReprPresentationProvider._instance = SafeReprPresentationProvider()
|
||||
|
||||
# Register our presentation provider as the first item on the list,
|
||||
# so that we're in full control of presentation.
|
||||
str_handlers = pydevd_extutil.EXTENSION_MANAGER_INSTANCE.type_to_instance.setdefault(pydevd_extapi.StrPresentationProvider, []) # noqa
|
||||
str_handlers.insert(0, SafeReprPresentationProvider._instance)
|
||||
|
||||
PTVSD_DIR_PATH = os.path.dirname(os.path.abspath(get_abs_path_real_path_and_base_from_file(__file__)[0])) + os.path.sep
|
||||
NORM_PTVSD_DIR_PATH = os.path.normcase(PTVSD_DIR_PATH)
|
||||
|
||||
|
|
@ -344,7 +279,7 @@ class PydevdSocket(object):
|
|||
"""
|
||||
|
||||
def __init__(self, handle_msg, handle_close, getpeername, getsockname):
|
||||
# self.log = open('pydevd.log', 'w')
|
||||
# self.log = open(LOG_FILENAME, 'w')
|
||||
self._handle_msg = handle_msg
|
||||
self._handle_close = handle_close
|
||||
self._getpeername = getpeername
|
||||
|
|
@ -450,6 +385,8 @@ class PydevdSocket(object):
|
|||
if data.startswith(b'{'):
|
||||
# A json message was received.
|
||||
data = data.decode('utf-8')
|
||||
# self.log.write('<<<[' + data + ']\n\n')
|
||||
# self.log.flush()
|
||||
as_dict = json.loads(data)
|
||||
cmd_id = as_dict['pydevd_cmd_id']
|
||||
if 'request_seq' in as_dict:
|
||||
|
|
@ -470,8 +407,12 @@ class PydevdSocket(object):
|
|||
with self.lock:
|
||||
loop, fut = self.requests.pop(seq, (None, None))
|
||||
if fut is None:
|
||||
# self.log.write('handle message: %s' % (args,))
|
||||
# self.log.flush()
|
||||
self._handle_msg(cmd_id, seq, args)
|
||||
else:
|
||||
# self.log.write('set result message: %s' % (args,))
|
||||
# self.log.flush()
|
||||
loop.call_soon_threadsafe(fut.set_result, (cmd_id, seq, args))
|
||||
return result
|
||||
|
||||
|
|
@ -683,84 +624,6 @@ class VariablesSorter(object):
|
|||
return self.variables + self.single_underscore + self.double_underscore + self.dunder # noqa
|
||||
|
||||
|
||||
class ModulesManager(object):
|
||||
|
||||
def __init__(self, proc):
|
||||
self.module_id_to_details = {}
|
||||
self.path_to_module_id = {}
|
||||
self._lock = threading.Lock()
|
||||
self.proc = proc
|
||||
self._next_id = 1
|
||||
|
||||
def add_or_get_from_path(self, module_path):
|
||||
with self._lock:
|
||||
try:
|
||||
module_id = self.path_to_module_id[module_path]
|
||||
return self.module_id_to_details[module_id]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
search_path = self._get_platform_file_path(module_path)
|
||||
|
||||
for _, value in list(sys.modules.items()):
|
||||
try:
|
||||
path = self._get_platform_file_path(value.__file__)
|
||||
except AttributeError:
|
||||
path = None
|
||||
|
||||
if not path:
|
||||
continue
|
||||
|
||||
try:
|
||||
# This tries to open the files to obtain handles, which can be restricted
|
||||
# by file permissions, but ensures that long/short path mismatch, symlinks
|
||||
# etc are all accounted for. Fall back to comparing names in case of failure.
|
||||
if not os.path.samefile(path, search_path):
|
||||
continue
|
||||
except Exception:
|
||||
if path != search_path:
|
||||
continue
|
||||
|
||||
module_id = self._next_id
|
||||
self._next_id += 1
|
||||
|
||||
module = {
|
||||
'id': module_id,
|
||||
'package': value.__package__ if hasattr(value, '__package__') else None,
|
||||
'path': module_path,
|
||||
}
|
||||
|
||||
try:
|
||||
module['name'] = value.__qualname__
|
||||
except AttributeError:
|
||||
module['name'] = value.__name__
|
||||
|
||||
try:
|
||||
module['version'] = value.__version__
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
self.path_to_module_id[module_path] = module_id
|
||||
self.module_id_to_details[module_id] = module
|
||||
|
||||
self.proc.send_event('module', reason='new', module=module)
|
||||
return module
|
||||
|
||||
return None
|
||||
|
||||
def _get_platform_file_path(self, path):
|
||||
if platform.system() == 'Windows':
|
||||
return path.lower()
|
||||
return path
|
||||
|
||||
def get_all(self):
|
||||
with self._lock:
|
||||
return list(self.module_id_to_details.values())
|
||||
|
||||
def check_unloaded_modules(self, module_event):
|
||||
pass
|
||||
|
||||
|
||||
class InternalsFilter(object):
|
||||
"""Identifies debugger internal artifacts.
|
||||
"""
|
||||
|
|
@ -1346,21 +1209,12 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
self.is_process_created = False
|
||||
self.is_process_created_lock = threading.Lock()
|
||||
self.thread_map = IDMap()
|
||||
self.frame_map = IDMap()
|
||||
self.var_map = IDMap()
|
||||
self.source_map = IDMap()
|
||||
self.goto_target_map = IDMap()
|
||||
self.current_goto_request = None
|
||||
self.enable_source_references = False
|
||||
self.next_var_ref = 0
|
||||
self._path_mappings = []
|
||||
self.exceptions_mgr = ExceptionsManager(self)
|
||||
self.modules_mgr = ModulesManager(self)
|
||||
self.internals_filter = InternalsFilter()
|
||||
self.new_thread_lock = threading.Lock()
|
||||
|
||||
# adapter state
|
||||
self.path_casing = PathUnNormcase()
|
||||
self._detached = False
|
||||
self._path_mappings_received = False
|
||||
self._path_mappings_applied = False
|
||||
|
|
@ -1475,8 +1329,8 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
pydevd_events = EventHandlers()
|
||||
|
||||
def on_pydevd_event(self, cmd_id, seq, args):
|
||||
# self.log.write('on_pydevd_event: %s %s %s\n' % (cmd_id, seq, args))
|
||||
# self.log.flush()
|
||||
# with open(LOG_FILENAME, 'a+') as stream:
|
||||
# stream.write('on_pydevd_event: %s %s %s\n' % (cmd_id, seq, args))
|
||||
|
||||
# TODO: docstring
|
||||
if not self._detached:
|
||||
|
|
@ -1492,20 +1346,6 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
def parse_xml_response(args):
|
||||
return untangle.parse(io.BytesIO(args.encode('utf8'))).xml
|
||||
|
||||
@async_method
|
||||
def using_format(self, fmt):
|
||||
while not SafeReprPresentationProvider._lock.acquire(False):
|
||||
yield self.sleep()
|
||||
provider = SafeReprPresentationProvider._instance
|
||||
|
||||
@contextlib.contextmanager
|
||||
def context():
|
||||
with provider.using_format(fmt):
|
||||
yield
|
||||
provider._lock.release()
|
||||
|
||||
yield futures.Result(context())
|
||||
|
||||
def _wait_for_pydevd_ready(self):
|
||||
# TODO: Call self._ensure_pydevd_requests_handled?
|
||||
pass
|
||||
|
|
@ -1545,9 +1385,6 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
|
||||
def _process_debug_options(self, opts):
|
||||
"""Process the launch arguments to configure the debugger."""
|
||||
if opts.get('FIX_FILE_PATH_CASE', False):
|
||||
self.path_casing.enable()
|
||||
|
||||
if opts.get('REDIRECT_OUTPUT', False):
|
||||
redirect_output = 'STDOUT\tSTDERR'
|
||||
else:
|
||||
|
|
@ -1709,61 +1546,24 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
def on_source(self, request, args):
|
||||
"""Request to get the source"""
|
||||
source_reference = args.get('sourceReference', 0)
|
||||
filename = '' if source_reference == 0 else \
|
||||
self.source_map.to_pydevd(source_reference)
|
||||
|
||||
if source_reference == 0:
|
||||
self.send_error_response(request, 'Source unavailable')
|
||||
else:
|
||||
if sys.version_info < (3,) and not isinstance(filename, bytes):
|
||||
filename = filename.encode(sys.getfilesystemencoding())
|
||||
server_filename = path_to_unicode(pydevd_file_utils.norm_file_to_server(filename))
|
||||
pydevd_request = copy.deepcopy(request)
|
||||
del pydevd_request['seq'] # A new seq should be created for pydevd.
|
||||
_, _, resp_args = yield self.pydevd_request(
|
||||
pydevd_comm.CMD_LOAD_SOURCE,
|
||||
pydevd_request,
|
||||
is_json=True)
|
||||
|
||||
cmd = pydevd_comm.CMD_LOAD_SOURCE
|
||||
_, _, content = yield self.pydevd_request(cmd, server_filename)
|
||||
self.send_response(request, content=content)
|
||||
|
||||
def get_source_reference(self, filename):
|
||||
"""Gets the source reference only in remote debugging scenarios.
|
||||
And we know that the path returned is the same as the server path
|
||||
(i.e. path has not been translated)"""
|
||||
|
||||
if self.start_reason == 'launch':
|
||||
return 0
|
||||
|
||||
# If we have no path mappings, then always enable source references.
|
||||
autogen = len(self._path_mappings) == 0
|
||||
|
||||
try:
|
||||
return self.source_map.to_vscode(filename, autogen=autogen)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# If file has been mapped, then source is available on client.
|
||||
for local_prefix, remote_prefix in self._path_mappings:
|
||||
if filename.startswith(local_prefix):
|
||||
return 0
|
||||
|
||||
return self.source_map.to_vscode(filename, autogen=True)
|
||||
|
||||
def _cleanup_frames_and_variables(self, pyd_tid, preserve_frames=()):
|
||||
""" Delete frames and variables for a given thread, except for the ones in preserve list.
|
||||
"""
|
||||
for pyd_fid, vsc_fid in self.frame_map.pairs():
|
||||
if pyd_fid[0] == pyd_tid and pyd_fid[1] not in preserve_frames:
|
||||
self.frame_map.remove(pyd_fid, vsc_fid)
|
||||
|
||||
for pyd_var, vsc_var in self.var_map.pairs():
|
||||
if pyd_var[0] == pyd_tid and pyd_fid[1] not in preserve_frames:
|
||||
self.var_map.remove(pyd_var, vsc_var)
|
||||
body = resp_args['body']
|
||||
self.send_response(request, **body)
|
||||
|
||||
@async_handler
|
||||
def on_stackTrace(self, request, args):
|
||||
# TODO: docstring
|
||||
vsc_tid = int(args['threadId'])
|
||||
startFrame = int(args.get('startFrame', 0))
|
||||
levels = int(args.get('levels', 0))
|
||||
fmt = args.get('format', {})
|
||||
|
||||
try:
|
||||
pyd_tid = self.thread_map.to_pydevd(vsc_tid)
|
||||
|
|
@ -1773,230 +1573,48 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
request,
|
||||
'Thread {} not found'.format(vsc_tid))
|
||||
return
|
||||
pydevd_request = copy.deepcopy(request)
|
||||
del pydevd_request['seq'] # A new seq should be created for pydevd.
|
||||
# Translate threadId for pydevd.
|
||||
pydevd_request['arguments']['threadId'] = pyd_tid
|
||||
_, _, resp_args = yield self.pydevd_request(
|
||||
pydevd_comm.CMD_GET_THREAD_STACK,
|
||||
pydevd_request,
|
||||
is_json=True)
|
||||
|
||||
try:
|
||||
cmd = pydevd_comm.CMD_GET_THREAD_STACK
|
||||
_, _, resp_args = yield self.pydevd_request(cmd, pyd_tid)
|
||||
xml = self.parse_xml_response(resp_args)
|
||||
xframes = list(xml.thread.frame)
|
||||
except Exception:
|
||||
xframes = []
|
||||
|
||||
totalFrames = len(xframes)
|
||||
if levels == 0:
|
||||
levels = totalFrames
|
||||
|
||||
stackFrames = []
|
||||
preserve_fids = []
|
||||
for xframe in xframes:
|
||||
if startFrame > 0:
|
||||
startFrame -= 1
|
||||
continue
|
||||
|
||||
if levels <= 0:
|
||||
break
|
||||
levels -= 1
|
||||
|
||||
pyd_fid = int(xframe['id'])
|
||||
preserve_fids.append(pyd_fid)
|
||||
key = (pyd_tid, pyd_fid)
|
||||
fid = self.frame_map.to_vscode(key, autogen=True)
|
||||
name = unquote(xframe['name'])
|
||||
# pydevd encodes if necessary and then uses urllib.quote.
|
||||
norm_path = self.path_casing.un_normcase(unquote_xml_path(xframe['file'])) # noqa
|
||||
source_reference = self.get_source_reference(norm_path)
|
||||
if not self.internals_filter.is_internal_path(norm_path):
|
||||
module = self.modules_mgr.add_or_get_from_path(norm_path)
|
||||
else:
|
||||
module = None
|
||||
line = int(xframe['line'])
|
||||
frame_name = self._format_frame_name(
|
||||
fmt,
|
||||
name,
|
||||
module,
|
||||
line,
|
||||
norm_path)
|
||||
|
||||
stackFrames.append({
|
||||
'id': fid,
|
||||
'name': frame_name,
|
||||
'source': {
|
||||
'path': norm_path,
|
||||
'sourceReference': source_reference
|
||||
},
|
||||
'line': line, 'column': 1,
|
||||
})
|
||||
|
||||
user_frames = []
|
||||
for frame in stackFrames:
|
||||
path = frame['source']['path']
|
||||
if not self.internals_filter.is_internal_path(path) and \
|
||||
self._should_debug(path):
|
||||
user_frames.append(frame)
|
||||
|
||||
self._cleanup_frames_and_variables(pyd_tid, preserve_fids)
|
||||
|
||||
totalFrames = len(user_frames)
|
||||
self.send_response(request,
|
||||
stackFrames=user_frames,
|
||||
totalFrames=totalFrames)
|
||||
|
||||
def _format_frame_name(self, fmt, name, module, line, path):
|
||||
frame_name = name
|
||||
if fmt.get('module', False):
|
||||
if module:
|
||||
if name == '<module>':
|
||||
frame_name = module['name']
|
||||
else:
|
||||
frame_name = '%s.%s' % (module['name'], name)
|
||||
else:
|
||||
_, tail = os.path.split(path)
|
||||
tail = tail[0:-3] if tail.lower().endswith('.py') else tail
|
||||
if name == '<module>':
|
||||
frame_name = '%s in %s' % (name, tail)
|
||||
else:
|
||||
frame_name = '%s.%s' % (tail, name)
|
||||
|
||||
if fmt.get('line', False):
|
||||
frame_name = '%s : %d' % (frame_name, line)
|
||||
|
||||
return frame_name
|
||||
stackFrames = resp_args['body']['stackFrames']
|
||||
totalFrames = resp_args['body']['totalFrames']
|
||||
self.send_response(request, stackFrames=stackFrames, totalFrames=totalFrames)
|
||||
|
||||
@async_handler
|
||||
def on_scopes(self, request, args):
|
||||
# TODO: docstring
|
||||
vsc_fid = int(args['frameId'])
|
||||
pyd_tid, pyd_fid = self.frame_map.to_pydevd(vsc_fid)
|
||||
pyd_var = (pyd_tid, pyd_fid, 'FRAME')
|
||||
vsc_var = self.var_map.to_vscode(pyd_var, autogen=True)
|
||||
scope = {
|
||||
'name': 'Locals',
|
||||
'expensive': False,
|
||||
'variablesReference': vsc_var,
|
||||
}
|
||||
self.send_response(request, scopes=[scope])
|
||||
pydevd_request = copy.deepcopy(request)
|
||||
del pydevd_request['seq'] # A new seq should be created for pydevd.
|
||||
_, _, resp_args = yield self.pydevd_request(
|
||||
-1,
|
||||
pydevd_request,
|
||||
is_json=True)
|
||||
|
||||
scopes = resp_args['body']['scopes']
|
||||
self.send_response(request, scopes=scopes)
|
||||
|
||||
@async_handler
|
||||
def on_variables(self, request, args):
|
||||
"""Handles DAP VariablesRequest."""
|
||||
pydevd_request = copy.deepcopy(request)
|
||||
del pydevd_request['seq'] # A new seq should be created for pydevd.
|
||||
_, _, resp_args = yield self.pydevd_request(
|
||||
pydevd_comm.CMD_GET_VARIABLE,
|
||||
pydevd_request,
|
||||
is_json=True)
|
||||
|
||||
vsc_var = int(args['variablesReference'])
|
||||
fmt = args.get('format', {})
|
||||
|
||||
try:
|
||||
pyd_var = self.var_map.to_pydevd(vsc_var)
|
||||
except KeyError:
|
||||
self.send_error_response(
|
||||
request,
|
||||
'Variable {} not found in frame'.format(vsc_var))
|
||||
return
|
||||
|
||||
if len(pyd_var) == 3:
|
||||
cmd = pydevd_comm.CMD_GET_FRAME
|
||||
else:
|
||||
cmd = pydevd_comm.CMD_GET_VARIABLE
|
||||
cmdargs = (unicode(s) for s in pyd_var)
|
||||
msg = '\t'.join(cmdargs)
|
||||
with (yield self.using_format(fmt)):
|
||||
_, _, resp_args = yield self.pydevd_request(cmd, msg)
|
||||
|
||||
try:
|
||||
xml = self.parse_xml_response(resp_args)
|
||||
except SAXParseException:
|
||||
self.send_error_response(request, resp_args)
|
||||
return
|
||||
|
||||
try:
|
||||
xvars = xml.var
|
||||
except AttributeError:
|
||||
xvars = []
|
||||
|
||||
variables = VariablesSorter()
|
||||
for xvar in xvars:
|
||||
attributes = []
|
||||
var_name = unquote(xvar['name'])
|
||||
var_type = unquote(xvar['type'])
|
||||
var_value = unquote(xvar['value'])
|
||||
var = {
|
||||
'name': var_name,
|
||||
'type': var_type,
|
||||
'value': var_value,
|
||||
}
|
||||
|
||||
if self._is_raw_string(var_type):
|
||||
attributes.append('rawString')
|
||||
|
||||
if bool(xvar['isRetVal']):
|
||||
attributes.append('readOnly')
|
||||
var['name'] = '(return) %s' % var_name
|
||||
else:
|
||||
if bool(xvar['isContainer']):
|
||||
pyd_child = pyd_var + (var_name,)
|
||||
var['variablesReference'] = self.var_map.to_vscode(
|
||||
pyd_child, autogen=True)
|
||||
|
||||
eval_name = self._get_variable_evaluate_name(
|
||||
pyd_var, var_name)
|
||||
if eval_name:
|
||||
var['evaluateName'] = eval_name
|
||||
|
||||
if len(attributes) > 0:
|
||||
var['presentationHint'] = {'attributes': attributes}
|
||||
|
||||
variables.append(var)
|
||||
|
||||
self.send_response(request, variables=variables.get_sorted_variables())
|
||||
|
||||
def _is_raw_string(self, var_type):
|
||||
return var_type in ('str', 'unicode', 'bytes', 'bytearray')
|
||||
|
||||
def _get_variable_evaluate_name(self, pyd_var_parent, var_name):
|
||||
# TODO: docstring
|
||||
eval_name = None
|
||||
pyd_var_len = len(pyd_var_parent)
|
||||
if pyd_var_len > 3:
|
||||
# This means the current variable has a parent i.e, it is not a
|
||||
# FRAME variable. These require evaluateName to work in VS
|
||||
# watch window
|
||||
var = pyd_var_parent + (var_name,)
|
||||
eval_name = var[3]
|
||||
for s in var[4:]:
|
||||
try:
|
||||
# Check and get the dictionary key or list index.
|
||||
# Note: this is best effort, keys that are object
|
||||
# references will not work
|
||||
i = self._get_index_or_key(s)
|
||||
eval_name += '[{}]'.format(i)
|
||||
except Exception:
|
||||
eval_name += '.' + s
|
||||
elif pyd_var_len == 3:
|
||||
return var_name
|
||||
|
||||
return eval_name
|
||||
|
||||
def _get_index_or_key(self, text):
|
||||
# Dictionary resolver in pydevd provides key
|
||||
# in '<repr> (<hash>)' format
|
||||
result = re.match("(.*)\ \(([0-9]*)\)", text,
|
||||
re.IGNORECASE | re.UNICODE)
|
||||
if result and len(result.groups()) == 2:
|
||||
try:
|
||||
# check if group 2 is a hash
|
||||
int(result.group(2))
|
||||
return result.group(1)
|
||||
except Exception:
|
||||
pass
|
||||
# In the result XML from pydevd list indexes appear
|
||||
# as names. If the name is a number then it is a index.
|
||||
return int(text)
|
||||
variables = resp_args['body']['variables']
|
||||
self.send_response(request, variables=variables)
|
||||
|
||||
@async_handler
|
||||
def on_setVariable(self, request, args):
|
||||
"""Handles DAP SetVariableRequest."""
|
||||
var_name = args['name']
|
||||
var_value = args['value']
|
||||
vsc_var = int(args['variablesReference'])
|
||||
fmt = args.get('format', {})
|
||||
|
||||
if var_name.startswith('(return) '):
|
||||
self.send_error_response(
|
||||
|
|
@ -2004,183 +1622,53 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
'Cannot change return value')
|
||||
return
|
||||
|
||||
try:
|
||||
pyd_var = self.var_map.to_pydevd(vsc_var)
|
||||
except KeyError:
|
||||
self.send_error_response(
|
||||
request,
|
||||
'Variable {} not found in frame'.format(vsc_var))
|
||||
return
|
||||
pydevd_request = copy.deepcopy(request)
|
||||
del pydevd_request['seq'] # A new seq should be created for pydevd.
|
||||
_, _, resp_args = yield self.pydevd_request(
|
||||
pydevd_comm.CMD_CHANGE_VARIABLE,
|
||||
pydevd_request,
|
||||
is_json=True)
|
||||
|
||||
lhs_expr = self._get_variable_evaluate_name(pyd_var, var_name)
|
||||
if not lhs_expr:
|
||||
lhs_expr = var_name
|
||||
expr = '%s = %s' % (lhs_expr, var_value)
|
||||
# pydevd message format doesn't permit tabs in expressions
|
||||
expr = expr.replace('\t', ' ')
|
||||
|
||||
pyd_tid = unicode(pyd_var[0])
|
||||
pyd_fid = unicode(pyd_var[1])
|
||||
|
||||
# VSC gives us variablesReference to the parent of the variable
|
||||
# being set, and variable name; but pydevd wants the ID
|
||||
# (or rather path) of the variable itself.
|
||||
pyd_var += (var_name,)
|
||||
vsc_var = self.var_map.to_vscode(pyd_var, autogen=True)
|
||||
|
||||
cmd_args = [pyd_tid, pyd_fid, 'LOCAL', expr, '1']
|
||||
with (yield self.using_format(fmt)):
|
||||
yield self.pydevd_request(
|
||||
pydevd_comm.CMD_EXEC_EXPRESSION,
|
||||
'\t'.join(cmd_args),
|
||||
)
|
||||
|
||||
cmd_args = [pyd_tid, pyd_fid, 'LOCAL', lhs_expr, '1']
|
||||
with (yield self.using_format(fmt)):
|
||||
_, _, resp_args = yield self.pydevd_request(
|
||||
pydevd_comm.CMD_EVALUATE_EXPRESSION,
|
||||
'\t'.join(cmd_args),
|
||||
)
|
||||
|
||||
try:
|
||||
xml = self.parse_xml_response(resp_args)
|
||||
except SAXParseException:
|
||||
self.send_error_response(request, resp_args)
|
||||
return
|
||||
|
||||
try:
|
||||
xvar = xml.var
|
||||
except AttributeError:
|
||||
self.send_response(request, success=False)
|
||||
return
|
||||
|
||||
response = {
|
||||
'type': unquote(xvar['type']),
|
||||
'value': unquote(xvar['value']),
|
||||
}
|
||||
if bool(xvar['isContainer']):
|
||||
response['variablesReference'] = vsc_var
|
||||
|
||||
self.send_response(request, **response)
|
||||
body = resp_args['body']
|
||||
self.send_response(request, **body)
|
||||
|
||||
@async_handler
|
||||
def on_evaluate(self, request, args):
|
||||
"""Handles DAP EvaluateRequest."""
|
||||
|
||||
# pydevd message format doesn't permit tabs in expressions
|
||||
expr = args['expression'].replace('\n', '@LINE@').replace('\t', ' ')
|
||||
fmt = args.get('format', {})
|
||||
pydevd_request = copy.deepcopy(request)
|
||||
del pydevd_request['seq'] # A new seq should be created for pydevd.
|
||||
_, _, resp_args = yield self.pydevd_request(
|
||||
pydevd_comm.CMD_EVALUATE_EXPRESSION,
|
||||
pydevd_request,
|
||||
is_json=True)
|
||||
|
||||
vsc_fid = int(args['frameId'])
|
||||
pyd_tid, pyd_fid = self.frame_map.to_pydevd(vsc_fid)
|
||||
|
||||
cmd_args = (pyd_tid, pyd_fid, 'LOCAL', expr, '1')
|
||||
msg = '\t'.join(unicode(s) for s in cmd_args)
|
||||
with (yield self.using_format(fmt)):
|
||||
_, _, resp_args = yield self.pydevd_request(
|
||||
pydevd_comm.CMD_EVALUATE_EXPRESSION,
|
||||
msg)
|
||||
|
||||
try:
|
||||
xml = self.parse_xml_response(resp_args)
|
||||
except SAXParseException:
|
||||
self.send_error_response(request, resp_args)
|
||||
return
|
||||
|
||||
try:
|
||||
xvar = xml.var
|
||||
except AttributeError:
|
||||
self.send_response(request, success=False)
|
||||
return
|
||||
|
||||
context = args.get('context', '')
|
||||
is_eval_error = xvar['isErrorOnEval']
|
||||
if context == 'hover' and is_eval_error == 'True':
|
||||
self.send_response(
|
||||
request,
|
||||
result=None,
|
||||
variablesReference=0)
|
||||
return
|
||||
|
||||
if context == 'repl' and is_eval_error == 'True':
|
||||
# try exec for repl requests
|
||||
with (yield self.using_format(fmt)):
|
||||
_, _, resp_args = yield self.pydevd_request(
|
||||
pydevd_comm.CMD_EXEC_EXPRESSION,
|
||||
msg)
|
||||
try:
|
||||
xml2 = self.parse_xml_response(resp_args)
|
||||
xvar2 = xml2.var
|
||||
result_type = unquote(xvar2['type'])
|
||||
result = unquote(xvar2['value'])
|
||||
except Exception:
|
||||
# if resp_args is not xml then it contains the error traceback
|
||||
result_type = unquote(xvar['type'])
|
||||
result = unquote(xvar['value'])
|
||||
self.send_response(
|
||||
request,
|
||||
result=(None
|
||||
if result == 'None' and result_type == 'NoneType'
|
||||
else result),
|
||||
type=result_type,
|
||||
variablesReference=0,
|
||||
)
|
||||
return
|
||||
|
||||
pyd_var = (pyd_tid, pyd_fid, 'EXPRESSION', expr)
|
||||
vsc_var = self.var_map.to_vscode(pyd_var, autogen=True)
|
||||
var_type = unquote(xvar['type'])
|
||||
var_value = unquote(xvar['value'])
|
||||
response = {
|
||||
'type': var_type,
|
||||
'result': var_value,
|
||||
}
|
||||
|
||||
if self._is_raw_string(var_type):
|
||||
response['presentationHint'] = {'attributes': ['rawString']}
|
||||
|
||||
if bool(xvar['isContainer']):
|
||||
response['variablesReference'] = vsc_var
|
||||
|
||||
self.send_response(request, **response)
|
||||
body = resp_args['body']
|
||||
self.send_response(request, **body)
|
||||
|
||||
@async_handler
|
||||
def on_setExpression(self, request, args):
|
||||
# TODO: docstring
|
||||
pydevd_request = copy.deepcopy(request)
|
||||
del pydevd_request['seq'] # A new seq should be created for pydevd.
|
||||
_, _, resp_args = yield self.pydevd_request(
|
||||
-1,
|
||||
pydevd_request,
|
||||
is_json=True)
|
||||
|
||||
vsc_fid = int(args['frameId'])
|
||||
pyd_tid, pyd_fid = self.frame_map.to_pydevd(vsc_fid)
|
||||
fmt = args.get('format', {})
|
||||
|
||||
lhs_expr = args.get('expression')
|
||||
rhs_expr = args.get('value')
|
||||
expr = '%s = (%s)' % (lhs_expr, rhs_expr)
|
||||
|
||||
# pydevd message format doesn't permit tabs in expressions
|
||||
expr = expr.replace('\t', ' ')
|
||||
|
||||
cmd_args = (pyd_tid, pyd_fid, 'LOCAL', expr, '1')
|
||||
msg = '\t'.join(unicode(s) for s in cmd_args)
|
||||
with (yield self.using_format(fmt)):
|
||||
yield self.pydevd_request(
|
||||
pydevd_comm.CMD_EXEC_EXPRESSION,
|
||||
msg)
|
||||
|
||||
# Return 'None' here, VS will call getVariables to retrieve
|
||||
# updated values anyway. Doing eval on the left-hand-side
|
||||
# expression may have side-effects
|
||||
self.send_response(request, value=None)
|
||||
body = resp_args['body']
|
||||
self.send_response(request, **body)
|
||||
|
||||
@async_handler
|
||||
def on_modules(self, request, args):
|
||||
modules = list(self.modules_mgr.get_all())
|
||||
user_modules = []
|
||||
for module in modules:
|
||||
if not self.internals_filter.is_internal_path(module['path']):
|
||||
user_modules.append(module)
|
||||
self.send_response(request,
|
||||
modules=user_modules,
|
||||
totalModules=len(user_modules))
|
||||
pydevd_request = copy.deepcopy(request)
|
||||
del pydevd_request['seq'] # A new seq should be created for pydevd.
|
||||
_, _, resp_args = yield self.pydevd_request(
|
||||
-1,
|
||||
pydevd_request,
|
||||
is_json=True)
|
||||
|
||||
body = resp_args.get('body', {})
|
||||
self.send_response(request, **body)
|
||||
|
||||
@async_handler
|
||||
def on_pause(self, request, args):
|
||||
|
|
@ -2265,44 +1753,6 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
pydevd_comm.CMD_SET_NEXT_STATEMENT,
|
||||
'{}\t{}\t*'.format(pyd_tid, line))
|
||||
|
||||
def _get_hit_condition_expression(self, hit_condition):
|
||||
"""Following hit condition values are supported
|
||||
|
||||
* x or == x when breakpoint is hit x times
|
||||
* >= x when breakpoint is hit more than or equal to x times
|
||||
* % x when breakpoint is hit multiple of x times
|
||||
|
||||
Returns '@HIT@ == x' where @HIT@ will be replaced by number of hits
|
||||
"""
|
||||
if not hit_condition:
|
||||
return None
|
||||
|
||||
expr = hit_condition.strip()
|
||||
try:
|
||||
int(expr)
|
||||
return '@HIT@ == {}'.format(expr)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if expr.startswith('%'):
|
||||
return '@HIT@ {} == 0'.format(expr)
|
||||
|
||||
if expr.startswith('==') or \
|
||||
expr.startswith('>') or \
|
||||
expr.startswith('<'):
|
||||
return '@HIT@ {}'.format(expr)
|
||||
|
||||
return hit_condition
|
||||
|
||||
def _get_bp_type(self, path):
|
||||
bp_type = 'python-line'
|
||||
if not path.lower().endswith('.py'):
|
||||
if self.debug_options.get('DJANGO_DEBUG', False):
|
||||
bp_type = 'django-line'
|
||||
elif self.debug_options.get('FLASK_DEBUG', False):
|
||||
bp_type = 'jinja2-line'
|
||||
return bp_type
|
||||
|
||||
@async_handler
|
||||
def on_setBreakpoints(self, request, args):
|
||||
if not self._path_mappings_received:
|
||||
|
|
@ -2436,20 +1886,8 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
|
||||
@async_handler
|
||||
def on_completions(self, request, args):
|
||||
vsc_fid = args.get('frameId', None)
|
||||
|
||||
try:
|
||||
pyd_tid, pyd_fid = self.frame_map.to_pydevd(vsc_fid)
|
||||
except KeyError:
|
||||
self.send_error_response(
|
||||
request,
|
||||
'Frame {} not found'.format(vsc_fid))
|
||||
return
|
||||
|
||||
pydevd_request = copy.deepcopy(request)
|
||||
del pydevd_request['seq'] # A new seq should be created for pydevd.
|
||||
# Translate frameId for pydevd.
|
||||
pydevd_request['arguments']['frameId'] = (pyd_tid, pyd_fid)
|
||||
_, _, resp_args = yield self.pydevd_request(
|
||||
pydevd_comm.CMD_GET_COMPLETIONS,
|
||||
pydevd_request,
|
||||
|
|
@ -2525,6 +1963,11 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
|
||||
# PyDevd protocol event handlers
|
||||
|
||||
@pydevd_events.handler(pydevd_comm.CMD_MODULE_EVENT)
|
||||
def on_pydevd_module_event(self, seq, args):
|
||||
body = args.get('body', {})
|
||||
self.send_event('module', **body)
|
||||
|
||||
@pydevd_events.handler(pydevd_comm.CMD_INPUT_REQUESTED)
|
||||
def on_pydevd_input_requested(self, seq, args):
|
||||
'''
|
||||
|
|
@ -2572,16 +2015,6 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
# TODO: docstring
|
||||
pyd_tid = args.strip()
|
||||
|
||||
# All frames, and variables for
|
||||
# this thread are now invalid; clear their IDs.
|
||||
for pyd_fid, vsc_fid in self.frame_map.pairs():
|
||||
if pyd_fid[0] == pyd_tid:
|
||||
self.frame_map.remove(pyd_fid, vsc_fid)
|
||||
|
||||
for pyd_var, vsc_var in self.var_map.pairs():
|
||||
if pyd_var[0] == pyd_tid:
|
||||
self.var_map.remove(pyd_var, vsc_var)
|
||||
|
||||
try:
|
||||
vsc_tid = self.thread_map.to_vscode(pyd_tid, autogen=False)
|
||||
except KeyError:
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ def test_django_breakpoint_no_multiproc(bp_target, start_method):
|
|||
assert resp_stacktrace.body['totalFrames'] > 1
|
||||
frames = resp_stacktrace.body['stackFrames']
|
||||
assert frames[0] == {
|
||||
'id': 1,
|
||||
'id': ANY.int,
|
||||
'name': bp_name,
|
||||
'source': {
|
||||
'sourceReference': ANY,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from tests.helpers.timeline import Event
|
|||
|
||||
|
||||
def test_variables_and_evaluate(pyfile, run_as, start_method):
|
||||
|
||||
@pyfile
|
||||
def code_to_debug():
|
||||
from dbgimporter import import_and_enable_debugger
|
||||
|
|
@ -71,7 +72,7 @@ def test_variables_and_evaluate(pyfile, run_as, start_method):
|
|||
'type': 'int',
|
||||
'value': '2',
|
||||
'name': '__len__',
|
||||
'evaluateName': "b.__len__"
|
||||
'evaluateName': "b.__len__()"
|
||||
}
|
||||
|
||||
# simple variable
|
||||
|
|
@ -106,6 +107,7 @@ def test_variables_and_evaluate(pyfile, run_as, start_method):
|
|||
|
||||
|
||||
def test_set_variable(pyfile, run_as, start_method):
|
||||
|
||||
@pyfile
|
||||
def code_to_debug():
|
||||
import backchannel
|
||||
|
|
@ -162,6 +164,7 @@ def test_set_variable(pyfile, run_as, start_method):
|
|||
|
||||
|
||||
def test_variable_sort(pyfile, run_as, start_method):
|
||||
|
||||
@pyfile
|
||||
def code_to_debug():
|
||||
from dbgimporter import import_and_enable_debugger
|
||||
|
|
@ -240,15 +243,20 @@ def test_variable_sort(pyfile, run_as, start_method):
|
|||
|
||||
|
||||
def test_return_values(pyfile, run_as, start_method):
|
||||
|
||||
@pyfile
|
||||
def code_to_debug():
|
||||
from dbgimporter import import_and_enable_debugger
|
||||
import_and_enable_debugger()
|
||||
|
||||
class MyClass(object):
|
||||
|
||||
def do_something(self):
|
||||
return 'did something'
|
||||
|
||||
def my_func():
|
||||
return 'did more things'
|
||||
|
||||
MyClass().do_something()
|
||||
my_func()
|
||||
print('done')
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from tests.helpers.timeline import Event
|
|||
|
||||
|
||||
def test_with_dot_remote_root(pyfile, tmpdir, run_as, start_method):
|
||||
|
||||
@pyfile
|
||||
def code_to_debug():
|
||||
from dbgimporter import import_and_enable_debugger
|
||||
|
|
@ -57,7 +58,9 @@ def test_with_dot_remote_root(pyfile, tmpdir, run_as, start_method):
|
|||
session.send_request('continue').wait_for_response(freeze=False)
|
||||
session.wait_for_exit()
|
||||
|
||||
|
||||
def test_with_path_mappings(pyfile, tmpdir, run_as, start_method):
|
||||
|
||||
@pyfile
|
||||
def code_to_debug():
|
||||
from dbgimporter import import_and_enable_debugger
|
||||
|
|
@ -93,9 +96,16 @@ def test_with_path_mappings(pyfile, tmpdir, run_as, start_method):
|
|||
hit = session.wait_for_thread_stopped('breakpoint')
|
||||
frames = hit.stacktrace.body['stackFrames']
|
||||
assert frames[0]['source']['path'] == Path(path_local)
|
||||
source_reference = frames[0]['source']['sourceReference']
|
||||
assert source_reference > 0
|
||||
|
||||
remote_code_path = session.read_json()
|
||||
assert path_remote == Path(remote_code_path)
|
||||
|
||||
resp_variables = session.send_request('source', arguments={
|
||||
'sourceReference': source_reference
|
||||
}).wait_for_response()
|
||||
assert "print('done')" in (resp_variables.body['content'])
|
||||
|
||||
session.send_request('continue').wait_for_response(freeze=False)
|
||||
session.wait_for_exit()
|
||||
|
|
|
|||
61
tests/func/test_set_expression.py
Normal file
61
tests/func/test_set_expression.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
from __future__ import print_function, with_statement, absolute_import
|
||||
|
||||
from tests.helpers.pattern import ANY
|
||||
from tests.helpers.session import DebugSession
|
||||
from tests.helpers.timeline import Event
|
||||
|
||||
|
||||
def test_set_expression(pyfile, run_as, start_method):
|
||||
|
||||
@pyfile
|
||||
def code_to_debug():
|
||||
import backchannel
|
||||
from dbgimporter import import_and_enable_debugger
|
||||
import_and_enable_debugger()
|
||||
import ptvsd
|
||||
a = 1
|
||||
ptvsd.break_into_debugger()
|
||||
backchannel.write_json(a)
|
||||
|
||||
with DebugSession() as session:
|
||||
session.initialize(
|
||||
target=(run_as, code_to_debug),
|
||||
start_method=start_method,
|
||||
ignore_unobserved=[Event('continued')],
|
||||
use_backchannel=True,
|
||||
)
|
||||
session.start_debugging()
|
||||
hit = session.wait_for_thread_stopped()
|
||||
|
||||
resp_scopes = session.send_request('scopes', arguments={
|
||||
'frameId': hit.frame_id
|
||||
}).wait_for_response()
|
||||
scopes = resp_scopes.body['scopes']
|
||||
assert len(scopes) > 0
|
||||
|
||||
resp_variables = session.send_request('variables', arguments={
|
||||
'variablesReference': scopes[0]['variablesReference']
|
||||
}).wait_for_response()
|
||||
variables = list(v for v in resp_variables.body['variables'] if v['name'] == 'a')
|
||||
assert variables == [{
|
||||
'type': 'int',
|
||||
'value': '1',
|
||||
'name': 'a',
|
||||
'evaluateName': "a"
|
||||
}]
|
||||
|
||||
resp_set_variable = session.send_request('setExpression', arguments={
|
||||
'frameId': hit.frame_id,
|
||||
'expression': 'a',
|
||||
'value': '1000'
|
||||
}).wait_for_response()
|
||||
assert resp_set_variable.body == ANY.dict_with({
|
||||
'type': 'int',
|
||||
'value': '1000'
|
||||
})
|
||||
|
||||
session.send_request('continue').wait_for_response(freeze=False)
|
||||
|
||||
assert session.read_json() == 1000
|
||||
|
||||
session.wait_for_exit()
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import ptvsd.untangle
|
||||
|
||||
from tests.helpers.pattern import ANY
|
||||
from ptvsd.wrapper import ModulesManager
|
||||
|
||||
|
||||
class ModulesEventSink(object):
|
||||
def __init__(self):
|
||||
self.event_data = []
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def send_event(self, event, **kwargs):
|
||||
with self._lock:
|
||||
self.event_data.append({
|
||||
'event': event,
|
||||
'args': kwargs,
|
||||
})
|
||||
|
||||
|
||||
class TestModulesManager(object):
|
||||
def test_invalid_module(self):
|
||||
sink = ModulesEventSink()
|
||||
mgr = ModulesManager(sink)
|
||||
assert mgr.add_or_get_from_path('abc.py') is None
|
||||
assert 0 == len(sink.event_data)
|
||||
assert [] == mgr.get_all()
|
||||
|
||||
def test_valid_new_module(self):
|
||||
sink = ModulesEventSink()
|
||||
mgr = ModulesManager(sink)
|
||||
|
||||
orig_module = sys.modules['ptvsd.untangle']
|
||||
expected_module = ANY.dict_with({
|
||||
'id': ANY.int,
|
||||
'name': orig_module.__name__,
|
||||
'package': orig_module.__package__,
|
||||
'path': orig_module.__file__,
|
||||
'version': orig_module.__version__,
|
||||
})
|
||||
|
||||
assert expected_module == mgr.add_or_get_from_path(ptvsd.untangle.__file__)
|
||||
assert 1 == len(sink.event_data)
|
||||
assert [expected_module] == mgr.get_all()
|
||||
assert sink.event_data == [
|
||||
{
|
||||
'event': 'module',
|
||||
'args': {
|
||||
'reason': 'new',
|
||||
'module': expected_module,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
def test_get_only_module(self):
|
||||
sink = ModulesEventSink()
|
||||
mgr = ModulesManager(sink)
|
||||
|
||||
expected_module = ANY.dict_with({
|
||||
'id': 1,
|
||||
'name': 'abc.xyz',
|
||||
'package': 'abc',
|
||||
'path': '/abc/xyz.py',
|
||||
'version': '1.2.3.4a1',
|
||||
})
|
||||
|
||||
mgr.path_to_module_id['/abc/xyz.py'] = 1
|
||||
mgr.module_id_to_details[1] = expected_module
|
||||
|
||||
assert expected_module == mgr.add_or_get_from_path('/abc/xyz.py')
|
||||
assert 0 == len(sink.event_data)
|
||||
assert [expected_module] == mgr.get_all()
|
||||
|
||||
def test_add_multi_thread(self):
|
||||
sink = ModulesEventSink()
|
||||
self.mgr = ModulesManager(sink)
|
||||
|
||||
orig_module = sys.modules['ptvsd.untangle']
|
||||
expected_module = ANY.dict_with({
|
||||
'id': ANY.int,
|
||||
'name': orig_module.__name__,
|
||||
'package': orig_module.__package__,
|
||||
'path': orig_module.__file__,
|
||||
'version': orig_module.__version__,
|
||||
})
|
||||
self.path = orig_module.__file__
|
||||
|
||||
def thread_worker(test, expected):
|
||||
time.sleep(0.01)
|
||||
assert expected_module == test.mgr.add_or_get_from_path(test.path)
|
||||
|
||||
threads = []
|
||||
for _ in range(10):
|
||||
thread = threading.Thread(target=thread_worker,
|
||||
args=(self, expected_module))
|
||||
threads.append(thread)
|
||||
thread.start()
|
||||
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
|
||||
assert 1 == len(sink.event_data)
|
||||
assert [expected_module] == self.mgr.get_all()
|
||||
Loading…
Add table
Add a link
Reference in a new issue