blob: ba5d568d71446eef47172f1d4e045132868f05ea [file] [log] [blame]
// Copyright (c) 2024, 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.synthesis.globals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.D8;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime.CfRuntime;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class GlobalSyntheticOutputCliTest extends TestBase {
static final String EXPECTED =
StringUtils.lines("Hello", "all good...", "Hello again", "still good...");
private final TestParameters parameters;
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withDefaultDexRuntime().withApiLevel(AndroidApiLevel.N_MR1).build();
}
public GlobalSyntheticOutputCliTest(TestParameters parameters) {
this.parameters = parameters;
}
private static String getAndroidJar() {
return ToolHelper.getAndroidJar(AndroidApiLevel.LATEST).toString();
}
private String getApiLevelString() {
return "" + parameters.getApiLevel().getLevel();
}
private ProcessResult forkD8(String... args) throws IOException {
return forkD8(Arrays.asList(args));
}
private ProcessResult forkD8(List<String> args) throws IOException {
ImmutableList.Builder<String> command =
new ImmutableList.Builder<String>()
.add(CfRuntime.getSystemRuntime().getJavaExecutable().toString())
.add("-Dcom.android.tools.r8.enableApiOutliningAndStubbing=1")
.add("-cp")
.add(System.getProperty("java.class.path"))
.add(D8.class.getName())
.add("--min-api", getApiLevelString())
.add("--lib", getAndroidJar())
.addAll(args);
ProcessBuilder processBuilder = new ProcessBuilder(command.build());
ProcessResult result = ToolHelper.runProcess(processBuilder);
assertEquals(result.toString(), 0, result.exitCode);
return result;
}
@Test
public void testDexNoGlobals() throws Exception {
Path input1 = ToolHelper.getClassFileForTestClass(TestClass1.class);
Path input2 = ToolHelper.getClassFileForTestClass(TestClass2.class);
Path dexOut = temp.newFolder().toPath().resolve("out.jar");
Path globalsOut = temp.newFolder().toPath().resolve("out.zip");
forkD8(
input1.toString(),
input2.toString(),
"--intermediate",
"--output",
dexOut.toString(),
"--globals-output",
globalsOut.toString());
assertTrue(Files.exists(dexOut));
assertTrue(Files.exists(globalsOut));
Path finalOut = temp.newFolder().toPath().resolve("out.jar");
forkD8(dexOut.toString(), "--globals", globalsOut.toString(), "--output", finalOut.toString());
testForD8()
.addProgramFiles(finalOut)
.run(parameters.getRuntime(), TestClass1.class)
.assertSuccessWithOutput(EXPECTED);
}
@Test
public void testDexIndexedZip() throws Exception {
Path input1 = transformClass(TestClass1.class);
Path input2 = transformClass(TestClass2.class);
Path dexOut = temp.newFolder().toPath().resolve("out.jar");
Path globalsOut = temp.newFolder().toPath().resolve("out.zip");
forkD8(
input1.toString(),
input2.toString(),
"--intermediate",
"--output",
dexOut.toString(),
"--globals-output",
globalsOut.toString());
assertTrue(Files.exists(dexOut));
assertTrue(Files.exists(globalsOut));
Path finalOut = temp.newFolder().toPath().resolve("out.jar");
forkD8(dexOut.toString(), "--globals", globalsOut.toString(), "--output", finalOut.toString());
testForD8()
.addProgramFiles(finalOut)
.run(parameters.getRuntime(), TestClass1.class)
.assertSuccessWithOutput(EXPECTED);
}
@Test
public void testDexIndexedDir() throws Exception {
Path input1 = transformClass(TestClass1.class);
Path input2 = transformClass(TestClass2.class);
Path dexOut = temp.newFolder().toPath().resolve("out.jar");
Path globalsOut = temp.newFolder("out").toPath();
forkD8(
input1.toString(),
input2.toString(),
"--intermediate",
"--output",
dexOut.toString(),
"--globals-output",
globalsOut.toString());
Path expectedGlobalsFile = globalsOut.resolve("classes.globals");
assertTrue(Files.exists(dexOut));
assertTrue(Files.isDirectory(globalsOut));
assertTrue(Files.exists(expectedGlobalsFile));
Path finalOut = temp.newFolder().toPath().resolve("out.jar");
forkD8(
dexOut.toString(),
"--globals",
expectedGlobalsFile.toString(),
"--output",
finalOut.toString());
testForD8()
.addProgramFiles(finalOut)
.run(parameters.getRuntime(), TestClass1.class)
.assertSuccessWithOutput(EXPECTED);
}
@Test
public void testDexPerClassZip() throws Exception {
Path input1 = transformClass(TestClass1.class);
Path input2 = transformClass(TestClass2.class);
Path dexOut = temp.newFolder().toPath().resolve("out.jar");
Path globalsOut = temp.newFolder().toPath().resolve("out.zip");
forkD8(
input1.toString(),
input2.toString(),
"--file-per-class",
"--output",
dexOut.toString(),
"--globals-output",
globalsOut.toString());
assertTrue(Files.exists(dexOut));
assertTrue(Files.exists(globalsOut));
Path finalOut = temp.newFolder().toPath().resolve("out.jar");
forkD8(dexOut.toString(), "--globals", globalsOut.toString(), "--output", finalOut.toString());
testForD8()
.addProgramFiles(finalOut)
.run(parameters.getRuntime(), TestClass1.class)
.assertSuccessWithOutput(EXPECTED);
}
@Test
public void testDexPerClassDir() throws Exception {
Path input1 = transformClass(TestClass1.class);
Path input2 = transformClass(TestClass2.class);
Path dexOut = temp.newFolder().toPath().resolve("out.jar");
Path globalsOut = temp.newFolder("out").toPath();
forkD8(
input1.toString(),
input2.toString(),
"--file-per-class",
"--output",
dexOut.toString(),
"--globals-output",
globalsOut.toString());
assertTrue(Files.exists(dexOut));
assertTrue(Files.isDirectory(globalsOut));
List<Path> globalFiles =
ImmutableList.of(
globalsOut.resolve(binaryName(TestClass1.class) + ".globals"),
globalsOut.resolve(binaryName(TestClass2.class) + ".globals"));
globalFiles.forEach(f -> assertTrue(Files.exists(f)));
Path finalOut = temp.newFolder().toPath().resolve("out.jar");
forkD8(
ImmutableList.<String>builder()
.add(dexOut.toString())
.addAll(
ListUtils.flatMap(globalFiles, f -> ImmutableList.of("--globals", f.toString())))
.add("--output")
.add(finalOut.toString())
.build());
testForD8()
.addProgramFiles(finalOut)
.run(parameters.getRuntime(), TestClass1.class)
.assertSuccessWithOutput(EXPECTED);
}
@Test
public void testDexPerClassFileZip() throws Exception {
Path input1 = transformClass(TestClass1.class);
Path input2 = transformClass(TestClass2.class);
Path dexOut = temp.newFolder().toPath().resolve("out.jar");
Path globalsOut = temp.newFolder().toPath().resolve("out.zip");
forkD8(
input1.toString(),
input2.toString(),
"--file-per-class-file",
"--output",
dexOut.toString(),
"--globals-output",
globalsOut.toString());
assertTrue(Files.exists(dexOut));
assertTrue(Files.exists(globalsOut));
Path finalOut = temp.newFolder().toPath().resolve("out.jar");
forkD8(dexOut.toString(), "--globals", globalsOut.toString(), "--output", finalOut.toString());
testForD8()
.addProgramFiles(finalOut)
.run(parameters.getRuntime(), TestClass1.class)
.assertSuccessWithOutput(EXPECTED);
}
@Test
public void testDexPerClassFileDir() throws Exception {
Path input1 = transformClass(TestClass1.class);
Path input2 = transformClass(TestClass2.class);
Path dexOut = temp.newFolder().toPath().resolve("out.jar");
Path globalsOut = temp.newFolder("out").toPath();
forkD8(
input1.toString(),
input2.toString(),
"--file-per-class-file",
"--output",
dexOut.toString(),
"--globals-output",
globalsOut.toString());
assertTrue(Files.exists(dexOut));
assertTrue(Files.isDirectory(globalsOut));
List<Path> globalFiles =
ImmutableList.of(
globalsOut.resolve(binaryName(TestClass1.class) + ".globals"),
globalsOut.resolve(binaryName(TestClass2.class) + ".globals"));
globalFiles.forEach(f -> assertTrue(Files.exists(f)));
Path finalOut = temp.newFolder().toPath().resolve("out.jar");
forkD8(
ImmutableList.<String>builder()
.add(dexOut.toString())
.addAll(
ListUtils.flatMap(globalFiles, f -> ImmutableList.of("--globals", f.toString())))
.add("--output")
.add(finalOut.toString())
.build());
testForD8()
.addProgramFiles(finalOut)
.run(parameters.getRuntime(), TestClass1.class)
.assertSuccessWithOutput(EXPECTED);
}
// Transform the class such that its handlers use the AuthenticationRequiredException which
// is introduced with API level 25. This triggers the need for a class stub for the exception.
private Path transformClass(Class<?> clazz) throws IOException {
byte[] bytes =
transformer(clazz)
.transformTryCatchBlock(
MethodPredicate.all(),
(start, end, handler, type, visitor) -> {
assertEquals("java/lang/Exception", type);
visitor.visitTryCatchBlock(
start, end, handler, "android/app/AuthenticationRequiredException");
})
.transform();
Path file = temp.newFolder().toPath().resolve("input.class");
Files.write(file, bytes);
return file;
}
static class TestClass1 {
public static void main(String[] args) {
Runnable r =
() -> {
try {
System.out.println("Hello");
} catch (Exception /* will be AuthenticationRequiredException */ e) {
System.out.println("fail...");
throw e;
}
System.out.println("all good...");
};
r.run();
TestClass2.main(args);
}
}
static class TestClass2 {
public static void main(String[] args) {
try {
System.out.println("Hello again");
} catch (Exception /* will be AuthenticationRequiredException */ e) {
System.out.println("fail...");
throw e;
}
System.out.println("still good...");
}
}
}