From 028a0c2372e0fe73c2917220141ca55850d2ce9b Mon Sep 17 00:00:00 2001 From: Elizaveta Shashkova Date: Tue, 10 Sep 2019 11:45:19 +0300 Subject: [PATCH] IDEA-CR-53299: PY-37497 Variables View cleanup GitOrigin-RevId: 2d7ce065e5b42f6a43320ffd735f978851529805 --- .../python/console/protocol/ArrayData.java | 2 +- .../python/console/protocol/ArrayHeaders.java | 2 +- .../python/console/protocol/ColHeader.java | 2 +- .../console/protocol/CompletionOption.java | 2 +- .../python/console/protocol/DebugValue.java | 144 +++++++++++++++--- .../ExceedingArrayDimensionsException.java | 2 +- .../console/protocol/GetArrayResponse.java | 2 +- .../protocol/KeyboardInterruptException.java | 2 +- .../protocol/PythonConsoleBackendService.java | 2 +- .../PythonConsoleFrontendService.java | 2 +- .../python/console/protocol/RowHeader.java | 2 +- .../UnsupportedArrayTypeException.java | 2 +- .../pydev/_pydevd_bundle/pydevd_thrift.py | 47 +++--- .../pydev/_pydevd_bundle/pydevd_utils.py | 66 +++++++- .../pydev/_pydevd_bundle/pydevd_xml.py | 79 ++++------ .../pydev/pydev_console/console.thrift | 11 +- .../types/pydevd_plugin_numpy_types.py | 4 +- python/helpers/third_party/thriftpy/README.md | 8 +- .../python/debugger/PyDebugValue.java | 77 +++++++++- .../python/debugger/PyDebugValueGroups.kt | 72 +++++++++ .../python/debugger/PyFrameAccessor.java | 4 + .../debugger/pydev/GetFrameCommand.java | 2 +- .../console/PydevConsoleCommunication.java | 8 +- .../console/PydevConsoleCommunicationUtil.kt | 4 +- .../python/debugger/PyDebugProcess.java | 5 + .../python/debugger/PyStackFrame.java | 88 +++-------- python/testData/debug/test_shapes.py | 31 ++++ .../env/python/PythonConsoleTest.java | 38 ++++- .../env/python/console/PyConsoleTask.java | 9 ++ .../env/python/debug/PythonDebuggerTest.java | 42 ++++- 30 files changed, 580 insertions(+), 181 deletions(-) create mode 100644 python/pydevSrc/com/jetbrains/python/debugger/PyDebugValueGroups.kt create mode 100644 python/testData/debug/test_shapes.py diff --git a/python/gen/com/jetbrains/python/console/protocol/ArrayData.java b/python/gen/com/jetbrains/python/console/protocol/ArrayData.java index 3020472ea088..46645f21e286 100644 --- a/python/gen/com/jetbrains/python/console/protocol/ArrayData.java +++ b/python/gen/com/jetbrains/python/console/protocol/ArrayData.java @@ -7,7 +7,7 @@ package com.jetbrains.python.console.protocol; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2018-08-07") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2019-10-01") public class ArrayData implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("ArrayData"); diff --git a/python/gen/com/jetbrains/python/console/protocol/ArrayHeaders.java b/python/gen/com/jetbrains/python/console/protocol/ArrayHeaders.java index e94b91bb6eb6..2f698dc000c3 100644 --- a/python/gen/com/jetbrains/python/console/protocol/ArrayHeaders.java +++ b/python/gen/com/jetbrains/python/console/protocol/ArrayHeaders.java @@ -7,7 +7,7 @@ package com.jetbrains.python.console.protocol; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2018-08-07") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2019-10-01") public class ArrayHeaders implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("ArrayHeaders"); diff --git a/python/gen/com/jetbrains/python/console/protocol/ColHeader.java b/python/gen/com/jetbrains/python/console/protocol/ColHeader.java index cd868feb42f7..f019f68ff4bc 100644 --- a/python/gen/com/jetbrains/python/console/protocol/ColHeader.java +++ b/python/gen/com/jetbrains/python/console/protocol/ColHeader.java @@ -7,7 +7,7 @@ package com.jetbrains.python.console.protocol; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2018-08-07") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2019-10-01") public class ColHeader implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("ColHeader"); diff --git a/python/gen/com/jetbrains/python/console/protocol/CompletionOption.java b/python/gen/com/jetbrains/python/console/protocol/CompletionOption.java index 21b159b15ac4..e8b07f9f4158 100644 --- a/python/gen/com/jetbrains/python/console/protocol/CompletionOption.java +++ b/python/gen/com/jetbrains/python/console/protocol/CompletionOption.java @@ -7,7 +7,7 @@ package com.jetbrains.python.console.protocol; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2018-08-07") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2019-10-01") public class CompletionOption implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("CompletionOption"); diff --git a/python/gen/com/jetbrains/python/console/protocol/DebugValue.java b/python/gen/com/jetbrains/python/console/protocol/DebugValue.java index f6b4f2cba110..be55f755087b 100644 --- a/python/gen/com/jetbrains/python/console/protocol/DebugValue.java +++ b/python/gen/com/jetbrains/python/console/protocol/DebugValue.java @@ -10,7 +10,7 @@ package com.jetbrains.python.console.protocol; /** * Corresponds to `PyDebugValue`. */ -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2018-08-07") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2019-10-01") public class DebugValue implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("DebugValue"); @@ -19,9 +19,10 @@ public class DebugValue implements org.apache.thrift.TBase byName = new java.util.HashMap(); @@ -69,11 +72,13 @@ public class DebugValue implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("ExceedingArrayDimensionsException"); diff --git a/python/gen/com/jetbrains/python/console/protocol/GetArrayResponse.java b/python/gen/com/jetbrains/python/console/protocol/GetArrayResponse.java index 5b029f479367..35409a6a5a0a 100644 --- a/python/gen/com/jetbrains/python/console/protocol/GetArrayResponse.java +++ b/python/gen/com/jetbrains/python/console/protocol/GetArrayResponse.java @@ -11,7 +11,7 @@ package com.jetbrains.python.console.protocol; * Corresponds to `ArrayChunk`. * */ -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2018-08-07") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2019-10-01") public class GetArrayResponse implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("GetArrayResponse"); diff --git a/python/gen/com/jetbrains/python/console/protocol/KeyboardInterruptException.java b/python/gen/com/jetbrains/python/console/protocol/KeyboardInterruptException.java index 06eaa1de608b..3de0381801d0 100644 --- a/python/gen/com/jetbrains/python/console/protocol/KeyboardInterruptException.java +++ b/python/gen/com/jetbrains/python/console/protocol/KeyboardInterruptException.java @@ -7,7 +7,7 @@ package com.jetbrains.python.console.protocol; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2018-10-25") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2019-10-01") public class KeyboardInterruptException extends org.apache.thrift.TException implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("KeyboardInterruptException"); diff --git a/python/gen/com/jetbrains/python/console/protocol/PythonConsoleBackendService.java b/python/gen/com/jetbrains/python/console/protocol/PythonConsoleBackendService.java index 4d97309d9561..8b89c98f2784 100644 --- a/python/gen/com/jetbrains/python/console/protocol/PythonConsoleBackendService.java +++ b/python/gen/com/jetbrains/python/console/protocol/PythonConsoleBackendService.java @@ -7,7 +7,7 @@ package com.jetbrains.python.console.protocol; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2019-10-01") public class PythonConsoleBackendService { public interface Iface { diff --git a/python/gen/com/jetbrains/python/console/protocol/PythonConsoleFrontendService.java b/python/gen/com/jetbrains/python/console/protocol/PythonConsoleFrontendService.java index b11852c12a5e..98fc9f9b7d4d 100644 --- a/python/gen/com/jetbrains/python/console/protocol/PythonConsoleFrontendService.java +++ b/python/gen/com/jetbrains/python/console/protocol/PythonConsoleFrontendService.java @@ -7,7 +7,7 @@ package com.jetbrains.python.console.protocol; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2018-10-25") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2019-10-01") public class PythonConsoleFrontendService { public interface Iface { diff --git a/python/gen/com/jetbrains/python/console/protocol/RowHeader.java b/python/gen/com/jetbrains/python/console/protocol/RowHeader.java index 79544953df24..9e906c0b2335 100644 --- a/python/gen/com/jetbrains/python/console/protocol/RowHeader.java +++ b/python/gen/com/jetbrains/python/console/protocol/RowHeader.java @@ -7,7 +7,7 @@ package com.jetbrains.python.console.protocol; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2018-08-07") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2019-10-01") public class RowHeader implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("RowHeader"); diff --git a/python/gen/com/jetbrains/python/console/protocol/UnsupportedArrayTypeException.java b/python/gen/com/jetbrains/python/console/protocol/UnsupportedArrayTypeException.java index 3a1d5b28fc71..3e1c340bbdf7 100644 --- a/python/gen/com/jetbrains/python/console/protocol/UnsupportedArrayTypeException.java +++ b/python/gen/com/jetbrains/python/console/protocol/UnsupportedArrayTypeException.java @@ -7,7 +7,7 @@ package com.jetbrains.python.console.protocol; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2018-08-07") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2019-10-01") public class UnsupportedArrayTypeException extends org.apache.thrift.TException implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("UnsupportedArrayTypeException"); diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_thrift.py b/python/helpers/pydev/_pydevd_bundle/pydevd_thrift.py index c1769a80ffce..6a033bfc05f6 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_thrift.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_thrift.py @@ -14,10 +14,10 @@ from _pydevd_bundle.pydevd_constants import dict_iter_items, dict_keys, IS_PY3K, BUILTINS_MODULE_NAME, MAXIMUM_VARIABLE_REPRESENTATION_SIZE, RETURN_VALUES_DICT, LOAD_VALUES_POLICY, ValuesPolicy, DEFAULT_VALUES_DICT, \ NUMPY_NUMERIC_TYPES from _pydevd_bundle.pydevd_extension_api import TypeResolveProvider, StrPresentationProvider +from _pydevd_bundle.pydevd_utils import take_first_n_coll_elements, is_numeric_container, is_pandas_container, pandas_to_str, is_string from _pydevd_bundle.pydevd_vars import get_label, array_default_format, is_able_to_format_number, MAXIMUM_ARRAY_SIZE from pydev_console.protocol import DebugValue, GetArrayResponse, ArrayData, ArrayHeaders, ColHeader, RowHeader, \ UnsupportedArrayTypeException, ExceedingArrayDimensionsException -from _pydevd_bundle.pydevd_utils import take_first_n_coll_elements try: import types @@ -312,11 +312,11 @@ def var_to_struct(val, name, format='%s', do_trim=True, evaluate_full_value=True elif v.__class__ in (list, tuple): if len(v) > pydevd_resolver.MAX_ITEMS_TO_HANDLE: - value = '%s: %s' % (str(v.__class__), take_first_n_coll_elements( - v, pydevd_resolver.MAX_ITEMS_TO_HANDLE)) + value = '%s' % take_first_n_coll_elements( + v, pydevd_resolver.MAX_ITEMS_TO_HANDLE) value = value.rstrip(')]}') + '...' else: - value = '%s: %s' % (str(v.__class__, v)) + value = '%s' % str(v) else: value = format % v else: @@ -333,25 +333,34 @@ def var_to_struct(val, name, format='%s', do_trim=True, evaluate_full_value=True if type_qualifier: debug_value.qualifier = type_qualifier - if value: - # cannot be too big... communication may not handle it. - if len(value) > MAXIMUM_VARIABLE_REPRESENTATION_SIZE and do_trim: - value = value[0:MAXIMUM_VARIABLE_REPRESENTATION_SIZE] - value += '...' + # cannot be too big... communication may not handle it. + if len(value) > MAXIMUM_VARIABLE_REPRESENTATION_SIZE and do_trim: + value = value[0:MAXIMUM_VARIABLE_REPRESENTATION_SIZE] + value += '...' - # fix to work with unicode values + # fix to work with unicode values + try: + if not IS_PY3K: + if value.__class__ == unicode: # @UndefinedVariable + value = value.encode('utf-8') + else: + if value.__class__ == bytes: + value = value.encode('utf-8') + except TypeError: # in java, unicode is a function + pass + + if is_pandas_container(type_qualifier, typeName, v): + value = pandas_to_str(v, typeName, pydevd_resolver.MAX_ITEMS_TO_HANDLE) + debug_value.value = value + + if is_numeric_container(type_qualifier, typeName, v): + debug_value.shape = str(v.shape) + elif hasattr(v, '__len__') and not is_string(v): try: - if not IS_PY3K: - if value.__class__ == unicode: # @UndefinedVariable - value = value.encode('utf-8') - else: - if value.__class__ == bytes: - value = value.encode('utf-8') - except TypeError: # in java, unicode is a function + debug_value.shape = str(len(v)) + except: pass - debug_value.value = value - if is_exception_on_eval: debug_value.isErrorOnEval = True else: diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_utils.py b/python/helpers/pydev/_pydevd_bundle/pydevd_utils.py index 9191ea6417ab..bd6566b6154c 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_utils.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_utils.py @@ -1,7 +1,9 @@ from __future__ import nested_scopes -import traceback + import os +import traceback import warnings + import pydevd_file_utils try: @@ -440,3 +442,65 @@ def get_var_and_offset(var): if isinstance(var, VariableWithOffset): return var.data, var.offset return var, 0 + + +def is_pandas_container(type_qualifier, var_type, var): + return var_type in ("DataFrame", "Series") and type_qualifier.startswith("pandas") and hasattr(var, "shape") + + +def is_numpy_container(type_qualifier, var_type, var): + return var_type == "ndarray" and type_qualifier == "numpy" and hasattr(var, "shape") + + +def is_numeric_container(type_qualifier, var_type, var): + return is_numpy_container(type_qualifier, var_type, var) or is_pandas_container(type_qualifier, var_type, var) + + +def _series_to_str(s, max_items, show_index=True): + res = [] + i = 0 + for item in s.iteritems(): + # item: (index, value) + if show_index: + res.append(str(item)) + else: + res.append(str(item[1])) + i += 1 + if i > max_items: + break + return ' '.join(res) + + +def _df_to_str(df, max_items, rows_sep=', '): + res = [] + for c in df.columns: + res.append(str(c)) + rows = [] + i = 0 + for item in df.iterrows(): + # item: (index, Series) + ind = "[%s: " % item[0] + values = _series_to_str(item[1], max_items, show_index=False) + rows.append(ind + values + "]") + i += item[1].size + if i > max_items: + break + res.append(rows_sep.join(rows)) + return ' '.join(res) + + +def pandas_to_str(df, type_name, max_items): + try: + if type_name == "Series": + return _series_to_str(df, max_items) + elif type_name == "DataFrame": + return _df_to_str(df, max_items) + else: + return str(df) + except Exception as e: + pydev_log.warn("Failed to format pandas variable: " + str(e)) + return str(df) + + +def format_numpy_array(num_array, max_items): + return str(num_array[:max_items]).replace('\n', ',').strip() diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_xml.py b/python/helpers/pydev/_pydevd_bundle/pydevd_xml.py index 2aa3e5fc4d16..3e1292bc8446 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_xml.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_xml.py @@ -4,16 +4,17 @@ The methods from this file are used for the debugger interaction. Please note that Python console now uses Thrift structures with the similar methods contained in `pydevd_thrift.py` file. """ -from _pydev_bundle import pydev_log +import sys import traceback + +from _pydev_bundle import pydev_log +from _pydev_bundle.pydev_imports import quote from _pydevd_bundle import pydevd_extension_utils from _pydevd_bundle import pydevd_resolver -import sys from _pydevd_bundle.pydevd_constants import dict_iter_items, dict_keys, IS_PY3K, \ BUILTINS_MODULE_NAME, MAXIMUM_VARIABLE_REPRESENTATION_SIZE, RETURN_VALUES_DICT, LOAD_VALUES_POLICY, ValuesPolicy, DEFAULT_VALUES_DICT -from _pydev_bundle.pydev_imports import quote from _pydevd_bundle.pydevd_extension_api import TypeResolveProvider, StrPresentationProvider -from _pydevd_bundle.pydevd_utils import take_first_n_coll_elements +from _pydevd_bundle.pydevd_utils import take_first_n_coll_elements, is_numeric_container, is_pandas_container, pandas_to_str, is_string try: import types @@ -22,6 +23,7 @@ try: except: frame_type = None + def make_valid_xml_value(s): # Same thing as xml.sax.saxutils.escape but also escaping double quotes. return s.replace("&", "&").replace('<', '<').replace('>', '>').replace('"', '"') @@ -244,10 +246,6 @@ def is_numpy(x): or 'float' in type_name or 'complex' in type_name -def is_numeric_container(var_type, var): - return var_type in ("ndarray", "DataFrame", "Series") and hasattr(var, "shape") - - def should_evaluate_full_value(val): return LOAD_VALUES_POLICY == ValuesPolicy.SYNC or ((is_builtin(type(val)) or is_numpy(type(val))) and not isinstance(val, (list, tuple, dict, set, frozenset))) @@ -319,26 +317,12 @@ def var_to_xml(val, name, doTrim=True, additional_in_xml='', evaluate_full_value elif v.__class__ in (list, tuple, set, frozenset, dict): if len(v) > pydevd_resolver.MAX_ITEMS_TO_HANDLE: - value = '%s: %s' % (str(v.__class__), take_first_n_coll_elements( - v, pydevd_resolver.MAX_ITEMS_TO_HANDLE)) + value = '%s' % take_first_n_coll_elements(v, pydevd_resolver.MAX_ITEMS_TO_HANDLE) value = value.rstrip(')]}') + '...' else: - value = '%s: %s' % (str(v.__class__), v) + value = '%s' % v else: - try: - cName = str(v.__class__) - if cName.find('.') != -1: - cName = cName.split('.')[-1] - - elif cName.find("'") != -1: # does not have '.' (could be something like ) - cName = cName[cName.index("'") + 1:] - - if cName.endswith("'>"): - cName = cName[:-2] - except: - cName = str(v.__class__) - - value = ('%s: ' + format) % (cName, v) + value = format % v else: value = str(v) except: @@ -359,31 +343,34 @@ def var_to_xml(val, name, doTrim=True, additional_in_xml='', evaluate_full_value else: xml_qualifier = '' - if value: - # cannot be too big... communication may not handle it. - if len(value) > MAXIMUM_VARIABLE_REPRESENTATION_SIZE and doTrim: - value = value[0:MAXIMUM_VARIABLE_REPRESENTATION_SIZE] - value += '...' + # cannot be too big... communication may not handle it. + if len(value) > MAXIMUM_VARIABLE_REPRESENTATION_SIZE and doTrim: + value = value[0:MAXIMUM_VARIABLE_REPRESENTATION_SIZE] + value += '...' - # fix to work with unicode values - try: - if not IS_PY3K: - if value.__class__ == unicode: # @UndefinedVariable - value = value.encode('utf-8') - else: - if value.__class__ == bytes: - value = value.encode('utf-8') - except TypeError: # in java, unicode is a function - pass + # fix to work with unicode values + try: + if not IS_PY3K: + if value.__class__ == unicode: # @UndefinedVariable + value = value.encode('utf-8') + else: + if value.__class__ == bytes: + value = value.encode('utf-8') + except TypeError: # in java, unicode is a function + pass - xml_value = ' value="%s"' % (make_valid_xml_value(quote(value, '/>_= '))) - else: - xml_value = '' + if is_pandas_container(type_qualifier, typeName, v): + value = pandas_to_str(v, typeName, pydevd_resolver.MAX_ITEMS_TO_HANDLE) + xml_value = ' value="%s"' % (make_valid_xml_value(quote(value, '/>_= '))) - if is_numeric_container(typeName, v): + xml_shape = '' + if is_numeric_container(type_qualifier, typeName, v): xml_shape = ' shape="%s"' % make_valid_xml_value(str(v.shape)) - else: - xml_shape = '' + elif hasattr(v, "__len__") and not is_string(v): + try: + xml_shape = ' shape="%s"' % make_valid_xml_value("%s" % str(len(v))) + except: + pass if is_exception_on_eval: xml_container = ' isErrorOnEval="True"' diff --git a/python/helpers/pydev/pydev_console/console.thrift b/python/helpers/pydev/pydev_console/console.thrift index 309ca626e84b..ebcdbc58a40d 100644 --- a/python/helpers/pydev/pydev_console/console.thrift +++ b/python/helpers/pydev/pydev_console/console.thrift @@ -1,5 +1,9 @@ namespace java com.jetbrains.python.console.protocol +/** +* Script for Java classes generation: /community/python/thrift/console-compile.sh +*/ + /** * `com.jetbrains.python.console.PydevConsoleCommunication` */ @@ -13,9 +17,10 @@ struct DebugValue { 3: string qualifier, 4: string value, 5: bool isContainer, - 6: bool isReturnedValue, - 7: bool isIPythonHidden, - 8: bool isErrorOnEval, + 6: string shape, + 7: bool isReturnedValue, + 8: bool isIPythonHidden, + 9: bool isErrorOnEval, } typedef list GetFrameResponse diff --git a/python/helpers/pydev/pydevd_plugins/extensions/types/pydevd_plugin_numpy_types.py b/python/helpers/pydev/pydevd_plugins/extensions/types/pydevd_plugin_numpy_types.py index 1b1c2cceddee..704f5149aee9 100644 --- a/python/helpers/pydev/pydevd_plugins/extensions/types/pydevd_plugin_numpy_types.py +++ b/python/helpers/pydev/pydevd_plugins/extensions/types/pydevd_plugin_numpy_types.py @@ -1,7 +1,7 @@ from _pydevd_bundle.pydevd_constants import IS_PYCHARM from _pydevd_bundle.pydevd_extension_api import TypeResolveProvider, StrPresentationProvider from _pydevd_bundle.pydevd_resolver import defaultResolver, MAX_ITEMS_TO_HANDLE, TOO_LARGE_ATTR, TOO_LARGE_MSG -from _pydevd_bundle.pydevd_utils import get_var_and_offset +from _pydevd_bundle.pydevd_utils import get_var_and_offset, format_numpy_array from .pydevd_helpers import find_mod_attr try: @@ -109,7 +109,7 @@ class NDArrayStrProvider(object): return nd_array is not None and issubclass(type_object, nd_array) def get_str(self, val): - return str(val[:MAX_ITEMS_TO_HANDLE]) + return format_numpy_array(val, MAX_ITEMS_TO_HANDLE) class NdArrayItemsContainerProvider(object): diff --git a/python/helpers/third_party/thriftpy/README.md b/python/helpers/third_party/thriftpy/README.md index be7d61d47b51..9e1562b35533 100644 --- a/python/helpers/third_party/thriftpy/README.md +++ b/python/helpers/third_party/thriftpy/README.md @@ -1 +1,7 @@ -#Thriftpy with dependencies \ No newline at end of file +#Thriftpy with dependencies + +Python Console protocol description: +`/community/python/helpers/pydev/pydev_console/console.thrift` + +Script for Java classes generation: +`/community/python/thrift/console-compile.sh` diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java b/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java index d4785551b1ae..56e1ec0c962c 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java @@ -19,6 +19,8 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static com.jetbrains.python.debugger.PyDebugValueGroupsKt.*; + // todo: null modifier for modify modules, class objects etc. public class PyDebugValue extends XNamedValue { private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.pydev.PyDebugValue"); @@ -236,17 +238,42 @@ public class PyDebugValue extends XNamedValue { } } + /** + * Evaluate full name to access variable at runtime + * Used for evaluation values of "Returned values". They are saved in a separate dictionary on Python side, so the variable's name + * should be transformed. + * + * @return full variable name at runtime + */ @NotNull public String getFullName() { return wrapWithPrefix(getName()); } + /** + * Remove util information from variable name to hide it from user + * + * @return variable name without util information + */ + @NotNull + public String getVisibleName() { + return removeId(myName); + } + + /** + * Removes object id from variable name. Object id is saved inside dict keys to find object at runtime + * + * @param name variable name with or without object id ('a' (11259136)) + * @return variable name without object id ('a') + */ @NotNull private static String removeId(@NotNull String name) { - if (name.indexOf('(') != -1) { - name = name.substring(0, name.indexOf('(')).trim(); + if (name.endsWith(")")) { + final int lastInd = name.lastIndexOf('('); + if (lastInd != -1) { + name = name.substring(0, lastInd).trim(); + } } - return name; } @@ -260,7 +287,7 @@ public class PyDebugValue extends XNamedValue { } private static boolean isLen(@NotNull String name) { - return "__len__".equals(name); + return DUNDER_LEN.equals(name); } @NotNull @@ -274,6 +301,34 @@ public class PyDebugValue extends XNamedValue { } } + private String getTypeString() { + if (myShape != null) { + return myType + ": " + myShape; + } + return myType; + } + + private void setElementPresentation(@NotNull XValueNode node, @NotNull String value) { + if (myParent != null && "set".equals(myParent.getType())) { + // hide object id and '=' when showing set elements + node.setPresentation(getValueIcon(), new XRegularValuePresentation(value, getTypeString()) { + @NotNull + @Override + public String getSeparator() { + return myName.equals(DUNDER_LEN) ? " = " : ""; + } + + @Override + public boolean isShowName() { + return myName.equals(DUNDER_LEN); + } + }, myContainer); + } + else { + node.setPresentation(getValueIcon(), getTypeString(), value, myContainer); + } + } + @Override public void computePresentation(@NotNull XValueNode node, @NotNull XValuePlace place) { String value = PyTypeHandler.format(this); @@ -281,7 +336,7 @@ public class PyDebugValue extends XNamedValue { if (value.length() >= MAX_VALUE) { value = value.substring(0, MAX_VALUE); } - node.setPresentation(getValueIcon(), myType, value, myContainer); + setElementPresentation(node, value); } public void updateNodeValueAfterLoading(@NotNull XValueNode node, @@ -297,7 +352,7 @@ public class PyDebugValue extends XNamedValue { }, myContainer); } else { - node.setPresentation(getValueIcon(), myType, value, myContainer); + setElementPresentation(node, value); } if (isNumericContainer()) return; // do not update FullValueEvaluator not to break Array Viewer @@ -406,10 +461,16 @@ public class PyDebugValue extends XNamedValue { if (isLargeCollection()) { values = processLargeCollection(values); + } + if (myFrameAccessor.isSimplifiedView()) { + extractChildrenToGroup(PROTECTED_ATTRS_NAME, AllIcons.Nodes.C_protected, node, values, (String name) -> name.startsWith("_"), + getPROTECTED_ATTRS_EXCLUDED()); + } + else { node.addChildren(values, true); + } + if (isLargeCollection()) { updateOffset(node, values); - } else { - node.addChildren(values, true); } getAsyncValues(myFrameAccessor, values); diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValueGroups.kt b/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValueGroups.kt new file mode 100644 index 000000000000..d88381b8da48 --- /dev/null +++ b/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValueGroups.kt @@ -0,0 +1,72 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.python.debugger + +import com.intellij.xdebugger.frame.XCompositeNode +import com.intellij.xdebugger.frame.XValue +import com.intellij.xdebugger.frame.XValueChildrenList +import com.intellij.xdebugger.frame.XValueGroup +import javax.swing.Icon + + +const val PROTECTED_ATTRS_NAME = "Protected Attributes" +const val DUNDER_LEN = "__len__" +val PROTECTED_ATTRS_EXCLUDED = setOf(DUNDER_LEN, "__exception__") + + +fun extractChildrenToGroup(groupName: String, + icon: Icon, + node: XCompositeNode, + children: XValueChildrenList, + predicate: (String) -> Boolean, + excludedNames: Set) { + val filterResult = filterChildren(children, predicate, excludedNames) + node.addChildren(filterResult.filteredChildren, filterResult.groupElements.isEmpty()) + addGroupValues(groupName, icon, node, filterResult.groupElements, null) +} + + +private class FilterResult { + val filteredChildren: XValueChildrenList = XValueChildrenList() + val groupElements: MutableMap = mutableMapOf() +} + + +private fun filterChildren(children: XValueChildrenList, predicate: (String) -> Boolean, excludedNames: Set): FilterResult { + val result = FilterResult() + for (i in 0 until children.size()) { + val value = children.getValue(i) + val name = children.getName(i) + if (value is PyDebugValue) { + if (predicate(value.name) && !excludedNames.contains(value.name)) { + result.groupElements[name] = value + } + else { + result.filteredChildren.add(name, value) + } + } + } + return result +} + +fun addGroupValues(groupName: String, + groupIcon: Icon, + node: XCompositeNode, + groupElements: Map, + nameSuffix: String?) { + if (groupElements.isEmpty()) return + val group = object : XValueGroup(groupName) { + override fun computeChildren(node: XCompositeNode) { + val list = XValueChildrenList() + for ((key, value) in groupElements) { + val name = if (nameSuffix == null) key else key + nameSuffix + list.add(name, value) + } + node.addChildren(list, true) + } + + override fun getIcon(): Icon { + return groupIcon + } + } + node.addChildren(XValueChildrenList.bottomGroup(group), true) +} \ No newline at end of file diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java b/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java index 860ac2765ca5..53865e99f93b 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java @@ -51,6 +51,10 @@ public interface PyFrameAccessor { default void setCurrentRootNode(@NotNull XCompositeNode node) {} + default boolean isSimplifiedView() { + return false; + } + @Nullable default XCompositeNode getCurrentRootNode() { return null; diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetFrameCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetFrameCommand.java index a520f42e24ca..cde8df61097d 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetFrameCommand.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetFrameCommand.java @@ -42,7 +42,7 @@ public class GetFrameCommand extends AbstractFrameCommand { for (PyDebugValue value : values) { if (!value.getName().startsWith(RemoteDebugger.TEMP_VAR_PREFIX)) { final PyDebugValue debugValue = extend(value); - myFrameVariables.add(debugValue.getName(), debugValue); + myFrameVariables.add(debugValue.getVisibleName(), debugValue); } } } diff --git a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java index 0d957b41e8ea..06674aaeca5f 100644 --- a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java +++ b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java @@ -20,7 +20,6 @@ import com.intellij.xdebugger.XSourcePosition; import com.intellij.xdebugger.frame.XCompositeNode; import com.intellij.xdebugger.frame.XValueChildrenList; import com.intellij.xdebugger.frame.XValueNode; -import com.jetbrains.python.parsing.console.PythonConsoleData; import com.jetbrains.python.console.protocol.*; import com.jetbrains.python.console.pydev.AbstractConsoleCommunication; import com.jetbrains.python.console.pydev.InterpreterResponse; @@ -28,6 +27,8 @@ import com.jetbrains.python.console.pydev.PydevCompletionVariant; import com.jetbrains.python.debugger.*; import com.jetbrains.python.debugger.containerview.PyViewNumericContainerAction; import com.jetbrains.python.debugger.pydev.GetVariableCommand; +import com.jetbrains.python.debugger.settings.PyDebuggerSettings; +import com.jetbrains.python.parsing.console.PythonConsoleData; import org.apache.thrift.TException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -569,6 +570,11 @@ public abstract class PydevConsoleCommunication extends AbstractConsoleCommunica myCurrentRootNode = node; } + @Override + public boolean isSimplifiedView() { + return PyDebuggerSettings.getInstance().isSimplifiedView(); + } + @Override @Nullable public XCompositeNode getCurrentRootNode() { diff --git a/python/src/com/jetbrains/python/console/PydevConsoleCommunicationUtil.kt b/python/src/com/jetbrains/python/console/PydevConsoleCommunicationUtil.kt index f1230b977dec..3efd456c620d 100644 --- a/python/src/com/jetbrains/python/console/PydevConsoleCommunicationUtil.kt +++ b/python/src/com/jetbrains/python/console/PydevConsoleCommunicationUtil.kt @@ -18,14 +18,14 @@ fun parseVars(vars: List, parent: PyDebugValue?, frameAccessor: PyFr if (parent != null) { pyDebugValue.parent = parent } - list.add(pyDebugValue.name, pyDebugValue) + list.add(pyDebugValue.visibleName, pyDebugValue) } return list } fun createPyDebugValue(value: DebugValue, frameAccessor: PyFrameAccessor) = PyDebugValue(value.name, value.type, value.qualifier, value.value ?: "", - value.isContainer, null, value.isReturnedValue, value.isIPythonHidden, value.isErrorOnEval, + value.isContainer, value.shape, value.isReturnedValue, value.isIPythonHidden, value.isErrorOnEval, frameAccessor) fun createArrayChunk(response: GetArrayResponse, frameAccessor: PyFrameAccessor): ArrayChunk { diff --git a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java index f3522d278a57..a29052e8703f 100644 --- a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java +++ b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java @@ -1218,6 +1218,11 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr myFrameListeners.add(listener); } + @Override + public boolean isSimplifiedView() { + return PyDebuggerSettings.getInstance().isSimplifiedView(); + } + @Nullable private static XSourcePosition typeToPosition(PyType pyType) { final PyClassType classType = PyUtil.as(pyType, PyClassType.class); diff --git a/python/src/com/jetbrains/python/debugger/PyStackFrame.java b/python/src/com/jetbrains/python/debugger/PyStackFrame.java index da7c33328c0c..67a42ef87572 100644 --- a/python/src/com/jetbrains/python/debugger/PyStackFrame.java +++ b/python/src/com/jetbrains/python/debugger/PyStackFrame.java @@ -1,7 +1,6 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.jetbrains.python.debugger; -import com.google.common.collect.Lists; import com.intellij.icons.AllIcons; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ReadAction; @@ -15,16 +14,20 @@ import com.intellij.ui.ColoredTextContainer; import com.intellij.ui.SimpleTextAttributes; import com.intellij.xdebugger.XSourcePosition; import com.intellij.xdebugger.evaluation.XDebuggerEvaluator; -import com.intellij.xdebugger.frame.*; +import com.intellij.xdebugger.frame.XCompositeNode; +import com.intellij.xdebugger.frame.XStackFrame; +import com.intellij.xdebugger.frame.XValue; +import com.intellij.xdebugger.frame.XValueChildrenList; import com.jetbrains.python.debugger.settings.PyDebuggerSettings; import icons.PythonIcons; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.swing.*; import java.util.*; import java.util.stream.IntStream; +import static com.jetbrains.python.debugger.PyDebugValueGroupsKt.addGroupValues; + public class PyStackFrame extends XStackFrame { @@ -91,25 +94,15 @@ public class PyStackFrame extends XStackFrame { } }); - component.append(myFrameInfo.getName(), gray(SimpleTextAttributes.REGULAR_ATTRIBUTES, isExternal)); - component.append(", ", gray(SimpleTextAttributes.REGULAR_ATTRIBUTES, isExternal)); - component.append(myPosition.getFile().getName(), gray(SimpleTextAttributes.REGULAR_ATTRIBUTES, isExternal)); - component.append(":", gray(SimpleTextAttributes.REGULAR_ATTRIBUTES, isExternal)); - component.append(Integer.toString(myPosition.getLine() + 1), gray(SimpleTextAttributes.REGULAR_ATTRIBUTES, isExternal)); + component.append(myFrameInfo.getName(), gray(isExternal)); + component.append(", ", gray(isExternal)); + component.append(myPosition.getFile().getName(), gray(isExternal)); + component.append(":", gray(isExternal)); + component.append(Integer.toString(myPosition.getLine() + 1), gray(isExternal)); } - private static SimpleTextAttributes gray(SimpleTextAttributes attributes, boolean gray) { - if (!gray) { - return attributes; - } - else { - return getGrayAttributes(attributes); - } - } - - protected static SimpleTextAttributes getGrayAttributes(SimpleTextAttributes attributes) { - return (attributes.getStyle() & SimpleTextAttributes.STYLE_ITALIC) != 0 - ? SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES : SimpleTextAttributes.GRAYED_ATTRIBUTES; + protected static SimpleTextAttributes gray(boolean gray) { + return (gray) ? SimpleTextAttributes.GRAYED_ATTRIBUTES : SimpleTextAttributes.REGULAR_ATTRIBUTES; } @Override @@ -145,7 +138,7 @@ public class PyStackFrame extends XStackFrame { final XValueChildrenList filteredChildren = new XValueChildrenList(); final HashMap returnedValues = new HashMap<>(); final ArrayList> specialValuesGroups = new ArrayList<>(); - IntStream.range(0, NUMBER_OF_GROUPS).mapToObj(i -> new HashMap()).forEach(specialValuesGroups::add); + IntStream.range(0, NUMBER_OF_GROUPS).mapToObj(i -> new HashMap()).forEach(specialValuesGroups::add); boolean isSpecialEmpty = true; for (int i = 0; i < children.size(); i++) { @@ -183,55 +176,22 @@ public class PyStackFrame extends XStackFrame { } node.addChildren(filteredChildren, returnedValues.isEmpty() && isSpecialEmpty); if (!returnedValues.isEmpty()) { - addReturnedValuesGroup(node, returnedValues); + addGroupValues(RETURN_VALUES_GROUP_NAME, AllIcons.Debugger.WatchLastReturnValue, node, returnedValues, "()"); } if (!isSpecialEmpty) { - addSpecialValuesGroup(node, specialValuesGroups); + final Map specialElements = mergeSpecialGroupElementsOrdered(specialValuesGroups); + addGroupValues(SPECIAL_VARIABLES_GROUP_NAME, PythonIcons.Python.Debug.SpecialVar, node, specialElements, null); } } - private static void addReturnedValuesGroup(@NotNull final XCompositeNode node, Map returnedValues) { - final ArrayList group = Lists.newArrayList(); - group.add(new XValueGroup(RETURN_VALUES_GROUP_NAME) { - @Override - public void computeChildren(@NotNull XCompositeNode node) { - XValueChildrenList list = new XValueChildrenList(); - for (Map.Entry entry : returnedValues.entrySet()) { - list.add(entry.getKey() + "()", entry.getValue()); - } - node.addChildren(list, true); + private static Map mergeSpecialGroupElementsOrdered(List> specialValuesGroups) { + final LinkedHashMap result = new LinkedHashMap<>(); + for (Map group : specialValuesGroups) { + for (Map.Entry entry : group.entrySet()) { + result.put(entry.getKey(), entry.getValue()); } - - @NotNull - @Override - public Icon getIcon() { - return AllIcons.Debugger.WatchLastReturnValue; - } - }); - node.addChildren(XValueChildrenList.topGroups(group), true); - } - - private static void addSpecialValuesGroup(@NotNull final XCompositeNode node, List> specialValuesGroups) { - final ArrayList group = Lists.newArrayList(); - group.add(new XValueGroup(SPECIAL_VARIABLES_GROUP_NAME) { - @Override - public void computeChildren(@NotNull XCompositeNode node) { - XValueChildrenList list = new XValueChildrenList(); - for (Map group : specialValuesGroups) { - for (Map.Entry entry : group.entrySet()) { - list.add(entry.getKey(), entry.getValue()); - } - } - node.addChildren(list, true); - } - - @NotNull - @Override - public Icon getIcon() { - return PythonIcons.Python.Debug.SpecialVar; - } - }); - node.addChildren(XValueChildrenList.topGroups(group), true); + } + return result; } public String getThreadId() { diff --git a/python/testData/debug/test_shapes.py b/python/testData/debug/test_shapes.py new file mode 100644 index 000000000000..11e110d4d545 --- /dev/null +++ b/python/testData/debug/test_shapes.py @@ -0,0 +1,31 @@ +import numpy as np +import pandas as pd + + +class MyCollection: + def __init__(self, size): + self.size = size + + def __len__(self): + return self.size + + +list1 = [i for i in range(120)] + +dict1 = {'a': 1, 'b': 2} + +custom = MyCollection(5) + +df1 = pd.DataFrame({'row': [0, 1, 2], + 'One_X': [1.14444, 1.144444, 1.144444], + 'One_Y': [1.24444, 1.244444, 1.244444], + 'Two_X': [1.11, 1.11, 1.11], + 'Two_Y': [1.22, 1.22, 1.22], + 'LABELS': ['A', 'B', 'C']}, + index=['a', 'b', 'c']) + +n_array = np.random.random_sample((3, 2)) + +series = pd.Series([10, 20, 30, 40, 50], index=['a', 'b', 'c', 'd', 'e']) + +print("Executed") diff --git a/python/testSrc/com/jetbrains/env/python/PythonConsoleTest.java b/python/testSrc/com/jetbrains/env/python/PythonConsoleTest.java index 4aefd9d30a8f..d9ebf799e554 100644 --- a/python/testSrc/com/jetbrains/env/python/PythonConsoleTest.java +++ b/python/testSrc/com/jetbrains/env/python/PythonConsoleTest.java @@ -1,6 +1,7 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.jetbrains.env.python; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.intellij.xdebugger.impl.ui.tree.nodes.XDebuggerTreeNode; import com.jetbrains.TestEnv; @@ -8,6 +9,7 @@ import com.jetbrains.env.PyEnvTestCase; import com.jetbrains.env.Staging; import com.jetbrains.env.StagingOn; import com.jetbrains.env.python.console.PyConsoleTask; +import com.jetbrains.python.debugger.PyDebugValue; import org.jetbrains.annotations.NotNull; import org.junit.Assert; import org.junit.Test; @@ -19,6 +21,8 @@ import java.util.Set; import java.util.function.Predicate; import static com.intellij.testFramework.UsefulTestCase.assertContainsElements; +import static com.jetbrains.env.python.debug.PyBaseDebuggerTask.findDebugValueByName; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** @@ -198,9 +202,41 @@ public class PythonConsoleTest extends PyEnvTestCase { do { if (pred.test(getConsoleView().getDebuggerTreeRootNode())) return; Thread.sleep(TIMEOUT / NUMBER_OF_ATTEMPTS); - } while (startedAt + TIMEOUT > System.currentTimeMillis()); + } + while (startedAt + TIMEOUT > System.currentTimeMillis()); Assert.fail(message); } }); } + + @Test + public void testCollectionsShapes() { + runPythonTest(new PyConsoleTask("/debug") { + @Override + public void testing() throws Exception { + exec("from test_shapes import *"); + waitForOutput("Executed"); + + final List frameVariables = loadFrame(); + PyDebugValue var = findDebugValueByName(frameVariables, "list1"); + assertEquals("120", var.getShape()); + var = findDebugValueByName(frameVariables, "dict1"); + assertEquals("2", var.getShape()); + var = findDebugValueByName(frameVariables, "custom"); + assertEquals("5", var.getShape()); + var = findDebugValueByName(frameVariables, "df1"); + assertEquals("(3, 6)", var.getShape()); + var = findDebugValueByName(frameVariables, "n_array"); + assertEquals("(3, 2)", var.getShape()); + var = findDebugValueByName(frameVariables, "series"); + assertEquals("(5,)", var.getShape()); + } + + @NotNull + @Override + public Set getTags() { + return ImmutableSet.of("pandas"); + } + }); + } } diff --git a/python/testSrc/com/jetbrains/env/python/console/PyConsoleTask.java b/python/testSrc/com/jetbrains/env/python/console/PyConsoleTask.java index 3a41e2678bc0..1b9c0b1b5fe6 100644 --- a/python/testSrc/com/jetbrains/env/python/console/PyConsoleTask.java +++ b/python/testSrc/com/jetbrains/env/python/console/PyConsoleTask.java @@ -38,6 +38,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.Semaphore; +import static com.jetbrains.env.python.debug.PyBaseDebuggerTask.convertToList; import static org.assertj.core.api.Assertions.assertThat; /** @@ -63,6 +64,10 @@ public class PyConsoleTask extends PyExecutionFixtureTestTask { super(null); } + public PyConsoleTask(String relativeTestDataPath) { + super(relativeTestDataPath); + } + @Nullable @Override public Set getTagsToCover() { @@ -402,6 +407,10 @@ public class PyConsoleTask extends PyExecutionFixtureTestTask { return result; } + protected List loadFrame() throws PyDebuggerException { + return convertToList(myCommunication.loadFrame()); + } + protected void input(String text) { myConsoleView.executeInConsole(text); } diff --git a/python/testSrc/com/jetbrains/env/python/debug/PythonDebuggerTest.java b/python/testSrc/com/jetbrains/env/python/debug/PythonDebuggerTest.java index 2ef1a09b6812..1f338574ddf1 100644 --- a/python/testSrc/com/jetbrains/env/python/debug/PythonDebuggerTest.java +++ b/python/testSrc/com/jetbrains/env/python/debug/PythonDebuggerTest.java @@ -33,7 +33,10 @@ import org.junit.Assume; import org.junit.Test; import java.nio.file.Paths; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; import static com.jetbrains.env.python.debug.PyBaseDebuggerTask.addExceptionBreakpoint; import static org.junit.Assert.*; @@ -2130,4 +2133,41 @@ public class PythonDebuggerTest extends PyEnvTestCase { } }); } + + @Test + public void testCollectionsShapes() { + runPythonTest(new PyDebuggerTask("/debug", "test_shapes.py") { + @Override + public void before() { + toggleBreakpoint(getFilePath(getScriptName()), 30); + } + + @Override + public void testing() throws Exception { + waitForPause(); + List frameVariables = loadFrame(); + PyDebugValue var = findDebugValueByName(frameVariables, "list1"); + assertEquals("120", var.getShape()); + var = findDebugValueByName(frameVariables, "dict1"); + assertEquals("2", var.getShape()); + var = findDebugValueByName(frameVariables, "custom"); + assertEquals("5", var.getShape()); + var = findDebugValueByName(frameVariables, "df1"); + assertEquals("(3, 6)", var.getShape()); + var = findDebugValueByName(frameVariables, "n_array"); + assertEquals("(3, 2)", var.getShape()); + var = findDebugValueByName(frameVariables, "series"); + assertEquals("(5,)", var.getShape()); + + resume(); + waitForTerminate(); + } + + @NotNull + @Override + public Set getTags() { + return ImmutableSet.of("pandas"); + } + }); + } }