Support Kotlin debug testing

Bug: 63608278
Change-Id: Idfd145db8dd5182a6fbdf611643ba84083f797cd
diff --git a/build.gradle b/build.gradle
index a00c516..ef19156 100644
--- a/build.gradle
+++ b/build.gradle
@@ -51,6 +51,12 @@
         }
         output.resourcesDir = 'build/classes/debugTestResourcesJava8'
     }
+    debugTestResourcesKotlin {
+        java {
+            srcDirs = ['src/test/debugTestResourcesKotlin']
+        }
+        output.resourcesDir = 'build/classes/debugTestResourcesKotlin'
+    }
     examples {
         java {
             srcDirs = ['src/test/examples', 'build/generated/source/proto/examples/javalite/' ]
@@ -127,6 +133,7 @@
     supportLibs 'com.android.support:support-v4:25.4.0'
     supportLibs 'junit:junit:4.12'
     supportLibs 'com.android.support.test.espresso:espresso-core:3.0.0'
+    debugTestResourcesKotlinCompileOnly 'org.jetbrains.kotlin:kotlin-stdlib:1.1.3'
 }
 
 protobuf {
@@ -172,6 +179,7 @@
                 "jdwp-tests.tar.gz",
                 "jasmin.tar.gz",
                 "jctf.tar.gz",
+                "kotlin.tar.gz",
                 "android_cts_baseline.tar.gz",
         ],
         // All dex-vms have a fixed OS of Linux, as they are only supported on Linux, and will be run in a Docker
@@ -523,9 +531,23 @@
         from "build/test/debugTestResourcesJava8/classes"
         include "**/*.class"
     }
+    def kotlinResourcesDir = file("src/test/debugTestResourcesKotlin")
+    def kotlinHostJar = "debug_test_resources_kotlin.jar"
+    task "jar_debugTestResourcesKotlin"(type: Exec) {
+        if (OperatingSystem.current().isWindows()) {
+            executable file("third_party/kotlin/kotlinc/bin/kotlinc.bat")
+        } else {
+            executable file("third_party/kotlin/kotlinc/bin/kotlinc");
+        }
+        args "-include-runtime"
+        args "-d"
+        args "build/test/${kotlinHostJar}"
+        args fileTree(dir: kotlinResourcesDir, include: '**/*.kt')
+    }
     dependsOn downloadDeps
     dependsOn jar_debugTestResources
     dependsOn jar_debugTestResourcesJava8
