diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/pydevd_base_schema.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/pydevd_base_schema.py index 35366030..8d0b7467 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/pydevd_base_schema.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/_debug_adapter/pydevd_base_schema.py @@ -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) diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_api.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_api.py index bb41f56d..542941f5 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_api.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_api.py @@ -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) diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py index f26f9043..d66db8a2 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py @@ -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("") - _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 += pydevd_xml.var_to_xml(result, "") + 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 += pydevd_xml.var_to_xml(result, "") - 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 += pydevd_xml.var_to_xml(result, expression, trim_if_too_big) 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 diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm_constants.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm_constants.py index 11215404..00e5a007 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm_constants.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm_constants.py @@ -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', diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_constants.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_constants.py index 291f4ae9..a6e25733 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_constants.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_constants.py @@ -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 diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_dont_trace_files.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_dont_trace_files.py index f56ca85e..6bc0ff14 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_dont_trace_files.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_dont_trace_files.py @@ -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, diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py index 80bc5e34..5308d1fa 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py @@ -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 == '': + 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 == '': + 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) diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py index 25cd5bba..76103566 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py @@ -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('' % (quote(make_valid_xml_value(filename_in_utf8), '/>_= \t'), lineno)) append("") - curr_frame = curr_frame.f_back except: traceback.print_exc() diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command.py index e1db6db4..636ad863 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command.py @@ -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()) diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py index f292b97c..72ea644d 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py @@ -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 diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_resolver.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_resolver.py index 42bce325..de14d0bc 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_resolver.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_resolver.py @@ -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: diff --git a/src/ptvsd/safe_repr.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_safe_repr.py similarity index 99% rename from src/ptvsd/safe_repr.py rename to src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_safe_repr.py index 92d44941..90a6311c 100644 --- a/src/ptvsd/safe_repr.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_safe_repr.py @@ -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 diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py index ffe3b802..69b7e273 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py @@ -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: diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py index 3ed895e0..d4992d96 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py @@ -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 diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py index d635bf2e..81af7f7f 100644 --- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py +++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py @@ -16,12 +16,14 @@ try: except: frame_type = None + def make_valid_xml_value(s): # Same thing as xml.sax.saxutils.escape but also escaping double quotes. return s.replace("&", "&").replace('<', '<').replace('>', '>').replace('"', '"') class ExceptionOnEvaluate: + def __init__(self, result): self.result = result @@ -204,14 +206,14 @@ class TypeResolveHandler(object): _TYPE_RESOLVE_HANDLER = TypeResolveHandler() -""" +""" def get_type(o): Receives object and returns a triple (typeObject, typeString, resolver). - + resolver != None means that variable is a container, and should be displayed as a hierarchy. - + Use the resolver to get its attributes. - + All container objects should have a resolver. """ get_type = _TYPE_RESOLVE_HANDLER.get_type @@ -271,9 +273,7 @@ def frame_vars_to_xml(frame_f_locals, hidden_ns=None): return return_values_xml + xml -def var_to_xml(val, name, trim_if_too_big=True, additional_in_xml='', evaluate_full_value=True): - """ single variable or dictionary to xml representation """ - +def get_variable_details(val, evaluate_full_value=True, to_string=None): try: # This should be faster than isinstance (but we have to protect against not having a '__class__' attribute). is_exception_on_eval = val.__class__ == ExceptionOnEvaluate @@ -285,15 +285,19 @@ def var_to_xml(val, name, trim_if_too_big=True, additional_in_xml='', evaluate_f else: v = val - _type, typeName, resolver = get_type(v) + _type, type_name, resolver = get_type(v) type_qualifier = getattr(_type, "__module__", "") if not evaluate_full_value: value = DEFAULT_VALUE else: try: - str_from_provider = _str_from_providers(v, _type, typeName) + str_from_provider = _str_from_providers(v, _type, type_name) if str_from_provider is not None: value = str_from_provider + + elif to_string is not None: + value = to_string(v) + elif hasattr(v, '__class__'): if v.__class__ == frame_type: value = pydevd_resolver.frameResolver.get_frame_name(v) @@ -326,12 +330,32 @@ def var_to_xml(val, name, trim_if_too_big=True, additional_in_xml='', evaluate_f except: value = 'Unable to get repr for %s' % v.__class__ + # fix to work with unicode values + try: + if not IS_PY3K: + if value.__class__ == unicode: # @UndefinedVariable + value = value.encode('utf-8', 'replace') + else: + if value.__class__ == bytes: + value = value.decode('utf-8', 'replace') + except TypeError: + pass + + return type_name, type_qualifier, is_exception_on_eval, resolver, value + + +def var_to_xml(val, name, trim_if_too_big=True, additional_in_xml='', evaluate_full_value=True): + """ single variable or dictionary to xml representation """ + + type_name, type_qualifier, is_exception_on_eval, resolver, value = get_variable_details( + val, evaluate_full_value) + try: name = quote(name, '/>_= ') # TODO: Fix PY-5834 without using quote except: pass - xml = '_= '))) else: xml_value = '' diff --git a/src/ptvsd/_vendored/pydevd/pydevd.py b/src/ptvsd/_vendored/pydevd/pydevd.py index ec0ad321..e5fd33f1 100644 --- a/src/ptvsd/_vendored/pydevd/pydevd.py +++ b/src/ptvsd/_vendored/pydevd/pydevd.py @@ -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)') diff --git a/src/ptvsd/_vendored/pydevd/pydevd_concurrency_analyser/pydevd_concurrency_logger.py b/src/ptvsd/_vendored/pydevd/pydevd_concurrency_analyser/pydevd_concurrency_logger.py index 5cbfcf3b..80014980 100644 --- a/src/ptvsd/_vendored/pydevd/pydevd_concurrency_analyser/pydevd_concurrency_logger.py +++ b/src/ptvsd/_vendored/pydevd/pydevd_concurrency_analyser/pydevd_concurrency_logger.py @@ -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(' 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='', 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', ('', 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 diff --git a/src/ptvsd/_vendored/pydevd/tests_python/test_resolvers.py b/src/ptvsd/_vendored/pydevd/tests_python/test_resolvers.py new file mode 100644 index 00000000..18298d70 --- /dev/null +++ b/src/ptvsd/_vendored/pydevd/tests_python/test_resolvers.py @@ -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 + } + diff --git a/tests/test_safe_repr.py b/src/ptvsd/_vendored/pydevd/tests_python/test_safe_repr.py similarity index 86% rename from tests/test_safe_repr.py rename to src/ptvsd/_vendored/pydevd/tests_python/test_safe_repr.py index da0b2136..7cd27ecb 100644 --- a/tests/test_safe_repr.py +++ b/src/ptvsd/_vendored/pydevd/tests_python/test_safe_repr.py @@ -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'\') 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, '') 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)]) diff --git a/src/ptvsd/_vendored/pydevd/tests_python/test_suspended_frames_manager.py b/src/ptvsd/_vendored/pydevd/tests_python/test_suspended_frames_manager.py new file mode 100644 index 00000000..552ec5ad --- /dev/null +++ b/src/ptvsd/_vendored/pydevd/tests_python/test_suspended_frames_manager.py @@ -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__()'} + }) diff --git a/src/ptvsd/wrapper.py b/src/ptvsd/wrapper.py index c38c96bc..dc31a9b1 100644 --- a/src/ptvsd/wrapper.py +++ b/src/ptvsd/wrapper.py @@ -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 == '': - 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 == '': - 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 ' ()' 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: diff --git a/tests/func/test_django.py b/tests/func/test_django.py index 77971fa1..f62c4755 100644 --- a/tests/func/test_django.py +++ b/tests/func/test_django.py @@ -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, diff --git a/tests/func/test_evaluate.py b/tests/func/test_evaluate.py index e148c518..fa378542 100644 --- a/tests/func/test_evaluate.py +++ b/tests/func/test_evaluate.py @@ -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') diff --git a/tests/func/test_path_mapping.py b/tests/func/test_path_mapping.py index 619be6f5..8ebf4038 100644 --- a/tests/func/test_path_mapping.py +++ b/tests/func/test_path_mapping.py @@ -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() diff --git a/tests/func/test_set_expression.py b/tests/func/test_set_expression.py new file mode 100644 index 00000000..44870eaa --- /dev/null +++ b/tests/func/test_set_expression.py @@ -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() diff --git a/tests/test_modules_manager.py b/tests/test_modules_manager.py deleted file mode 100644 index 513bdc7d..00000000 --- a/tests/test_modules_manager.py +++ /dev/null @@ -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()