Add java8 debug tests

Adds tests for debugging lambda and default methods. The test should
be written to support desugaring.

Bug: 37731140
Bug: 38218137
Change-Id: I2e79346c722262331e560786a8999733f7b596b6
diff --git a/build.gradle b/build.gradle
index 8eb9643..f0cd8a2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -29,6 +29,12 @@
         }
         output.resourcesDir = 'build/classes/debugTestResources'
     }
+    debugTestResourcesJava8 {
+        java {
+            srcDirs = ['src/test/debugTestResourcesJava8']
+        }
+        output.resourcesDir = 'build/classes/debugTestResourcesJava8'
+    }
     examples {
         java {
             srcDirs = ['src/test/examples']
@@ -341,7 +347,6 @@
 }
 
 task buildDebugTestResourcesJars {
-    dependsOn downloadDeps
     def resourcesDir = file("src/test/debugTestResources")
     def hostJar = "debug_test_resources.jar"
     task "compile_debugTestResources"(type: JavaCompile) {
@@ -358,7 +363,25 @@
         from "build/test/debugTestResources/classes"
         include "**/*.class"
     }
+    def java8ResourcesDir = file("src/test/debugTestResourcesJava8")
+    def java8HostJar = "debug_test_resources_java8.jar"
+    task "compile_debugTestResourcesJava8"(type: JavaCompile) {
+        source = fileTree(dir: java8ResourcesDir, include: '**/*.java')
+        destinationDir = file("build/test/debugTestResourcesJava8/classes")
+        classpath = sourceSets.main.compileClasspath
+        sourceCompatibility = JavaVersion.VERSION_1_8
+        targetCompatibility = JavaVersion.VERSION_1_8
+        options.compilerArgs += ["-g", "-Xlint:-options"]
+    }
+    task "jar_debugTestResourcesJava8"(type: Jar, dependsOn: "compile_debugTestResourcesJava8") {
+        archiveName = java8HostJar
+        destinationDir = file("build/test/")
+        from "build/test/debugTestResourcesJava8/classes"
+        include "**/*.class"
+    }
+    dependsOn downloadDeps
     dependsOn jar_debugTestResources
+    dependsOn jar_debugTestResourcesJava8
 }
 
 task buildExampleJars {
diff --git a/src/test/debugTestResourcesJava8/DebugDefaultMethod.java b/src/test/debugTestResourcesJava8/DebugDefaultMethod.java
new file mode 100644
index 0000000..db216b3
--- /dev/null
+++ b/src/test/debugTestResourcesJava8/DebugDefaultMethod.java
@@ -0,0 +1,35 @@
+// 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.
+
+public class DebugDefaultMethod {
+
+  interface I {
+    default void doSomething(String msg) {
+      String name = getClass().getName();
+      System.out.println(name + ": " + msg);
+    }
+  }
+
+  static class DefaultImpl implements I {
+  }
+
+  static class OverrideImpl implements I {
+
+    @Override
+    public void doSomething(String msg) {
+      String newMsg = "OVERRIDE" + msg;
+      System.out.println(newMsg);
+    }
+  }
+
+  private static void testDefaultMethod(I i) {
+    i.doSomething("Test");
+  }
+
+  public static void main(String[] args) {
+    testDefaultMethod(new DefaultImpl());
+    testDefaultMethod(new OverrideImpl());
+  }
+
+}
diff --git a/src/test/debugTestResourcesJava8/DebugLambda.java b/src/test/debugTestResourcesJava8/DebugLambda.java
new file mode 100644
index 0000000..c3b8695
--- /dev/null
+++ b/src/test/debugTestResourcesJava8/DebugLambda.java
@@ -0,0 +1,23 @@
+// 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.
+
+public class DebugLambda {
+
+  interface I {
+    int getInt();
+  }
+
+  private static void printInt(I i) {
+    System.out.println(i.getInt());
+  }
+
+  public static void testLambda(int i, int j) {
+    printInt(() -> i + j);
+  }
+
+  public static void main(String[] args) {
+    DebugLambda.testLambda(5, 10);
+  }
+
+}
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 0b042a9..5f9a95c 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -4,11 +4,12 @@
 package com.android.tools.r8.debug;
 
 import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.D8;
 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.OffOrAuto;
 import com.google.common.collect.ImmutableList;
 import java.io.File;
 import java.nio.file.Path;
@@ -78,11 +79,14 @@
 
   private static final Path DEBUGGEE_JAR = Paths
       .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");
 
   @ClassRule
   public static TemporaryFolder temp = new TemporaryFolder();
   private static Path jdwpDexD8 = null;
   private static Path debuggeeDexD8 = null;
+  private static Path debuggeeJava8DexD8 = null;
 
   @Rule
   public TestName testName = new TestName();
@@ -95,7 +99,7 @@
       Path jdwpJar = ToolHelper.getJdwpTestsJarPath(minSdk);
       Path dexOutputDir = temp.newFolder("d8-jdwp-jar").toPath();
       jdwpDexD8 = dexOutputDir.resolve("classes.dex");
-      D8.run(
+      ToolHelper.runD8(
           D8Command.builder()
               .addProgramFiles(jdwpJar)
               .setOutputPath(dexOutputDir)
@@ -106,7 +110,7 @@
     {
       Path dexOutputDir = temp.newFolder("d8-debuggee-jar").toPath();
       debuggeeDexD8 = dexOutputDir.resolve("classes.dex");
-      D8.run(
+      ToolHelper.runD8(
           D8Command.builder()
               .addProgramFiles(DEBUGGEE_JAR)
               .setOutputPath(dexOutputDir)
@@ -114,6 +118,25 @@
               .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;
+          });
+    }
+  }
+
+  protected final boolean supportsDefaultMethod() {
+    return ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm()) >= Constants.ANDROID_N_API;
   }
 
   protected final void runDebugTest(String debuggeeClass, JUnit3Wrapper.Command... commands)
@@ -123,6 +146,22 @@
 
   protected final void runDebugTest(String debuggeeClass, List<JUnit3Wrapper.Command> commands)
       throws Throwable {
+    runDebugTest(debuggeeDexD8, debuggeeClass, commands);
+  }
+
+  protected final void runDebugTestJava8(String debuggeeClass, JUnit3Wrapper.Command... commands)
+      throws Throwable {
+    runDebugTestJava8(debuggeeClass, Arrays.asList(commands));
+  }
+
+  protected final void runDebugTestJava8(String debuggeeClass, List<JUnit3Wrapper.Command> commands)
+      throws Throwable {
+    runDebugTest(debuggeeJava8DexD8, debuggeeClass, commands);
+  }
+
+  private void runDebugTest(Path debuggeeExec, 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());
@@ -131,7 +170,7 @@
             .getDexVm(), UNSUPPORTED_ART_VERSIONS.contains(ToolHelper.getDexVm()));
 
     // Run with ART.
-    String[] paths = new String[]{jdwpDexD8.toString(), debuggeeDexD8.toString()};
+    String[] paths = new String[]{jdwpDexD8.toString(), debuggeeExec.toString()};
     new JUnit3Wrapper(debuggeeClass, paths, commands).runBare();
   }
 
