Add test using class not found on Android (java.lang.ClassValue)
Bug: b/259501764
Change-Id: Iadfb06d7a0f73e1b76dd02fbc56878b680ca088e
diff --git a/src/test/java/com/android/tools/r8/apimodel/ClassValueTest.java b/src/test/java/com/android/tools/r8/apimodel/ClassValueTest.java
new file mode 100644
index 0000000..ad9ea40
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ClassValueTest.java
@@ -0,0 +1,224 @@
+// Copyright (c) 2022, 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.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+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.Assume.assumeTrue;
+
+import com.android.tools.r8.ProguardVersion;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+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;
+
+@RunWith(Parameterized.class)
+public class ClassValueTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ private static final String EXPECTED_OUTPUT = StringUtils.lines(TestClass.class.getTypeName());
+
+ private void computeValuePresent(CodeInspector inspector) {
+ assertThat(
+ inspector.clazz(ClassValueSub.class).uniqueMethodWithOriginalName("computeValue"),
+ isPresent());
+ }
+
+ private void computeValueAbsent(CodeInspector inspector) {
+ assertThat(
+ inspector.clazz(ClassValueSub.class).uniqueMethodWithOriginalName("computeValue"),
+ isAbsent());
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForRuntime(parameters)
+ .addInnerClasses(getClass())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::computeValuePresent)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .addDontWarn(ClassValue.class)
+ .compile()
+ .inspect(this::computeValueAbsent)
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ parameters.isCfRuntime(),
+ r -> r.assertFailureWithErrorThatThrows(AbstractMethodError.class),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+ }
+
+ @Test
+ public void testProguard() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForProguard(ProguardVersion.V7_0_0)
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ // ProGuard warns about the inner class attributes referring to this outer class.
+ .addDontWarn(this.getClass().getTypeName())
+ // ProGuard also warns about ClassValueSub not having method get.
+ .addDontWarn(ClassValueSub.class.getTypeName())
+ .compile()
+ .inspect(this::computeValueAbsent)
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ parameters.isCfRuntime(),
+ r -> r.assertFailureWithErrorThatThrows(AbstractMethodError.class),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+ }
+
+ @Test
+ public void testR8KeepExtendsMissingType() throws Exception {
+ testForR8(parameters.getBackend())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .addDontWarn(ClassValue.class)
+ // Try to keep computeValue on classes extending unknown type.
+ .addKeepRules("-keep class * extends " + ClassValue.class.getTypeName() + " { *; }")
+ .allowUnusedProguardConfigurationRules()
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics.assertInfoThatMatches(
+ diagnosticMessage(
+ containsString(
+ "Proguard configuration rule does not match anything: "
+ + "`-keep class * extends "
+ + ClassValue.class.getTypeName()
+ + " {"))))
+ .inspect(this::computeValueAbsent)
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ parameters.isCfRuntime(),
+ r -> r.assertFailureWithErrorThatThrows(AbstractMethodError.class),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+ }
+
+ @Test
+ public void testProguardKeepExtendsMissingTypeProguard() throws Exception {
+ assumeTrue(parameters.isCfRuntime() && parameters.asCfRuntime().getVm() == CfVm.JDK11);
+ testForProguard(ProguardVersion.V7_0_0)
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ // ProGuard warns about the inner class attributes referring to this outer class.
+ .addDontWarn(this.getClass().getTypeName())
+ // Just -dontwarn on ClassValue is not sufficient. ProGuard also warns about ClassValueSub
+ // not having method get.
+ .addDontWarn(ClassValueSub.class.getTypeName())
+ // Try to keep computeValue on classes extending unknown type.
+ .addKeepRules("-keep class * extends " + ClassValue.class.getTypeName() + " { *; }")
+ // TODO(b/261971620): Support extends matching names of missing classes.
+ .compile()
+ .inspect(this::computeValueAbsent)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertFailureWithErrorThatThrows(AbstractMethodError.class);
+ }
+
+ @Test
+ public void testR8KeepConcreteMethod() throws Exception {
+ for (String dontWarn :
+ ImmutableList.of(ClassValue.class.getTypeName(), ClassValueSub.class.getTypeName())) {
+ testForR8(parameters.getBackend())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .addDontWarn(dontWarn)
+ .addKeepRules(
+ "-keep class "
+ + ClassValueSub.class.getTypeName()
+ + " { ** computeValue(java.lang.Class); }")
+ .compile()
+ .inspect(this::computeValuePresent)
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ parameters.isCfRuntime(),
+ r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+ }
+ }
+
+ @Test
+ public void testProguardKeepConcreteMethod() throws Exception {
+ assumeTrue(parameters.isCfRuntime() && parameters.asCfRuntime().getVm() == CfVm.JDK11);
+ testForProguard(ProguardVersion.V7_0_0)
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ // ProGuard warns about the inner class attributes referring to this outer class.
+ .addDontWarn(this.getClass().getTypeName())
+ // Just -dontwarn on ClassValue is not sufficient. ProGuard also warns about ClassValueSub
+ // not having method get.
+ .addDontWarn(ClassValueSub.class.getTypeName())
+ .addKeepRules(
+ "-keep class "
+ + ClassValueSub.class.getTypeName()
+ + " { ** computeValue(java.lang.Class); }")
+ .compile()
+ .inspect(this::computeValuePresent)
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ parameters.isCfRuntime(),
+ r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+ }
+
+ static class ClassValueSub extends ClassValue<Object> {
+ @Override
+ protected Object computeValue(Class<?> clazz) {
+ return clazz.getTypeName();
+ }
+ }
+
+ static class TestClass {
+ static ClassValueSub classValueSub = new ClassValueSub();
+
+ public static void main(String[] args) {
+ System.out.println(classValueSub.get(TestClass.class));
+ }
+ }
+}