Add more test cases for keeping annotated members.

Bug: 159418523
Bug: 159971974
Change-Id: If78efdee88dfdb2307d832e1ce06cf5ce8a1b89e
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index 84f4716..d989fbf 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -7,15 +7,29 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Sets.SetView;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -29,6 +43,11 @@
   private static final String PRESENT_ANNOTATION =
       "com.android.tools.r8.com.google.common.annotations.VisibleForTesting";
 
+  private static final String CLASS_WITH_ANNOTATED_METHOD =
+      "com.android.tools.r8.com.google.common.math.IntMath";
+
+  private static final String ANNOTATED_METHOD = "lessThanBranchFree";
+
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withNoneRuntime().build();
@@ -43,29 +62,236 @@
     CodeInspector inspector = new CodeInspector(R8_JAR);
     assertThat(inspector.clazz(ABSENT_ANNOTATION), not(isPresent()));
     assertThat(inspector.clazz(PRESENT_ANNOTATION), isPresent());
+    ClassSubject clazz = inspector.clazz(CLASS_WITH_ANNOTATED_METHOD);
+    MethodSubject method = clazz.uniqueMethodWithName(ANNOTATED_METHOD);
+    assertThat(method, isPresent());
   }
 
-  // TODO(b/159966986): Fix this!
-  @Test(expected = CompilationFailedException.class)
-  public void testAbsentAnnotation() throws Exception {
-    testForR8(Backend.CF)
-        .addProgramFiles(R8_JAR)
-        .addKeepRules("-keep class * { @" + ABSENT_ANNOTATION + " *; }")
-        .setMinApi(10000)
-        .compileWithExpectedDiagnostics(
-            diagnostics ->
-                diagnostics.assertErrorsMatch(diagnosticException(AssertionError.class)));
-  }
-
-  // TODO(b/159966986): Fix this!
+  // TODO(b/159966986): A general keep rule should not cause compiler assertion errors.
   @Test(expected = CompilationFailedException.class)
   public void testPresentAnnotation() throws Exception {
     testForR8(Backend.CF)
-        .addProgramFiles(Paths.get(ToolHelper.THIRD_PARTY_DIR, "r8", "r8.jar"))
+        .addProgramFiles(R8_JAR)
         .addKeepRules("-keep class * { @" + PRESENT_ANNOTATION + " *; }")
-        .setMinApi(10000)
         .compileWithExpectedDiagnostics(
             diagnostics ->
                 diagnostics.assertErrorsMatch(diagnosticException(AssertionError.class)));
   }
