Merge branch '45_json_variables'

This commit is contained in:
fabioz 2019-02-26 13:08:08 -03:00
commit 11ffd749be
33 changed files with 1953 additions and 1025 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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',

View file

@ -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

View file

@ -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,

View 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)

View file

@ -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()

View file

@ -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())

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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("&", "&amp;").replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;')
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 = ''

View file

@ -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)')

View file

@ -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:

View file

@ -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

View file

@ -61,6 +61,7 @@ args = dict(
'_pydev_imps',
'_pydev_runfiles',
'_pydevd_bundle',
'_pydevd_bundle._debug_adapter',
'_pydevd_frame_eval',
'pydev_ipython',

View file

@ -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!')

View file

@ -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!')

View file

@ -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!')

View file

@ -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()

View file

@ -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

View 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
}

View file

@ -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)])

View file

@ -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__()'}
})

View file

@ -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:

View file

@ -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,

View file

@ -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')

View file

@ -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()

View 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()

View file

@ -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()