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
This commit is contained in:
Fabio Zadrozny 2020-04-17 16:08:15 -03:00
parent ade162b1d3
commit d2014babd6
19 changed files with 603 additions and 148 deletions

View file

@ -490,8 +490,7 @@ class BaseInterpreterInterface:
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))

View file

@ -28,6 +28,7 @@ import ctypes
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 @@ else:
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 @@ class PyDevdAPI(object):
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).

View file

@ -96,7 +96,7 @@ from _pydevd_bundle import pydevd_vm_type
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()

View file

@ -18,6 +18,12 @@ try:
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.

View file

@ -25,7 +25,7 @@ from _pydevd_bundle.pydevd_comm_constants import (
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 @@ class PyDevJsonCommandProcessor(object):
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 @@ class PyDevJsonCommandProcessor(object):
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 @@ class PyDevJsonCommandProcessor(object):
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 @@ class PyDevJsonCommandProcessor(object):
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 @@ class PyDevJsonCommandProcessor(object):
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,

View file

@ -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 @@ import traceback
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 @@ try:
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 @@ class DefaultResolver:
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 @@ class DefaultResolver:
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 @@ class DefaultResolver:
# 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,)
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 not used___dict__:
attr = getattr(var, name)
else:
attr = var.__dict__[name]
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 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()
# 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
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 @@ class DictResolver:
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 @@ class TupleResolver: # to enumerate tuples and lists
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 @@ class SetResolver:
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 @@ djangoFormResolver = DjangoFormResolver()
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 ''

View file

@ -5,12 +5,13 @@ from _pydevd_bundle.pydevd_constants import get_frame, dict_items, RETURN_VALUES
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 @@ class _AbstractVariable(object):
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 @@ class _ObjectVariable(_AbstractVariable):
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 @@ class _ObjectVariable(_AbstractVariable):
# 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 @@ class _ObjectVariable(_AbstractVariable):
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 @@ class _ObjectVariable(_AbstractVariable):
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 @@ class _FrameVariable(_AbstractVariable):
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 @@ class _FramesTracker(object):
# 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 @@ class _FramesTracker(object):
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)

View file

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

View file

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

View file