+    dependsOn jar_debugTestResourcesKotlin
 }
 
 task buildExampleJars {
diff --git a/src/test/debugTestResourcesKotlin/KotlinApp.kt b/src/test/debugTestResourcesKotlin/KotlinApp.kt
new file mode 100644
index 0000000..7c15337
--- /dev/null
+++ b/src/test/debugTestResourcesKotlin/KotlinApp.kt
@@ -0,0 +1,21 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+class KotlinApp {
+    companion object {
+        @JvmStatic fun main(args: Array<String>) {
+            println("Hello world!")
+            val instance = KotlinApp()
+            instance.processObject(instance, instance::printObject)
+        }
+    }
+
+    fun processObject(obj: Any, func: (Any) -> Unit) {
+        func(obj)
+    }
+
+    fun printObject(obj: Any) {
+        println(obj)
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 57a1ce8..6340728 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -3,15 +3,18 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.debug;
 
+import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OffOrAuto;
 import com.google.common.collect.ImmutableList;
 import java.io.File;
+import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayDeque;
@@ -89,12 +92,15 @@
       .get(ToolHelper.BUILD_DIR, "test", "debug_test_resources.jar");
   private static final Path DEBUGGEE_JAVA8_JAR = Paths
       .get(ToolHelper.BUILD_DIR, "test", "debug_test_resources_java8.jar");
+  private static final Path DEBUGGEE_KOTLIN_JAR = Paths
+      .get(ToolHelper.BUILD_DIR, "test", "debug_test_resources_kotlin.jar");
 
   @ClassRule
   public static TemporaryFolder temp = new TemporaryFolder();
   private static Path jdwpDexD8 = null;
   private static Path debuggeeDexD8 = null;
   private static Path debuggeeJava8DexD8 = null;
+  private static Path debuggeeKotlinDexD8 = null;
 
   @Rule
   public TestName testName = new TestName();
@@ -103,43 +109,31 @@
   public static void setUp() throws Exception {
     // Convert jar to dex with d8 with debug info
     int minSdk = ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm());
-    {
-      Path dexOutputDir = temp.newFolder("d8-jdwp-jar").toPath();
-      jdwpDexD8 = dexOutputDir.resolve("classes.dex");
-      ToolHelper.runD8(
-          D8Command.builder()
-              .addProgramFiles(JDWP_JAR)
-              .setOutputPath(dexOutputDir)
-              .setMinApiLevel(minSdk)
-              .setMode(CompilationMode.DEBUG)
-              .build());
-    }
-    {
-      Path dexOutputDir = temp.newFolder("d8-debuggee-jar").toPath();
-      debuggeeDexD8 = dexOutputDir.resolve("classes.dex");
-      ToolHelper.runD8(
-          D8Command.builder()
-              .addProgramFiles(DEBUGGEE_JAR)
-              .setOutputPath(dexOutputDir)
-              .setMinApiLevel(minSdk)
-              .setMode(CompilationMode.DEBUG)
-              .build());
-    }
-    {
-      Path dexOutputDir = temp.newFolder("d8-debuggee-java8-jar").toPath();
-      debuggeeJava8DexD8 = dexOutputDir.resolve("classes.dex");
-      ToolHelper.runD8(
-          D8Command.builder()
-              .addProgramFiles(DEBUGGEE_JAVA8_JAR)
-              .setOutputPath(dexOutputDir)
-              .setMinApiLevel(minSdk)
-              .setMode(CompilationMode.DEBUG)
-              .build(),
-          options -> {
-            // Enable desugaring for preN runtimes
-            options.interfaceMethodDesugaring = OffOrAuto.Auto;
-          });
-    }
+    jdwpDexD8 = compileJarToDex(JDWP_JAR, minSdk, "d8-jdwp-jar", null);
+    debuggeeDexD8 = compileJarToDex(DEBUGGEE_JAR, minSdk, "d8-debuggee-jar", null);
+    debuggeeJava8DexD8 = compileJarToDex(DEBUGGEE_JAVA8_JAR, minSdk, "d8-debuggee-java8-jar",
+        options -> {
+          // Enable desugaring for preN runtimes
+          options.interfaceMethodDesugaring = OffOrAuto.Auto;
+        });
+    debuggeeKotlinDexD8 = compileJarToDex(DEBUGGEE_KOTLIN_JAR, minSdk, "d8-debuggee-kotlin-jar",
+        null);
+  }
+
+  private static Path compileJarToDex(Path jarToCompile, int minSdk, String tempDirName,
+      Consumer<InternalOptions> optionsConsumer) throws IOException, CompilationException {
+    assert jarToCompile.toFile().exists();
+    Path dexOutputDir = temp.newFolder(tempDirName).toPath();
+    ToolHelper.runD8(
+        D8Command.builder()
+            .addProgramFiles(jarToCompile)
+            .setOutputPath(dexOutputDir)
+            .setMinApiLevel(minSdk)
+            .setMode(CompilationMode.DEBUG)
+            .build(),
+        optionsConsumer);
+    return dexOutputDir.resolve("classes.dex");
+
   }
 
   protected final boolean supportsDefaultMethod() {
@@ -158,7 +152,7 @@
 
   protected final void runDebugTest(String debuggeeClass, List<JUnit3Wrapper.Command> commands)
       throws Throwable {
-    runDebugTest(false, debuggeeClass, commands);
+    runDebugTest(LanguageFeatures.JAVA_7, debuggeeClass, commands);
   }
 
   protected final void runDebugTestJava8(String debuggeeClass, JUnit3Wrapper.Command... commands)
@@ -168,12 +162,54 @@
 
   protected final void runDebugTestJava8(String debuggeeClass, List<JUnit3Wrapper.Command> commands)
       throws Throwable {
-    runDebugTest(true, debuggeeClass, commands);
+    runDebugTest(LanguageFeatures.JAVA_8, debuggeeClass, commands);
   }
 
-  private void runDebugTest(boolean useJava8, String debuggeeClass,
-      List<JUnit3Wrapper.Command> commands)
+  protected final void runDebugTestKotlin(String debuggeeClass, JUnit3Wrapper.Command... commands)
       throws Throwable {
+    runDebugTestKotlin(debuggeeClass, Arrays.asList(commands));
+  }
+
+  protected final void runDebugTestKotlin(String debuggeeClass,
+      List<JUnit3Wrapper.Command> commands) throws Throwable {
+    runDebugTest(LanguageFeatures.KOTLIN, debuggeeClass, commands);
+  }
+
+  protected enum LanguageFeatures {
+    JAVA_7(DEBUGGEE_JAR) {
+      @Override
+      public Path getDexPath() {
+        return debuggeeDexD8;
+      }
+    },
+    JAVA_8(DEBUGGEE_JAVA8_JAR) {
+      @Override
+      public Path getDexPath() {
+        return debuggeeJava8DexD8;
+      }
+    },
+    KOTLIN(DEBUGGEE_KOTLIN_JAR) {
+      @Override
+      public Path getDexPath() {
+        return debuggeeKotlinDexD8;
+      }
+    };
+
+    private final Path jarPath;
+
+    LanguageFeatures(Path jarPath) {
+      this.jarPath = jarPath;
+    }
+
+    public Path getJarPath() {
+      return jarPath;
+    }
+
+    public abstract Path getDexPath();
+  }
+
+  private void runDebugTest(LanguageFeatures languageFeatures, String debuggeeClass,
+      List<JUnit3Wrapper.Command> commands) throws Throwable {
     // Skip test due to unsupported runtime.
     Assume.assumeTrue("Skipping test " + testName.getMethodName() + " because ART is not supported",
         ToolHelper.artSupported());
@@ -183,15 +219,9 @@
 
     String[] paths;
     if (RUNTIME_KIND == RuntimeKind.JAVA) {
-      paths = new String[] {
-          JDWP_JAR.toString(),
-          useJava8 ? DEBUGGEE_JAVA8_JAR.toString() : DEBUGGEE_JAR.toString()
-      };
+      paths = new String[] { JDWP_JAR.toString(), languageFeatures.getJarPath().toString() };
     } else {
-      paths = new String[] {
-          jdwpDexD8.toString(),
-          useJava8 ? debuggeeJava8DexD8.toString() : debuggeeDexD8.toString()
-      };
+      paths = new String[] { jdwpDexD8.toString(), languageFeatures.getDexPath().toString() };
     }
     new JUnit3Wrapper(debuggeeClass, paths, commands).runBare();
   }
@@ -580,6 +610,7 @@
         }
 
         public String getSourceFile() {
+          // TODO(shertz) support JSR-45
           Location location = getLocation();
           CommandPacket sourceFileCommand = new CommandPacket(
               JDWPCommands.ReferenceTypeCommandSet.CommandSetID,
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinTest.java b/src/test/java/com/android/tools/r8/debug/KotlinTest.java
new file mode 100644
index 0000000..1700964
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/KotlinTest.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class KotlinTest extends DebugTestBase {
+
+  @Test
+  public void testKotlinApp() throws Throwable {
+    runDebugTestKotlin("KotlinApp",
+        breakpoint("KotlinApp$Companion", "main"),
+        run(),
+        inspect(s -> {
+          Assert.assertEquals("KotlinApp.kt", s.getSourceFile());
+          Assert.assertEquals(8, s.getLineNumber());
+          s.checkLocal("this");
+          s.checkLocal("args");
+        }),
+        stepOver(),
+        inspect(s -> {
+          Assert.assertEquals(9, s.getLineNumber());
+          s.checkLocal("this");
+          s.checkLocal("args");
+        }),
+        stepOver(),
+        inspect(s -> {
+          Assert.assertEquals(10, s.getLineNumber());
+          s.checkLocal("this");
+          s.checkLocal("args");
+          s.checkLocal("instance");
+        }),
+        run());
+  }
+
+}
diff --git a/third_party/kotlin.tar.gz.sha1 b/third_party/kotlin.tar.gz.sha1
new file mode 100644
index 0000000..942c691
--- /dev/null
+++ b/third_party/kotlin.tar.gz.sha1
@@ -0,0 +1 @@
+21b6244cb0a5bca1f1d046d00540a610c02cd714
\ No newline at end of file