This is an automated email from the ASF dual-hosted git repository.

guanhuali 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 ca2481dd65 [ZEPPELIN-6016] Rewrite and enable Livy integration tests 
(#4743)
ca2481dd65 is described below

commit ca2481dd652d8c9156a81e3f43637a0b102006f5
Author: Cheng Pan <cheng...@apache.org>
AuthorDate: Tue Apr 23 17:02:17 2024 +0800

    [ZEPPELIN-6016] Rewrite and enable Livy integration tests (#4743)
    
    * wip
    
    * nit
    
    * nit
    
    * wip
    
    * wip
    
    * fix
    
    * [ZEPPELIN-5973] Bump Livy 0.8.0-incubating
    
    * nit
    
    * Spark 3.5.1
    
    * test
    
    * fix
    
    * comment
    
    * nit
    
    * nit
    
    * nit
---
 .github/workflows/core.yml                         |  23 ++--
 livy/README.md                                     |   9 +-
 livy/pom.xml                                       | 116 +----------------
 .../apache/zeppelin/livy/LivyInterpreterIT.java    |  64 +++------
 .../zeppelin/livy/LivySQLInterpreterTest.java      |   2 +-
 .../org/apache/zeppelin/livy/WithLivyServer.java   | 143 +++++++++++++++++++++
 testing/downloadLivy.sh                            |  16 ++-
 7 files changed, 195 insertions(+), 178 deletions(-)

diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml
index e441c82384..f004879e8b 100644
--- a/.github/workflows/core.yml
+++ b/.github/workflows/core.yml
@@ -423,7 +423,11 @@ jobs:
           rm -rf spark/interpreter/metastore_db
           ./mvnw verify -pl spark-submit,spark/interpreter -am 
-Dtest=org/apache/zeppelin/spark/* -Pspark-3.5 -Pspark-scala-2.13 -Phadoop3 
-Pintegration -DfailIfNoTests=false ${MAVEN_ARGS}
 
-  livy-0-7-with-spark-3-4-1-under-python3:
+  # The version combination is based on the facts:
+  # 1. official Livy 0.8 binary tarball is built against Spark 2.4
+  # 2. official Spark 2.4 binary tarball is built against Scala 2.11
+  # 3. Spark 2.4 support Python 2.7, 3.4 to 3.7
+  livy-0-8-with-spark-2-4-under-python37:
     runs-on: ubuntu-20.04
     steps:
       - name: Checkout
@@ -449,14 +453,14 @@ jobs:
       - name: install environment
         run: |
           ./mvnw install -DskipTests -pl livy -am  ${MAVEN_ARGS}
-          ./testing/downloadSpark.sh "3.4.1" "3"
-          ./testing/downloadLivy.sh "0.7.1-incubating"
-      - name: Setup conda environment with python 3.9 and R
+          ./testing/downloadSpark.sh "2.4.8" "2.7"
+          ./testing/downloadLivy.sh "0.8.0-incubating" "2.11"
+      - name: Setup conda environment with python 3.7 and R
         uses: conda-incubator/setup-miniconda@v2
         with:
-          activate-environment: python_3_with_R
-          environment-file: testing/env_python_3_with_R.yml
-          python-version: 3.9
+          activate-environment: python_37_with_R
+          environment-file: testing/env_python_3.7_with_R.yml
+          python-version: 3.7
           miniforge-variant: Mambaforge
           channels: conda-forge,defaults
           channel-priority: true
@@ -466,7 +470,10 @@ jobs:
         run: |
           R -e "IRkernel::installspec()"
       - name: run tests
-        run: ./mvnw verify -pl livy -am  ${MAVEN_ARGS}
+        run: |
+          export SPARK_HOME=$PWD/spark-2.4.8-bin-hadoop2.7
+          export LIVY_HOME=$PWD/apache-livy-0.8.0-incubating_2.11-bin
+          ./mvnw verify -pl livy -am ${MAVEN_ARGS}
 
   default-build:
     runs-on: ubuntu-20.04
diff --git a/livy/README.md b/livy/README.md
index 54311d9344..406af566ee 100644
--- a/livy/README.md
+++ b/livy/README.md
@@ -2,14 +2,13 @@
 Livy interpreter for Apache Zeppelin
 
 # Prerequisities
-You can follow the instructions at [Livy Quick 
Start](http://livy.io/quickstart.html) to set up livy.
+You can follow the instructions at [Livy Get 
Started](https://livy.apache.org/get-started/) to set up livy.
 
 # Run Integration Tests
-You can add integration test to 
[LivyInterpreter.java](https://github.com/apache/zeppelin/blob/master/livy/src/test/java/org/apache/zeppelin/livy/LivyInterpreterIT.java)
 and run the integration test either via the CI environment or locally. You 
need to download livy-0.2 and spark-1.5.2 to local, then use the following 
script to run the integration test.
+You can add integration test to 
[LivyInterpreter.java](https://github.com/apache/zeppelin/blob/master/livy/src/test/java/org/apache/zeppelin/livy/LivyInterpreterIT.java)
 and run the integration test either via the CI environment or locally. You 
need to download livy-0.8 and spark-2.4.8 to local, then use the following 
script to run the integration test.
 
 ```bash
-#!/usr/bin/env bash
-export LIVY_HOME=<path_of_livy_0.2.0>
-export SPARK_HOME=<path_of_spark-1.5.2>
+export LIVY_HOME=<path_of_livy_0.8.0>
+export SPARK_HOME=<path_of_spark-2.4.8>
 ./mvnw clean verify -pl livy -DfailIfNoTests=false
 ```
diff --git a/livy/pom.xml b/livy/pom.xml
index e004b8424e..96344673a2 100644
--- a/livy/pom.xml
+++ b/livy/pom.xml
@@ -37,107 +37,9 @@
         <commons.exec.version>1.3</commons.exec.version>
         <spring.web.version>4.3.0.RELEASE</spring.web.version>
         
<spring.security.kerberosclient>1.0.1.RELEASE</spring.security.kerberosclient>
-
-        <!--test library versions-->
-        <livy.version>0.7.1-incubating</livy.version>
-        <spark.version>2.4.8</spark.version>
-        <hadoop.version>${hadoop3.3.version}</hadoop.version>
     </properties>
 
     <dependencies>
-        <dependency>
-            <groupId>org.apache.livy</groupId>
-            <artifactId>livy-integration-test</artifactId>
-            <version>${livy.version}</version>
-            <scope>test</scope>
-            <exclusions>
-                <exclusion>
-                    <groupId>org.xerial.snappy</groupId>
-                    <artifactId>snappy-java</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>org.apache.spark</groupId>
-                    <artifactId>spark-core_${scala.binary.version}</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>org.apache.spark</groupId>
-                    <artifactId>spark-sql_${scala.binary.version}</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>org.apache.spark</groupId>
-                    
<artifactId>spark-streaming_${scala.binary.version}</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>org.apache.spark</groupId>
-                    <artifactId>spark-hive_${scala.binary.version}</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>org.apache.spark</groupId>
-                    <artifactId>spark-repl_${scala.binary.version}</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>org.apache.spark</groupId>
-                    <artifactId>spark-yarn_${scala.binary.version}</artifactId>
-                </exclusion>
-                <exclusion>
-                    <artifactId>hadoop-client</artifactId>
-                    <groupId>org.apache.hadoop</groupId>
-                </exclusion>
-                <exclusion>
-                    <artifactId>hadoop-common</artifactId>
-                    <groupId>org.apache.hadoop</groupId>
-                </exclusion>
-                <exclusion>
-                    <artifactId>hadoop-hdfs</artifactId>
-                    <groupId>org.apache.hadoop</groupId>
-                </exclusion>
-                <exclusion>
-                    <artifactId>hadoop-yarn-client</artifactId>
-                    <groupId>org.apache.hadoop</groupId>
-                </exclusion>
-                <exclusion>
-                    <artifactId>hadoop-yarn-server-tests</artifactId>
-                    <groupId>org.apache.hadoop</groupId>
-                </exclusion>
-            </exclusions>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.livy</groupId>
-            <artifactId>livy-test-lib</artifactId>
-            <version>${livy.version}</version>
-            <scope>test</scope>
-            <exclusions>
-                <exclusion>
-                    <groupId>org.xerial.snappy</groupId>
-                    <artifactId>snappy-java</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>org.apache.spark</groupId>
-                    <artifactId>spark-core_${scala.binary.version}</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>org.apache.spark</groupId>
-                    <artifactId>spark-sql_${scala.binary.version}</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>org.apache.spark</groupId>
-                    
<artifactId>spark-streaming_${scala.binary.version}</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>org.apache.spark</groupId>
-                    <artifactId>spark-hive_${scala.binary.version}</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>org.apache.spark</groupId>
-                    <artifactId>spark-repl_${scala.binary.version}</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>org.apache.spark</groupId>
-                    <artifactId>spark-yarn_${scala.binary.version}</artifactId>
-                </exclusion>
-            </exclusions>
-        </dependency>
-
         <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-exec</artifactId>
@@ -172,26 +74,16 @@
         </dependency>
 
         <dependency>
-            <groupId>org.apache.hadoop</groupId>
-            <artifactId>hadoop-client-api</artifactId>
-            <version>${hadoop.version}</version>
-            <scope>test</scope>
-        </dependency>
-
-        <dependency>
-            <groupId>org.apache.hadoop</groupId>
-            <artifactId>hadoop-client-runtime</artifactId>
-            <version>${hadoop.version}</version>
+            <groupId>org.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
             <scope>test</scope>
         </dependency>
 
         <dependency>
-            <groupId>org.apache.hadoop</groupId>
-            <artifactId>hadoop-client-minicluster</artifactId>
-            <version>${hadoop.version}</version>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
             <scope>test</scope>
         </dependency>
-
     </dependencies>
 
     <build>
diff --git a/livy/src/test/java/org/apache/zeppelin/livy/LivyInterpreterIT.java 
b/livy/src/test/java/org/apache/zeppelin/livy/LivyInterpreterIT.java
index f5b09f6920..16c060011f 100644
--- a/livy/src/test/java/org/apache/zeppelin/livy/LivyInterpreterIT.java
+++ b/livy/src/test/java/org/apache/zeppelin/livy/LivyInterpreterIT.java
@@ -18,8 +18,6 @@
 package org.apache.zeppelin.livy;
 
 import org.apache.commons.io.IOUtils;
-import org.apache.livy.test.framework.Cluster;
-import org.apache.livy.test.framework.Cluster$;
 import org.apache.zeppelin.interpreter.Interpreter;
 import org.apache.zeppelin.interpreter.InterpreterContext;
 import org.apache.zeppelin.interpreter.InterpreterException;
@@ -31,9 +29,7 @@ import 
org.apache.zeppelin.interpreter.InterpreterResultMessageOutput;
 import org.apache.zeppelin.interpreter.LazyOpenInterpreter;
 import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
 import org.apache.zeppelin.user.AuthenticationInfo;
-import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -48,46 +44,23 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.mock;
 
-@Disabled("FIXME: temporarily disable the broken tests")
-public class LivyInterpreterIT {
-  private static final Logger LOGGER = 
LoggerFactory.getLogger(LivyInterpreterIT.class);
-  private static Cluster cluster;
+public class LivyInterpreterIT extends WithLivyServer {
+  private static final Logger LOG = 
LoggerFactory.getLogger(LivyInterpreterIT.class);
   private static Properties properties;
 
   @BeforeAll
-  public static void setUp() {
+  public static void beforeAll() throws IOException {
     if (!checkPreCondition()) {
       return;
     }
-    cluster = Cluster$.MODULE$.get();
-    LOGGER.info("Starting livy at {}", cluster.livyEndpoint());
+    WithLivyServer.beforeAll();
     properties = new Properties();
-    properties.setProperty("zeppelin.livy.url", cluster.livyEndpoint());
+    properties.setProperty("zeppelin.livy.url", LIVY_ENDPOINT);
     properties.setProperty("zeppelin.livy.session.create_timeout", "120");
     properties.setProperty("zeppelin.livy.spark.sql.maxResult", "100");
     properties.setProperty("zeppelin.livy.displayAppInfo", "false");
   }
 
-  @AfterAll
-  public static void tearDown() {
-    if (cluster != null) {
-      LOGGER.info("Shutting down livy at {}", cluster.livyEndpoint());
-      cluster.cleanUp();
-    }
-  }
-
-  public static boolean checkPreCondition() {
-    if (System.getenv("LIVY_HOME") == null) {
-      LOGGER.warn(("livy integration is skipped because LIVY_HOME is not 
set"));
-      return false;
-    }
-    if (System.getenv("SPARK_HOME") == null) {
-      LOGGER.warn(("livy integration is skipped because SPARK_HOME is not 
set"));
-      return false;
-    }
-    return true;
-  }
-
 
   @Test
   void testSparkInterpreter() throws InterpreterException {
@@ -141,7 +114,6 @@ public class LivyInterpreterIT {
         .setAuthenticationInfo(authInfo)
         .setInterpreterOut(output)
         .build();
-    ;
 
     InterpreterResult result = sparkInterpreter.interpret("sc.parallelize(1 to 
10).sum()", context);
     assertEquals(InterpreterResult.Code.SUCCESS, result.code(), 
result.toString());
@@ -294,11 +266,10 @@ public class LivyInterpreterIT {
     assertEquals(InterpreterResult.Code.ERROR, result.code());
     assertEquals(InterpreterResult.Type.TEXT, 
result.message().get(0).getType());
 
-    if (!isSpark2) {
-      assertTrue(result.message().get(0).getData().contains("Table not 
found"));
-    } else {
-      assertTrue(result.message().get(0).getData().contains("Table or view not 
found"));
-    }
+    String errMsg = result.message().get(0).getData();
+    assertTrue(errMsg.contains("Table not found") ||
+        errMsg.contains("Table or view not found") ||
+        errMsg.contains("TABLE_OR_VIEW_NOT_FOUND"));
 
     // test sql cancel
     if 
(sqlInterpreter.getLivyVersion().newerThanEquals(LivyVersion.LIVY_0_3_0)) {
@@ -431,7 +402,7 @@ public class LivyInterpreterIT {
         
assertTrue(result.message().get(0).getData().contains("[Row(_1=u'hello', 
_2=20)]")
             || result.message().get(0).getData().contains("[Row(_1='hello', 
_2=20)]"));
       } else {
-        result = 
pysparkInterpreter.interpret("df=spark.createDataFrame([(\"hello\",20)])\n"
+        result = 
pysparkInterpreter.interpret("df=spark.createDataFrame([('hello',20)])\n"
             + "df.collect()", context);
         assertEquals(InterpreterResult.Code.SUCCESS, result.code(), 
result.toString());
         assertEquals(1, result.message().size());
@@ -485,7 +456,7 @@ public class LivyInterpreterIT {
   }
 
   @Test
-  void testSparkInterpreterWithDisplayAppInfo_StringWithoutTruncation()
+  void testSparkInterpreterStringWithoutTruncation()
       throws InterpreterException {
     if (!checkPreCondition()) {
       return;
@@ -493,7 +464,6 @@ public class LivyInterpreterIT {
     InterpreterGroup interpreterGroup = new InterpreterGroup("group_1");
     interpreterGroup.put("session_1", new ArrayList<Interpreter>());
     Properties properties2 = new Properties(properties);
-    properties2.put("zeppelin.livy.displayAppInfo", "true");
     // enable spark ui because it is disabled by livy integration test
     properties2.put("livy.spark.ui.enabled", "true");
     
properties2.put(LivySparkSQLInterpreter.ZEPPELIN_LIVY_SPARK_SQL_FIELD_TRUNCATE, 
"false");
@@ -519,21 +489,19 @@ public class LivyInterpreterIT {
     try {
       InterpreterResult result = sparkInterpreter.interpret("sc.version", 
context);
       assertEquals(InterpreterResult.Code.SUCCESS, result.code(), 
result.toString());
-      assertEquals(2, result.message().size());
-      // check yarn appId and ensure it is not null
-      assertTrue(result.message().get(1).getData().contains("Spark Application 
Id: application_"));
+      assertEquals(1, result.message().size(), result.toString());
 
       // html output
       String htmlCode = "println(\"%html <h1> hello </h1>\")";
       result = sparkInterpreter.interpret(htmlCode, context);
       assertEquals(InterpreterResult.Code.SUCCESS, result.code(), 
result.toString());
-      assertEquals(2, result.message().size());
+      assertEquals(1, result.message().size());
       assertEquals(InterpreterResult.Type.HTML, 
result.message().get(0).getType());
 
       // detect spark version
       result = sparkInterpreter.interpret("sc.version", context);
       assertEquals(InterpreterResult.Code.SUCCESS, result.code(), 
result.toString());
-      assertEquals(2, result.message().size());
+      assertEquals(1, result.message().size());
 
       boolean isSpark2 = isSpark2(sparkInterpreter, context);
 
@@ -552,7 +520,7 @@ public class LivyInterpreterIT {
                 + ".toDF(\"col_1\", \"col_2\")\n"
                 + "df.collect()", context);
         assertEquals(InterpreterResult.Code.SUCCESS, result.code(), 
result.toString());
-        assertEquals(2, result.message().size());
+        assertEquals(1, result.message().size());
         assertTrue(result.message().get(0).getData()
             .contains("Array[org.apache.spark.sql.Row] = 
Array([12characters12characters,20])"));
       }
@@ -673,7 +641,7 @@ public class LivyInterpreterIT {
     try {
       InterpreterResult result = sparkInterpreter.interpret("sc.version\n" +
               "assert(sc.getConf.get(\"spark.executor.cores\") == \"4\" && " +
-                      "sc.getConf.get(\"spark.app.name\") == 
\"zeppelin-livy\")"
+                     "sc.getConf.get(\"spark.app.name\") == \"zeppelin-livy\")"
               , context);
       assertEquals(InterpreterResult.Code.SUCCESS, result.code(), 
result.toString());
       assertEquals(1, result.message().size());
diff --git 
a/livy/src/test/java/org/apache/zeppelin/livy/LivySQLInterpreterTest.java 
b/livy/src/test/java/org/apache/zeppelin/livy/LivySQLInterpreterTest.java
index 6294473371..3461c096a4 100644
--- a/livy/src/test/java/org/apache/zeppelin/livy/LivySQLInterpreterTest.java
+++ b/livy/src/test/java/org/apache/zeppelin/livy/LivySQLInterpreterTest.java
@@ -35,7 +35,7 @@ class LivySQLInterpreterTest {
   private LivySparkSQLInterpreter sqlInterpreter;
 
   @BeforeEach
-  public void setUp() {
+  public void beforeEach() {
     Properties properties = new Properties();
     properties.setProperty("zeppelin.livy.url", "http://localhost:8998";);
     properties.setProperty("zeppelin.livy.session.create_timeout", "120");
diff --git a/livy/src/test/java/org/apache/zeppelin/livy/WithLivyServer.java 
b/livy/src/test/java/org/apache/zeppelin/livy/WithLivyServer.java
new file mode 100644
index 0000000000..d0a77e427b
--- /dev/null
+++ b/livy/src/test/java/org/apache/zeppelin/livy/WithLivyServer.java
@@ -0,0 +1,143 @@
+/*
+ * 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.livy;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.awaitility.Awaitility.await;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+abstract class WithLivyServer {
+  private static final Logger LOG = 
LoggerFactory.getLogger(WithLivyServer.class);
+
+  private static Optional<Process> livy = Optional.empty();
+
+  protected static final String LIVY_HOME = System.getenv("LIVY_HOME");
+  protected static final String SPARK_HOME = System.getenv("SPARK_HOME");
+
+  protected static final File TMP_DIR = new 
File(System.getProperty("java.io.tmpdir"), "livy-it");
+  protected static final File LIVY_CONF_DIR = new File(TMP_DIR, "conf");
+  protected static final File LIVY_LOG_DIR = new File(TMP_DIR, "logs");
+
+  protected static String LIVY_ENDPOINT;
+
+  public static boolean checkPreCondition() {
+    if (System.getenv("LIVY_HOME") == null) {
+      LOG.warn(("livy integration is skipped because LIVY_HOME is not set"));
+      return false;
+    }
+    if (System.getenv("SPARK_HOME") == null) {
+      LOG.warn(("livy integration is skipped because SPARK_HOME is not set"));
+      return false;
+    }
+    return true;
+  }
+
+  @BeforeAll
+  public static void beforeAll() throws IOException {
+    if (!checkPreCondition()) {
+      return;
+    }
+    assertFalse(livy.isPresent());
+    if (TMP_DIR.exists()) {
+      FileUtils.deleteQuietly(TMP_DIR);
+    }
+    assertTrue(LIVY_CONF_DIR.mkdirs());
+    assertTrue(LIVY_LOG_DIR.mkdirs());
+    Files.copy(
+        Paths.get(LIVY_HOME, "conf", "log4j.properties.template"),
+        LIVY_CONF_DIR.toPath().resolve("log4j.properties"));
+
+    LOG.info("SPARK_HOME: {}", SPARK_HOME);
+    LOG.info("LIVY_HOME: {}", LIVY_HOME);
+    LOG.info("LIVY_CONF_DIR: {}", LIVY_CONF_DIR.getAbsolutePath());
+    LOG.info("LIVY_LOG_DIR: {}", LIVY_LOG_DIR.getAbsolutePath());
+
+    File logFile = new File(TMP_DIR, "output.log");
+    assertTrue(logFile.createNewFile());
+    LOG.info("Redirect Livy's log to {}", logFile.getAbsolutePath());
+
+    ProcessBuilder pb = new ProcessBuilder(LIVY_HOME + "/bin/livy-server")
+        .directory(TMP_DIR)
+        .redirectErrorStream(true)
+        .redirectOutput(ProcessBuilder.Redirect.appendTo(logFile));
+
+    pb.environment().put("JAVA_HOME", System.getProperty("java.home"));
+    pb.environment().put("LIVY_CONF_DIR", LIVY_CONF_DIR.getAbsolutePath());
+    pb.environment().put("LIVY_LOG_DIR", LIVY_LOG_DIR.getAbsolutePath());
+    pb.environment().put("SPARK_LOCAL_IP", "127.0.0.1");
+    Process livyProc = pb.start();
+
+    await().atMost(30, SECONDS).pollInterval(2, SECONDS).until(() -> {
+      try {
+        int exitCode = livyProc.exitValue();
+        throw new IOException("Child process exited unexpectedly (exit code " 
+ exitCode + ")");
+      } catch (IllegalThreadStateException ignore) {
+        // Process does not exit, try again.
+      }
+      try (Stream<String> lines = Files.lines(logFile.toPath(), 
StandardCharsets.UTF_8)) {
+        // An example of bootstrap log:
+        //   24/03/24 05:51:38 INFO WebServer: Starting server on 
http://cheng-pan-mbp.lan:8998
+        Optional<String> started =
+            lines.filter(line -> line.contains("Starting server on 
")).findFirst();
+        started.ifPresent(line ->
+            LIVY_ENDPOINT = StringUtils.substringAfter(line, "Starting server 
on ").trim());
+        return started.isPresent();
+      }
+    });
+
+    LOG.info("Livy Server is started at {}", LIVY_ENDPOINT);
+    livy = Optional.of(livyProc);
+  }
+
+  @AfterAll
+  public static void afterAll() {
+    livy.filter(Process::isAlive).ifPresent(proc -> {
+      try {
+        LOG.info("Stopping the Livy Server running at {}", LIVY_ENDPOINT);
+        proc.destroy();
+        if (!proc.waitFor(10, SECONDS)) {
+          LOG.warn("Forcibly stopping the Livy Server running at {}", 
LIVY_ENDPOINT);
+          proc.destroyForcibly();
+          assertFalse(proc.isAlive());
+        }
+      } catch (InterruptedException ignore) {
+        if (proc.isAlive()) {
+          LOG.warn("Forcibly stopping the Livy Server running at {}", 
LIVY_ENDPOINT);
+          proc.destroyForcibly();
+          assertFalse(proc.isAlive());
+        }
+      }
+    });
+  }
+}
diff --git a/testing/downloadLivy.sh b/testing/downloadLivy.sh
index 7f2faf3ffe..f09837a757 100755
--- a/testing/downloadLivy.sh
+++ b/testing/downloadLivy.sh
@@ -16,13 +16,21 @@
 # limitations under the License.
 #
 
-if [[ "$#" -ne 1 ]]; then
-    echo "usage) $0 [livy version]"
-    echo "   eg) $0 0.2"
+if [[ "$#" -ne 1 && "$#" -ne 2 ]]; then
+    echo "usage) $0 <livy version> [scala version]"
+    echo "   eg) $0 0.7.1-incubating"
+    echo "       $0 0.8.0-incubating 2.11"
     exit 0
 fi
 
+# See simple version normalization:
+# http://stackoverflow.com/questions/16989598/bash-comparing-version-numbers
+function version { echo "$@" | awk -F. '{ printf("%03d%03d%03d\n", $1,$2,$3); 
}'; }
 LIVY_VERSION="${1}"
+SCALA_VERSION_SUFFIX=""
+if [ $(version $LIVY_VERSION) -ge $(version "0.8.0") ]; then
+    SCALA_VERSION_SUFFIX="_${2}"
+fi
 
 set -xe
 
@@ -49,7 +57,7 @@ download_with_retry() {
 }
 
 LIVY_CACHE=".livy-dist"
-LIVY_ARCHIVE="apache-livy-${LIVY_VERSION}-bin"
+LIVY_ARCHIVE="apache-livy-${LIVY_VERSION}${SCALA_VERSION_SUFFIX}-bin"
 export LIVY_HOME="${ZEPPELIN_HOME}/livy-server-$LIVY_VERSION"
 echo "LIVY_HOME is ${LIVY_HOME}"
 

Reply via email to