Add support for using R8 partial in existing D8/R8 tests

Change-Id: Iab5c235d4fbf7ae34fc69d95e20f93a0f49bcb2f
diff --git a/src/main/java/com/android/tools/r8/partial/R8PartialCompilationConfiguration.java b/src/main/java/com/android/tools/r8/partial/R8PartialCompilationConfiguration.java
index c1ec90c..22b2096 100644
--- a/src/main/java/com/android/tools/r8/partial/R8PartialCompilationConfiguration.java
+++ b/src/main/java/com/android/tools/r8/partial/R8PartialCompilationConfiguration.java
@@ -18,6 +18,7 @@
 import com.google.common.base.Splitter;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Random;
 import java.util.function.Consumer;
 
 public class R8PartialCompilationConfiguration {
@@ -25,22 +26,25 @@
   private final boolean enabled;
   private final R8PartialPredicateCollection includePredicates;
   private final R8PartialPredicateCollection excludePredicates;
+  private final Random randomizeForTesting;
 
   public Consumer<InternalOptions> d8DexOptionsConsumer = ConsumerUtils.emptyConsumer();
   public Consumer<InternalOptions> r8OptionsConsumer = ConsumerUtils.emptyConsumer();
 
   private static final R8PartialCompilationConfiguration disabledConfiguration =
-      new R8PartialCompilationConfiguration(false, null, null);
+      new R8PartialCompilationConfiguration(false, null, null, null);
 
   private R8PartialCompilationConfiguration(
       boolean enabled,
       R8PartialPredicateCollection includePredicates,
-      R8PartialPredicateCollection excludePredicates) {
+      R8PartialPredicateCollection excludePredicates,
+      Random randomizeForTesting) {
     assert !enabled || !includePredicates.isEmpty();
     assert !enabled || excludePredicates != null;
     this.enabled = enabled;
     this.includePredicates = includePredicates;
     this.excludePredicates = excludePredicates;
+    this.randomizeForTesting = randomizeForTesting;
   }
 
   public R8PartialPredicateCollection getIncludePredicates() {
@@ -52,6 +56,9 @@
   }
 
   public boolean test(DexProgramClass clazz) {
+    if (randomizeForTesting != null) {
+      return randomizeForTesting.nextBoolean();
+    }
     return includePredicates.test(clazz) && !excludePredicates.test(clazz);
   }
 
@@ -83,17 +90,22 @@
     return enabled;
   }
 
+  public boolean isRandomizeForTestingEnabled() {
+    return randomizeForTesting != null;
+  }
+
   public static class Builder {
     private final R8PartialPredicateCollection includePredicates =
         new R8PartialPredicateCollection();
     private final R8PartialPredicateCollection excludePredicates =
         new R8PartialPredicateCollection();
+    private Random randomizeForTesting;
 
     private Builder() {}
 
     public R8PartialCompilationConfiguration build() {
       return new R8PartialCompilationConfiguration(
-          !includePredicates.isEmpty(), includePredicates, excludePredicates);
+          !includePredicates.isEmpty(), includePredicates, excludePredicates, randomizeForTesting);
     }
 
     public Builder includeAll() {
@@ -106,6 +118,14 @@
       return this;
     }
 
+    public Builder randomizeForTesting() {
+      randomizeForTesting = new Random();
+      long seed = System.currentTimeMillis();
+      randomizeForTesting.setSeed(seed);
+      System.out.println("Partial compilation seed: " + seed);
+      return this;
+    }
+
     public Builder addJavaTypeIncludePattern(String pattern) {
       includePredicates.add(
           createPredicate("L" + DescriptorUtils.getBinaryNameFromJavaType(pattern)));
diff --git a/src/test/java/com/android/tools/r8/partial/D8R8TestWithPartialCompilationTest.java b/src/test/java/com/android/tools/r8/partial/D8R8TestWithPartialCompilationTest.java
new file mode 100644
index 0000000..f1b1bc2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/partial/D8R8TestWithPartialCompilationTest.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2025, 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.partial;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 D8R8TestWithPartialCompilationTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().withPartialCompilation().build();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    parameters.assumeDexRuntime();
+    testForD8(parameters)
+        .addInnerClasses(getClass())
+        .release()
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println("Hello, world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/partial/MisconfiguredD8R8TestWithPartialCompilationTest.java b/src/test/java/com/android/tools/r8/partial/MisconfiguredD8R8TestWithPartialCompilationTest.java
new file mode 100644
index 0000000..086b8af
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/partial/MisconfiguredD8R8TestWithPartialCompilationTest.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2025, 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.partial;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 MisconfiguredD8R8TestWithPartialCompilationTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().withPartialCompilation().build();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    parameters.assumeDexRuntime();
+    try {
+      testForD8(parameters.getBackend())
+          .addInnerClasses(getClass())
+          .release()
+          .setMinApi(parameters)
+          .compile();
+      assertTrue(parameters.getPartialCompilationTestParameters().isNone());
+    } catch (AssertionError e) {
+      assertTrue(parameters.getPartialCompilationTestParameters().isSome());
+    }
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    try {
+      testForR8(parameters.getBackend())
+          .addInnerClasses(getClass())
+          .addKeepMainRule(Main.class)
+          .setMinApi(parameters)
+          .compile();
+      assertTrue(parameters.getPartialCompilationTestParameters().isNone());
+    } catch (AssertionError e) {
+      assertTrue(parameters.getPartialCompilationTestParameters().isSome());
+    }
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println("Hello, world!");
+    }
+  }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java b/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java
index f6d7950..7687324 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java
@@ -67,6 +67,10 @@
     return this;
   }
 
