[KeepAnno] Introduce test builders to share testing set up

This should make it easier to update existing tests and maintain
their configuration.

Bug: b/321674067
Change-Id: I1164aea74bd40c7caf61e332b80dfb278a44254f
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoParameters.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoParameters.java
new file mode 100644
index 0000000..ec1446b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoParameters.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2024, 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.keepanno;
+
+import com.android.tools.r8.TestParameters;
+
+public class KeepAnnoParameters {
+
+  public enum KeepAnnoConfig {
+    REFERENCE,
+    R8_NATIVE,
+    R8_LEGACY,
+    PG;
+  }
+
+  private final TestParameters parameters;
+  private final KeepAnnoConfig config;
+
+  KeepAnnoParameters(TestParameters parameters, KeepAnnoConfig config) {
+    this.parameters = parameters;
+    this.config = config;
+  }
+
+  @Override
+  public String toString() {
+    return config.name() + ", " + parameters;
+  }
+
+  public TestParameters parameters() {
+    return parameters;
+  }
+
+  public KeepAnnoConfig config() {
+    return config;
+  }
+
+  public boolean isReference() {
+    return config == KeepAnnoConfig.REFERENCE;
+  }
+
+  public boolean isShrinker() {
+    return !isReference();
+  }
+
+  public boolean isR8() {
+    return config == KeepAnnoConfig.R8_NATIVE || config == KeepAnnoConfig.R8_LEGACY;
+  }
+
+  public boolean isPG() {
+    return config == KeepAnnoConfig.PG;
+  }
+
+  public boolean isNative() {
+    return config == KeepAnnoConfig.R8_NATIVE;
+  }
+
+  public boolean isExtract() {
+    return config == KeepAnnoConfig.R8_LEGACY || config == KeepAnnoConfig.PG;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBase.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBase.java
new file mode 100644
index 0000000..698e0bc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBase.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2024, 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.keepanno;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class KeepAnnoTestBase extends TestBase {
+
+  public static List<KeepAnnoParameters> createParameters(
+      TestParametersCollection parametersCollection) {
+    List<KeepAnnoParameters> keepAnnoParams = new ArrayList<>();
+    for (TestParameters parameters : parametersCollection) {
+      keepAnnoParams.add(
+          new KeepAnnoParameters(parameters, KeepAnnoParameters.KeepAnnoConfig.REFERENCE));
+      keepAnnoParams.add(
+          new KeepAnnoParameters(parameters, KeepAnnoParameters.KeepAnnoConfig.R8_NATIVE));
+      keepAnnoParams.add(
+          new KeepAnnoParameters(parameters, KeepAnnoParameters.KeepAnnoConfig.R8_LEGACY));
+      if (parameters.isCfRuntime()) {
+        keepAnnoParams.add(
+            new KeepAnnoParameters(parameters, KeepAnnoParameters.KeepAnnoConfig.PG));
+      }
+    }
+    return keepAnnoParams;
+  }
+
+  public KeepAnnoTestBuilder testForKeepAnno(KeepAnnoParameters params) throws IOException {
+    return KeepAnnoTestBuilder.forKeepAnnoTest(params, temp);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
new file mode 100644
index 0000000..cd475f0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
@@ -0,0 +1,234 @@
+// Copyright (c) 2024, 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.keepanno;
+
+import com.android.tools.r8.ExternalR8TestBuilder;
+import com.android.tools.r8.ProguardTestBuilder;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.examples.sync.Sync.Consumer;
+import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractorOptions;
+import java.io.IOException;
+import java.util.List;
+import org.junit.rules.TemporaryFolder;
+
+public abstract class KeepAnnoTestBuilder {
+
+  public static KeepAnnoTestBuilder forKeepAnnoTest(KeepAnnoParameters params, TemporaryFolder temp)
+      throws IOException {
+    switch (params.config()) {
+      case REFERENCE:
+        return new ReferenceBuilder(params, temp);
+      case R8_NATIVE:
+        return new R8NativeBuilder(params, temp);
+      case R8_LEGACY:
+        return new R8LegacyBuilder(params, temp);
+      case PG:
+        return new PGBuilder(params, temp);
+      default:
+        throw new IllegalStateException("Unexpected keep anno config: " + params.config());
+    }
+  }
+
+  private final KeepAnnoParameters keepAnnoParams;
+
+  private KeepAnnoTestBuilder(KeepAnnoParameters params, TemporaryFolder temp) {
+    this.keepAnnoParams = params;
+  }
+
+  public final TestParameters parameters() {
+    return keepAnnoParams.parameters();
+  }
+
+  public abstract KeepAnnoTestBuilder addProgramClasses(List<Class<?>> programClasses)
+      throws IOException;
+
+  public abstract KeepAnnoTestBuilder addKeepMainRule(Class<?> mainClass);
+
+  public abstract SingleTestRunResult<?> run(Class<?> mainClass) throws Exception;
+
+  public KeepAnnoTestBuilder applyIfR8(
+      Consumer<TestShrinkerBuilder<?, ?, ?, ?, ?>> builderConsumer) {
+    return this;
+  }
+
+  public KeepAnnoTestBuilder applyIfR8Native(Consumer<R8TestBuilder<?>> builderConsumer) {
+    return this;
+  }
+
+  public KeepAnnoTestBuilder applyIfPG(Consumer<ProguardTestBuilder> builderConsumer) {
+    return this;
+  }
+
+  public final KeepAnnoTestBuilder setExcludedOuterClass(Class<?> clazz) {
+    return applyIfPG(b -> b.addDontWarn(clazz));
+  }
+
+  public final KeepAnnoTestBuilder allowUnusedProguardConfigurationRules() {
+    return applyIfR8Native(R8TestBuilder::allowUnusedProguardConfigurationRules);
+  }
+
+  private static class ReferenceBuilder extends KeepAnnoTestBuilder {
+
+    private final TestBuilder<? extends SingleTestRunResult<?>, ?> builder;
+
+    public ReferenceBuilder(KeepAnnoParameters params, TemporaryFolder temp) {
+      super(params, temp);
+      if (parameters().isCfRuntime()) {
+        builder = TestBase.testForJvm(temp);
+      } else {
+        assert parameters().isDexRuntime();
+        builder = TestBase.testForD8(temp).setMinApi(parameters());
+      }
+    }
+
+    @Override
+    public KeepAnnoTestBuilder addProgramClasses(List<Class<?>> programClasses) {
+      builder.addProgramClasses(programClasses);
+      return this;
+    }
+
+    @Override
+    public KeepAnnoTestBuilder addKeepMainRule(Class<?> mainClass) {
+      // Nothing to keep in JVM/D8.
+      return this;
+    }
+
+    @Override
+    public SingleTestRunResult<?> run(Class<?> mainClass) throws Exception {
+      return builder.run(parameters().getRuntime(), mainClass);
+    }
+  }
+
+  private static class R8NativeBuilder extends KeepAnnoTestBuilder {
+
+    private final R8FullTestBuilder builder;
+
+    public R8NativeBuilder(KeepAnnoParameters params, TemporaryFolder temp) {
+      super(params, temp);
+      builder =
+          TestBase.testForR8(temp, parameters().getBackend())
+              .enableExperimentalKeepAnnotations()
+              .setMinApi(parameters());
+    }
+
+    @Override
+    public KeepAnnoTestBuilder applyIfR8(
+        Consumer<TestShrinkerBuilder<?, ?, ?, ?, ?>> builderConsumer) {
+      builderConsumer.accept(builder);
+      return this;
+    }
+
+    @Override
+    public KeepAnnoTestBuilder applyIfR8Native(Consumer<R8TestBuilder<?>> builderConsumer) {
+      builderConsumer.accept(builder);
+      return this;
+    }
+
+    @Override
+    public KeepAnnoTestBuilder addProgramClasses(List<Class<?>> programClasses) {
+      builder.addProgramClasses(programClasses);
+      return this;
+    }
+
+    @Override
+    public KeepAnnoTestBuilder addKeepMainRule(Class<?> mainClass) {
+      builder.addKeepMainRule(mainClass);
+      return this;
+    }
+
+    @Override
+    public SingleTestRunResult<?> run(Class<?> mainClass) throws Exception {
+      return builder.run(parameters().getRuntime(), mainClass);
+    }
+  }
+
+  private static class R8LegacyBuilder extends KeepAnnoTestBuilder {
+
+    private final KeepRuleExtractorOptions extractorOptions =
+        KeepRuleExtractorOptions.getR8Options();
+    private final ExternalR8TestBuilder builder;
+
+    public R8LegacyBuilder(KeepAnnoParameters params, TemporaryFolder temp) throws IOException {
+      super(params, temp);
+      builder =
+          TestBase.testForExternalR8(temp, parameters().getBackend())
+              .useProvidedR8(KeepAnnoTestUtils.R8_LIB)
+              .addProgramFiles(KeepAnnoTestUtils.getKeepAnnoLib(temp))
+              .setMinApi(parameters());
+    }
+
+    @Override
+    public KeepAnnoTestBuilder applyIfR8(
+        Consumer<TestShrinkerBuilder<?, ?, ?, ?, ?>> builderConsumer) {
+      builderConsumer.accept(builder);
+      return this;
+    }
+
+    @Override
+    public KeepAnnoTestBuilder addProgramClasses(List<Class<?>> programClasses) throws IOException {
+      List<String> rules = KeepAnnoTestUtils.extractRules(programClasses, extractorOptions);
+      builder.addProgramClasses(programClasses);
+      builder.addKeepRules(rules);
+      return this;
+    }
+
+    @Override
+    public KeepAnnoTestBuilder addKeepMainRule(Class<?> mainClass) {
+      builder.addKeepMainRule(mainClass);
+      return this;
+    }
+
+    @Override
+    public SingleTestRunResult<?> run(Class<?> mainClass) throws Exception {
+      return builder.run(parameters().getRuntime(), mainClass);
+    }
+  }
+
+  private static class PGBuilder extends KeepAnnoTestBuilder {
+
+    private final KeepRuleExtractorOptions extractorOptions =
+        KeepRuleExtractorOptions.getPgOptions();
+    private final ProguardTestBuilder builder;
+
+    public PGBuilder(KeepAnnoParameters params, TemporaryFolder temp) throws IOException {
+      super(params, temp);
+      builder =
+          TestBase.testForProguard(KeepAnnoTestUtils.PG_VERSION, temp)
+              .addProgramFiles(KeepAnnoTestUtils.getKeepAnnoLib(temp))
+              .setMinApi(parameters());
+    }
+
+    @Override
+    public KeepAnnoTestBuilder applyIfPG(Consumer<ProguardTestBuilder> builderConsumer) {
+      builderConsumer.accept(builder);
+      return this;
+    }
+
+    @Override
+    public KeepAnnoTestBuilder addProgramClasses(List<Class<?>> programClasses) throws IOException {
+      List<String> rules = KeepAnnoTestUtils.extractRules(programClasses, extractorOptions);
+      builder.addProgramClasses(programClasses);
+      builder.addKeepRules(rules);
+      return this;
+    }
+
+    @Override
+    public KeepAnnoTestBuilder addKeepMainRule(Class<?> mainClass) {
+      builder.addKeepMainRule(mainClass);
+      return this;
+    }
+
+    @Override
+    public SingleTestRunResult<?> run(Class<?> mainClass) throws Exception {
+      return builder.run(parameters().getRuntime(), mainClass);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestUtils.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestUtils.java
index 7f79344..7335975 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestUtils.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestUtils.java
@@ -6,11 +6,7 @@
 
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
-import com.android.tools.r8.ExternalR8TestBuilder;
-import com.android.tools.r8.ProguardTestBuilder;
 import com.android.tools.r8.ProguardVersion;
-import com.android.tools.r8.TestShrinkerBuilder;
-import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
 import com.android.tools.r8.keepanno.ast.KeepDeclaration;
@@ -32,7 +28,7 @@
   public static ProguardVersion PG_VERSION = ProguardVersion.V7_3_2;
 
   // TODO(b/321674067): Downgrade this to oldest supported AGP, such as R8 8.0.35.
-  private static Path R8_LIB = Paths.get(ToolHelper.THIRD_PARTY_DIR, "r8", "r8lib_8.2.20-dev.jar");
+  public static Path R8_LIB = Paths.get(ToolHelper.THIRD_PARTY_DIR, "r8", "r8lib_8.2.20-dev.jar");
 
   public static Path getKeepAnnoLib(TemporaryFolder temp) throws IOException {
     Path archive = temp.newFolder().toPath().resolve("keepanno.jar");
@@ -68,25 +64,4 @@
     return rules;
   }
 
-  public static ThrowableConsumer<ProguardTestBuilder> addInputClassesAndRulesPG(
-      List<Class<?>> inputClasses) {
-    return builder -> {
-      addInputClassesAndRulesShared(inputClasses, builder);
-    };
-  }
-
-  public static ThrowableConsumer<ExternalR8TestBuilder> addInputClassesAndRulesR8(
-      List<Class<?>> inputClasses) {
-    return builder -> {
-      builder.useProvidedR8(R8_LIB);
-      addInputClassesAndRulesShared(inputClasses, builder);
-    };
-  }
-
-  private static void addInputClassesAndRulesShared(
-      List<Class<?>> inputClasses, TestShrinkerBuilder<?, ?, ?, ?, ?> builder) throws IOException {
-    Path keepAnnoLib = getKeepAnnoLib(builder.getState().getTempFolder());
-    List<String> rules = extractRules(inputClasses, KeepRuleExtractorOptions.getPgOptions());
-    builder.addProgramClasses(inputClasses).addProgramFiles(keepAnnoLib).addKeepRules(rules);
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsedByReflectionAnnotationTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsedByReflectionAnnotationTest.java
index e6edebc..0bc2417 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepUsedByReflectionAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsedByReflectionAnnotationTest.java
@@ -6,12 +6,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 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.TestParametersCollection;
 import com.android.tools.r8.keepanno.annotations.KeepCondition;
 import com.android.tools.r8.keepanno.annotations.KeepConstraint;
 import com.android.tools.r8.keepanno.annotations.KeepItemKind;
@@ -26,112 +21,50 @@
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class KeepUsedByReflectionAnnotationTest extends TestBase {
+public class KeepUsedByReflectionAnnotationTest extends KeepAnnoTestBase {
 
   static final String EXPECTED = StringUtils.lines("Hello, world");
 
-  private final TestParameters parameters;
+  private final KeepAnnoParameters parameters;
 
   @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+  public static List<KeepAnnoParameters> data() {
+    return createParameters(
+        getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build());
   }
 
-  public KeepUsedByReflectionAnnotationTest(TestParameters parameters) {
+  public KeepUsedByReflectionAnnotationTest(KeepAnnoParameters parameters) {
     this.parameters = parameters;
   }
 
   @Test
-  public void testReference() throws Exception {
-    testForRuntime(parameters)
+  public void test() throws Exception {
+    Class<?> mainClass = TestClass.class;
+    testForKeepAnno(parameters)
         .addProgramClasses(getInputClasses())
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(EXPECTED);
-  }
-
-  @Test
-  public void testR8() throws Exception {
-    testForR8(parameters.getBackend())
-        .enableExperimentalKeepAnnotations()
-        .addProgramClasses(getInputClasses())
-        .addKeepMainRule(TestClass.class)
-        .setMinApi(parameters)
-        .run(parameters.getRuntime(), TestClass.class)
+        .addKeepMainRule(mainClass)
+        .setExcludedOuterClass(getClass())
+        .run(mainClass)
         .assertSuccessWithOutput(EXPECTED)
-        .inspect(this::checkOutput);
+        .applyIf(parameters.isShrinker(), r -> r.inspect(this::checkOutput));
   }
 
   @Test
-  public void testExtractR8() throws Exception {
-    testForExternalR8(parameters.getBackend())
-        .apply(KeepAnnoTestUtils.addInputClassesAndRulesR8(getInputClasses()))
-        .addKeepMainRule(TestClass.class)
-        .setMinApi(parameters)
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(EXPECTED)
-        .inspect(this::checkOutput);
-  }
-
-  @Test
-  public void testExtractPG() throws Exception {
-    assumeTrue(parameters.isCfRuntime());
-    testForProguard(KeepAnnoTestUtils.PG_VERSION)
-        .addDontWarn(getClass())
-        .apply(KeepAnnoTestUtils.addInputClassesAndRulesPG(getInputClasses()))
-        .addKeepMainRule(TestClass.class)
-        .setMinApi(parameters)
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(EXPECTED)
-        .inspect(this::checkOutput);
-  }
-
-  @Test
-  public void testNoRefReference() throws Exception {
-    testForRuntime(parameters)
+  public void testNoRef() throws Exception {
+    Class<?> mainClass = TestClassNoRef.class;
+    testForKeepAnno(parameters)
         .addProgramClasses(getInputClasses())
-        .run(parameters.getRuntime(), TestClassNoRef.class)
-        .assertSuccessWithOutput(EXPECTED);
-  }
-
-  @Test
-  public void testNoRefR8() throws Exception {
-    testForR8(parameters.getBackend())
-        .enableExperimentalKeepAnnotations()
-        .addProgramClasses(getInputClasses())
-        .addKeepMainRule(TestClassNoRef.class)
+        .addKeepMainRule(mainClass)
         .allowUnusedProguardConfigurationRules()
-        .setMinApi(parameters)
-        .run(parameters.getRuntime(), TestClassNoRef.class)
+        .setExcludedOuterClass(getClass())
+        .run(mainClass)
         .assertSuccessWithOutput(EXPECTED)
-        .inspect(this::checkOutputNoRef);
-  }
-
-  @Test
-  public void testNoRefExtractR8() throws Exception {
-    testForExternalR8(parameters.getBackend())
-        .apply(KeepAnnoTestUtils.addInputClassesAndRulesR8(getInputClasses()))
-        .addKeepMainRule(TestClassNoRef.class)
-        .setMinApi(parameters)
-        .run(parameters.getRuntime(), TestClassNoRef.class)
-        .assertSuccessWithOutput(EXPECTED)
-        .inspect(this::checkOutputNoRef);
-  }
-
-  @Test
-  public void testNoRefExtractPG() throws Exception {
-    assumeTrue(parameters.isCfRuntime());
-    testForProguard(ProguardVersion.V7_3_2)
-        .addDontWarn(getClass())
-        .apply(KeepAnnoTestUtils.addInputClassesAndRulesPG(getInputClasses()))
-        .addKeepMainRule(TestClassNoRef.class)
-        .setMinApi(parameters)
-        .run(parameters.getRuntime(), TestClassNoRef.class)
-        .assertSuccessWithOutput(EXPECTED)
+        .applyIf(parameters.isR8(), r -> r.inspect(this::checkOutputNoRef))
         // PG does not eliminate B so the same output remains.
-        .inspect(this::checkOutput);
+        .applyIf(parameters.isPG(), r -> r.inspect(this::checkOutput));
   }
 
-  public List<Class<?>> getInputClasses() {
+  private List<Class<?>> getInputClasses() {
     return ImmutableList.of(TestClass.class, TestClassNoRef.class, A.class, B.class, C.class);
   }