@@ -172,6 +211,10 @@
     return new JUnit3Wrapper.Command.StepCommand(stepDepth, stepFilter);
   }
 
+  protected final JUnit3Wrapper.Command checkLocal(String localName) {
+    return inspect(t -> t.checkLocal(localName));
+  }
+
   protected final JUnit3Wrapper.Command checkLocal(String localName, Value expectedValue) {
     return inspect(t -> t.checkLocal(localName, expectedValue));
   }
@@ -308,6 +351,18 @@
         // Capture the context of the event suspension.
         updateEventContext((EventThread) parsedEvent);
 
+        if (DEBUG_TESTS && debuggeeState.location != null) {
+          // Dump location
+          String classSig = getMirror().getClassSignature(debuggeeState.location.classID);
+          String methodName = getMirror()
+              .getMethodName(debuggeeState.location.classID, debuggeeState.location.methodID);
+          String methodSig = getMirror()
+              .getMethodSignature(debuggeeState.location.classID, debuggeeState.location.methodID);
+          System.out.println(String
+              .format("Suspended in %s#%s%s@%x", classSig, methodName, methodSig,
+                  Long.valueOf(debuggeeState.location.index)));
+        }
+
         // Handle event.
         EventHandler eh = events.get(requestID);
         assert eh != null;
@@ -346,6 +401,10 @@
         return this.location;
       }
 
