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