+  R8PartialCompilationConfiguration getR8PartialConfiguration() {
+    return r8PartialConfiguration;
+  }
+
   public R8PartialTestBuilder setR8PartialConfiguration(
       Consumer<R8PartialCompilationConfiguration.Builder> consumer) {
     assert r8PartialConfiguration.equals(R8PartialCompilationConfiguration.disabledConfiguration())
diff --git a/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java b/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java
index 7b415ba..bdcbe32 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.metadata.R8BuildMetadata;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.partial.R8PartialCompilationConfiguration;
 import com.android.tools.r8.profile.art.ArtProfileConsumer;
 import com.android.tools.r8.profile.art.ArtProfileProvider;
 import com.android.tools.r8.profile.art.model.ExternalArtProfile;
@@ -190,7 +191,7 @@
         compileResult.assertOnlyInfos();
         break;
       case NONE:
-        if (allowUnusedProguardConfigurationRules) {
+        if (allowUnusedProguardConfigurationRules || !checkNoUnusedProguardConfigurationRules()) {
           compileResult
               .assertAllInfosMatch(Matchers.proguardConfigurationRuleDoesNotMatch())
               .assertNoErrorMessages()
@@ -207,12 +208,24 @@
     }
     if (allowUnusedProguardConfigurationRules) {
       compileResult.assertInfoThatMatches(Matchers.proguardConfigurationRuleDoesNotMatch());
-    } else {
+    } else if (checkNoUnusedProguardConfigurationRules()) {
       compileResult.assertNoInfoThatMatches(Matchers.proguardConfigurationRuleDoesNotMatch());
     }
     return compileResult;
   }
 
+  private boolean checkNoUnusedProguardConfigurationRules() {
+    // Allow unused Proguard configuration rules in R8 partial when using a random partitioning.
+    if (isR8PartialTestBuilder()) {
+      R8PartialCompilationConfiguration configuration =
+          asR8PartialTestBuilder().getR8PartialConfiguration();
+      if (configuration != null && configuration.isRandomizeForTestingEnabled()) {
+        return false;
+      }
+    }
+    return true;
+  }
+
   private static StringBuilder wrapProguardMapConsumer(Builder builder) {
     StringBuilder pgMapOutput = new StringBuilder();
     StringConsumer pgMapConsumer = builder.getProguardMapConsumer();
diff --git a/src/test/testbase/java/com/android/tools/r8/TestBase.java b/src/test/testbase/java/com/android/tools/r8/TestBase.java
index 0ca2f67..ca48bf6 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestBase.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestBase.java
@@ -13,6 +13,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.DataResourceProvider.Visitor;
@@ -107,6 +108,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
@@ -207,9 +209,49 @@
   }
 
   public R8FullTestBuilder testForR8(Backend backend) {
+    assert verifyNoPartialCompilationTestParameters();
     return testForR8(temp, backend);
   }
 
+  private boolean verifyNoPartialCompilationTestParameters() {
+    try {
+      Class<?> testClass = getClass();
+      for (Field field : testClass.getDeclaredFields()) {
+        if (field.getType() == TestParameters.class) {
+          field.setAccessible(true);
+          TestParameters parameters = (TestParameters) field.get(this);
+          assertTrue(parameters.getPartialCompilationTestParameters().isNone());
+        }
+      }
+    } catch (IllegalAccessException t) {
+      throw new RuntimeException(t);
+    }
+    return true;
+  }
+
+  public R8TestBuilder<?, ?, ?> testForR8(TestParameters parameters) {
+    return testForR8(parameters.getBackend(), parameters.getPartialCompilationTestParameters())
+        .setMinApi(parameters);
+  }
+
+  public R8TestBuilder<?, ?, ?> testForR8(
+      Backend backend, PartialCompilationTestParameters parameters) {
+    if (parameters.isNone()) {
+      return testForR8(backend);
+    }
+    assumeTrue(parameters.isIncludeAll() || parameters.isRandom());
+    return testForR8Partial(backend)
+        .setR8PartialConfiguration(
+            builder -> {
+              if (parameters.isIncludeAll()) {
+                builder.includeAll();
+              } else {
+                assert parameters.isRandom();
+                builder.randomizeForTesting();
+              }
+            });
+  }
+
   public R8PartialTestBuilder testForR8Partial(Backend backend) {
     return testForR8Partial(temp, backend);
   }
@@ -238,6 +280,20 @@
     return testForD8(temp, backend);
   }
 
+  public TestCompilerBuilder<?, ?, ?, ?, ?> testForD8(TestParameters parameters) {
+    return testForD8(parameters.getBackend(), parameters.getPartialCompilationTestParameters())
+        .setMinApi(parameters);
+  }
+
+  public TestCompilerBuilder<?, ?, ?, ?, ?> testForD8(
+      Backend backend, PartialCompilationTestParameters partialCompilationTestParameters) {
+    if (partialCompilationTestParameters.isNone()) {
+      return testForD8(backend);
+    }
+    assumeTrue(partialCompilationTestParameters.isExcludeAll());
+    return testForR8Partial(backend).setR8PartialConfiguration(builder -> builder.excludeAll());
+  }
+
   public AssistantTestBuilder testForAssistant() {
     return AssistantTestBuilder.create(new TestState(temp));
   }