+
+  @Test
+  public void testPresentAnnotationSplit() throws Exception {
+    // These rules should be equivalent to the above.
+    testForR8(Backend.CF)
+        .addProgramFiles(R8_JAR)
+        .addKeepRules(
+            "-keep class *", "-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *; }")
+        .compile();
+  }
+
+  @Test
+  public void testWithMembersAbsentAnnotation() throws Exception {
+    testForR8(Backend.CF)
+        .addProgramFiles(R8_JAR)
+        .allowUnusedProguardConfigurationRules()
+        .addKeepRules("-keepclasseswithmembers class * { @" + ABSENT_ANNOTATION + " *; }")
+        .compile()
+        .inspect(inspector -> assertEquals(0, inspector.allClasses().size()));
+  }
+
+  @Test
+  public void testWithMembersPresentAnnotation() throws Exception {
+    testForR8(Backend.CF)
+        .addProgramFiles(R8_JAR)
+        .addKeepRules("-keepclasseswithmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject clazz = inspector.clazz(CLASS_WITH_ANNOTATED_METHOD);
+              assertThat(clazz, isPresent());
+              assertThat(clazz.uniqueMethodWithName(ANNOTATED_METHOD), isPresent());
+            });
+  }
+
+  @Test
+  public void testUnsatisfiedClassMembersPresentAnnotation() throws Exception {
+    testForR8(Backend.CF)
+        .addProgramFiles(R8_JAR)
+        // TODO(b/159971974): Technically this rule does not hit anything and should fail due to
+        //  missing allowUnusedProguardConfigurationRules()
+        .addKeepRules("-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
+        .compile()
+        .inspect(inspector -> assertEquals(0, inspector.allClasses().size()));
+  }
+
+  @Test
+  public void testSatisfiedClassMembersPresentAnnotation() throws Exception {
+    testForR8(Backend.CF)
+        .addProgramFiles(R8_JAR)
+        .addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
+        .addKeepRules("-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
+        .compile()
+        .inspect(
+            inspector -> {
+              assertEquals(1, inspector.allClasses().size());
+              List<FoundMethodSubject> methods =
+                  inspector.clazz(CLASS_WITH_ANNOTATED_METHOD).allMethods();
+              assertEquals(
+                  1, methods.stream().filter(m -> m.getOriginalName().equals("<init>")).count());
+              assertEquals(
+                  1,
+                  methods.stream()
+                      .filter(m -> m.getOriginalName().equals(ANNOTATED_METHOD))
+                      .count());
+              assertEquals(2, methods.size());
+            });
+  }
+
+  @Test
+  public void testUnsatisfiedConditionalPresentAnnotation() throws Exception {
+    testForR8(Backend.CF)
+        .addProgramFiles(R8_JAR)
+        .allowUnusedProguardConfigurationRules()
+        .addKeepRules("-if class * -keep class <1> { @" + PRESENT_ANNOTATION + " *** *(...); }")
+        .compile()
+        .inspect(inspector -> assertEquals(0, inspector.allClasses().size()));
+  }
+
+  @Test
+  public void testSatisfiedConditionalPresentAnnotation() throws Exception {
+    testForR8(Backend.CF)
+        .addProgramFiles(R8_JAR)
+        .addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
+        .addKeepRules("-if class * -keep class <1> { @" + PRESENT_ANNOTATION + " *** *(...); }")
+        .compile()
+        .inspect(
+            inspector -> {
+              assertEquals(1, inspector.allClasses().size());
+              List<FoundMethodSubject> methods =
+                  inspector.clazz(CLASS_WITH_ANNOTATED_METHOD).allMethods();
+              assertEquals(
+                  1, methods.stream().filter(m -> m.getOriginalName().equals("<init>")).count());
+              // TODO(b/132318609): This should have the annotated method, but does not due to the
+              //  annotation being removed.
+              assertEquals(
+                  0,
+                  methods.stream()
+                      .filter(m -> m.getOriginalName().equals(ANNOTATED_METHOD))
+                      .count());
+              assertEquals(1, methods.size());
+            });
+  }
+
+  @Test
+  public void testConditionalEqualsKeepClassMembers() throws Exception {
+    GraphInspector referenceInspector =
+        testForR8(Backend.CF)
+            .enableGraphInspector()
+            .addProgramFiles(R8_JAR)
+            .addKeepMainRule(R8.class)
+            .addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
+            // TODO(b/132318609): Remove keep annotation once fixed.
+            .addKeepClassRules(PRESENT_ANNOTATION)
+            .addKeepRules("-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
+            .compile()
+            .graphInspector();
+
+    GraphInspector ifThenKeepClassMembersInspector =
+        testForR8(Backend.CF)
+            .enableGraphInspector()
+            .addProgramFiles(R8_JAR)
+            .addKeepMainRule(R8.class)
+            .addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
+            // TODO(b/132318609): Remove keep annotation once fixed.
+            .addKeepClassRules(PRESENT_ANNOTATION)
+            .addKeepRules(
+                "-if class * "
+                    + "-keepclassmembers class <1> { @"
+                    + PRESENT_ANNOTATION
+                    + " *** *(...); }")
+            .compile()
+            .graphInspector();
+    // TODO(b/159418523): It appears that the insertion in never-inline causes additional retention.
+    assertRetainedClassesEqual(referenceInspector, ifThenKeepClassMembersInspector, false, true);
+
+    GraphInspector ifThenKeepClassesWithMembersInspector =
+        testForR8(Backend.CF)
+            .enableGraphInspector()
+            .addProgramFiles(R8_JAR)
+            .addKeepMainRule(R8.class)
+            .addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
+            // TODO(b/132318609): Remove keep annotation once fixed.
+            .addKeepClassRules(PRESENT_ANNOTATION)
+            .addKeepRules(
+                "-if class * "
+                    + "-keepclasseswithmembers class <1> { @"
+                    + PRESENT_ANNOTATION
+                    + " *** *(...); }")
+            .compile()
+            .graphInspector();
+    // TODO(b/159418523): It appears that the insertion in never-inline causes additional retention.
+    assertRetainedClassesEqual(
+        referenceInspector, ifThenKeepClassesWithMembersInspector, false, true);
+
+    GraphInspector ifHasMemberThenKeepClassInspector =
+        testForR8(Backend.CF)
+            .enableGraphInspector()
+            .addProgramFiles(R8_JAR)
+            .addKeepMainRule(R8.class)
+            .addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
+            // TODO(b/132318609): Remove keep annotation once fixed.
+            .addKeepClassRules(PRESENT_ANNOTATION)
+            .addKeepRules(
+                "-if class * { @"
+                    + PRESENT_ANNOTATION
+                    + " *** *(...); } "
+                    + "-keep class <1> { @"
+                    + PRESENT_ANNOTATION
+                    + " *** <2>(...); }")
+            .compile()
+            .graphInspector();
+    // TODO(b/159418523): It appears that the insertion in never-inline causes additional retention.
+    //  Also, here neither is a subset of the other. That also appears wrong.
+    assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector, true, true);
+  }
+
+  private void assertRetainedClassesEqual(
+      GraphInspector referenceResult,
+      GraphInspector conditionalResult,
+      boolean expectReferenceIsLarger,
+      boolean expectConditionalIsLarger) {
+    Set<String> referenceClasses =
+        new TreeSet<>(
+            referenceResult.codeInspector().allClasses().stream()
+                .map(c -> c.getOriginalName())
+                .collect(Collectors.toSet()));
+
+    Set<String> conditionalClasses =
+        conditionalResult.codeInspector().allClasses().stream()
+            .map(c -> c.getOriginalName())
+            .collect(Collectors.toSet());
+    {
+      SetView<String> notInReference = Sets.difference(conditionalClasses, referenceClasses);
+      if (expectConditionalIsLarger) {
+        assertFalse("Expected classes in -if rule to retain more.", notInReference.isEmpty());
+      } else {
+        assertEquals(
+            "Classes in -if rule that are not in -keepclassmembers rule",
+            Collections.emptySet(),
+            notInReference);
+      }
+    }
+    {
+      SetView<String> notInConditional = Sets.difference(referenceClasses, conditionalClasses);
+      if (expectReferenceIsLarger) {
+        assertFalse(
+            "Expected classes in -keepclassmembers rule to retain more.",
+            notInConditional.isEmpty());
+      } else {
+        assertEquals(
+            "Classes in -keepclassmembers rule that are not in -if rule",
+            Collections.emptySet(),
+            new TreeSet<>(notInConditional));
+      }
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
index fe98a17..8d6dd4b 100644
--- a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
@@ -661,6 +661,10 @@
     }
   }
 
+  public CodeInspector codeInspector() {
+    return inspector;
+  }
+
   public Set<GraphNode> getRoots() {
     return Collections.unmodifiableSet(roots);
   }