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());
+ }
+}