From 6a7762244c688e8da07d678e727a127e1ebcf8af Mon Sep 17 00:00:00 2001 From: Dmitry Trofimov Date: Sun, 21 Feb 2016 01:20:34 +0100 Subject: [PATCH] Collect return types when tracing call signatures --- .../pydev/_pydevd_bundle/pydevd_comm.py | 76 +++++++------ .../pydev/_pydevd_bundle/pydevd_frame.py | 24 ++++- .../pydev/_pydevd_bundle/pydevd_signature.py | 101 +++++++++++++++--- .../python/debugger/PySignature.java | 23 ++++ .../debugger/pydev/AbstractCommand.java | 8 +- .../python/debugger/pydev/ProtocolParser.java | 11 +- .../debugger/PyCallSignatureTypeProvider.java | 23 +++- .../debugger/PySignatureCacheManagerImpl.java | 15 ++- .../docstrings/PyDocstringGenerator.java | 3 +- .../psi/types/PyDynamicallyEvaluatedType.java | 1 - python/testData/dynamicTypes/1.py | 4 + python/testData/dynamicTypes/1_after.py | 11 ++ .../env/python/PyDynamicTypesTest.java | 88 +++++++++++++++ 13 files changed, 314 insertions(+), 74 deletions(-) create mode 100644 python/testData/dynamicTypes/1.py create mode 100644 python/testData/dynamicTypes/1_after.py create mode 100644 python/testSrc/com/jetbrains/env/python/PyDynamicTypesTest.java diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py b/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py index 59e78f140254..60ba0ac8d049 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py @@ -58,10 +58,9 @@ each command has a format: * PYDB - pydevd, the python end ''' -from _pydev_imps import _pydev_threading as threading +from _pydev_bundle.pydev_imports import _queue from _pydev_imps import _pydev_time as time, _pydev_thread from _pydev_imps._pydev_socket import socket, AF_INET, SOCK_STREAM, SHUT_RD, SHUT_WR -from _pydev_bundle.pydev_imports import _queue from _pydevd_bundle.pydevd_constants import * #@UnusedWildImport try: @@ -115,10 +114,9 @@ CMD_REMOVE_DJANGO_EXCEPTION_BREAK = 126 CMD_SET_NEXT_STATEMENT = 127 CMD_SMART_STEP_INTO = 128 CMD_EXIT = 129 + CMD_SIGNATURE_CALL_TRACE = 130 - - CMD_SET_PY_EXCEPTION = 131 CMD_GET_FILE_CONTENTS = 132 CMD_SET_PROPERTY_TRACE = 133 @@ -142,35 +140,36 @@ CMD_RETURN = 502 CMD_ERROR = 901 ID_TO_MEANING = { - '101':'CMD_RUN', - '102':'CMD_LIST_THREADS', - '103':'CMD_THREAD_CREATE', - '104':'CMD_THREAD_KILL', - '105':'CMD_THREAD_SUSPEND', - '106':'CMD_THREAD_RUN', - '107':'CMD_STEP_INTO', - '108':'CMD_STEP_OVER', - '109':'CMD_STEP_RETURN', - '110':'CMD_GET_VARIABLE', - '111':'CMD_SET_BREAK', - '112':'CMD_REMOVE_BREAK', - '113':'CMD_EVALUATE_EXPRESSION', - '114':'CMD_GET_FRAME', - '115':'CMD_EXEC_EXPRESSION', - '116':'CMD_WRITE_TO_CONSOLE', - '117':'CMD_CHANGE_VARIABLE', - '118':'CMD_RUN_TO_LINE', - '119':'CMD_RELOAD_CODE', - '120':'CMD_GET_COMPLETIONS', - '121':'CMD_CONSOLE_EXEC', - '122':'CMD_ADD_EXCEPTION_BREAK', - '123':'CMD_REMOVE_EXCEPTION_BREAK', - '124':'CMD_LOAD_SOURCE', - '125':'CMD_ADD_DJANGO_EXCEPTION_BREAK', - '126':'CMD_REMOVE_DJANGO_EXCEPTION_BREAK', - '127':'CMD_SET_NEXT_STATEMENT', - '128':'CMD_SMART_STEP_INTO', + '101': 'CMD_RUN', + '102': 'CMD_LIST_THREADS', + '103': 'CMD_THREAD_CREATE', + '104': 'CMD_THREAD_KILL', + '105': 'CMD_THREAD_SUSPEND', + '106': 'CMD_THREAD_RUN', + '107': 'CMD_STEP_INTO', + '108': 'CMD_STEP_OVER', + '109': 'CMD_STEP_RETURN', + '110': 'CMD_GET_VARIABLE', + '111': 'CMD_SET_BREAK', + '112': 'CMD_REMOVE_BREAK', + '113': 'CMD_EVALUATE_EXPRESSION', + '114': 'CMD_GET_FRAME', + '115': 'CMD_EXEC_EXPRESSION', + '116': 'CMD_WRITE_TO_CONSOLE', + '117': 'CMD_CHANGE_VARIABLE', + '118': 'CMD_RUN_TO_LINE', + '119': 'CMD_RELOAD_CODE', + '120': 'CMD_GET_COMPLETIONS', + '121': 'CMD_CONSOLE_EXEC', + '122': 'CMD_ADD_EXCEPTION_BREAK', + '123': 'CMD_REMOVE_EXCEPTION_BREAK', + '124': 'CMD_LOAD_SOURCE', + '125': 'CMD_ADD_DJANGO_EXCEPTION_BREAK', + '126': 'CMD_REMOVE_DJANGO_EXCEPTION_BREAK', + '127': 'CMD_SET_NEXT_STATEMENT', + '128': 'CMD_SMART_STEP_INTO', '129': 'CMD_EXIT', + '130': 'CMD_SIGNATURE_CALL_TRACE', '131': 'CMD_SET_PY_EXCEPTION', @@ -184,14 +183,13 @@ ID_TO_MEANING = { '139': 'CMD_SEND_CURR_EXCEPTION_TRACE_PROCEEDED', '140': 'CMD_IGNORE_THROWN_EXCEPTION_AT', '141': 'CMD_ENABLE_DONT_TRACE', + '143': 'CMD_GET_ARRAY', + '144': 'CMD_STEP_INTO_MY_CODE', + '145': 'CMD_GET_CONCURRENCY_EVENT', - '501':'CMD_VERSION', - '502':'CMD_RETURN', - '901':'CMD_ERROR', - - '143':'CMD_GET_ARRAY', - '144':'CMD_STEP_INTO_MY_CODE', - '145':'CMD_GET_CONCURRENCY_EVENT', + '501': 'CMD_VERSION', + '502': 'CMD_RETURN', + '901': 'CMD_ERROR', } MAX_IO_MSG_SIZE = 1000 #if the io is too big, we'll not send all (could make the debugger too non-responsive) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py b/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py index b449dee9c89d..f03c41b44173 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py @@ -13,13 +13,14 @@ from _pydevd_bundle.pydevd_comm import CMD_STEP_CAUGHT_EXCEPTION, CMD_STEP_RETUR from _pydevd_bundle.pydevd_constants import STATE_SUSPEND, dict_contains, get_thread_id, STATE_RUN, dict_iter_values from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame, just_raised from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame + try: from inspect import CO_GENERATOR except: CO_GENERATOR = 0 try: - from _pydevd_bundle.pydevd_signature import send_signature_call_trace + from _pydevd_bundle.pydevd_signature import send_signature_call_trace, send_signature_return_trace except ImportError: def send_signature_call_trace(*args, **kwargs): pass @@ -80,6 +81,12 @@ class PyDBFrame: # No longer cdef because object was dying when only a reference return self.trace_exception + def trace_return(self, frame, event, arg): + if event == 'return': + main_debugger, filename, info, thread = self._args + send_signature_return_trace(main_debugger, frame, filename, arg) + return self.trace_return + # IFDEF CYTHON # def should_stop_on_exception(self, frame, str event, arg): # cdef PyDBAdditionalThreadInfo info; @@ -274,12 +281,15 @@ class PyDBFrame: # No longer cdef because object was dying when only a reference try: # print 'frame trace_dispatch', frame.f_lineno, frame.f_code.co_name, event info.is_tracing = True + need_trace_return = False if main_debugger._finish_debugging_session: return None if event == 'call' and main_debugger.signature_factory: - send_signature_call_trace(main_debugger, frame, filename) + need_trace_return = send_signature_call_trace(main_debugger, frame, filename) + if event == 'return' and main_debugger.signature_factory: + send_signature_return_trace(main_debugger, frame, filename, arg) plugin_manager = main_debugger.plugin @@ -341,7 +351,10 @@ class PyDBFrame: # No longer cdef because object was dying when only a reference if has_exception_breakpoints: return self.trace_exception else: - return None + if need_trace_return: + return self.trace_return + else: + return None else: #checks the breakpoint to see if there is a context match in some function @@ -361,7 +374,10 @@ class PyDBFrame: # No longer cdef because object was dying when only a reference if has_exception_breakpoints: return self.trace_exception else: - return None + if need_trace_return: + return self.trace_return + else: + return None #We may have hit a breakpoint or we are already in step mode. Either way, let's check what we should do in this frame diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_signature.py b/python/helpers/pydev/_pydevd_bundle/pydevd_signature.py index e529994048d0..5af54c8098a3 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_signature.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_signature.py @@ -19,6 +19,7 @@ class Signature(object): self.name = name self.args = [] self.args_str = [] + self.return_type = None def add_arg(self, name, type): self.args.append((name, type)) @@ -28,32 +29,39 @@ class Signature(object): return "%s %s(%s)"%(self.file, self.name, ", ".join(self.args_str)) +def get_type_of_value(value, ignore_module_name=('__main__', '__builtin__', 'builtins')): + tp = type(value) + class_name = tp.__name__ + if class_name == 'instance': # old-style classes + tp = value.__class__ + class_name = tp.__name__ + + if hasattr(tp, '__module__') and tp.__module__ and tp.__module__ not in ignore_module_name: + class_name = "%s.%s"%(tp.__module__, class_name) + + return class_name + + class SignatureFactory(object): def __init__(self): self._caller_cache = {} - self._ignore_module_name = ('__main__', '__builtin__', 'builtins') + self.cache = CallSignatureCache() def is_in_scope(self, filename): return not pydevd_utils.not_in_project_roots(filename) - def create_signature(self, frame): + def create_signature(self, frame, with_args=True): try: code = frame.f_code locals = frame.f_locals filename, modulename, funcname = self.file_module_function_of(frame) res = Signature(filename, funcname) - for i in xrange(0, code.co_argcount): - name = code.co_varnames[i] - tp = type(locals[name]) - class_name = tp.__name__ - if class_name == 'instance': # old-style classes - tp = locals[name].__class__ - class_name = tp.__name__ - - if hasattr(tp, '__module__') and tp.__module__ and tp.__module__ not in self._ignore_module_name: - class_name = "%s.%s"%(tp.__module__, class_name) - - res.add_arg(name, class_name) + if with_args: + for i in xrange(0, code.co_argcount): + name = code.co_varnames[i] + class_name = get_type_of_value(locals[name]) + + res.add_arg(name, class_name) return res except: import traceback @@ -109,6 +117,41 @@ class SignatureFactory(object): return filename, modulename, funcname + +def get_signature_info(signature): + return signature.file, signature.name, ' '.join([arg[1]for arg in signature.args]) + + +def get_frame_info(frame): + co = frame.f_code + return co.co_name, frame.f_lineno, co.co_filename + + +class CallSignatureCache(object): + def __init__(self): + self.cache = {} + + def add(self, signature): + filename, name, args_type = get_signature_info(signature) + + if not filename in self.cache: + self.cache[filename] = {} + + calls_from_file = self.cache[filename] + + if not name in calls_from_file: + calls_from_file[name] = {} + + name_calls = calls_from_file[name] + name_calls[args_type] = None + + def is_in_cache(self, signature): + filename, name, args_type = get_signature_info(signature) + if filename in self.cache and name in self.cache[filename] and args_type in self.cache[filename][name]: + return True + return False + + def create_signature_message(signature): cmdTextList = [""] @@ -116,14 +159,40 @@ def create_signature_message(signature): for arg in signature.args: cmdTextList.append('' % (pydevd_vars.make_valid_xml_value(arg[0]), pydevd_vars.make_valid_xml_value(arg[1]))) + + if signature.return_type is not None: + cmdTextList.append('' % (pydevd_vars.make_valid_xml_value(signature.return_type))) cmdTextList.append("") cmdText = ''.join(cmdTextList) return NetCommand(CMD_SIGNATURE_CALL_TRACE, 0, cmdText) + def send_signature_call_trace(dbg, frame, filename): - if dbg.signature_factory.is_in_scope(filename): - dbg.writer.add_command(create_signature_message(dbg.signature_factory.create_signature(frame))) + if dbg.signature_factory and dbg.signature_factory.is_in_scope(filename): + signature = dbg.signature_factory.create_signature(frame) + if dbg.signature_factory.cache is not None: + if not dbg.signature_factory.cache.is_in_cache(signature): + dbg.signature_factory.cache.add(signature) + dbg.writer.add_command(create_signature_message(signature)) + return True + else: + # we don't send signature if it is cached + return False + else: + dbg.writer.add_command(create_signature_message(signature)) + return True + return False + + +def send_signature_return_trace(dbg, frame, filename, return_value): + if dbg.signature_factory and dbg.signature_factory.is_in_scope(filename): + signature = dbg.signature_factory.create_signature(frame, with_args=False) + signature.return_type = get_type_of_value(return_value) + dbg.writer.add_command(create_signature_message(signature)) + return True + + return False diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PySignature.java b/python/pydevSrc/com/jetbrains/python/debugger/PySignature.java index a75887aba711..9d13e6421554 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/PySignature.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/PySignature.java @@ -13,6 +13,8 @@ import java.util.List; public class PySignature { private final String myFile; private final String myFunctionName; + + private NamedParameter myReturnType = null; private final List myArgs = Lists.newArrayList(); @@ -46,6 +48,22 @@ public class PySignature { return myArgs; } + public NamedParameter getReturnType() { + return myReturnType; + } + + public PySignature addReturnType(@Nullable String returnType) { + if (StringUtil.isNotEmpty(returnType)) { + if (myReturnType != null) { + myReturnType.addType(returnType); + } + else { + myReturnType = new NamedParameter("", returnType); + } + } + return this; + } + @NotNull public PySignature addAllArgs(@NotNull PySignature signature) { for (NamedParameter param : signature.getArgs()) { @@ -71,6 +89,11 @@ public class PySignature { return null; } + @Nullable + public String getReturnTypeQualifiedName() { + return myReturnType != null ? myReturnType.getTypeQualifiedName() : null; + } + public static class NamedParameter { private final String myName; diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java index 586d150caf79..94e4adb12a41 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java @@ -30,6 +30,7 @@ public abstract class AbstractCommand { public static final int LOAD_SOURCE = 124; public static final int SMART_STEP_INTO = 128; public static final int EXIT = 129; + public static final int CALL_SIGNATURE_TRACE = 130; public static final int CMD_SET_PY_EXCEPTION = 131; @@ -45,6 +46,9 @@ public abstract class AbstractCommand { public static final int CMD_ENABLE_DONT_TRACE = 141; public static final int SHOW_CONSOLE = 142; + public static final int GET_ARRAY = 143; + public static final int STEP_INTO_MY_CODE = 144; + public static final int LOG_CONCURRENCY_EVENT = 145; public static final int ERROR = 901; @@ -52,9 +56,7 @@ public abstract class AbstractCommand { public static final String NEW_LINE_CHAR = "@_@NEW_LINE_CHAR@_@"; public static final String TAB_CHAR = "@_@TAB_CHAR@_@"; - public static final int GET_ARRAY = 143; - public static final int STEP_INTO_MY_CODE = 144; - public static final int LOG_CONCURRENCY_EVENT = 145; + @NotNull private final RemoteDebugger myDebugger; private final int myCommandCode; diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java index cbfe858b2dad..ea4d5173b31b 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java @@ -31,10 +31,15 @@ public class ProtocolParser { while (reader.hasMoreChildren()) { reader.moveDown(); - if (!"arg".equals(reader.getNodeName())) { - throw new PyDebuggerException("Expected , found " + reader.getNodeName()); + if ("arg".equals(reader.getNodeName())) { + signature.addArgument(readString(reader, "name", ""), readString(reader, "type", "")); + } else + if ("return".equals(reader.getNodeName())) { + signature.addReturnType(readString(reader, "type", "")); + } else { + throw new PyDebuggerException("Expected or , found " + reader.getNodeName()); } - signature.addArgument(readString(reader, "name", ""), readString(reader, "type", "")); + reader.moveUp(); } diff --git a/python/src/com/jetbrains/python/debugger/PyCallSignatureTypeProvider.java b/python/src/com/jetbrains/python/debugger/PyCallSignatureTypeProvider.java index 57d37479c444..984f9017f341 100644 --- a/python/src/com/jetbrains/python/debugger/PyCallSignatureTypeProvider.java +++ b/python/src/com/jetbrains/python/debugger/PyCallSignatureTypeProvider.java @@ -16,6 +16,7 @@ package com.jetbrains.python.debugger; import com.intellij.openapi.util.Ref; +import com.jetbrains.python.psi.PyCallable; import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.PyNamedParameter; import com.jetbrains.python.psi.types.*; @@ -25,6 +26,7 @@ import org.jetbrains.annotations.NotNull; * @author traff */ public class PyCallSignatureTypeProvider extends PyTypeProviderBase { + @Override public Ref getParameterType(@NotNull final PyNamedParameter param, @NotNull final PyFunction func, @NotNull TypeEvalContext context) { @@ -34,8 +36,25 @@ public class PyCallSignatureTypeProvider extends PyTypeProviderBase { if (typeName != null) { final PyType type = PyTypeParser.getTypeByName(param, typeName); if (type != null) { - final PyType evaluatedType = PyDynamicallyEvaluatedType.create(type); - return Ref.create(evaluatedType); + return Ref.create(PyDynamicallyEvaluatedType.create(type)); + } + } + } + return null; + } + + @Override + public Ref getReturnType(@NotNull final PyCallable callable, @NotNull TypeEvalContext context) { + if (callable instanceof PyFunction) { + PyFunction function = (PyFunction)callable; + PySignature signature = PySignatureCacheManager.getInstance(function.getProject()).findSignature(function); + if (signature != null) { + final String typeName = signature.getReturnType().getTypeQualifiedName(); + if (typeName != null) { + final PyType type = PyTypeParser.getTypeByName(function, typeName); + if (type != null) { + return Ref.create(PyDynamicallyEvaluatedType.create(type)); + } } } } diff --git a/python/src/com/jetbrains/python/debugger/PySignatureCacheManagerImpl.java b/python/src/com/jetbrains/python/debugger/PySignatureCacheManagerImpl.java index 49ab3ad28202..cbb5bef6f6d0 100644 --- a/python/src/com/jetbrains/python/debugger/PySignatureCacheManagerImpl.java +++ b/python/src/com/jetbrains/python/debugger/PySignatureCacheManagerImpl.java @@ -104,7 +104,7 @@ public class PySignatureCacheManagerImpl extends PySignatureCacheManager { else { //noinspection ConstantConditions lines[i] = signatureToString(stringToSignature(file. - getCanonicalPath(), lines[i]).addAllArgs(signature)); + getCanonicalPath(), lines[i]).addAllArgs(signature).addReturnType(signature.getReturnTypeQualifiedName())); } } i++; @@ -140,7 +140,9 @@ public class PySignatureCacheManagerImpl extends PySignatureCacheManager { } private static String signatureToString(PySignature signature) { - return signature.getFunctionName() + "\t" + StringUtil.join(arguments(signature), "\t"); + return signature.getFunctionName() + "\t" + StringUtil.join(arguments(signature), "\t") + + (signature.getReturnType() != null + ? "\t" + StringUtil.join(signature.getReturnType().getTypesList(), "\t") : ""); } private static List arguments(PySignature signature) { @@ -248,8 +250,11 @@ public class PySignatureCacheManagerImpl extends PySignatureCacheManager { if (var.length == 2) { signature = signature.addArgument(var[0], var[1]); } + else if (var.length == 1) { + signature = signature.addReturnType(var[0]); + } else { - throw new IllegalStateException("Should be : format. " + parts[i] + " instead."); + throw new IllegalStateException("Should be : format for arg or for return type; '" + parts[i] + "' instead."); } } return signature; @@ -259,7 +264,7 @@ public class PySignatureCacheManagerImpl extends PySignatureCacheManager { @Nullable private static VirtualFile getFile(@NotNull PySignature signature) { - return LocalFileSystem.getInstance().findFileByPath(signature.getFile()); + return LocalFileSystem.getInstance().refreshAndFindFileByPath(signature.getFile()); } @Nullable @@ -291,7 +296,7 @@ public class PySignatureCacheManagerImpl extends PySignatureCacheManager { } }); } - }, "Cleaning the cache of dynamically collected types", true, myProject); + }, "Cleaning the Cache of Dynamically Collected Types", true, myProject); String message; diff --git a/python/src/com/jetbrains/python/documentation/docstrings/PyDocstringGenerator.java b/python/src/com/jetbrains/python/documentation/docstrings/PyDocstringGenerator.java index bd40187d1ee0..9e70d29a26eb 100644 --- a/python/src/com/jetbrains/python/documentation/docstrings/PyDocstringGenerator.java +++ b/python/src/com/jetbrains/python/documentation/docstrings/PyDocstringGenerator.java @@ -275,7 +275,8 @@ public class PyDocstringGenerator { if (param.getType() == null) { String type; if (signature != null) { - type = StringUtil.notNullize(signature.getArgTypeQualifiedName(param.getName())); + type = StringUtil.notNullize(param.isReturnValue() ? signature.getReturnTypeQualifiedName() : + signature.getArgTypeQualifiedName(param.getName())); } else { type = paramTypes.get(paramCoordinates); diff --git a/python/src/com/jetbrains/python/psi/types/PyDynamicallyEvaluatedType.java b/python/src/com/jetbrains/python/psi/types/PyDynamicallyEvaluatedType.java index 4dff5a00d886..5e961060c1a1 100644 --- a/python/src/com/jetbrains/python/psi/types/PyDynamicallyEvaluatedType.java +++ b/python/src/com/jetbrains/python/psi/types/PyDynamicallyEvaluatedType.java @@ -15,7 +15,6 @@ */ package com.jetbrains.python.psi.types; -import com.intellij.openapi.project.Project; import com.jetbrains.python.PyNames; import org.jetbrains.annotations.NotNull; diff --git a/python/testData/dynamicTypes/1.py b/python/testData/dynamicTypes/1.py new file mode 100644 index 000000000000..e7c846b2c640 --- /dev/null +++ b/python/testData/dynamicTypes/1.py @@ -0,0 +1,4 @@ +def foo(x): + return x + +foo(1) \ No newline at end of file diff --git a/python/testData/dynamicTypes/1_after.py b/python/testData/dynamicTypes/1_after.py new file mode 100644 index 000000000000..e647aea92013 --- /dev/null +++ b/python/testData/dynamicTypes/1_after.py @@ -0,0 +1,11 @@ +def foo(x): + """ + + :param x: + :type x: int + :return: + :rtype: int + """ + return x + +foo(1) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/env/python/PyDynamicTypesTest.java b/python/testSrc/com/jetbrains/env/python/PyDynamicTypesTest.java new file mode 100644 index 000000000000..4d0922cbd5d4 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/PyDynamicTypesTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.env.python; + +import com.google.common.collect.Lists; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.openapi.editor.LogicalPosition; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.testFramework.EditorTestUtil; +import com.jetbrains.env.PyEnvTestCase; +import com.jetbrains.env.python.debug.PyDebuggerTask; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.debugger.PyDebuggerOptionsProvider; +import com.jetbrains.python.debugger.PySignatureCacheManagerImpl; + +import java.io.IOException; + +/** + * @author traff + */ + +public class PyDynamicTypesTest extends PyEnvTestCase { + public void test1() throws Exception { + doTest(getTestName(true) + ".py"); + } + + private void doTest(final String scriptName) { + runPythonTest(new PyDebuggerTask("/" + "dynamicTypes", scriptName) { + @Override + public void setUp(String testName) throws Exception { + super.setUp(testName); + } + + @Override + public void before() throws Exception { + PyDebuggerOptionsProvider.getInstance(myFixture.getProject()).setSaveCallSignatures(true); + } + + @Override + public void after() throws Exception { + PyDebuggerOptionsProvider.getInstance(myFixture.getProject()).setSaveCallSignatures(false); + } + + @Override + public void testing() throws Exception { + waitForTerminate(); + + edt(() -> { + myFixture.configureByFile("dynamicTypes/" + scriptName); + + try { + //copy signature attributes from real file to temporary test file + PySignatureCacheManagerImpl.CALL_SIGNATURES_ATTRIBUTE.writeAttributeBytes(myFixture.getFile().getVirtualFile(), + PySignatureCacheManagerImpl.CALL_SIGNATURES_ATTRIBUTE + .readAttributeBytes(LocalFileSystem.getInstance() + .refreshAndFindFileByPath( + getTestDataPath() + + "/dynamicTypes/" + + scriptName))); + } + catch (IOException e) { + throw new RuntimeException(e); + } + + EditorTestUtil.setCaretsAndSelection(myFixture.getEditor(), new EditorTestUtil.CaretAndSelectionState( + Lists.newArrayList(new EditorTestUtil.CaretInfo(new LogicalPosition(0, 6), null)), null)); + final IntentionAction action = myFixture.findSingleIntention(PyBundle.message("INTN.doc.string.stub")); + myFixture.launchAction(action); + myFixture.checkResultByFile("dynamicTypes/" + getTestName(true) + "_after.py"); + }); + } + }); + } +} +