This is an automated email from the ASF dual-hosted git repository. zjffdu pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/zeppelin.git
The following commit(s) were added to refs/heads/master by this push: new 13d3912 [ZEPPELIN-4508]. Completion doesn't work in jupyter interpreter 13d3912 is described below commit 13d391273fc6d77fe0a64471e6bc585e9c866d53 Author: Jeff Zhang <zjf...@apache.org> AuthorDate: Wed Dec 25 11:59:36 2019 +0800 [ZEPPELIN-4508]. Completion doesn't work in jupyter interpreter ### What is this PR for? `getCompletions` is missing for `JupyterInterpreter`, this PR fix that and also add IPythonKernelTest to verify all the features of `JupyterInterpreter` ### What type of PR is it? [Bug Fix] ### Todos * [ ] - Task ### What is the Jira issue? * https://jira.apache.org/jira/browse/ZEPPELIN-4508 ### How should this be tested? * CI pass ### Screenshots (if appropriate) ### Questions: * Does the licenses files need update? No * Is there breaking changes for older versions? No * Does this needs documentation? No Author: Jeff Zhang <zjf...@apache.org> Closes #3567 from zjffdu/ZEPPELIN-4508 and squashes the following commits: 9d9544061 [Jeff Zhang] [ZEPPELIN-4508]. Completion doesn't work in jupyter interpreter --- zeppelin-jupyter-interpreter/pom.xml | 2 +- .../zeppelin/jupyter/JupyterInterpreter.java | 20 +- .../src/main/resources/interpreter-setting.json | 1 + .../apache/zeppelin/jupyter/IPythonKernelTest.java | 261 +++++++++++++++++++++ 4 files changed, 282 insertions(+), 2 deletions(-) diff --git a/zeppelin-jupyter-interpreter/pom.xml b/zeppelin-jupyter-interpreter/pom.xml index ffc8daa..a924717 100644 --- a/zeppelin-jupyter-interpreter/pom.xml +++ b/zeppelin-jupyter-interpreter/pom.xml @@ -31,7 +31,7 @@ <artifactId>zeppelin-jupyter-interpreter</artifactId> <packaging>jar</packaging> <version>0.9.0-SNAPSHOT</version> - <name>Zeppelin: Jupyter Adapter</name> + <name>Zeppelin: Jupyter Interpreter</name> <properties> <interpreter.name>jupyter</interpreter.name> diff --git a/zeppelin-jupyter-interpreter/src/main/java/org/apache/zeppelin/jupyter/JupyterInterpreter.java b/zeppelin-jupyter-interpreter/src/main/java/org/apache/zeppelin/jupyter/JupyterInterpreter.java index b359468..66d42e8 100644 --- a/zeppelin-jupyter-interpreter/src/main/java/org/apache/zeppelin/jupyter/JupyterInterpreter.java +++ b/zeppelin-jupyter-interpreter/src/main/java/org/apache/zeppelin/jupyter/JupyterInterpreter.java @@ -23,8 +23,10 @@ import org.apache.zeppelin.interpreter.BaseZeppelinContext; import org.apache.zeppelin.interpreter.InterpreterContext; import org.apache.zeppelin.interpreter.InterpreterException; import org.apache.zeppelin.interpreter.InterpreterResult; +import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Properties; @@ -86,7 +88,7 @@ public class JupyterInterpreter extends AbstractInterpreter { throw new InterpreterException("No kernel is specified"); } JupyterKernelInterpreter kernelInterpreter = kernelInterpreterMap.get(kernel); - if (kernelInterpreter != null) { + if (kernelInterpreter == null) { throw new InterpreterException("No such interpreter: " + kernel); } kernelInterpreter.cancel(context); @@ -109,4 +111,20 @@ public class JupyterInterpreter extends AbstractInterpreter { } return kernelInterpreter.getProgress(context); } + + @Override + public List<InterpreterCompletion> completion( + String buf, + int cursor, + InterpreterContext context) throws InterpreterException { + String kernel = context.getLocalProperties().get("kernel"); + if (kernel == null) { + throw new InterpreterException("No kernel is specified"); + } + JupyterKernelInterpreter kernelInterpreter = kernelInterpreterMap.get(kernel); + if (kernelInterpreter == null) { + throw new InterpreterException("No such interpreter: " + kernel); + } + return kernelInterpreter.completion(buf, cursor, context); + } } diff --git a/zeppelin-jupyter-interpreter/src/main/resources/interpreter-setting.json b/zeppelin-jupyter-interpreter/src/main/resources/interpreter-setting.json index 19181f7..fbb1fdf 100644 --- a/zeppelin-jupyter-interpreter/src/main/resources/interpreter-setting.json +++ b/zeppelin-jupyter-interpreter/src/main/resources/interpreter-setting.json @@ -8,6 +8,7 @@ "editor": { "language": "text", "editOnDblClick": false, + "completionKey": "TAB", "completionSupport": true } } diff --git a/zeppelin-jupyter-interpreter/src/test/java/org/apache/zeppelin/jupyter/IPythonKernelTest.java b/zeppelin-jupyter-interpreter/src/test/java/org/apache/zeppelin/jupyter/IPythonKernelTest.java new file mode 100644 index 0000000..af8f881 --- /dev/null +++ b/zeppelin-jupyter-interpreter/src/test/java/org/apache/zeppelin/jupyter/IPythonKernelTest.java @@ -0,0 +1,261 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.zeppelin.jupyter; + +import org.apache.zeppelin.interpreter.Interpreter; +import org.apache.zeppelin.interpreter.InterpreterContext; +import org.apache.zeppelin.interpreter.InterpreterException; +import org.apache.zeppelin.interpreter.InterpreterGroup; +import org.apache.zeppelin.interpreter.InterpreterOutput; +import org.apache.zeppelin.interpreter.InterpreterResult; +import org.apache.zeppelin.interpreter.InterpreterResultMessage; +import org.apache.zeppelin.interpreter.LazyOpenInterpreter; +import org.apache.zeppelin.interpreter.remote.RemoteInterpreterEventClient; +import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + + +/** + * TODO(zjffdu) These tests are copied from python module. + * Should reorganize them to avoid code duplication. + */ +public class IPythonKernelTest { + + protected InterpreterGroup intpGroup; + protected Interpreter interpreter; + + @Before + public void setUp() throws InterpreterException { + Properties properties = new Properties(); + interpreter = new LazyOpenInterpreter(new JupyterInterpreter(properties)); + intpGroup = new InterpreterGroup(); + intpGroup.put("session_1", new ArrayList<Interpreter>()); + intpGroup.get("session_1").add(interpreter); + interpreter.setInterpreterGroup(intpGroup); + + interpreter.open(); + } + + @Test + public void testPythonBasics() throws InterpreterException, InterruptedException, IOException { + InterpreterContext context = getInterpreterContext(); + InterpreterResult result = + interpreter.interpret("import sys\nprint(sys.version[0])", context); + assertEquals(InterpreterResult.Code.SUCCESS, result.code()); + Thread.sleep(100); + List<InterpreterResultMessage> interpreterResultMessages = + context.out.toInterpreterResultMessage(); + assertEquals(1, interpreterResultMessages.size()); + + // single output without print + context = getInterpreterContext(); + result = interpreter.interpret("'hello world'", context); + Thread.sleep(100); + assertEquals(InterpreterResult.Code.SUCCESS, result.code()); + interpreterResultMessages = context.out.toInterpreterResultMessage(); + assertEquals(1, interpreterResultMessages.size()); + assertEquals("'hello world'", interpreterResultMessages.get(0).getData().trim()); + + // unicode + context = getInterpreterContext(); + result = interpreter.interpret("print(u'你好')", context); + Thread.sleep(100); + assertEquals(InterpreterResult.Code.SUCCESS, result.code()); + interpreterResultMessages = context.out.toInterpreterResultMessage(); + assertEquals(1, interpreterResultMessages.size()); + assertEquals("你好\n", interpreterResultMessages.get(0).getData()); + + // only the last statement is printed + context = getInterpreterContext(); + result = interpreter.interpret("'hello world'\n'hello world2'", context); + Thread.sleep(100); + assertEquals(InterpreterResult.Code.SUCCESS, result.code()); + interpreterResultMessages = context.out.toInterpreterResultMessage(); + assertEquals(1, interpreterResultMessages.size()); + assertEquals("'hello world2'", interpreterResultMessages.get(0).getData().trim()); + + // single output + context = getInterpreterContext(); + result = interpreter.interpret("print('hello world')", context); + Thread.sleep(100); + assertEquals(InterpreterResult.Code.SUCCESS, result.code()); + interpreterResultMessages = context.out.toInterpreterResultMessage(); + assertEquals(1, interpreterResultMessages.size()); + assertEquals("hello world\n", interpreterResultMessages.get(0).getData()); + + // multiple output + context = getInterpreterContext(); + result = interpreter.interpret("print('hello world')\nprint('hello world2')", context); + Thread.sleep(100); + assertEquals(InterpreterResult.Code.SUCCESS, result.code()); + interpreterResultMessages = context.out.toInterpreterResultMessage(); + assertEquals(1, interpreterResultMessages.size()); + assertEquals("hello world\nhello world2\n", interpreterResultMessages.get(0).getData()); + + // assignment + context = getInterpreterContext(); + result = interpreter.interpret("abc=1", context); + Thread.sleep(100); + assertEquals(InterpreterResult.Code.SUCCESS, result.code()); + interpreterResultMessages = context.out.toInterpreterResultMessage(); + assertEquals(0, interpreterResultMessages.size()); + + // if block + context = getInterpreterContext(); + result = + interpreter.interpret("if abc > 0:\n\tprint('True')\nelse:\n\tprint('False')", context); + Thread.sleep(100); + assertEquals(InterpreterResult.Code.SUCCESS, result.code()); + interpreterResultMessages = context.out.toInterpreterResultMessage(); + assertEquals(1, interpreterResultMessages.size()); + assertEquals("True\n", interpreterResultMessages.get(0).getData()); + + // for loop + context = getInterpreterContext(); + result = interpreter.interpret("for i in range(3):\n\tprint(i)", context); + Thread.sleep(100); + assertEquals(InterpreterResult.Code.SUCCESS, result.code()); + interpreterResultMessages = context.out.toInterpreterResultMessage(); + assertEquals(1, interpreterResultMessages.size()); + assertEquals("0\n1\n2\n", interpreterResultMessages.get(0).getData()); + + // syntax error + context = getInterpreterContext(); + result = interpreter.interpret("print(unknown)", context); + Thread.sleep(100); + assertEquals(InterpreterResult.Code.ERROR, result.code()); + + interpreterResultMessages = context.out.toInterpreterResultMessage(); + assertEquals(1, interpreterResultMessages.size()); + assertTrue(interpreterResultMessages.get(0).getData().contains( + "name 'unknown' is not defined")); + + // raise runtime exception + context = getInterpreterContext(); + result = interpreter.interpret("1/0", context); + Thread.sleep(100); + assertEquals(InterpreterResult.Code.ERROR, result.code()); + + interpreterResultMessages = context.out.toInterpreterResultMessage(); + assertEquals(1, interpreterResultMessages.size()); + assertTrue(interpreterResultMessages.get(0).getData().contains("ZeroDivisionError")); + + + // ZEPPELIN-1133 + context = getInterpreterContext(); + result = interpreter.interpret( + "from __future__ import print_function\n" + + "def greet(name):\n" + + " print('Hello', name)\n" + + "greet('Jack')", context); + Thread.sleep(100); + assertEquals(InterpreterResult.Code.SUCCESS, result.code()); + interpreterResultMessages = context.out.toInterpreterResultMessage(); + assertEquals(1, interpreterResultMessages.size()); + assertEquals("Hello Jack\n", interpreterResultMessages.get(0).getData()); + + // ZEPPELIN-1114 + context = getInterpreterContext(); + result = interpreter.interpret("print('there is no Error: ok')", context); + Thread.sleep(100); + assertEquals(InterpreterResult.Code.SUCCESS, result.code()); + interpreterResultMessages = context.out.toInterpreterResultMessage(); + assertEquals(1, interpreterResultMessages.size()); + assertEquals("there is no Error: ok\n", interpreterResultMessages.get(0).getData()); + + // ZEPPELIN-3687 + context = getInterpreterContext(); + result = interpreter.interpret("# print('Hello')", context); + Thread.sleep(100); + assertEquals(InterpreterResult.Code.SUCCESS, result.code()); + interpreterResultMessages = context.out.toInterpreterResultMessage(); + assertEquals(0, interpreterResultMessages.size()); + + context = getInterpreterContext(); + result = interpreter.interpret( + "# print('Hello')\n# print('How are u?')\n# time.sleep(1)", context); + Thread.sleep(100); + assertEquals(InterpreterResult.Code.SUCCESS, result.code()); + interpreterResultMessages = context.out.toInterpreterResultMessage(); + assertEquals(0, interpreterResultMessages.size()); + + // multiple text output + context = getInterpreterContext(); + result = interpreter.interpret( + "for i in range(1,4):\n" + "\tprint(i)", context); + assertEquals(InterpreterResult.Code.SUCCESS, result.code()); + interpreterResultMessages = context.out.toInterpreterResultMessage(); + assertEquals(1, interpreterResultMessages.size()); + assertEquals("1\n2\n3\n", interpreterResultMessages.get(0).getData()); + } + + @Test + public void testCodeCompletion() throws InterpreterException, IOException, InterruptedException { + // define `a` first + InterpreterContext context = getInterpreterContext(); + String st = "a='hello'"; + InterpreterResult result = interpreter.interpret(st, context); + Thread.sleep(100); + assertEquals(InterpreterResult.Code.SUCCESS, result.code()); + + // now we can get the completion for `a.` + context = getInterpreterContext(); + st = "a."; + List<InterpreterCompletion> completions = interpreter.completion(st, st.length(), context); + // it is different for python2 and python3 and may even different for different minor version + // so only verify it is larger than 20 + assertTrue(completions.size() > 20); + + context = getInterpreterContext(); + st = "a.co"; + completions = interpreter.completion(st, st.length(), context); + assertEquals(1, completions.size()); + assertEquals("count", completions.get(0).getValue()); + + // cursor is in the middle of code + context = getInterpreterContext(); + st = "a.co\b='hello"; + completions = interpreter.completion(st, 4, context); + assertEquals(1, completions.size()); + assertEquals("count", completions.get(0).getValue()); + } + + protected InterpreterContext getInterpreterContext() { + Map<String, String> localProperties = new HashMap<>(); + localProperties.put("kernel", "python"); + return InterpreterContext.builder() + .setNoteId("noteId") + .setParagraphId("paragraphId") + .setInterpreterOut(new InterpreterOutput(null)) + .setIntpEventClient(mock(RemoteInterpreterEventClient.class)) + .setLocalProperties(localProperties) + .build(); + } +}