[ApiModel] Add test for targeted outlining from androidx

Change-Id: I1698e672369f3af78809eb66be6f6d6e76a10b26
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelAndroidxApiImplTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelAndroidxApiImplTest.java
new file mode 100644
index 0000000..ff1875d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelAndroidxApiImplTest.java
@@ -0,0 +1,226 @@
+// 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.synthesis.SyntheticItemsTestUtils.syntheticApiOutlineClass;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompileResult;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+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;
+
+@RunWith(Parameterized.class)
+public class ApiModelAndroidxApiImplTest extends TestBase {
+
+  private static final String newTestApi26Descriptor = "Landroidx/TestApi26ImplDescriptor;";
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private AndroidApiLevel getMaxSupportedApiLevel() {
+    return parameters.isCfRuntime()
+        ? AndroidApiLevel.B
+        : parameters.asDexRuntime().maxSupportedApiLevel();
+  }
+
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    testBuilder
+        .addProgramClassFileData(
+            transformer(TestApi26Impl.class).setClassDescriptor(newTestApi26Descriptor).transform(),
+            transformer(Main.class)
+                .replaceClassDescriptorInMethodInstructions(
+                    descriptor(TestApi26Impl.class), newTestApi26Descriptor)
+                .transform())
+        .addLibraryClasses(LibraryClass23.class, LibraryClass26.class, LibraryClass30.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .setMinApi(parameters.getApiLevel())
+        .addAndroidBuildVersion(getMaxSupportedApiLevel())
+        .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(setMockApiLevelForClass(LibraryClass23.class, AndroidApiLevel.M))
+        .apply(setMockApiLevelForClass(LibraryClass26.class, AndroidApiLevel.O))
+        .apply(setMockApiLevelForClass(LibraryClass30.class, AndroidApiLevel.R))
+        .apply(
+            setMockApiLevelForMethod(
+                LibraryClass23.class.getDeclaredMethod("foo"), AndroidApiLevel.M))
+        .apply(
+            setMockApiLevelForMethod(
+                LibraryClass26.class.getDeclaredMethod("bar"), AndroidApiLevel.O))
+        .apply(
+            setMockApiLevelForMethod(
+                LibraryClass30.class.getDeclaredMethod("baz"), AndroidApiLevel.R));
+  }
+
+  private void setupRuntime(TestCompileResult<?, ?> compileResult) throws Exception {
+    if (!parameters.isDexRuntime()) {
+      return;
+    }
+    AndroidApiLevel maxSupportedApiLevel = parameters.asDexRuntime().maxSupportedApiLevel();
+    if (maxSupportedApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.R)) {
+      compileResult.addBootClasspathFiles(
+          buildOnDexRuntime(
+              parameters, LibraryClass23.class, LibraryClass26.class, LibraryClass30.class));
+    } else if (maxSupportedApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.O)) {
+      compileResult.addBootClasspathFiles(
+          buildOnDexRuntime(parameters, LibraryClass23.class, LibraryClass26.class));
+    } else if (maxSupportedApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.M)) {
+      compileResult.addBootClasspathFiles(buildOnDexRuntime(parameters, LibraryClass23.class));
+    }
+  }
+
+  @Test
+  public void testD8Debug() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .compile()
+        .inspect(inspector -> inspect(inspector, false))
+        .apply(this::setupRuntime)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .setMode(CompilationMode.RELEASE)
+        .apply(this::setupTestBuilder)
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .compile()
+        .inspect(inspector -> inspect(inspector, false))
+        .apply(this::setupRuntime)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .apply(this::setupTestBuilder)
+        .addKeepMainRule(Main.class)
+        .addDontObfuscate()
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .compile()
+        .inspect(inspector -> inspect(inspector, true))
+        .apply(this::setupRuntime)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  private void inspect(CodeInspector inspector, boolean isR8) {
+    assertThat(inspector.clazz(Main.class), isPresent());
+    ClassReference classReference = Reference.classFromDescriptor(newTestApi26Descriptor);
+    if (parameters.isCfRuntime()) {
+      assertThat(inspector.clazz(classReference), isPresent());
+    } else {
+      assertThat(inspector.clazz(classReference), notIf(isPresent(), isR8));
+      assertThat(
+          inspector.clazz(syntheticApiOutlineClass(classReference, 0)),
+          notIf(isPresent(), parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.R)));
+      assertThat(
+          inspector.clazz(syntheticApiOutlineClass(classReference, 1)),
+          notIf(isPresent(), parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O)));
+      assertThat(
+          inspector.clazz(syntheticApiOutlineClass(classReference, 2)),
+          notIf(isPresent(), parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.M)));
+    }
+  }
+
+  private void checkOutput(SingleTestRunResult<?> runResult) {
+    if (getMaxSupportedApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.R)) {
+      runResult.assertSuccessWithOutputLines(
+          "LibraryClass23::foo", "LibraryClass26::bar", "LibraryClass30::baz");
+    } else if (getMaxSupportedApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O)) {
+      runResult.assertSuccessWithOutputLines("LibraryClass23::foo", "LibraryClass26::bar");
+    } else if (getMaxSupportedApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.M)) {
+      runResult.assertSuccessWithOutputLines("LibraryClass23::foo");
+    } else {
+      runResult.assertSuccessWithOutputLines("Api version not high enough");
+    }
+  }
+
+  public static class LibraryClass23 {
+
+    public static void foo() {
+      System.out.println("LibraryClass23::foo");
+    }
+  }
+
+  public static class LibraryClass26 {
+
+    public static void bar() {
+      System.out.println("LibraryClass26::bar");
+    }
+  }
+
+  public static class LibraryClass30 {
+
+    public static void baz() {
+      System.out.println("LibraryClass30::baz");
+    }
+  }
+
+  public static class /* androidx. */ TestApi26Impl {
+
+    public static void has23Reference() {
+      LibraryClass23.foo();
+    }
+
+    public static void has26Reference() {
+      LibraryClass26.bar();
+    }
+
+    public static void has30Reference() {
+      LibraryClass30.baz();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      if (AndroidBuildVersion.VERSION >= 23) {
+        TestApi26Impl.has23Reference();
+        if (AndroidBuildVersion.VERSION >= 26) {
+          TestApi26Impl.has26Reference();
+          if (AndroidBuildVersion.VERSION >= 30) {
+            TestApi26Impl.has30Reference();
+          }
+        }
+      } else {
+        System.out.println("Api version not high enough");
+      }
+    }
+  }
+}