Compatibility tests for allowshrinking.

Bug: 171289133
Change-Id: I0b740b28921d95fa573e30ac5aacc44c27aac5e3
diff --git a/src/test/java/com/android/tools/r8/ProguardVersion.java b/src/test/java/com/android/tools/r8/ProguardVersion.java
index 8c9050a..20560fc 100644
--- a/src/test/java/com/android/tools/r8/ProguardVersion.java
+++ b/src/test/java/com/android/tools/r8/ProguardVersion.java
@@ -20,6 +20,10 @@
     this.version = version;
   }
 
+  public static ProguardVersion getLatest() {
+    return V7_0_0;
+  }
+
   public Path getProguardScript() {
     Path scriptDirectory = Paths.get(ToolHelper.THIRD_PARTY_DIR).resolve("proguard");
     if (this == V7_0_0) {
@@ -33,8 +37,12 @@
     return scriptDirectory.resolve("bin/proguard.sh");
   }
 
+  public String getVersion() {
+    return version;
+  }
+
   @Override
   public String toString() {
-    return "Proguard " + version.toString();
+    return "Proguard " + version;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/proguard/KeepAllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/proguard/KeepAllowShrinkingCompatibilityTest.java
new file mode 100644
index 0000000..130dc2d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/proguard/KeepAllowShrinkingCompatibilityTest.java
@@ -0,0 +1,142 @@
+// Copyright (c) 2020, 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.proguard;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepAllowShrinkingCompatibilityTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final boolean allowOptimization;
+  private final boolean allowObfuscation;
+  private final Shrinker shrinker;
+
+  @Parameterized.Parameters(name = "{0}, opt:{1}, obf:{2}, {3}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(),
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        ImmutableList.of(Shrinker.R8, Shrinker.PG));
+  }
+
+  public KeepAllowShrinkingCompatibilityTest(
+      TestParameters parameters,
+      boolean allowOptimization,
+      boolean allowObfuscation,
+      Shrinker shrinker) {
+    this.parameters = parameters;
+    this.allowOptimization = allowOptimization;
+    this.allowObfuscation = allowObfuscation;
+    this.shrinker = shrinker;
+  }
+
+  String getExpected() {
+    return StringUtils.lines(
+        "A::foo",
+        // Reflective lookup of A::foo will only work if optimization and obfuscation are disabled.
+        Boolean.toString(
+            !allowOptimization
+                && !allowObfuscation
+                // TODO(b/171289133): Remove this exception once fixed.
+                && !shrinker.isR8()),
+        "false");
+  }
+
+  @Test
+  public void test() throws Exception {
+    if (shrinker.isR8()) {
+      run(
+          testForR8(parameters.getBackend())
+              // TODO(b/171289133): The keep rule should not be "unmatched".
+              .allowUnusedProguardConfigurationRules(allowOptimization && allowObfuscation));
+    } else {
+      run(testForProguard(shrinker.getProguardVersion()).addDontWarn(getClass()));
+    }
+  }
+
+  public <T extends TestShrinkerBuilder<?, ?, ?, ?, T>> void run(T builder) throws Exception {
+    String keepRule =
+        "-keep,allowshrinking"
+            + (allowOptimization ? ",allowoptimization" : "")
+            + (allowObfuscation ? ",allowobfuscation" : "")
+            + " class * { java.lang.String foo(); }";
+    builder
+        .addInnerClasses(KeepAllowShrinkingCompatibilityTest.class)
+        .addKeepClassAndMembersRules(TestClass.class)
+        .addKeepRules(keepRule)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class, A.class.getTypeName())
+        .assertSuccessWithOutput(getExpected())
+        .inspect(
+            inspector -> {
+              ClassSubject aClass = inspector.clazz(A.class);
+              ClassSubject bClass = inspector.clazz(B.class);
+              // The class constants will force A and B to be retained, but not the foo methods.
+              assertThat(bClass, isPresentAndRenamed(allowObfuscation));
+              assertThat(aClass, isPresentAndRenamed(allowObfuscation));
+              assertThat(bClass.uniqueMethodWithName("foo"), not(isPresent()));
+              MethodSubject aFoo = aClass.uniqueMethodWithName("foo");
+              // TODO(b/171289133): Remove R8 check once fixed.
+              if (allowOptimization || shrinker.isR8()) {
+                assertThat(aFoo, not(isPresent()));
+              } else {
+                assertThat(aFoo, isPresentAndRenamed(allowObfuscation));
+                assertThat(inspector.clazz(TestClass.class).mainMethod(), invokesMethod(aFoo));
+              }
+            });
+  }
+
+  static class A {
+    public String foo() {
+      return "A::foo";
+    }
+  }
+
+  static class B {
+    public String foo() {
+      return "B::foo";
+    }
+  }
+
+  static class TestClass {
+
+    public static boolean hasFoo(String name) {
+      try {
+        return Class.forName(name).getDeclaredMethod("foo") != null;
+      } catch (Exception e) {
+        return false;
+      }
+    }
+
+    public static void main(String[] args) {
+      // Direct call to A.foo, if optimization is not allowed it will be kept.
+      A a = new A();
+      System.out.println(a.foo());
+      // Reference to A should not retain A::foo when allowoptimization is set.
+      // Note: if using class constant A.class, PG will actually retain A::foo !?
+      System.out.println(hasFoo(a.getClass().getTypeName()));
+      // Reference to B should not retain B::foo regardless of allowoptimization.
+      System.out.println(hasFoo(B.class.getTypeName()));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/proguard/KeepClassMembersAllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/proguard/KeepClassMembersAllowShrinkingCompatibilityTest.java
new file mode 100644
index 0000000..cb83f31
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/proguard/KeepClassMembersAllowShrinkingCompatibilityTest.java
@@ -0,0 +1,142 @@
+// Copyright (c) 2020, 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.proguard;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepClassMembersAllowShrinkingCompatibilityTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final boolean allowOptimization;
+  private final boolean allowObfuscation;
+  private final Shrinker shrinker;
+
+  @Parameterized.Parameters(name = "{0}, opt:{1}, obf:{2}, {3}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(),
+        BooleanUtils.values(),
+        BooleanUtils.values(),
+        ImmutableList.of(Shrinker.R8, Shrinker.PG));
+  }
+
+  public KeepClassMembersAllowShrinkingCompatibilityTest(
+      TestParameters parameters,
+      boolean allowOptimization,
+      boolean allowObfuscation,
+      Shrinker shrinker) {
+    this.parameters = parameters;
+    this.allowOptimization = allowOptimization;
+    this.allowObfuscation = allowObfuscation;
+    this.shrinker = shrinker;
+  }
+
+  String getExpected() {
+    return StringUtils.lines(
+        "A::foo",
+        // Reflective lookup of A::foo will only work if optimization and obfuscation are disabled.
+        Boolean.toString(
+            !allowOptimization
+                && !allowObfuscation
+                // TODO(b/171289133): Remove this exception once fixed.
+                && !shrinker.isR8()),
+        "false");
+  }
+
+  @Test
+  public void test() throws Exception {
+    if (shrinker.isR8()) {
+      run(
+          testForR8(parameters.getBackend())
+              // TODO(b/171289133): The keep rule should not be "unmatched".
+              .allowUnusedProguardConfigurationRules(allowOptimization && allowObfuscation));
+    } else {
+      run(testForProguard(shrinker.getProguardVersion()).addDontWarn(getClass()));
+    }
+  }
+
+  public <T extends TestShrinkerBuilder<?, ?, ?, ?, T>> void run(T builder) throws Exception {
+    String keepRule =
+        "-keepclassmembers,allowshrinking"
+            + (allowOptimization ? ",allowoptimization" : "")
+            + (allowObfuscation ? ",allowobfuscation" : "")
+            + " class * { java.lang.String foo(); }";
+    builder
+        .addInnerClasses(KeepClassMembersAllowShrinkingCompatibilityTest.class)
+        .addKeepClassAndMembersRules(TestClass.class)
+        .addKeepRules(keepRule)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class, A.class.getTypeName())
+        .assertSuccessWithOutput(getExpected())
+        .inspect(
+            inspector -> {
+              ClassSubject aClass = inspector.clazz(A.class);
+              ClassSubject bClass = inspector.clazz(B.class);
+              // The class constants will force A and B to be retained, but not the foo methods.
+              assertThat(bClass, isPresentAndRenamed());
+              assertThat(aClass, isPresentAndRenamed());
+              assertThat(bClass.uniqueMethodWithName("foo"), not(isPresent()));
+              MethodSubject aFoo = aClass.uniqueMethodWithName("foo");
+              // TODO(b/171289133): Remove R8 check once fixed.
+              if (allowOptimization || shrinker.isR8()) {
+                assertThat(aFoo, not(isPresent()));
+              } else {
+                assertThat(aFoo, isPresentAndRenamed(allowObfuscation));
+                assertThat(inspector.clazz(TestClass.class).mainMethod(), invokesMethod(aFoo));
+              }
+            });
+  }
+
+  static class A {
+    public String foo() {
+      return "A::foo";
+    }
+  }
+
+  static class B {
+    public String foo() {
+      return "B::foo";
+    }
+  }
+
+  static class TestClass {
+
+    public static boolean hasFoo(String name) {
+      try {
+        return Class.forName(name).getDeclaredMethod("foo") != null;
+      } catch (Exception e) {
+        return false;
+      }
+    }
+
+    public static void main(String[] args) {
+      // Direct call to A.foo, if optimization is not allowed it will be kept.
+      A a = new A();
+      System.out.println(a.foo());
+      // Reference to A should not retain A::foo when allowoptimization is set.
+      // Note: if using class constant A.class, PG will actually retain A::foo !?
+      System.out.println(hasFoo(a.getClass().getTypeName()));
+      // Reference to B should not retain B::foo regardless of allowoptimization.
+      System.out.println(hasFoo(B.class.getTypeName()));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/proguard/AllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/proguard/KeepStaticMethodAllowShrinkingCompatibilityTest.java
similarity index 75%
rename from src/test/java/com/android/tools/r8/proguard/AllowShrinkingCompatibilityTest.java
rename to src/test/java/com/android/tools/r8/proguard/KeepStaticMethodAllowShrinkingCompatibilityTest.java
index ca30a1f..a1fa1c3 100644
--- a/src/test/java/com/android/tools/r8/proguard/AllowShrinkingCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/proguard/KeepStaticMethodAllowShrinkingCompatibilityTest.java
@@ -10,13 +10,14 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.ProguardVersion;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -24,30 +25,38 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class AllowShrinkingCompatibilityTest extends TestBase {
+public class KeepStaticMethodAllowShrinkingCompatibilityTest extends TestBase {
 
   private final boolean allowOptimization;
   private final TestParameters parameters;
-  private final ProguardVersion proguardVersion;
+  private final Shrinker shrinker;
 
   @Parameters(name = "{1}, {2}, allow optimization: {0}")
   public static List<Object[]> data() {
     return buildParameters(
         BooleanUtils.values(),
         getTestParameters().withCfRuntimes().build(),
-        ProguardVersion.values());
+        ImmutableList.of(Shrinker.R8, Shrinker.PG));
   }
 
-  public AllowShrinkingCompatibilityTest(
-      boolean allowOptimization, TestParameters parameters, ProguardVersion proguardVersion) {
+  public KeepStaticMethodAllowShrinkingCompatibilityTest(
+      boolean allowOptimization, TestParameters parameters, Shrinker shrinker) {
     this.allowOptimization = allowOptimization;
     this.parameters = parameters;
-    this.proguardVersion = proguardVersion;
+    this.shrinker = shrinker;
   }
 
   @Test
   public void test() throws Exception {
-    testForProguard(proguardVersion)
+    if (shrinker.isPG()) {
+      run(testForProguard(shrinker.getProguardVersion()).addDontWarn(getClass()));
+    } else {
+      run(testForR8(parameters.getBackend()));
+    }
+  }
+
+  private <T extends TestShrinkerBuilder<?, ?, ?, ?, T>> void run(T builder) throws Exception {
+    builder
         .addProgramClasses(TestClass.class, Companion.class)
         .addKeepMainRule(TestClass.class)
         .addKeepRules(
@@ -56,7 +65,6 @@
                 + " class "
                 + Companion.class.getTypeName()
                 + " { <methods>; }")
-        .addDontWarn(getClass())
         .compile()
         .inspect(
             inspector -> {
@@ -64,12 +72,15 @@
               assertThat(testClassSubject, isPresent());
 
               ClassSubject companionClassSubject = inspector.clazz(Companion.class);
-              assertThat(companionClassSubject, notIf(isPresent(), allowOptimization));
+              // TODO(b/171289133): Remove the R8 check one fixed.
+              assertThat(
+                  companionClassSubject, notIf(isPresent(), allowOptimization || shrinker.isR8()));
 
               MethodSubject mainMethodSubject = testClassSubject.mainMethod();
               MethodSubject getMethodSubject = companionClassSubject.uniqueMethodWithName("get");
 
-              if (allowOptimization) {
+              // TODO(b/171289133): Remove the R8 check once fixed.
+              if (allowOptimization || shrinker.isR8()) {
                 assertTrue(
                     testClassSubject
                         .mainMethod()
diff --git a/src/test/java/com/android/tools/r8/proguard/Shrinker.java b/src/test/java/com/android/tools/r8/proguard/Shrinker.java
new file mode 100644
index 0000000..8cd4867
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/proguard/Shrinker.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2020, 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.proguard;
+
+import com.android.tools.r8.ProguardVersion;
+
+class Shrinker {
+
+  public static final Shrinker R8 = new Shrinker(null);
+  public static final Shrinker PG = new Shrinker(ProguardVersion.getLatest());
+  public static final Shrinker PG5 = new Shrinker(ProguardVersion.V5_2_1);
+  public static final Shrinker PG6 = new Shrinker(ProguardVersion.V6_0_1);
+  public static final Shrinker PG7 = new Shrinker(ProguardVersion.V7_0_0);
+
+  private final ProguardVersion proguardVersion;
+
+  private Shrinker(ProguardVersion proguardVersion) {
+    this.proguardVersion = proguardVersion;
+  }
+
+  boolean isR8() {
+    return proguardVersion == null;
+  }
+
+  boolean isPG() {
+    return proguardVersion != null;
+  }
+
+  public ProguardVersion getProguardVersion() {
+    return proguardVersion;
+  }
+
+  @Override
+  public String toString() {
+    return isR8() ? "r8" : "pg" + proguardVersion.getVersion();
+  }
+}