More java8 debug tests

Adds new cases for lambda (using method reference) and default/static
method in interface.

Bug: 38218137
Bug: 37731140
Bug: 62290295
Change-Id: I5e387c6194cef313fbbd4b66e1d78db6ff5cbdf8
diff --git a/src/test/debugTestResourcesJava8/DebugDefaultMethod.java b/src/test/debugTestResourcesJava8/DebugInterfaceMethod.java
similarity index 77%
rename from src/test/debugTestResourcesJava8/DebugDefaultMethod.java
rename to src/test/debugTestResourcesJava8/DebugInterfaceMethod.java
index db216b3..b407698 100644
--- a/src/test/debugTestResourcesJava8/DebugDefaultMethod.java
+++ b/src/test/debugTestResourcesJava8/DebugInterfaceMethod.java
@@ -2,13 +2,17 @@
 // 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 {
+public class DebugInterfaceMethod {
 
   interface I {
     default void doSomething(String msg) {
       String name = getClass().getName();
       System.out.println(name + ": " + msg);
     }
+
+    static void printString(String msg) {
+      System.out.println(msg);
+    }
   }
 
   static class DefaultImpl implements I {
@@ -27,9 +31,14 @@
     i.doSomething("Test");
   }
 
+  private static void testStaticMethod() {
+    I.printString("I'm a static method in interface");
+  }
+
   public static void main(String[] args) {
     testDefaultMethod(new DefaultImpl());
     testDefaultMethod(new OverrideImpl());
+    testStaticMethod();
   }
 
 }
diff --git a/src/test/debugTestResourcesJava8/DebugLambda.java b/src/test/debugTestResourcesJava8/DebugLambda.java
index c3b8695..007cd0c 100644
--- a/src/test/debugTestResourcesJava8/DebugLambda.java
+++ b/src/test/debugTestResourcesJava8/DebugLambda.java
@@ -16,8 +16,52 @@
     printInt(() -> i + j);
   }
 
-  public static void main(String[] args) {
-    DebugLambda.testLambda(5, 10);
+  private static void printInt2(I i) {
+    System.out.println(i.getInt());
   }
 
+  public static void testLambdaWithMethodReference() {
+    printInt2(DebugLambda::returnOne);
+  }
+
+  private static int returnOne() {
+    return 1;
+  }
+
+  private static void printInt3(BinaryOpInterface i, int a, int b) {
+    System.out.println(i.binaryOp(a, b));
+  }
+
+  public static void testLambdaWithArguments(int i, int j) {
+    printInt3((a, b) -> {
+      return a + b;
+    }, i, j);
+  }
+
+  interface ObjectProvider {
+    Object foo(String a, String b, String c);
+  }
+
+  private static void testLambdaWithMethodReferenceAndConversion(ObjectProvider objectProvider) {
+    System.out.println(objectProvider.foo("A", "B", "C"));
+  }
+
+  public static void main(String[] args) {
+    DebugLambda.testLambda(5, 10);
+    DebugLambda.testLambdaWithArguments(5, 10);
+    DebugLambda.testLambdaWithMethodReference();
+    DebugLambda.testLambdaWithMethodReferenceAndConversion(DebugLambda::concatObjects);
+  }
+
+  private static Object concatObjects(Object... objects) {
+    StringBuilder sb = new StringBuilder();
+    for (Object o : objects) {
+      sb.append(o.toString());
+    }
+    return sb.toString();
+  }
+
+  interface BinaryOpInterface {
+    int binaryOp(int a, int b);
+  }
 }
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 9cffe15..4cef632 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -20,6 +20,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Queue;
 import java.util.TreeMap;
 import java.util.function.Consumer;
