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