Add a test for JaCoCo using CONSTANT_Dynamic for class file version 55
Bug: 178172809
Change-Id: I914f3b4edf49f196fc53a761a37f004e6ff55c7a
diff --git a/src/test/java/com/android/tools/r8/D8TestCompileResult.java b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
index 6d7634c..3908d64 100644
--- a/src/test/java/com/android/tools/r8/D8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
@@ -5,7 +5,10 @@
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.utils.AndroidApp;
+import java.io.IOException;
+import java.nio.file.Path;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
public class D8TestCompileResult extends TestCompileResult<D8TestCompileResult, D8TestRunResult> {
private final String proguardMap;
@@ -54,4 +57,13 @@
public D8TestRunResult createRunResult(TestRuntime runtime, ProcessResult result) {
return new D8TestRunResult(app, runtime, result, proguardMap);
}
+
+ public D8TestRunResult runWithJaCoCo(
+ Path output, TestRuntime runtime, String mainClass, String... args)
+ throws IOException, ExecutionException {
+ setSystemProperty("jacoco-agent.destfile", output.toString());
+ setSystemProperty("jacoco-agent.dumponexit", "true");
+ setSystemProperty("jacoco-agent.output", "file");
+ return run(runtime, mainClass, args);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index 9951a04..f92a490 100644
--- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -158,6 +158,23 @@
return self();
}
+ public JvmTestBuilder enableJaCoCoAgentForOfflineInstrumentedCode(Path jacocoAgent, Path output) {
+ addProgramFiles(jacocoAgent);
+ addVmArguments(
+ "-Djacoco-agent.destfile=" + output.toString(),
+ "-Djacoco-agent.dumponexit=true",
+ "-Djacoco-agent.output=file");
+ return self();
+ }
+
+ public JvmTestBuilder enableJaCoCoAgent(Path jacocoAgent, Path output) {
+ addProgramFiles(jacocoAgent);
+ addVmArguments(
+ String.format(
+ "-javaagent:%s=destfile=%s,dumponexit=true,output=file", jacocoAgent, output));
+ return self();
+ }
+
public JvmTestBuilder addVmArguments(Collection<String> arguments) {
vmArguments.addAll(arguments);
return self();
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 54e3139..a1bdeb8 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -151,8 +151,9 @@
private static final String PROGUARD5_2_1 = "third_party/proguard/proguard5.2.1/bin/proguard";
private static final String PROGUARD6_0_1 = "third_party/proguard/proguard6.0.1/bin/proguard";
private static final String PROGUARD = PROGUARD5_2_1;
- public static final String JACOCO_AGENT = "third_party/jacoco/0.8.6/lib/jacocoagent.jar";
- public static final String JACOCO_CLI = "third_party/jacoco/0.8.6/lib/jacococli.jar";
+ public static final Path JACOCO_ROOT = Paths.get("third_party", "jacoco", "0.8.6");
+ public static final Path JACOCO_AGENT = JACOCO_ROOT.resolve(Paths.get("lib", "jacocoagent.jar"));
+ public static final Path JACOCO_CLI = JACOCO_ROOT.resolve(Paths.get("lib", "jacococli.jar"));
public static final String PROGUARD_SETTINGS_FOR_INTERNAL_APPS = "third_party/proguardsettings/";
private static final String RETRACE6_0_1 = "third_party/proguard/proguard6.0.1/bin/retrace";
@@ -1592,6 +1593,36 @@
return processResult;
}
+ public static ProcessResult runJaCoCoInstrument(Path sourceClassFiles, Path outputDirectory)
+ throws IOException {
+ List<String> cmdline = new ArrayList<>();
+ cmdline.add(TestRuntime.getSystemRuntime().asCf().getJavaExecutable().toString());
+ cmdline.add("-jar");
+ cmdline.add(ToolHelper.JACOCO_CLI.toString());
+ cmdline.add("instrument");
+ cmdline.add(sourceClassFiles.toString());
+ cmdline.add("--dest");
+ cmdline.add(outputDirectory.toString());
+ ProcessBuilder builder = new ProcessBuilder(cmdline);
+ return ToolHelper.runProcess(builder);
+ }
+
+ public static ProcessResult runJaCoCoReport(Path classfiles, Path jacocoExec, Path reportFile)
+ throws IOException {
+ List<String> cmdline = new ArrayList<>();
+ cmdline.add(TestRuntime.getSystemRuntime().asCf().getJavaExecutable().toString());
+ cmdline.add("-jar");
+ cmdline.add(ToolHelper.JACOCO_CLI.toString());
+ cmdline.add("report");
+ cmdline.add(jacocoExec.toString());
+ cmdline.add("--classfiles");
+ cmdline.add(classfiles.toString());
+ cmdline.add("--csv");
+ cmdline.add(reportFile.toString());
+ ProcessBuilder builder = new ProcessBuilder(cmdline);
+ return ToolHelper.runProcess(builder);
+ }
+
private static Path findNonConflictingDestinationFilePath(Path testOutputPath) {
int index = 0;
Path destFilePath;
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
new file mode 100644
index 0000000..ee5142f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
@@ -0,0 +1,238 @@
+// Copyright (c) 2021, 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.desugar.constantdynamic;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
+import static com.android.tools.r8.utils.DescriptorUtils.JAVA_PACKAGE_SEPARATOR;
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class JacocoConstantDynamicTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public boolean useConstantDynamic;
+
+ @Parameters(name = "{0}, useConstantDynamic: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+ BooleanUtils.values());
+ }
+
+ public static JacocoClasses testClassesNoConstantDynamic;
+ public static JacocoClasses testClassesConstantDynamic;
+
+ public JacocoClasses testClasses;
+
+ private static final String MAIN_CLASS = TestRunner.class.getTypeName();
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!");
+
+ @BeforeClass
+ public static void setUpInput() throws IOException {
+ testClassesNoConstantDynamic = testClasses(getStaticTemp(), CfVersion.V1_8);
+ testClassesConstantDynamic = testClasses(getStaticTemp(), CfVersion.V11);
+ }
+
+ @Before
+ public void setUp() throws IOException {
+ testClasses = useConstantDynamic ? testClassesConstantDynamic : testClassesNoConstantDynamic;
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(
+ parameters.isCfRuntime()
+ && (parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)
+ || !useConstantDynamic));
+
+ // Run non-instrumented code.
+ testForRuntime(parameters)
+ .addProgramFiles(testClasses.getOriginal())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+
+ // Run instrumented code without an agent.
+ testForRuntime(parameters)
+ .addProgramFiles(testClasses.getInstrumented())
+ .addProgramFiles(ToolHelper.JACOCO_AGENT)
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+
+ // Run non-instrumented code with an agent causing on the fly instrumentation on the JVM.
+ Path output = temp.newFolder().toPath();
+ Path agentOutputOnTheFly = output.resolve("on-the-fly");
+ testForJvm()
+ .addProgramFiles(testClasses.getOriginal())
+ .enableJaCoCoAgent(ToolHelper.JACOCO_AGENT, agentOutputOnTheFly)
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ List<String> onTheFlyReport = testClasses.generateReport(agentOutputOnTheFly);
+ assertEquals(2, onTheFlyReport.size());
+
+ // Run the instrumented code with offline instrumentation turned on.
+ Path agentOutputOffline = output.resolve("offline");
+ testForJvm()
+ .addProgramFiles(testClasses.getInstrumented())
+ .enableJaCoCoAgentForOfflineInstrumentedCode(ToolHelper.JACOCO_AGENT, agentOutputOffline)
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ List<String> offlineReport = testClasses.generateReport(agentOutputOffline);
+ assertEquals(onTheFlyReport, offlineReport);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.getRuntime().isDex());
+ if (!useConstantDynamic) {
+ Path output = temp.newFolder().toPath();
+ Path agentOutput = output.resolve("jacoco.exec");
+ testForD8(parameters.getBackend())
+ .addProgramFiles(testClasses.getInstrumented())
+ .addProgramFiles(ToolHelper.JACOCO_AGENT)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .runWithJaCoCo(agentOutput, parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ // TODO(sgjesse): Need to figure out why there is no instrumentation output for newer VMs.
+ if (parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
+ List<String> report = testClasses.generateReport(agentOutput);
+ assertEquals(2, report.size());
+ } else {
+ assertFalse(Files.exists(agentOutput));
+ }
+ } else {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testForD8(parameters.getBackend())
+ .addProgramFiles(testClasses.getInstrumented())
+ .addProgramFiles(ToolHelper.JACOCO_AGENT)
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ // Check that the error is reported as an error to the diagnostics handler.
+ diagnostics.assertErrorsCount(1);
+ diagnostics.assertAllErrorsMatch(
+ allOf(
+ diagnosticMessage(containsString("Unsupported dynamic constant")),
+ // The fatal error is not given an origin, so it can't provide it.
+ // Note: This could be fixed by delaying reporting and associate the
+ // info
+ // at the top-level handler. It would require mangling of the
+ // diagnostic,
+ // so maybe not that elegant.
+ diagnosticOrigin(Origin.unknown())));
+ diagnostics.assertWarningsCount(0);
+ diagnostics.assertInfosCount(0);
+ }));
+ }
+ }
+
+ private static JacocoClasses testClasses(TemporaryFolder temp, CfVersion version)
+ throws IOException {
+ return new JacocoClasses(
+ transformer(TestRunner.class)
+ .setVersion(version) /*.setClassDescriptor("LTestRunner;")*/
+ .transform(),
+ temp);
+ }
+
+ // Two sets of class files with and without JaCoCo off line instrumentation.
+ private static class JacocoClasses {
+ private final Path dir;
+
+ private final Path originalJar;
+ private final Path instrumentedJar;
+
+ // Create JacocoClasses with just one class provided as bytes.
+ private JacocoClasses(byte[] clazz, TemporaryFolder temp) throws IOException {
+ dir = temp.newFolder().toPath();
+
+ // Write the class to a .class file with package sub-directories.
+ String typeName = extractClassName(clazz);
+ int lastDotIndex = typeName.lastIndexOf('.');
+ String pkg = typeName.substring(0, lastDotIndex);
+ String baseFileName = typeName.substring(lastDotIndex + 1) + CLASS_EXTENSION;
+ Path original = dir.resolve("original");
+ Files.createDirectories(original);
+ Path packageDir = original.resolve(pkg.replace(JAVA_PACKAGE_SEPARATOR, File.separatorChar));
+ Files.createDirectories(packageDir);
+ Path classFile = packageDir.resolve(baseFileName);
+ Files.write(classFile, clazz);
+
+ // Run offline instrumentation.
+ Path instrumented = dir.resolve("instrumented");
+ Files.createDirectories(instrumented);
+ runJacocoInstrumentation(original, instrumented);
+ originalJar = dir.resolve("original" + JAR_EXTENSION);
+ ZipUtils.zip(originalJar, original);
+ instrumentedJar = dir.resolve("instrumented" + JAR_EXTENSION);
+ ZipUtils.zip(instrumentedJar, instrumented);
+ }
+
+ public Path getOriginal() {
+ return originalJar;
+ }
+
+ public Path getInstrumented() {
+ return instrumentedJar;
+ }
+
+ public List<String> generateReport(Path jacocoExec) throws IOException {
+ Path report = dir.resolve("report.scv");
+ ProcessResult result = ToolHelper.runJaCoCoReport(originalJar, jacocoExec, report);
+ assertEquals(result.toString(), 0, result.exitCode);
+ return Files.readAllLines(report);
+ }
+
+ private void runJacocoInstrumentation(Path input, Path outdir) throws IOException {
+ ProcessResult result = ToolHelper.runJaCoCoInstrument(input, outdir);
+ assertEquals(result.toString(), 0, result.exitCode);
+ }
+ }
+
+ static class TestRunner {
+
+ public static void main(String[] args) {
+ System.out.println("Hello, world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationJacocoTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationJacocoTest.java
index 11829f6..f860ca2 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationJacocoTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionsConfigurationJacocoTest.java
@@ -100,7 +100,7 @@
List<String> cmdline = new ArrayList<>();
cmdline.add(TestRuntime.getSystemRuntime().asCf().getJavaExecutable().toString());
cmdline.add("-jar");
- cmdline.add(ToolHelper.JACOCO_CLI);
+ cmdline.add(ToolHelper.JACOCO_CLI.toString());
cmdline.add("instrument");
cmdline.add(input.toString());
cmdline.add("--dest");