From 05ffe6da614af4badb4cb57b09489e3b7b90ca7f Mon Sep 17 00:00:00 2001 From: Fabio Zadrozny Date: Fri, 17 Apr 2020 16:08:15 -0300 Subject: [PATCH] Show globals, group variables and show all dir() variables. Fixes https://github.com/microsoft/ptvsd/issues/118 Fixes https://github.com/microsoft/ptvsd/issues/763 Fixes https://github.com/microsoft/ptvsd/issues/1621 --- .../_pydev_bundle/pydev_console_utils.py | 3 +- .../pydevd/_pydevd_bundle/pydevd_api.py | 18 ++ .../pydevd/_pydevd_bundle/pydevd_comm.py | 14 +- .../pydevd/_pydevd_bundle/pydevd_constants.py | 6 + .../pydevd_process_net_command_json.py | 42 ++++- .../pydevd/_pydevd_bundle/pydevd_resolver.py | 172 +++++++++++------- .../_pydevd_bundle/pydevd_suspended_frames.py | 108 +++++++++-- .../pydevd/_pydevd_bundle/pydevd_utils.py | 70 ++++++- .../pydevd/_pydevd_bundle/pydevd_vars.py | 16 +- .../pydevd/_pydevd_bundle/pydevd_xml.py | 18 +- src/debugpy/_vendored/pydevd/pydevd.py | 2 + .../pydevd/tests_python/debugger_unittest.py | 9 + .../resources/_debugger_case_globals.py | 12 ++ .../pydevd/tests_python/test_debugger.py | 2 +- .../pydevd/tests_python/test_debugger_json.py | 134 ++++++++++++-- .../pydevd/tests_python/test_resolvers.py | 67 +++++-- .../test_suspended_frames_manager.py | 16 +- tests/debug/config.py | 1 + tests/debugpy/test_evaluate.py | 47 ++++- 19 files changed, 606 insertions(+), 151 deletions(-) create mode 100644 src/debugpy/_vendored/pydevd/tests_python/resources/_debugger_case_globals.py diff --git a/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_console_utils.py b/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_console_utils.py index ee4eb6a25..7c3a62643 100644 --- a/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_console_utils.py +++ b/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_console_utils.py @@ -490,8 +490,7 @@ def getVariable(self, attributes): if val_dict is None: val_dict = {} - keys = val_dict.keys() - for k in keys: + for k, val in dict_iter_items(val_dict): val = val_dict[k] evaluate_full_value = pydevd_xml.should_evaluate_full_value(val) xml.write(pydevd_vars.var_to_xml(val, k, evaluate_full_value=evaluate_full_value)) diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py index d151e9390..ca25aabce 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py @@ -28,6 +28,7 @@ from _pydevd_bundle.pydevd_collect_bytecode_info import code_to_bytecode_representation import itertools import linecache +from _pydevd_bundle.pydevd_utils import DAPGrouper try: import dis @@ -65,6 +66,19 @@ def iterate(): class PyDevdAPI(object): + class VariablePresentation(object): + + def __init__(self, special='group', function='group', class_='group', protected='inline'): + self._presentation = { + DAPGrouper.SCOPE_SPECIAL_VARS: special, + DAPGrouper.SCOPE_FUNCTION_VARS: function, + DAPGrouper.SCOPE_CLASS_VARS: class_, + DAPGrouper.SCOPE_PROTECTED_VARS: protected, + } + + def get_presentation(self, scope): + return self._presentation[scope] + def run(self, py_db): py_db.ready_to_run = True @@ -840,6 +854,10 @@ def set_source_mapping(self, py_db, source_filename, mapping): self.reapply_breakpoints(py_db) return '' + def set_variable_presentation(self, py_db, variable_presentation): + assert isinstance(variable_presentation, self.VariablePresentation) + py_db.variable_presentation = variable_presentation + def get_ppid(self): ''' Provides the parent pid (even for older versions of Python on Windows). diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py index bad885b70..0870382dc 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py @@ -96,7 +96,7 @@ import sys import traceback from _pydevd_bundle.pydevd_utils import quote_smart as quote, compare_object_attrs_key, \ - notify_about_gevent_if_needed, isinstance_checked + notify_about_gevent_if_needed, isinstance_checked, ScopeRequest from _pydev_bundle import pydev_log from _pydev_bundle.pydev_log import exception as pydev_log_exception from _pydev_bundle import _pydev_completer @@ -729,6 +729,11 @@ def internal_get_variable_json(py_db, request): ''' arguments = request.arguments # : :type arguments: VariablesArguments variables_reference = arguments.variablesReference + scope = None + if isinstance_checked(variables_reference, ScopeRequest): + scope = variables_reference + variables_reference = variables_reference.variable_reference + fmt = arguments.format if hasattr(fmt, 'to_dict'): fmt = fmt.to_dict() @@ -739,7 +744,7 @@ def internal_get_variable_json(py_db, request): except KeyError: pass else: - for child_var in variable.get_children_variables(fmt=fmt): + for child_var in variable.get_children_variables(fmt=fmt, scope=scope): variables.append(child_var.get_var_data(fmt=fmt)) body = VariablesResponseBody(variables) @@ -844,6 +849,11 @@ def internal_change_variable_json(py_db, request): # : :type arguments: SetVariableArguments arguments = request.arguments variables_reference = arguments.variablesReference + scope = None + if isinstance_checked(variables_reference, ScopeRequest): + scope = variables_reference + variables_reference = variables_reference.variable_reference + fmt = arguments.format if hasattr(fmt, 'to_dict'): fmt = fmt.to_dict() diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_constants.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_constants.py index 3c2fc53cf..254273b22 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_constants.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_constants.py @@ -18,6 +18,12 @@ except NameError: int_types = (int,) +# types does not include a MethodWrapperType +try: + MethodWrapperType = type([].__str__) +except: + MethodWrapperType = None + import sys # Note: the sys import must be here anyways (others depend on it) # Preload codecs to avoid imports to them later on which can potentially halt the debugger. diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py index a5714e736..6a482aac6 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py @@ -25,7 +25,7 @@ from _pydevd_bundle.pydevd_filtering import ExcludeFilter from _pydevd_bundle.pydevd_json_debug_options import _extract_debug_options, DebugOptions from _pydevd_bundle.pydevd_net_command import NetCommand -from _pydevd_bundle.pydevd_utils import convert_dap_log_message_to_expression +from _pydevd_bundle.pydevd_utils import convert_dap_log_message_to_expression, ScopeRequest from _pydevd_bundle.pydevd_constants import (PY_IMPL_NAME, DebugInfoHolder, PY_VERSION_STR, PY_IMPL_VERSION_STR, IS_64BIT_PROCESS) from _pydevd_bundle.pydevd_trace_dispatch import USING_CYTHON @@ -162,7 +162,7 @@ def on_request(py_db, request): else: if DebugInfoHolder.DEBUG_RECORD_SOCKET_READS and DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1: pydev_log.info('Process %s: %s\n' % ( - request.__class__.__name__, json.dumps(request.to_dict(), indent=4, sort_keys=True),)) + request.__class__.__name__, json.dumps(request.to_dict(update_ids_to_dap=True), indent=4, sort_keys=True),)) assert request.type == 'request' method_name = 'on_%s_request' % (request.command.lower(),) @@ -328,6 +328,33 @@ def _set_debug_options(self, py_db, args, start_reason): terminate_child_processes = args.get('terminateChildProcesses', True) self.api.set_terminate_child_processes(py_db, terminate_child_processes) + variable_presentation = args.get('variablePresentation', None) + if isinstance(variable_presentation, dict): + + def get_variable_presentation(setting, default): + value = variable_presentation.get(setting, default) + if value not in ('group', 'inline', 'hide'): + pydev_log.info( + 'The value set for "%s" (%s) in the variablePresentation is not valid. Valid values are: "group", "inline", "hide"' % ( + setting, value,)) + value = default + + return value + + default = get_variable_presentation('all', 'group') + + special_presentation = get_variable_presentation('special', default) + function_presentation = get_variable_presentation('function', default) + class_presentation = get_variable_presentation('class', default) + protected_presentation = get_variable_presentation('protected', default) + + self.api.set_variable_presentation(py_db, self.api.VariablePresentation( + special_presentation, + function_presentation, + class_presentation, + protected_presentation + )) + exclude_filters = [] if rules is not None: @@ -747,7 +774,10 @@ def on_scopes_request(self, py_db, request): frame_id = request.arguments.frameId variables_reference = frame_id - scopes = [Scope('Locals', int(variables_reference), False).to_dict()] + scopes = [ + Scope('Locals', ScopeRequest(int(variables_reference), 'locals'), False), + Scope('Globals', ScopeRequest(int(variables_reference), 'globals'), False), + ] body = ScopesResponseBody(scopes) scopes_response = pydevd_base_schema.build_response(request, kwargs={'body': body}) return NetCommand(CMD_RETURN, 0, scopes_response, is_json=True) @@ -817,6 +847,9 @@ def on_variables_request(self, py_db, request): arguments = request.arguments # : :type arguments: VariablesArguments variables_reference = arguments.variablesReference + if isinstance(variables_reference, ScopeRequest): + variables_reference = variables_reference.variable_reference + thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( variables_reference) if thread_id is not None: @@ -835,6 +868,9 @@ def on_setvariable_request(self, py_db, request): arguments = request.arguments # : :type arguments: SetVariableArguments variables_reference = arguments.variablesReference + if isinstance(variables_reference, ScopeRequest): + variables_reference = variables_reference.variable_reference + if arguments.name.startswith('(return) '): response = pydevd_base_schema.build_response( request, diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_resolver.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_resolver.py index 41e198ee9..0bcb7962f 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_resolver.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_resolver.py @@ -1,5 +1,5 @@ from _pydev_bundle import pydev_log -from _pydevd_bundle.pydevd_utils import hasattr_checked +from _pydevd_bundle.pydevd_utils import hasattr_checked, DAPGrouper try: import StringIO except: @@ -8,7 +8,8 @@ from os.path import basename from functools import partial -from _pydevd_bundle.pydevd_constants import dict_iter_items, dict_keys, xrange, IS_PY36_OR_GREATER +from _pydevd_bundle.pydevd_constants import dict_iter_items, dict_keys, xrange, IS_PY36_OR_GREATER, \ + MethodWrapperType, RETURN_VALUES_DICT, DebugInfoHolder, IS_PYPY from _pydevd_bundle.pydevd_safe_repr import SafeRepr # Note: 300 is already a lot to see in the outline (after that the user should really use the shell to get things) @@ -26,23 +27,6 @@ class UnableToResolveVariableException(Exception): pass -#======================================================================================================================= -# InspectStub -#======================================================================================================================= -class InspectStub: - - def isbuiltin(self, _args): - return False - - def isroutine(self, object): - return False - - -try: - import inspect -except: - inspect = InspectStub() - try: from collections import OrderedDict except: @@ -53,12 +37,6 @@ def isroutine(self, object): except: pass -# types does not include a MethodWrapperType -try: - MethodWrapperType = type([].__str__) -except: - MethodWrapperType = None - #======================================================================================================================= # See: pydevd_extension_api module for resolver interface #======================================================================================================================= @@ -99,9 +77,16 @@ def get_contents_debug_adapter_protocol(self, obj, fmt=None): lst = sorted(dict_iter_items(dct), key=lambda tup: sorted_attributes_key(tup[0])) if used___dict__: - return [(attr_name, attr_value, '.__dict__[%s]' % attr_name) for (attr_name, attr_value) in lst] + eval_name = '.__dict__[%s]' else: - return [(attr_name, attr_value, '.%s' % attr_name) for (attr_name, attr_value) in lst] + eval_name = '.%s' + + ret = [] + for attr_name, attr_value in lst: + entry = (attr_name, attr_value, eval_name % attr_name) + ret.append(entry) + + return ret def get_dictionary(self, var, names=None, used___dict__=False): if MethodWrapperType: @@ -183,11 +168,12 @@ def _get_py_dictionary(self, var, names=None, used___dict__=False): using obj.__dict__[name] instead of getattr(obj, name) ''' - # TODO: Those should be options (would fix https://github.com/Microsoft/ptvsd/issues/66). - filter_private = False - filter_special = True - filter_function = True - filter_builtin = True + # On PyPy we never show functions. This is because of a corner case where PyPy becomes + # absurdly slow -- it takes almost half a second to introspect a single numpy function (so, + # the related test, "test_case_16_resolve_numpy_array", times out... this probably isn't + # specific to numpy, but to any library where the CPython bridge is used, but as we + # can't be sure in the debugger, we play it safe and don't show it at all). + filter_function = IS_PYPY if not names: names, used___dict__ = self.get_names(var) @@ -197,45 +183,38 @@ def _get_py_dictionary(self, var, names=None, used___dict__=False): # optimize the operation by removing as many items as possible in the # first filters, leaving fewer items for later filters - if filter_builtin or filter_function: - for name in names: - try: - name_as_str = name - if name_as_str.__class__ != str: - name_as_str = '%r' % (name_as_str,) - - if filter_special: - if name_as_str.startswith('__') and name_as_str.endswith('__'): - continue - - if filter_private: - if name_as_str.startswith('_') or name_as_str.endswith('__'): - continue - if not used___dict__: - attr = getattr(var, name) - else: - attr = var.__dict__[name] - - # filter builtins? - if filter_builtin: - if inspect.isbuiltin(attr): - continue - - # filter functions? - if filter_function: - if inspect.isroutine(attr) or isinstance(attr, MethodWrapperType): - continue - except: - # if some error occurs getting it, let's put it to the user. - strIO = StringIO.StringIO() - traceback.print_exc(file=strIO) - attr = strIO.getvalue() - - d[name_as_str] = attr + for name in names: + try: + name_as_str = name + if name_as_str.__class__ != str: + name_as_str = '%r' % (name_as_str,) + + if not used___dict__: + attr = getattr(var, name) + else: + attr = var.__dict__[name] + + # filter functions? + if filter_function: + if inspect.isroutine(attr) or isinstance(attr, MethodWrapperType): + continue + except: + # if some error occurs getting it, let's put it to the user. + strIO = StringIO.StringIO() + traceback.print_exc(file=strIO) + attr = strIO.getvalue() + + d[name_as_str] = attr return d, used___dict__ +class DAPGrouperResolver: + + def get_contents_debug_adapter_protocol(self, obj, fmt=None): + return obj.get_contents_debug_adapter_protocol() + + #======================================================================================================================= # DictResolver #======================================================================================================================= @@ -322,10 +301,10 @@ def get_dictionary(self, dict): ret[TOO_LARGE_ATTR] = TOO_LARGE_MSG break - ret['__len__'] = len(dict) # in case if the class extends built-in type and has some additional fields additional_fields = defaultResolver.get_dictionary(dict) ret.update(additional_fields) + ret['__len__'] = len(dict) return ret @@ -396,10 +375,10 @@ def get_dictionary(self, var, fmt={}): 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) d.update(additional_fields) + d['__len__'] = len(var) return d @@ -452,10 +431,10 @@ def get_dictionary(self, var): 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) d.update(additional_fields) + d['__len__'] = len(var) return d def change_var_from_name(self, container, name, new_value): @@ -650,3 +629,56 @@ def get_frame_name(self, frame): dequeResolver = DequeResolver() orderedDictResolver = OrderedDictResolver() frameResolver = FrameResolver() +dapGrouperResolver = DAPGrouperResolver() + + +class InspectStub: + + def isbuiltin(self, _args): + return False + + def isroutine(self, object): + return False + + +try: + import inspect +except: + inspect = InspectStub() + + +def get_var_scope(attr_name, attr_value, evaluate_name, handle_return_values): + if attr_name.startswith("'"): + if attr_name.endswith("'"): + attr_name = attr_name[1:-1] + else: + i = attr_name.find("__' (") + if i >= 0: + # Handle attr_name such as: >>'__name__' (1732494379184)<< + attr_name = attr_name[1: i + 2] + + if handle_return_values and attr_name == RETURN_VALUES_DICT: + return '' + + elif attr_name == '__len__' and evaluate_name != '.__len__': + # Treat the __len__ we generate internally separate from the __len__ function + return '' + + if attr_name.startswith('__') and attr_name.endswith('__'): + return DAPGrouper.SCOPE_SPECIAL_VARS + + if attr_name.startswith('_') or attr_name.endswith('__'): + return DAPGrouper.SCOPE_PROTECTED_VARS + + try: + if inspect.isroutine(attr_value) or isinstance(attr_value, MethodWrapperType): + return DAPGrouper.SCOPE_FUNCTION_VARS + + elif inspect.isclass(attr_value): + return DAPGrouper.SCOPE_CLASS_VARS + except: + # It's possible that isinstance throws an exception when dealing with user-code. + if DebugInfoHolder.DEBUG_TRACE_LEVEL > 0: + pydev_log.exception() + + return '' diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py index a1734f227..6271a204e 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py @@ -5,12 +5,13 @@ dict_iter_items, ForkSafeLock 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, TOO_LARGE_ATTR +from _pydevd_bundle.pydevd_resolver import sorted_attributes_key, TOO_LARGE_ATTR, get_var_scope from _pydevd_bundle.pydevd_safe_repr import SafeRepr from _pydev_bundle import pydev_log from _pydevd_bundle import pydevd_vars from _pydev_bundle.pydev_imports import Exec from _pydevd_bundle.pydevd_frame_utils import FramesList +from _pydevd_bundle.pydevd_utils import ScopeRequest, DAPGrouper class _AbstractVariable(object): @@ -21,6 +22,10 @@ class _AbstractVariable(object): value = None evaluate_name = None + def __init__(self, py_db): + assert py_db is not None + self.py_db = py_db + def get_name(self): return self.name @@ -78,20 +83,61 @@ def get_var_data(self, fmt=None): return var_data - def get_children_variables(self, fmt=None): + def get_children_variables(self, fmt=None, scope=None): raise NotImplementedError() - def get_child_variable_named(self, name, fmt=None): - for child_var in self.get_children_variables(fmt=fmt): + def get_child_variable_named(self, name, fmt=None, scope=None): + for child_var in self.get_children_variables(fmt=fmt, scope=scope): if child_var.get_name() == name: return child_var return None + def _group_entries(self, lst, handle_return_values): + scope_to_grouper = {} + + group_entries = [] + if isinstance(self.value, DAPGrouper): + new_lst = lst + else: + new_lst = [] + get_presentation = self.py_db.variable_presentation.get_presentation + # Now that we have the contents, group items. + for attr_name, attr_value, evaluate_name in lst: + scope = get_var_scope(attr_name, attr_value, evaluate_name, handle_return_values) + + entry = (attr_name, attr_value, evaluate_name) + if scope: + presentation = get_presentation(scope) + if presentation == 'hide': + continue + + elif presentation == 'inline': + new_lst.append(entry) + + else: # group + if scope not in scope_to_grouper: + grouper = DAPGrouper(scope) + scope_to_grouper[scope] = grouper + else: + grouper = scope_to_grouper[scope] + + grouper.contents_debug_adapter_protocol.append(entry) + + else: + new_lst.append(entry) + + for scope in DAPGrouper.SCOPES_SORTED: + grouper = scope_to_grouper.get(scope) + if grouper is not None: + group_entries.append((scope, grouper, None)) + + return new_lst, group_entries + class _ObjectVariable(_AbstractVariable): - def __init__(self, name, value, register_variable, is_return_value=False, evaluate_name=None, frame=None): - _AbstractVariable.__init__(self) + def __init__(self, py_db, name, value, register_variable, is_return_value=False, evaluate_name=None, frame=None): + _AbstractVariable.__init__(self, py_db) self.frame = frame self.name = name self.value = value @@ -101,7 +147,7 @@ def __init__(self, name, value, register_variable, is_return_value=False, evalua self.evaluate_name = evaluate_name @overrides(_AbstractVariable.get_children_variables) - def get_children_variables(self, fmt=None): + def get_children_variables(self, fmt=None, scope=None): _type, _type_name, resolver = get_type(self.value) children_variables = [] @@ -117,6 +163,9 @@ def get_children_variables(self, fmt=None): # No evaluate name in this case. lst = [(key, value, None) for (key, value) in lst] + lst, group_entries = self._group_entries(lst, handle_return_values=False) + if group_entries: + lst = group_entries + lst parent_evaluate_name = self.evaluate_name if parent_evaluate_name: for key, val, evaluate_name in lst: @@ -126,12 +175,12 @@ def get_children_variables(self, fmt=None): else: evaluate_name = parent_evaluate_name + evaluate_name variable = _ObjectVariable( - key, val, self._register_variable, evaluate_name=evaluate_name, frame=self.frame) + self.py_db, key, val, self._register_variable, evaluate_name=evaluate_name, frame=self.frame) children_variables.append(variable) else: for key, val, evaluate_name in lst: # No evaluate name - variable = _ObjectVariable(key, val, self._register_variable, frame=self.frame) + variable = _ObjectVariable(self.py_db, key, val, self._register_variable, frame=self.frame) children_variables.append(variable) return children_variables @@ -159,7 +208,7 @@ def change_variable(self, name, value, py_db, fmt=None): new_key = container_resolver.change_var_from_name(self.value, name, new_value) if new_key is not None: return _ObjectVariable( - new_key, new_value, self._register_variable, evaluate_name=None, frame=self.frame) + self.py_db, new_key, new_value, self._register_variable, evaluate_name=None, frame=self.frame) return None else: @@ -184,8 +233,8 @@ def sorted_variables_key(obj): class _FrameVariable(_AbstractVariable): - def __init__(self, frame, register_variable): - _AbstractVariable.__init__(self) + def __init__(self, py_db, frame, register_variable): + _AbstractVariable.__init__(self, py_db) self.frame = frame self.name = self.frame.f_code.co_name @@ -202,21 +251,44 @@ def change_variable(self, name, value, py_db, fmt=None): return self.get_child_variable_named(name, fmt=fmt) @overrides(_AbstractVariable.get_children_variables) - def get_children_variables(self, fmt=None): + def get_children_variables(self, fmt=None, scope=None): children_variables = [] - for key, val in dict_items(self.frame.f_locals): + if scope is not None: + assert isinstance(scope, ScopeRequest) + scope = scope.scope + + if scope in ('locals', None): + dct = self.frame.f_locals + elif scope == 'globals': + dct = self.frame.f_globals + else: + raise AssertionError('Unexpected scope: %s' % (scope,)) + + lst, group_entries = self._group_entries([(x[0], x[1], None) for x in dict_items(dct) if x[0] != '_pydev_stop_at_break'], handle_return_values=True) + group_variables = [] + + for key, val, _ in group_entries: + # Make sure that the contents in the group are also sorted. + val.contents_debug_adapter_protocol.sort(key=lambda v:sorted_attributes_key(v[0])) + variable = _ObjectVariable(self.py_db, key, val, self._register_variable, False, key, frame=self.frame) + group_variables.append(variable) + + for key, val, _ in lst: 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), frame=self.frame) + self.py_db, return_key, return_value, self._register_variable, is_return_value, '%s[%r]' % (key, return_key), frame=self.frame) children_variables.append(variable) else: - variable = _ObjectVariable(key, val, self._register_variable, is_return_value, key, frame=self.frame) + variable = _ObjectVariable(self.py_db, key, val, self._register_variable, is_return_value, key, frame=self.frame) children_variables.append(variable) # Frame variables always sorted. children_variables.sort(key=sorted_variables_key) + if group_variables: + # Groups have priority over other variables. + children_variables = group_variables + children_variables return children_variables @@ -270,7 +342,7 @@ def obtain_as_variable(self, name, value, evaluate_name=None, frame=None): # Still not created, let's do it now. return _ObjectVariable( - name, value, self._register_variable, is_return_value=False, evaluate_name=evaluate_name, frame=frame) + self.py_db, name, value, self._register_variable, is_return_value=False, evaluate_name=evaluate_name, frame=frame) def get_main_thread_id(self): return self._main_thread_id @@ -306,7 +378,7 @@ def track(self, thread_id, frames_list, frame_custom_thread_id=None): for frame in frames_list: frame_id = id(frame) self._frame_id_to_frame[frame_id] = frame - _FrameVariable(frame, self._register_variable) # Instancing is enough to register. + _FrameVariable(self.py_db, 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) diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_utils.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_utils.py index 691fd970a..e9a1ffe98 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_utils.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_utils.py @@ -136,7 +136,7 @@ def get_clsname_for_code(code, frame): else: # instance method if hasattr(first_arg_obj, "__class__"): first_arg_class = first_arg_obj.__class__ - else: # old style class, fall back on type + else: # old style class, fall back on type first_arg_class = type(first_arg_obj) func_name = code.co_name if hasattr(first_arg_class, func_name): @@ -299,3 +299,71 @@ def isinstance_checked(obj, cls): except: return False + +class ScopeRequest(object): + + __slots__ = ['variable_reference', 'scope'] + + def __init__(self, variable_reference, scope): + assert scope in ('globals', 'locals') + self.variable_reference = variable_reference + self.scope = scope + + def __eq__(self, o): + if isinstance(o, ScopeRequest): + return self.variable_reference == o.variable_reference and self.scope == o.scope + + return False + + def __ne__(self, o): + return not self == o + + def __hash__(self): + return hash((self.variable_reference, self.scope)) + + +class DAPGrouper(object): + ''' + Note: this is a helper class to group variables on the debug adapter protocol (DAP). For + the xml protocol the type is just added to each variable and the UI can group/hide it as needed. + ''' + + SCOPE_SPECIAL_VARS = 'special variables' + SCOPE_PROTECTED_VARS = 'protected variables' + SCOPE_FUNCTION_VARS = 'function variables' + SCOPE_CLASS_VARS = 'class variables' + + SCOPES_SORTED = [ + SCOPE_SPECIAL_VARS, + SCOPE_PROTECTED_VARS, + SCOPE_FUNCTION_VARS, + SCOPE_CLASS_VARS, + ] + + __slots__ = ['variable_reference', 'scope', 'contents_debug_adapter_protocol'] + + def __init__(self, scope): + self.variable_reference = id(self) + self.scope = scope + self.contents_debug_adapter_protocol = [] + + def get_contents_debug_adapter_protocol(self): + return self.contents_debug_adapter_protocol[:] + + def __eq__(self, o): + if isinstance(o, ScopeRequest): + return self.variable_reference == o.variable_reference and self.scope == o.scope + + return False + + def __ne__(self, o): + return not self == o + + def __hash__(self): + return hash((self.variable_reference, self.scope)) + + def __repr__(self): + return '' + + def __str__(self): + return '' diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py index 0a17165df..48384c678 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py @@ -76,7 +76,7 @@ def getVariable(dbg, thread_id, frame_id, scope, attrs): if attrs is not None: attrList = attrs.split('\t') for k in attrList: - _type, _typeName, resolver = get_type(var) + _type, _type_name, resolver = get_type(var) var = resolver.resolve(var, k) return var @@ -103,7 +103,7 @@ def getVariable(dbg, thread_id, frame_id, scope, attrs): # An Expression can be in any scope (globals/locals), therefore it needs to evaluated as an expression var = evaluate_expression(dbg, frame, attrList[count], False) else: - _type, _typeName, resolver = get_type(var) + _type, _type_name, resolver = get_type(var) var = resolver.resolve(var, attrList[count]) else: if scope == "GLOBAL": @@ -116,7 +116,7 @@ def getVariable(dbg, thread_id, frame_id, scope, attrs): var.update(frame.f_locals) for k in attrList: - _type, _typeName, resolver = get_type(var) + _type, _type_name, resolver = get_type(var) var = resolver.resolve(var, k) return var @@ -137,8 +137,8 @@ def resolve_compound_variable_fields(dbg, thread_id, frame_id, scope, attrs): var = getVariable(dbg, thread_id, frame_id, scope, attrs) try: - _type, _typeName, resolver = get_type(var) - return _typeName, resolver.get_dictionary(var) + _type, type_name, resolver = get_type(var) + return type_name, resolver.get_dictionary(var) except: pydev_log.exception('Error evaluating: thread_id: %s\nframe_id: %s\nscope: %s\nattrs: %s.', thread_id, frame_id, scope, attrs) @@ -157,7 +157,7 @@ def resolve_var_object(var, attrs): else: attr_list = [] for k in attr_list: - type, _typeName, resolver = get_type(var) + type, _type_name, resolver = get_type(var) var = resolver.resolve(var, k) return var @@ -173,11 +173,11 @@ def resolve_compound_var_object_fields(var, attrs): attr_list = attrs.split('\t') for k in attr_list: - type, _typeName, resolver = get_type(var) + type, _type_name, resolver = get_type(var) var = resolver.resolve(var, k) try: - type, _typeName, resolver = get_type(var) + type, _type_name, resolver = get_type(var) return resolver.get_dictionary(var) except: pydev_log.exception() diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py index 86110317c..866a92259 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py @@ -7,7 +7,8 @@ DEFAULT_VALUE from _pydev_bundle.pydev_imports import quote from _pydevd_bundle.pydevd_extension_api import TypeResolveProvider, StrPresentationProvider -from _pydevd_bundle.pydevd_utils import isinstance_checked, hasattr_checked +from _pydevd_bundle.pydevd_utils import isinstance_checked, hasattr_checked, DAPGrouper +from _pydevd_bundle.pydevd_resolver import get_var_scope try: import types @@ -62,6 +63,8 @@ def _create_default_type_map(): except: pass # not available on all python versions + default_type_map.append((DAPGrouper, pydevd_resolver.dapGrouperResolver)) + try: default_type_map.append((set, pydevd_resolver.setResolver)) except: @@ -213,13 +216,13 @@ def str_from_providers(self, o, type_object, type_name): """ def get_type(o): - Receives object and returns a triple (typeObject, typeString, resolver). + Receives object and returns a triple (type_object, type_string, 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. + All container objects (i.e.: dict, list, tuple, object, etc) should have a resolver. """ get_type = _TYPE_RESOLVE_HANDLER.get_type @@ -260,6 +263,9 @@ def frame_vars_to_xml(frame_f_locals, hidden_ns=None): v = frame_f_locals[k] eval_full_val = should_evaluate_full_value(v) + if k == '_pydev_stop_at_break': + continue + if k == RETURN_VALUES_DICT: for name, val in dict_iter_items(v): return_values_xml += var_to_xml(val, name, additional_in_xml=' isRetVal="True"') @@ -354,6 +360,7 @@ def var_to_xml(val, name, trim_if_too_big=True, additional_in_xml='', evaluate_f type_name, type_qualifier, is_exception_on_eval, resolver, value = get_variable_details( val, evaluate_full_value) + scope = get_var_scope(name, val, '', True) try: name = quote(name, '/>_= ') # TODO: Fix PY-5834 without using quote except: @@ -384,4 +391,7 @@ def var_to_xml(val, name, trim_if_too_big=True, additional_in_xml='', evaluate_f else: xml_container = '' - return ''.join((xml, xml_qualifier, xml_value, xml_container, additional_in_xml, ' />\n')) + if scope: + return ''.join((xml, xml_qualifier, xml_value, xml_container, additional_in_xml, ' scope="', scope, '"', ' />\n')) + else: + return ''.join((xml, xml_qualifier, xml_value, xml_container, additional_in_xml, ' />\n')) diff --git a/src/debugpy/_vendored/pydevd/pydevd.py b/src/debugpy/_vendored/pydevd/pydevd.py index 66b1f1539..b097db83c 100644 --- a/src/debugpy/_vendored/pydevd/pydevd.py +++ b/src/debugpy/_vendored/pydevd/pydevd.py @@ -481,6 +481,8 @@ def __init__(self, set_as_global=True): # Set communication protocol PyDevdAPI().set_protocol(self, 0, PydevdCustomization.DEFAULT_PROTOCOL) + self.variable_presentation = PyDevdAPI.VariablePresentation() + # mtime to be raised when breakpoints change self.mtime = 0 diff --git a/src/debugpy/_vendored/pydevd/tests_python/debugger_unittest.py b/src/debugpy/_vendored/pydevd/tests_python/debugger_unittest.py index de5a8ebdc..e248e0a89 100644 --- a/src/debugpy/_vendored/pydevd/tests_python/debugger_unittest.py +++ b/src/debugpy/_vendored/pydevd/tests_python/debugger_unittest.py @@ -511,6 +511,15 @@ def run_process(self, args, writer): yield dct_with_stdout_stder except: fail_with_message = True + # Let's print the actuayl exception here (it doesn't appear properly on Python 2 and + # on Python 3 it's hard to find because pytest output is too verbose). + sys.stderr.write('***********\n') + sys.stderr.write('***********\n') + sys.stderr.write('***********\n') + traceback.print_exc() + sys.stderr.write('***********\n') + sys.stderr.write('***********\n') + sys.stderr.write('***********\n') raise if not writer.finished_ok: diff --git a/src/debugpy/_vendored/pydevd/tests_python/resources/_debugger_case_globals.py b/src/debugpy/_vendored/pydevd/tests_python/resources/_debugger_case_globals.py new file mode 100644 index 000000000..17bf0b182 --- /dev/null +++ b/src/debugpy/_vendored/pydevd/tests_python/resources/_debugger_case_globals.py @@ -0,0 +1,12 @@ +in_global_scope = 'in_global_scope_value' + + +class SomeClass(object): + + def method(self): + print('breakpoint here') + + +if __name__ == '__main__': + SomeClass().method() + print('TEST SUCEEDED') diff --git a/src/debugpy/_vendored/pydevd/tests_python/test_debugger.py b/src/debugpy/_vendored/pydevd/tests_python/test_debugger.py index d178c234e..72a502de7 100644 --- a/src/debugpy/_vendored/pydevd/tests_python/test_debugger.py +++ b/src/debugpy/_vendored/pydevd/tests_python/test_debugger.py @@ -707,7 +707,7 @@ def test_case_15(case_setup): writer.finished_ok = True -def test_case_16(case_setup): +def test_case_16_resolve_numpy_array(case_setup): # numpy.ndarray resolver try: import numpy diff --git a/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py b/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py index c8a47f887..56e4f059d 100644 --- a/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py +++ b/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py @@ -17,12 +17,13 @@ from _pydevd_bundle.pydevd_comm_constants import file_system_encoding from _pydevd_bundle.pydevd_constants import (int_types, IS_64BIT_PROCESS, PY_VERSION_STR, PY_IMPL_VERSION_STR, PY_IMPL_NAME, IS_PY36_OR_GREATER, IS_PY39_OR_GREATER, - IS_PY37_OR_GREATER) + IS_PY37_OR_GREATER, IS_PYPY) from tests_python import debugger_unittest from tests_python.debug_constants import TEST_CHERRYPY, IS_PY2, TEST_DJANGO, TEST_FLASK, IS_PY26, \ IS_PY27, IS_CPYTHON, TEST_GEVENT from tests_python.debugger_unittest import (IS_JYTHON, IS_APPVEYOR, overrides, get_free_port, wait_for_condition) +from _pydevd_bundle.pydevd_utils import DAPGrouper pytest_plugins = [ str('tests_python.debugger_fixtures'), @@ -108,7 +109,7 @@ def write_request(self, request): def write_make_initial_run(self): if not self._sent_launch_or_attach: - self.write_launch() + self._auto_write_launch() configuration_done_request = self.write_request(pydevd_schema.ConfigurationDoneRequest()) return self.wait_for_response(configuration_done_request) @@ -160,7 +161,7 @@ def write_set_breakpoints( Adds a breakpoint. ''' if send_launch_if_needed and not self._sent_launch_or_attach: - self.write_launch() + self._auto_write_launch() if isinstance(lines, int): lines = [lines] @@ -240,6 +241,12 @@ def _write_launch_or_attach(self, command, **arguments): request = {'type': 'request', 'command': command, 'arguments': arguments, 'seq':-1} self.wait_for_response(self.write_request(request)) + def _auto_write_launch(self): + self.write_launch(variablePresentation={ + "all": "hide", + "protected": "inline", + }) + def write_launch(self, **arguments): return self._write_launch_or_attach('launch', **arguments) @@ -357,8 +364,8 @@ def get_name_to_scope(self, frame_id): scopes = scopes_response.body.scopes name_to_scopes = dict((scope['name'], pydevd_schema.Scope(**scope)) for scope in scopes) - assert len(scopes) == 1 - assert sorted(name_to_scopes.keys()) == ['Locals'] + assert len(scopes) == 2 + assert sorted(name_to_scopes.keys()) == ['Globals', 'Locals'] assert not name_to_scopes['Locals'].expensive return name_to_scopes @@ -372,11 +379,21 @@ def get_locals_name_to_var(self, frame_id): return self.get_name_to_var(name_to_scope['Locals'].variablesReference) + def get_globals_name_to_var(self, frame_id): + name_to_scope = self.get_name_to_scope(frame_id) + + return self.get_name_to_var(name_to_scope['Globals'].variablesReference) + def get_local_var(self, frame_id, var_name): ret = self.get_locals_name_to_var(frame_id)[var_name] assert ret.name == var_name return ret + def get_global_var(self, frame_id, var_name): + ret = self.get_globals_name_to_var(frame_id)[var_name] + assert ret.name == var_name + return ret + def get_var(self, variables_reference, var_name=None, index=None): if var_name is not None: return self.get_name_to_var(variables_reference)[var_name] @@ -1205,7 +1222,7 @@ def test_dict_ordered(case_setup): # : :type variables_response: VariablesResponse variables_response = json_facade.get_variables_response(ref) - assert [(d['name'], d['value']) for d in variables_response.body.variables if not d['name'].startswith('_OrderedDict')] == [ + assert [(d['name'], d['value']) for d in variables_response.body.variables if (not d['name'].startswith('_OrderedDict')) and (d['name'] not in DAPGrouper.SCOPES_SORTED)] == [ ('4', "'first'"), ('3', "'second'"), ('2', "'last'"), ('__len__', '3')] json_facade.write_continue() @@ -1255,7 +1272,8 @@ def test_stack_and_variables_dict(case_setup): ] variables_response = json_facade.get_variables_response(dict_variable_reference) - assert variables_response.body.variables == [ + check = [x for x in variables_response.body.variables if x['name'] not in DAPGrouper.SCOPES_SORTED] + assert check == [ {'name': "'a'", 'value': '30', 'type': 'int', 'evaluateName': "variable_for_test_3['a']", 'variablesReference': 0 }, {'name': "'b'", 'value': '20', 'type': 'int', 'evaluateName': "variable_for_test_3['b']", 'variablesReference': 0}, {'name': '__len__', 'value': '2', 'type': 'int', 'evaluateName': 'len(variable_for_test_3)', 'variablesReference': 0, 'presentationHint': {'attributes': ['readOnly']}} @@ -1420,7 +1438,8 @@ 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_add_breakpoint(writer.get_line_index_with_content('Break here')) + json_facade.write_launch() + json_facade.write_set_breakpoints(writer.get_line_index_with_content('Break here')) json_facade.write_make_initial_run() json_hit = json_facade.wait_for_thread_stopped() @@ -1438,7 +1457,13 @@ def test_stack_and_variables_set_and_list(case_setup): ] variables_response = json_facade.get_variables_response(variables_references[0]) - assert variables_response.body.variables == [{ + cleaned_vars = _clear_groups(variables_response.body.variables) + if IS_PYPY: + # Functions are not found in PyPy. + assert cleaned_vars.groups_found == set([DAPGrouper.SCOPE_SPECIAL_VARS]) + else: + assert cleaned_vars.groups_found == set([DAPGrouper.SCOPE_SPECIAL_VARS, DAPGrouper.SCOPE_FUNCTION_VARS]) + assert cleaned_vars.variables == [{ u'name': u'0', u'type': u'str', u'value': u"'a'", @@ -1467,12 +1492,29 @@ def test_stack_and_variables_set_and_list(case_setup): writer.finished_ok = True +_CleanedVars = namedtuple('_CleanedVars', 'variables, groups_found') + + +def _clear_groups(variables): + groups_found = set() + new_variables = [] + for v in variables: + if v['name'] in DAPGrouper.SCOPES_SORTED: + groups_found.add(v['name']) + continue + + else: + new_variables.append(v) + + return _CleanedVars(new_variables, groups_found) + + @pytest.mark.skipif(IS_JYTHON, reason='Putting unicode on frame vars does not work on Jython.') def test_evaluate_unicode(case_setup): with case_setup.test_file('_debugger_case_local_variables.py') as writer: json_facade = JsonFacade(writer) - writer.write_add_breakpoint(writer.get_line_index_with_content('Break 2 here')) + json_facade.write_set_breakpoints(writer.get_line_index_with_content('Break 2 here')) json_facade.write_make_initial_run() json_hit = json_facade.wait_for_thread_stopped() @@ -3062,8 +3104,9 @@ def get_environ(writer): assert source_reference == 0 # When it's translated the source reference must be == 0 stack_frame_not_path_translated = stack_trace_response_body.stackFrames[1] - assert stack_frame_not_path_translated['name'].startswith( - 'tests_python.resource_path_translation.other.call_me_back1 :') + if not stack_frame_not_path_translated['name'].startswith( + 'tests_python.resource_path_translation.other.call_me_back1 :'): + raise AssertionError('Error. Found: >>%s<<.' % (stack_frame_not_path_translated['name'],)) assert stack_frame_not_path_translated['source']['path'].endswith('other.py') source_reference = stack_frame_not_path_translated['source']['sourceReference'] @@ -3241,10 +3284,16 @@ def test_case_django_no_attribute_exception_breakpoint(case_setup_django, jmc): if jmc: writer.write_set_project_roots([debugger_unittest._get_debugger_test_file('my_code')]) - json_facade.write_launch(debugOptions=['Django']) + json_facade.write_launch(debugOptions=['Django'], variablePresentation={ + "all": "hide", + "protected": "inline", + }) json_facade.write_set_exception_breakpoints(['raised']) else: - json_facade.write_launch(debugOptions=['DebugStdLib', 'Django']) + json_facade.write_launch(debugOptions=['DebugStdLib', 'Django'], variablePresentation={ + "all": "hide", + "protected": "inline", + }) # Don't set to all 'raised' because we'd stop on standard library exceptions here # (which is not something we want). json_facade.write_set_exception_breakpoints(exception_options=[ @@ -4074,6 +4123,63 @@ def test_send_json_message(case_setup): writer.finished_ok = True +def test_global_scope(case_setup): + with case_setup.test_file('_debugger_case_globals.py') as writer: + json_facade = JsonFacade(writer) + json_facade.write_set_breakpoints(writer.get_line_index_with_content('breakpoint here')) + + json_facade.write_make_initial_run() + json_hit = json_facade.wait_for_thread_stopped() + + local_var = json_facade.get_global_var(json_hit.frame_id, 'in_global_scope') + assert local_var.value == "'in_global_scope_value'" + json_facade.write_continue() + + writer.finished_ok = True + + +def _check_inline_var_presentation(json_facade, json_hit, variables_response): + var_names = [v['name'] for v in variables_response.body.variables] + assert var_names[:3] == ['SomeClass', 'in_global_scope', '__builtins__'] + + +def _check_hide_var_presentation(json_facade, json_hit, variables_response): + var_names = [v['name'] for v in variables_response.body.variables] + assert var_names == ['in_global_scope'] + + +def _check_class_group_special_inline_presentation(json_facade, json_hit, variables_response): + var_names = [v['name'] for v in variables_response.body.variables] + assert var_names[:3] == ['class variables', 'in_global_scope', '__builtins__'] + + variables_response = json_facade.get_variables_response(variables_response.body.variables[0]['variablesReference']) + var_names = [v['name'] for v in variables_response.body.variables] + assert var_names == ['SomeClass'] + + +@pytest.mark.parametrize('var_presentation, check_func', [ + ({"all": "inline"}, _check_inline_var_presentation), + ({"all": "hide"}, _check_hide_var_presentation), + ({"class": "group", "special": "inline"}, _check_class_group_special_inline_presentation), +]) +def test_variable_presentation(case_setup, var_presentation, check_func): + with case_setup.test_file('_debugger_case_globals.py') as writer: + json_facade = JsonFacade(writer) + json_facade.write_launch(variablePresentation=var_presentation) + json_facade.write_set_breakpoints(writer.get_line_index_with_content('breakpoint here')) + + json_facade.write_make_initial_run() + json_hit = json_facade.wait_for_thread_stopped() + name_to_scope = json_facade.get_name_to_scope(json_hit.frame_id) + + variables_response = json_facade.get_variables_response(name_to_scope['Globals'].variablesReference) + check_func(json_facade, json_hit, variables_response) + + json_facade.write_continue() + + writer.finished_ok = True + + if __name__ == '__main__': pytest.main(['-k', 'test_case_skipping_filters', '-s']) diff --git a/src/debugpy/_vendored/pydevd/tests_python/test_resolvers.py b/src/debugpy/_vendored/pydevd/tests_python/test_resolvers.py index 90a74ec5f..35fb7fea4 100644 --- a/src/debugpy/_vendored/pydevd/tests_python/test_resolvers.py +++ b/src/debugpy/_vendored/pydevd/tests_python/test_resolvers.py @@ -12,7 +12,7 @@ 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) + contents_debug_adapter_protocol = clear_contents_debug_adapter_protocol(dict_resolver.get_contents_debug_adapter_protocol(dct)) len_entry = contents_debug_adapter_protocol.pop(-1) check_len_entry(len_entry, ('__len__', 2)) if IS_PY36_OR_GREATER: @@ -31,7 +31,8 @@ def test_dict_resolver_hex(): from _pydevd_bundle.pydevd_resolver import DictResolver dict_resolver = DictResolver() dct = {(1, 10, 100): (10000, 100000, 100000)} - contents_debug_adapter_protocol = dict_resolver.get_contents_debug_adapter_protocol(dct, fmt={'hex': True}) + contents_debug_adapter_protocol = clear_contents_debug_adapter_protocol( + dict_resolver.get_contents_debug_adapter_protocol(dct, fmt={'hex': True})) len_entry = contents_debug_adapter_protocol.pop(-1) check_len_entry(len_entry, ('__len__', 1)) assert contents_debug_adapter_protocol == [ @@ -49,10 +50,10 @@ def __init__(self): self.b = 20 obj = MyObject() - dictionary = default_resolver.get_dictionary(obj) + dictionary = clear_contents_dictionary(default_resolver.get_dictionary(obj)) assert dictionary == {'a': 10, 'b': 20} - contents_debug_adapter_protocol = default_resolver.get_contents_debug_adapter_protocol(obj) + contents_debug_adapter_protocol = clear_contents_debug_adapter_protocol(default_resolver.get_contents_debug_adapter_protocol(obj)) assert contents_debug_adapter_protocol == [('a', 10, '.a'), ('b', 20, '.b')] @@ -115,14 +116,15 @@ def __init__(self): self.__dict__[(1, 2)] = (3, 4) obj = MyObject() - dictionary = default_resolver.get_dictionary(obj) + dictionary = clear_contents_dictionary(default_resolver.get_dictionary(obj)) if IS_PY2: assert 'attribute name must be string' in dictionary.pop('(1, 2)') assert dictionary == {} else: assert dictionary == {'(1, 2)': (3, 4)} - contents_debug_adapter_protocol = default_resolver.get_contents_debug_adapter_protocol(obj) + contents_debug_adapter_protocol = clear_contents_debug_adapter_protocol( + default_resolver.get_contents_debug_adapter_protocol(obj)) if IS_PY2: assert len(contents_debug_adapter_protocol) == 1 entry = contents_debug_adapter_protocol[0] @@ -145,7 +147,7 @@ def __init__(self): obj = MyObject() - dictionary = django_form_resolver.get_dictionary(obj) + dictionary = clear_contents_dictionary(django_form_resolver.get_dictionary(obj)) if IS_PY2: assert 'attribute name must be string' in dictionary.pop('(1, 2)') assert dictionary == {'errors': None} @@ -153,7 +155,7 @@ def __init__(self): assert dictionary == {'(1, 2)': (3, 4), 'errors': None} obj._errors = 'bar' - dictionary = django_form_resolver.get_dictionary(obj) + dictionary = clear_contents_dictionary(django_form_resolver.get_dictionary(obj)) if IS_PY2: assert 'attribute name must be string' in dictionary.pop('(1, 2)') assert dictionary == {'errors': 'bar', '_errors': 'bar'} @@ -161,12 +163,42 @@ def __init__(self): assert dictionary == {'(1, 2)': (3, 4), 'errors': 'bar', '_errors': 'bar'} +def clear_contents_debug_adapter_protocol(contents_debug_adapter_protocol): + lst = [] + for x in contents_debug_adapter_protocol: + if x[0] == '__len__': + if x[2] == '.__len__': + # i.e.: remove a builtin __len__ method, but not the __len__ we add with the length. + continue + lst.append(x) + + if not x[0].startswith('__'): + + if '= (3, 6): - assert var_names == ["'spam'", "'eggs'", "'abcd'", "__len__"] + # Note that the special __len__ we manually create is not added to special variables. + expected = [ + "special variables", + "function variables", + "'spam'", + "'eggs'", + "'abcd'", + "__len__", + ] else: - assert var_names == ["'abcd'", "'eggs'", "'spam'", "__len__"] + expected = [ + "special variables", + "function variables", + "'abcd'", + "'eggs'", + "'spam'", + "__len__", + ] + + assert var_names == expected # Numeric dict keys must be sorted as numbers. if not "https://github.com/microsoft/ptvsd/issues/213": @@ -178,7 +207,10 @@ def code_to_debug(): "variables", {"variablesReference": c_test["variablesReference"]} )["variables"] var_names = [v["name"] for v in c_test_vars] - assert var_names == ["1", "2", "10", "__len__"] + # Note that the special __len__ we manually create is not added to special variables. + expected = ["1", "2", "10", "__len__"] + + assert var_names == expected session.request_continue() @@ -301,6 +333,8 @@ def code_to_debug(): print((a, b, c, d)) # @bp with debug.Session() as session: + session.config["variablePresentation"] = {"all": "hide", "protected": "inline"} + with run(session, target(code_to_debug)): session.set_breakpoints(code_to_debug, all) @@ -480,7 +514,6 @@ def code_to_debug(): ), ] - d_vars = session.request( "variables", {"variablesReference": d["variablesReference"], "format": {"hex": True}},