Introduce ApiOutliningTestBase and simple api inlining tests

Bug: 188388130
Change-Id: I5e348f57e78504e5f87de06a1036c2a693884384
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 412c97cd..12126a4 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -51,6 +51,7 @@
 import com.android.tools.r8.naming.MapVersion;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
+import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.repackaging.Repackaging.DefaultRepackagingConfiguration;
 import com.android.tools.r8.repackaging.Repackaging.RepackagingConfiguration;
@@ -348,6 +349,9 @@
   // Contain the contents of the build properties file from the compiler command.
   public DumpOptions dumpOptions;
 
+  // A mapping from methods to the api-level introducing them.
+  public Map<MethodReference, AndroidApiLevel> methodApiMapping = new HashMap<>();
+
   // Hidden marker for classes.dex
   private boolean hasMarker = false;
   private Marker marker;
diff --git a/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningNoInliningOfHigherApiLevelIntoLowerTest.java b/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningNoInliningOfHigherApiLevelIntoLowerTest.java
new file mode 100644
index 0000000..7620ae4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningNoInliningOfHigherApiLevelIntoLowerTest.java
@@ -0,0 +1,87 @@
+// 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.apioutlining;
+
+import static com.android.tools.r8.apioutlining.ApiOutliningTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apioutlining.ApiOutliningTestHelper.verifyThat;
+import static org.junit.Assert.assertThrows;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Method;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ApiOutliningNoInliningOfHigherApiLevelIntoLowerTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public ApiOutliningNoInliningOfHigherApiLevelIntoLowerTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test()
+  public void testR8() throws Exception {
+    Method apiLevel21 = A.class.getDeclaredMethod("apiLevel21");
+    Method apiLevel22 = B.class.getDeclaredMethod("apiLevel22");
+    R8TestRunResult runResult =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(getClass())
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(Main.class)
+            .enableInliningAnnotations()
+            .apply(setMockApiLevelForMethod(apiLevel21, AndroidApiLevel.L))
+            .apply(setMockApiLevelForMethod(apiLevel22, AndroidApiLevel.L_MR1))
+            .run(parameters.getRuntime(), Main.class)
+            .assertSuccessWithOutputLines("A::apiLevel21", "B::apiLevel22");
+    if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L_MR1)) {
+      runResult.inspect(
+          verifyThat(parameters, apiLevel22)
+              .inlinedIntoFromApiLevel(apiLevel21, AndroidApiLevel.L_MR1));
+    } else {
+      // TODO(b/188388130): Should only inline on minApi >= 22.
+      assertThrows(
+          AssertionError.class,
+          () ->
+              runResult.inspect(
+                  verifyThat(parameters, apiLevel22)
+                      .inlinedIntoFromApiLevel(apiLevel21, AndroidApiLevel.L_MR1)));
+    }
+  }
+
+  public static class B {
+    public static void apiLevel22() {
+      System.out.println("B::apiLevel22");
+    }
+  }
+
+  public static class A {
+
+    @NeverInline
+    public static void apiLevel21() {
+      System.out.println("A::apiLevel21");
+      B.apiLevel22();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      A.apiLevel21();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningNoInliningOfHigherApiLevelTest.java b/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningNoInliningOfHigherApiLevelTest.java
new file mode 100644
index 0000000..44263ba
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningNoInliningOfHigherApiLevelTest.java
@@ -0,0 +1,86 @@
+// 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.apioutlining;
+
+import static com.android.tools.r8.apioutlining.ApiOutliningTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apioutlining.ApiOutliningTestHelper.verifyThat;
+import static org.junit.Assert.assertThrows;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Method;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ApiOutliningNoInliningOfHigherApiLevelTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public ApiOutliningNoInliningOfHigherApiLevelTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Method minApi = A.class.getDeclaredMethod("minApi");
+    Method apiLevel22 = B.class.getDeclaredMethod("apiLevel22");
+    R8TestRunResult runResult =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(getClass())
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(Main.class)
+            .enableInliningAnnotations()
+            .apply(setMockApiLevelForMethod(apiLevel22, AndroidApiLevel.L_MR1))
+            .run(parameters.getRuntime(), Main.class)
+            .assertSuccessWithOutputLines("A::minApi", "B::apiLevel22");
+    if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L_MR1)) {
+      runResult.inspect(
+          verifyThat(parameters, apiLevel22)
+              .inlinedIntoFromApiLevel(minApi, AndroidApiLevel.L_MR1));
+    } else {
+      // TODO(b/188388130): Should only inline on minApi >= 22.
+      assertThrows(
+          AssertionError.class,
+          () ->
+              runResult.inspect(
+                  verifyThat(parameters, apiLevel22)
+                      .inlinedIntoFromApiLevel(minApi, AndroidApiLevel.L_MR1)));
+    }
+  }
+
+  public static class B {
+    public static void apiLevel22() {
+      System.out.println("B::apiLevel22");
+    }
+  }
+
+  public static class A {
+
+    @NeverInline
+    public static void minApi() {
+      System.out.println("A::minApi");
+      B.apiLevel22();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      A.minApi();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningTestHelper.java b/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningTestHelper.java
new file mode 100644
index 0000000..f10a6e9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningTestHelper.java
@@ -0,0 +1,78 @@
+// 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.apioutlining;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.CodeMatchers;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.lang.reflect.Method;
+
+public abstract class ApiOutliningTestHelper {
+
+  static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>>
+      ThrowableConsumer<T> setMockApiLevelForMethod(Method method, AndroidApiLevel apiLevel) {
+    return compilerBuilder -> {
+      compilerBuilder.addOptionsModification(
+          options -> {
+            options.methodApiMapping.put(Reference.methodFromMethod(method), apiLevel);
+          });
+    };
+  }
+
+  static ApiOutliningMethodVerificationHelper verifyThat(TestParameters parameters, Method method) {
+    return new ApiOutliningMethodVerificationHelper(parameters, method);
+  }
+
+  public static class ApiOutliningMethodVerificationHelper {
+
+    private final Method methodOfInterest;
+    private final TestParameters parameters;
+
+    public ApiOutliningMethodVerificationHelper(
+        TestParameters parameters, Method methodOfInterest) {
+      this.methodOfInterest = methodOfInterest;
+      this.parameters = parameters;
+    }
+
+    protected ThrowingConsumer<CodeInspector, Exception> inlinedIntoFromApiLevel(
+        Method method, AndroidApiLevel apiLevel) {
+      return parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevel)
+          ? inlinedInto(method)
+          : notInlinedInto(method);
+    }
+
+    private ThrowingConsumer<CodeInspector, Exception> notInlinedInto(Method method) {
+      return inspector -> {
+        MethodSubject candidate = inspector.method(methodOfInterest);
+        assertThat(candidate, isPresent());
+        MethodSubject target = inspector.method(method);
+        assertThat(target, isPresent());
+        assertThat(target, CodeMatchers.invokesMethod(candidate));
+      };
+    }
+
+    private ThrowingConsumer<CodeInspector, Exception> inlinedInto(Method method) {
+      return inspector -> {
+        MethodSubject candidate = inspector.method(methodOfInterest);
+        if (!candidate.isPresent()) {
+          return;
+        }
+        MethodSubject target = inspector.method(method);
+        assertThat(target, isPresent());
+        assertThat(target, not(CodeMatchers.invokesMethod(candidate)));
+      };
+    }
+  }
+}