@@ -245,7 +246,10 @@
   }
 
   protected final JUnit3Wrapper.Command checkNoLocal() {
-    return inspect(t -> Assert.assertTrue(t.getLocalNames().isEmpty()));
+    return inspect(t -> {
+      List<String> localNames = t.getLocalNames();
+      Assert.assertTrue("Local variables: " + String.join(",", localNames), localNames.isEmpty());
+    });
   }
 
   protected final JUnit3Wrapper.Command checkLine(String sourceFile, int line) {
@@ -475,11 +479,13 @@
       }
 
       public void checkLocal(String localName) {
-        getVariableAt(getLocation(), localName);
+        Optional<Variable> localVar = getVariableAt(getLocation(), localName);
+        Assert.assertTrue("No local '" + localName + "'", localVar.isPresent());
       }
 
       public void checkLocal(String localName, Value expectedValue) {
-        Variable localVar = getVariableAt(getLocation(), localName);
+        Optional<Variable> localVar = getVariableAt(getLocation(), localName);
+        Assert.assertTrue("No local '" + localName + "'", localVar.isPresent());
 
         // Get value
         CommandPacket commandPacket = new CommandPacket(
@@ -488,8 +494,8 @@
         commandPacket.setNextValueAsThreadID(getThreadId());
         commandPacket.setNextValueAsFrameID(getFrameId());
         commandPacket.setNextValueAsInt(1);
-        commandPacket.setNextValueAsInt(localVar.getSlot());
-        commandPacket.setNextValueAsByte(localVar.getTag());
+        commandPacket.setNextValueAsInt(localVar.get().getSlot());
+        commandPacket.setNextValueAsByte(localVar.get().getTag());
         ReplyPacket replyPacket = getMirror().performCommand(commandPacket);
         checkReplyPacket(replyPacket, "StackFrame.GetValues command");
         int valuesCount = replyPacket.getNextValueAsInt();
@@ -594,11 +600,10 @@
       return index >= varStart && index < varEnd;
     }
 
-    private Variable getVariableAt(Location location, String localName) {
+    private Optional<Variable> getVariableAt(Location location, String localName) {
       return getVariablesAt(location).stream()
           .filter(v -> localName.equals(v.getName()))
-          .findFirst()
-          .get();
+          .findFirst();
     }
 
     private List<Variable> getVariablesAt(Location location) {
@@ -858,13 +863,16 @@
 
         @Override
         public void perform(JUnit3Wrapper testBase) {
-          Variable v = testBase.getVariableAt(testBase.debuggeeState.location, localName);
+          Optional<Variable> localVar = testBase
+              .getVariableAt(testBase.debuggeeState.location, localName);
+          Assert.assertTrue("No local '" + localName + "'", localVar.isPresent());
+
           CommandPacket setValues = new CommandPacket(StackFrameCommandSet.CommandSetID,
               StackFrameCommandSet.SetValuesCommand);
           setValues.setNextValueAsThreadID(testBase.getDebuggeeState().getThreadId());
           setValues.setNextValueAsFrameID(testBase.getDebuggeeState().getFrameId());
           setValues.setNextValueAsInt(1);
-          setValues.setNextValueAsInt(v.getSlot());
+          setValues.setNextValueAsInt(localVar.get().getSlot());
           setValues.setNextValueAsValue(newValue);
           ReplyPacket replyPacket = testBase.getMirror().performCommand(setValues);
           testBase.checkReplyPacket(replyPacket, "StackFrame.SetValues");
diff --git a/src/test/java/com/android/tools/r8/debug/DefaultMethodTest.java b/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java
similarity index 61%
rename from src/test/java/com/android/tools/r8/debug/DefaultMethodTest.java
rename to src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java
index e0eb5ea..a7dcb60 100644
--- a/src/test/java/com/android/tools/r8/debug/DefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java
@@ -9,13 +9,13 @@
 import java.util.List;
 import org.junit.Test;
 
-public class DefaultMethodTest extends DebugTestBase {
+public class InterfaceMethodTest extends DebugTestBase {
 
-  private static final String SOURCE_FILE = "DebugDefaultMethod.java";
+  private static final String SOURCE_FILE = "DebugInterfaceMethod.java";
 
   @Test
   public void testDefaultMethod() throws Throwable {
-    String debuggeeClass = "DebugDefaultMethod";
+    String debuggeeClass = "DebugInterfaceMethod";
     String parameterName = "msg";
     String localVariableName = "name";
 
@@ -23,13 +23,17 @@
     commands.add(breakpoint(debuggeeClass, "testDefaultMethod"));
     commands.add(run());
     commands.add(checkMethod(debuggeeClass, "testDefaultMethod"));
-    commands.add(checkLine(SOURCE_FILE, 27));
+    commands.add(checkLine(SOURCE_FILE, 31));
     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());
+    // TODO(shertz) we should see the local variable this even when desugaring.
+    if (supportsDefaultMethod()) {
+      commands.add(checkLocal("this"));
+    }
     commands.add(checkLocal(parameterName));
     commands.add(stepOver());
     commands.add(checkLocal(parameterName));
@@ -43,7 +47,7 @@
 
   @Test
   public void testOverrideDefaultMethod() throws Throwable {
-    String debuggeeClass = "DebugDefaultMethod";
+    String debuggeeClass = "DebugInterfaceMethod";
     String parameterName = "msg";
     String localVariableName = "newMsg";
 
@@ -52,13 +56,34 @@
     commands.add(run());
     commands.add(run() /* resume after 1st breakpoint */);
     commands.add(checkMethod(debuggeeClass, "testDefaultMethod"));
-    commands.add(checkLine(SOURCE_FILE, 27));
+    commands.add(checkLine(SOURCE_FILE, 31));
     commands.add(stepInto());
-    commands.add(checkMethod("DebugDefaultMethod$OverrideImpl", "doSomething"));
+    commands.add(checkMethod("DebugInterfaceMethod$OverrideImpl", "doSomething"));
+    commands.add(checkLocal("this"));
+    commands.add(checkLocal(parameterName));
+    commands.add(stepOver());
+    commands.add(checkLocal("this"));
+    commands.add(checkLocal(parameterName));
+    commands.add(checkLocal(localVariableName));
+    commands.add(run());
+
+    runDebugTestJava8(debuggeeClass, commands);
+  }
+
+  @Test
+  public void testStaticMethod() throws Throwable {
+    String debuggeeClass = "DebugInterfaceMethod";
+    String parameterName = "msg";
+
+    List<Command> commands = new ArrayList<>();
+    commands.add(breakpoint(debuggeeClass, "testStaticMethod"));
+    commands.add(run());
+    commands.add(checkMethod(debuggeeClass, "testStaticMethod"));
+    commands.add(checkLine(SOURCE_FILE, 35));
+    commands.add(stepInto());
     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
index 52d9c70..6bdbb90 100644
--- a/src/test/java/com/android/tools/r8/debug/LambdaTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
@@ -4,17 +4,18 @@
 
 package com.android.tools.r8.debug;
 
+import org.junit.Assert;
 import org.junit.Test;
 
+// TODO(shertz) test local variables
 public class LambdaTest extends DebugTestBase {
 
   public static final String SOURCE_FILE = "DebugLambda.java";
 
   @Test
-  public void testLambdaDebugging() throws Throwable {
+  public void testLambda_ExpressionOnSameLine() throws Throwable {
     String debuggeeClass = "DebugLambda";
     String initialMethodName = "printInt";
-    // TODO(shertz) test local variables
     runDebugTestJava8(debuggeeClass,
         breakpoint(debuggeeClass, initialMethodName),
         run(),
@@ -24,4 +25,56 @@
         checkLine(SOURCE_FILE, 16),
         run());
   }
+
+  @Test
+  public void testLambda_StatementOnNewLine() throws Throwable {
+    String debuggeeClass = "DebugLambda";
+    String initialMethodName = "printInt3";
+    runDebugTestJava8(debuggeeClass,
+        breakpoint(debuggeeClass, initialMethodName),
+        run(),
+        checkMethod(debuggeeClass, initialMethodName),
+        checkLine(SOURCE_FILE, 32),
+        stepInto(INTELLIJ_FILTER),
+        checkLine(SOURCE_FILE, 37),
+        run());
+  }
+
+  @Test
+  public void testLambda_StaticMethodReference_Trivial() throws Throwable {
+    String debuggeeClass = "DebugLambda";
+    String initialMethodName = "printInt2";
+    runDebugTestJava8(debuggeeClass,
+        breakpoint(debuggeeClass, initialMethodName),
+        run(),
+        checkMethod(debuggeeClass, initialMethodName),
+        checkLine(SOURCE_FILE, 20),
+        stepInto(INTELLIJ_FILTER),
+        isRunningJava() ? LambdaTest::doNothing : stepInto(INTELLIJ_FILTER),
+        checkMethod(debuggeeClass, "returnOne"),
+        checkLine(SOURCE_FILE, 28),
+        checkNoLocal(),
+        run());
+  }
+
+  @Test
+  public void testLambda_StaticMethodReference_NonTrivial() throws Throwable {
+    String debuggeeClass = "DebugLambda";
+    String initialMethodName = "testLambdaWithMethodReferenceAndConversion";
+    runDebugTestJava8(debuggeeClass,
+        breakpoint(debuggeeClass, initialMethodName),
+        run(),
+        checkMethod(debuggeeClass, initialMethodName),
+        checkLine(SOURCE_FILE, 46),
+        stepInto(INTELLIJ_FILTER),
+        inspect(t -> Assert.assertTrue(t.getCurrentMethodName().startsWith("lambda$"))),
+        stepInto(INTELLIJ_FILTER),
+        checkMethod(debuggeeClass, "concatObjects"),
+        checkLine(SOURCE_FILE, 57),
+        checkLocal("objects"),
+        run());
+  }
+
+  private static void doNothing(JUnit3Wrapper jUnit3Wrapper) {
+  }
 }