Reland "[ApiModel] Add d8 test replicating AGP setup""
This reverts commit 86b113a159adf3520ad4c6e92c02cabb5837fe2f.
Change-Id: Id65b8af19d33907004a08c07467aeec7a161ec5d
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifySingleClassPolicyAlwaysSatisfied.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifySingleClassPolicyAlwaysSatisfied.java
index 51d33b7..47824f9 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifySingleClassPolicyAlwaysSatisfied.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifySingleClassPolicyAlwaysSatisfied.java
@@ -18,7 +18,7 @@
@Override
public boolean canMerge(DexProgramClass program) {
- assert policy.canMerge(program);
+ assert policy.canMerge(program) : "Verification of single class policies failed";
return true;
}
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 68b6fc0..82f1d42 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -481,6 +481,11 @@
return super.addLibraryProvider(provider);
}
+ public T setUseDefaultRuntimeLibrary(boolean useDefaultRuntimeLibrary) {
+ this.useDefaultRuntimeLibrary = useDefaultRuntimeLibrary;
+ return self();
+ }
+
@Override
public T allowStdoutMessages() {
allowStdoutMessages = true;
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelD8GradleSetupTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelD8GradleSetupTest.java
new file mode 100644
index 0000000..6560933
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelD8GradleSetupTest.java
@@ -0,0 +1,299 @@
+// Copyright (c) 2023, 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.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/***
+ * This is a replication of b/268596049.
+ */
+@RunWith(Parameterized.class)
+public class ApiModelD8GradleSetupTest extends TestBase {
+
+ private static final AndroidApiLevel mockApiLevelOne = AndroidApiLevel.M;
+ private static final AndroidApiLevel mockApiLevelTwo = AndroidApiLevel.O;
+ private static final AndroidApiLevel mockApiLevelThree = AndroidApiLevel.R;
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+ testBuilder
+ .addLibraryClasses(LibraryClassOne.class, LibraryClassTwo.class, LibraryClassThree.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .apply(setMockApiLevelForClass(LibraryClassOne.class, mockApiLevelOne))
+ .apply(
+ setMockApiLevelForMethod(
+ LibraryClassOne.class.getDeclaredMethod("foo"), mockApiLevelOne))
+ .apply(setMockApiLevelForClass(LibraryClassTwo.class, mockApiLevelTwo))
+ .apply(
+ setMockApiLevelForMethod(
+ LibraryClassTwo.class.getDeclaredMethod("bar"), mockApiLevelTwo))
+ .apply(setMockApiLevelForClass(LibraryClassThree.class, mockApiLevelThree))
+ .apply(
+ setMockApiLevelForMethod(
+ LibraryClassThree.class.getDeclaredMethod("baz"), mockApiLevelThree))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+ .apply(ApiModelingTestHelper::enableStubbingOfClasses);
+ }
+
+ private boolean willHorizontallyMergeOutlines() {
+ // After api level mockApiLevelTwo we only have a single outline and therefore will not merge.
+ return parameters.getApiLevel().isLessThan(mockApiLevelTwo);
+ }
+
+ private boolean willStubLibraryClassThree() {
+ return parameters.getApiLevel().isLessThan(mockApiLevelThree);
+ }
+
+ public AndroidApiLevel getApiLevelForRuntime() {
+ return parameters.isCfRuntime()
+ ? AndroidApiLevel.B
+ : parameters.getRuntime().asDex().maxSupportedApiLevel();
+ }
+
+ public boolean addToBootClasspath(Class<?> clazz) {
+ if (clazz == LibraryClassOne.class) {
+ return getApiLevelForRuntime().isGreaterThanOrEqualTo(mockApiLevelOne);
+ }
+ if (clazz == LibraryClassTwo.class) {
+ return getApiLevelForRuntime().isGreaterThanOrEqualTo(mockApiLevelTwo);
+ }
+ assert clazz == LibraryClassThree.class;
+ return getApiLevelForRuntime().isGreaterThanOrEqualTo(mockApiLevelThree);
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(Main.class, ProgramClassOne.class, ProgramClassTwo.class)
+ .addAndroidBuildVersion(AndroidApiLevel.B)
+ .addLibraryClasses(LibraryClassOne.class, LibraryClassTwo.class, LibraryClassThree.class)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ @Test
+ public void testD8DebugWithMerge() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testD8(
+ CompilationMode.DEBUG,
+ this::inspectNumberOfClassesFromOutput,
+ HorizontallyMergedClassesInspector::assertNoClassesMerged);
+ }
+
+ @Test
+ public void testD8ReleaseForApiLevelWithOutlining() {
+ assumeTrue(parameters.isDexRuntime());
+ assumeTrue(willHorizontallyMergeOutlines());
+ // TODO(b/268596049): Ensure that we can obtain the api level for outlines.
+ CompilationFailedException compilationFailedException =
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testD8(
+ CompilationMode.RELEASE,
+ this::inspectNumberOfClassesFromOutput,
+ // We can pass any inspector since horizontal merging fails.
+ classesInspector -> {}));
+ Throwable cause = compilationFailedException.getCause();
+ assertNotNull(cause);
+ assertThat(cause.getMessage(), containsString("Verification of single class policies failed"));
+ }
+
+ @Test
+ public void testD8ReleaseForApiLevelWithNoOutlining() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ assumeFalse(willHorizontallyMergeOutlines());
+ testD8(
+ CompilationMode.RELEASE,
+ this::inspectNumberOfClassesFromOutput,
+ HorizontallyMergedClassesInspector::assertNoClassesMerged);
+ }
+
+ private void testD8(
+ CompilationMode mode,
+ ThrowingConsumer<CodeInspector, Exception> inspect,
+ ThrowableConsumer<HorizontallyMergedClassesInspector> horizontallyMergingConsumer)
+ throws Exception {
+ GlobalSyntheticsTestingConsumer globals = new GlobalSyntheticsTestingConsumer();
+ D8TestCompileResult compileResultProgramClass =
+ compileIntermediate(mode, globals, ProgramClassOne.class);
+ D8TestCompileResult compileResultProgramClassTwo =
+ compileIntermediate(mode, globals, ProgramClassTwo.class);
+ D8TestCompileResult compileResultMain =
+ compileIntermediate(
+ mode, globals, Main.class, ProgramClassOne.class, ProgramClassTwo.class);
+
+ if (willStubLibraryClassThree()) {
+ assertTrue(globals.isSingleGlobal());
+ } else {
+ assertFalse(globals.hasGlobals());
+ }
+
+ List<Class<?>> bootClassPath = new ArrayList<>();
+ if (addToBootClasspath(LibraryClassOne.class)) {
+ bootClassPath.add(LibraryClassOne.class);
+ }
+ if (addToBootClasspath(LibraryClassTwo.class)) {
+ bootClassPath.add(LibraryClassTwo.class);
+ }
+ if (addToBootClasspath(LibraryClassThree.class)) {
+ bootClassPath.add(LibraryClassThree.class);
+ }
+
+ testForD8()
+ .setMode(mode)
+ .setUseDefaultRuntimeLibrary(false)
+ .apply(b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals.getProviders()))
+ .addProgramFiles(
+ compileResultProgramClass.writeToZip(),
+ compileResultProgramClassTwo.writeToZip(),
+ compileResultMain.writeToZip())
+ .setMinApi(parameters.getApiLevel())
+ .addAndroidBuildVersion(getApiLevelForRuntime())
+ .addHorizontallyMergedClassesInspector(horizontallyMergingConsumer)
+ .compile()
+ .inspect(inspect)
+ .addBootClasspathFiles(
+ buildOnDexRuntime(parameters, bootClassPath.toArray(new Class<?>[0])))
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ private D8TestCompileResult compileIntermediate(
+ CompilationMode mode,
+ GlobalSyntheticsTestingConsumer globals,
+ Class<?> programClass,
+ Class<?>... classpathClass)
+ throws Exception {
+ return testForD8(parameters.getBackend())
+ .setMode(mode)
+ .setIntermediate(true)
+ .addProgramClasses(programClass)
+ .addClasspathClasses(classpathClass)
+ .apply(this::setupTestBuilder)
+ .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals))
+ .compile();
+ }
+
+ private void checkOutput(SingleTestRunResult<?> runResult) {
+ runResult.assertSuccessWithOutputLines(
+ addToBootClasspath(LibraryClassOne.class) ? "LibraryClassOne::foo" : "Not calling foo()",
+ addToBootClasspath(LibraryClassTwo.class) ? "LibraryClassTwo::bar" : "Not calling bar()",
+ addToBootClasspath(LibraryClassThree.class)
+ ? "LibraryClassThree::baz"
+ : "Not calling baz()");
+ }
+
+ private void inspectNumberOfClassesFromOutput(CodeInspector inspector) {
+ // We always have Main, ProgramClassOne, ProgramClassTwo and AndroidBuildVersion as program
+ // classes. Depending on the api a number of synthetic classes.
+ int numberOfClasses =
+ 4
+ + (willStubLibraryClassThree() ? 2 : 0)
+ + BooleanUtils.intValue(parameters.getApiLevel().isLessThan(mockApiLevelTwo))
+ + BooleanUtils.intValue(parameters.getApiLevel().isLessThan(mockApiLevelOne));
+ assertEquals(numberOfClasses, inspector.allClasses().size());
+ assertThat(inspector.clazz(Main.class), isPresent());
+ assertThat(inspector.clazz(ProgramClassOne.class), isPresent());
+ }
+
+ // Will be present from api level 23
+ public static class LibraryClassOne {
+
+ public static void foo() {
+ System.out.println("LibraryClassOne::foo");
+ }
+ }
+
+ // Will be present from api level 26
+ public static class LibraryClassTwo {
+
+ public static void bar() {
+ System.out.println("LibraryClassTwo::bar");
+ }
+ }
+
+ // Will be present form api level 30
+ public static class LibraryClassThree {
+
+ public void baz() {
+ System.out.println("LibraryClassThree::baz");
+ }
+ }
+
+ public static class ProgramClassOne {
+
+ public static void callOneAndTwo() {
+ if (AndroidBuildVersion.VERSION >= 23) {
+ LibraryClassOne.foo();
+ } else {
+ System.out.println("Not calling foo()");
+ }
+ if (AndroidBuildVersion.VERSION >= 26) {
+ LibraryClassTwo.bar();
+ } else {
+ System.out.println("Not calling bar()");
+ }
+ }
+ }
+
+ public static class ProgramClassTwo extends LibraryClassThree {}
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ ProgramClassOne.callOneAndTwo();
+ if (AndroidBuildVersion.VERSION >= 30) {
+ new ProgramClassTwo().baz();
+ } else {
+ System.out.println("Not calling baz()");
+ }
+ }
+ }
+}