+      public void checkLocal(String localName) {
+        getVariableAt(getLocation(), localName);
+      }
+
       public void checkLocal(String localName, Value expectedValue) {
         Variable localVar = getVariableAt(getLocation(), localName);
 
@@ -767,7 +826,27 @@
       public boolean skipLocation(VmMirror mirror, Location location) {
         // TODO(shertz) we also need to skip class loaders to act like IntelliJ.
         // Skip synthetic methods.
-        return isSyntheticMethod(mirror, location);
+        if (isLambdaMethod(mirror, location)) {
+          // Lambda methods are synthetic but we do want to stop there.
+          if (DEBUG_TESTS) {
+            System.out.println("NOT skipping lambda implementation method");
+          }
+          return false;
+        }
+        if (isInLambdaClass(mirror, location)) {
+          // Lambda classes must be skipped since they are only wrappers around lambda code.
+          if (DEBUG_TESTS) {
+            System.out.println("Skipping lambda class wrapper method");
+          }
+          return true;
+        }
+        if (isSyntheticMethod(mirror, location)) {
+          if (DEBUG_TESTS) {
+            System.out.println("Skipping synthetic method");
+          }
+          return true;
+        }
+        return false;
       }
 
       private static boolean isSyntheticMethod(VmMirror mirror, Location location) {
@@ -787,6 +866,16 @@
         }
         return false;
       }
+
+      private static boolean isInLambdaClass(VmMirror mirror, Location location) {
+        String classSig = mirror.getClassSignature(location.classID);
+        return classSig.contains("$$Lambda$");
+      }
+
+      private boolean isLambdaMethod(VmMirror mirror, Location location) {
+        String methodName = mirror.getMethodName(location.classID, location.methodID);
+        return methodName.startsWith("lambda$");
+      }
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/debug/DefaultMethodTest.java b/src/test/java/com/android/tools/r8/debug/DefaultMethodTest.java
new file mode 100644
index 0000000..ea116a7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/DefaultMethodTest.java
@@ -0,0 +1,65 @@
+// 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 com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+
+public class DefaultMethodTest extends DebugTestBase {
+
+  @Test
+  public void testDefaultMethod() throws Throwable {
+    String debuggeeClass = "DebugDefaultMethod";
+    String parameterName = "msg";
+    String localVariableName = "name";
+
+    List<Command> commands = new ArrayList<>();
+    commands.add(breakpoint(debuggeeClass, "testDefaultMethod"));
+    commands.add(run());
+    commands.add(checkMethod(debuggeeClass, "testDefaultMethod"));
+    commands.add(checkLine(27));
+    if (!supportsDefaultMethod()) {
+      // We desugared default method. This means we're going to step through an extra (forward)
+      // method first.
+      commands.add(stepInto());
+    }
+    commands.add(stepInto());
+    commands.add(checkLocal(parameterName));
+    commands.add(stepOver());
+    commands.add(checkLocal(parameterName));
+    commands.add(checkLocal(localVariableName));
+    // TODO(shertz) check current method name ?
+    commands.add(run());
+    commands.add(run()  /* resume after 2nd breakpoint */);
+
+    runDebugTestJava8(debuggeeClass, commands);
+  }
+
+  @Test
+  public void testOverrideDefaultMethod() throws Throwable {
+    String debuggeeClass = "DebugDefaultMethod";
+    String parameterName = "msg";
+    String localVariableName = "newMsg";
+
+    List<Command> commands = new ArrayList<>();
+    commands.add(breakpoint(debuggeeClass, "testDefaultMethod"));
+    commands.add(run());
+    commands.add(run() /* resume after 1st breakpoint */);
+    commands.add(checkMethod(debuggeeClass, "testDefaultMethod"));
+    commands.add(checkLine(27));
+    commands.add(stepInto());
+    commands.add(checkMethod("DebugDefaultMethod$OverrideImpl", "doSomething"));
+    commands.add(checkLocal(parameterName));
+    commands.add(stepOver());
+    commands.add(checkLocal(parameterName));
+    commands.add(checkLocal(localVariableName));
+    commands.add(run());
+
+    runDebugTestJava8(debuggeeClass, commands);
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaTest.java b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
new file mode 100644
index 0000000..467ae09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
@@ -0,0 +1,25 @@
+// 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.Test;
+
+public class LambdaTest extends DebugTestBase {
+
+  @Test
+  public void testLambdaDebugging() throws Throwable {
+    String debuggeeClass = "DebugLambda";
+    String initialMethodName = "printInt";
+    // TODO(shertz) test local variables
+    runDebugTestJava8(debuggeeClass,
+        breakpoint(debuggeeClass, initialMethodName),
+        run(),
+        checkMethod(debuggeeClass, initialMethodName),
+        checkLine(12),
+        stepInto(INTELLIJ_FILTER),
+        checkLine(16),
+        run());
+  }
+}