@ -7,7 +7,8 @@ from _pydevd_bundle.pydevd_constants import dict_iter_items, dict_keys, IS_PY3K,
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 @@ _TYPE_RESOLVE_HANDLER = TypeResolveHandler()
"""
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'))

View file

@ -481,6 +481,8 @@ class PyDB(object):
# 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

View file

@ -511,6 +511,15 @@ class DebuggerRunner(object):
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:

View file

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

View file

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

View file

@ -17,12 +17,13 @@ from _pydevd_bundle._debug_adapter.pydevd_schema import (ThreadEvent, ModuleEven
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 @@ class JsonFacade(object):
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 @@ class JsonFacade(object):
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 @@ class JsonFacade(object):
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 @@ class JsonFacade(object):
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 @@ class JsonFacade(object):
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 test_path_translation_and_source_reference(case_setup):
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'])

View file

@ -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 test_object_resolver_simple():
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 test_object_resolver__dict__non_strings():
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 test_django_forms_resolver():
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 test_django_forms_resolver():
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 test_django_forms_resolver():
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 '<built-in method' in str(x[1]) or '<method-wrapper' in str(x[1]) or '<bound method' in str(x[1]):
continue
lst.append(x)
return lst
def clear_contents_dictionary(dictionary):
dictionary = dictionary.copy()
for key in list(dictionary):
if key == '__len__':
continue
if key.startswith('__') or key in ('count', 'index'):
del dictionary[key]
return dictionary
def test_tuple_resolver():
from _pydevd_bundle.pydevd_resolver import TupleResolver
tuple_resolver = TupleResolver()
fmt = {'hex': True}
lst = tuple(range(11))
contents_debug_adapter_protocol = tuple_resolver.get_contents_debug_adapter_protocol(lst)
contents_debug_adapter_protocol = clear_contents_debug_adapter_protocol(
tuple_resolver.get_contents_debug_adapter_protocol(lst))
len_entry = contents_debug_adapter_protocol.pop(-1)
assert contents_debug_adapter_protocol == [
('00', 0, '[0]'),
@ -183,7 +215,7 @@ def test_tuple_resolver():
]
check_len_entry(len_entry, ('__len__', 11))
assert tuple_resolver.get_dictionary(lst) == {
assert clear_contents_dictionary(tuple_resolver.get_dictionary(lst)) == {
'00': 0,
'01': 1,
'02': 2,
@ -199,7 +231,8 @@ def test_tuple_resolver():
}
lst = tuple(range(17))
contents_debug_adapter_protocol = tuple_resolver.get_contents_debug_adapter_protocol(lst, fmt=fmt)
contents_debug_adapter_protocol = clear_contents_debug_adapter_protocol(
tuple_resolver.get_contents_debug_adapter_protocol(lst, fmt=fmt))
len_entry = contents_debug_adapter_protocol.pop(-1)
assert contents_debug_adapter_protocol == [
('0x00', 0, '[0]'),
@ -222,7 +255,7 @@ def test_tuple_resolver():
]
check_len_entry(len_entry, ('__len__', 17))
assert tuple_resolver.get_dictionary(lst, fmt=fmt) == {
assert clear_contents_dictionary(tuple_resolver.get_dictionary(lst, fmt=fmt)) == {
'0x00': 0,
'0x01': 1,
'0x02': 2,
@ -244,7 +277,7 @@ def test_tuple_resolver():
}
lst = tuple(range(10))
contents_debug_adapter_protocol = tuple_resolver.get_contents_debug_adapter_protocol(lst)
contents_debug_adapter_protocol = clear_contents_debug_adapter_protocol(tuple_resolver.get_contents_debug_adapter_protocol(lst))
len_entry = contents_debug_adapter_protocol.pop(-1)
assert contents_debug_adapter_protocol == [
('0', 0, '[0]'),
@ -260,7 +293,7 @@ def test_tuple_resolver():
]
check_len_entry(len_entry, ('__len__', 10))
assert tuple_resolver.get_dictionary(lst) == {
assert clear_contents_dictionary(tuple_resolver.get_dictionary(lst)) == {
'0': 0,
'1': 1,
'2': 2,
@ -274,7 +307,7 @@ def test_tuple_resolver():
'__len__': 10
}
contents_debug_adapter_protocol = tuple_resolver.get_contents_debug_adapter_protocol(lst, fmt=fmt)
contents_debug_adapter_protocol = clear_contents_debug_adapter_protocol(tuple_resolver.get_contents_debug_adapter_protocol(lst, fmt=fmt))
len_entry = contents_debug_adapter_protocol.pop(-1)
assert contents_debug_adapter_protocol == [
('0x0', 0, '[0]'),
@ -290,7 +323,7 @@ def test_tuple_resolver():
]
check_len_entry(len_entry, ('__len__', 10))
assert tuple_resolver.get_dictionary(lst, fmt=fmt) == {
assert clear_contents_dictionary(tuple_resolver.get_dictionary(lst, fmt=fmt)) == {
'0x0': 0,
'0x1': 1,
'0x2': 2,
@ -314,7 +347,7 @@ def test_tuple_resolver_mixed():
my_tuple = CustomTuple([1, 2])
my_tuple.some_value = 10
contents_debug_adapter_protocol = tuple_resolver.get_contents_debug_adapter_protocol(my_tuple)
contents_debug_adapter_protocol = clear_contents_debug_adapter_protocol(tuple_resolver.get_contents_debug_adapter_protocol(my_tuple))
len_entry = contents_debug_adapter_protocol.pop(-1)
check_len_entry(len_entry, ('__len__', 2))
assert contents_debug_adapter_protocol == [

View file

@ -15,10 +15,18 @@ def check_vars_dict_expected(as_dict, expected):
assert as_dict == expected
class _DummyPyDB(object):
def __init__(self):
from _pydevd_bundle.pydevd_api import PyDevdAPI
self.variable_presentation = PyDevdAPI.VariablePresentation()
def test_suspended_frames_manager():
from _pydevd_bundle.pydevd_suspended_frames import SuspendedFramesManager
from _pydevd_bundle.pydevd_utils import DAPGrouper
suspended_frames_manager = SuspendedFramesManager()
py_db = None
py_db = _DummyPyDB()
with suspended_frames_manager.track_frames(py_db) as tracker:
# : :type tracker: _FramesTracker
thread_id = 'thread1'
@ -57,7 +65,7 @@ def test_suspended_frames_manager():
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))
as_dict = (dict([x.get_name(), x.get_var_data()] for x in children_vars if x.get_name() not in DAPGrouper.SCOPES_SORTED))
assert as_dict == {
'0': {'name': '0', 'value': '1', 'type': 'int', 'evaluateName': 'var2[0]', 'variablesReference': 0 },
'__len__': {'name': '__len__', 'value': '1', 'type': 'int', 'evaluateName': 'len(var2)', 'variablesReference': 0, 'presentationHint': {'attributes': ['readOnly']}, },
@ -65,7 +73,7 @@ def test_suspended_frames_manager():
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))
as_dict = (dict([x.get_name(), x.get_var_data()] for x in children_vars if x.get_name() not in DAPGrouper.SCOPES_SORTED))
assert isinstance(as_dict['33'].pop('variablesReference'), int_types) # The variable reference is always a new int.
check_vars_dict_expected(as_dict, {
@ -99,7 +107,7 @@ def get_tuple_large_frame():
def test_get_child_variables():
from _pydevd_bundle.pydevd_suspended_frames import SuspendedFramesManager
suspended_frames_manager = SuspendedFramesManager()
py_db = None
py_db = _DummyPyDB()
for frame in (
get_dict_large_frame(),
get_set_large_frame(),

View file

@ -69,6 +69,7 @@ class DebugConfig(collections.MutableMapping):
"connect": (),
# Attach by PID
"processId": (),
"variablePresentation": {},
}
def __init__(self, *args, **kwargs):

View file

@ -60,6 +60,7 @@ def test_variables(pyfile, target, run):
print([a, b, c]) # @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)
@ -134,6 +135,10 @@ def test_variable_sort(pyfile, target, run):
print("done") # @bp
with debug.Session() as session:
session.config["variablePresentation"] = {
"special": "group",
"protected": "inline",
}
with run(session, target(code_to_debug)):
session.set_breakpoints(code_to_debug, all)
@ -155,11 +160,18 @@ def test_variable_sort(pyfile, target, run):
"__a_test",
"__b_test",
"__c_test",
"__a_test__",
"__b_test__",
"__c_test__",
]
special_vars_entry = [v for v in vars if v["name"] == "special variables"][0]
special_vars_variables = session.request(
"variables",
{"variablesReference": special_vars_entry["variablesReference"]},
)["variables"]
special_vars_variables = [
v["name"] for v in special_vars_variables if "_test" in v["name"]
]
assert special_vars_variables == ["__a_test__", "__b_test__", "__c_test__"]
# String dict keys must be sorted as strings.
b_test, = (v for v in vars if v["name"] == "b_test")
b_test_vars = session.request(
@ -167,9 +179,26 @@ def test_variable_sort(pyfile, target, run):
)["variables"]
var_names = [v["name"] for v in b_test_vars]
if sys.version_info[:2] >= (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 test_variable_sort(pyfile, target, run):
"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 test_hex_numbers(pyfile, target, run):
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 test_hex_numbers(pyfile, target, run):
),
]
d_vars = session.request(
"variables",
{"variablesReference": d["variablesReference"], "format": {"hex": True}},