Update test infrastructure to support R8 partial compilation

Bug: b/309743298
Change-Id: If1022b3fa419feb3976ee1f0d11ac2f8822d6ff1
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java
index ac11021..1a2a5db 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java
@@ -30,7 +30,7 @@
     test(testForR8Compat(parameters.getBackend()));
   }
 
-  private <T extends R8TestBuilder<T>> void test(T testBuilder) throws Exception {
+  private <B extends R8TestBuilder<?, ?, ?>> void test(B testBuilder) throws Exception {
     testBuilder
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java b/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
index b11215a..09c5556 100644
--- a/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
+++ b/src/test/java/com/android/tools/r8/internal/opensourceapps/TiviTest.java
@@ -63,7 +63,7 @@
         .compile();
   }
 
-  private void configure(R8TestBuilder<?> testBuilder) {
+  private void configure(R8TestBuilder<?, ?, ?> testBuilder) {
     testBuilder
         .addClasspathFiles(outDirectory.resolve("classpath.jar"))
         .addLibraryFiles(outDirectory.resolve("library.jar"))
diff --git a/src/test/java/com/android/tools/r8/internal/proto/ProtoRuntime.java b/src/test/java/com/android/tools/r8/internal/proto/ProtoRuntime.java
index a9dd2b2..3a12b3a 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/ProtoRuntime.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/ProtoRuntime.java
@@ -24,7 +24,7 @@
     this.syntheticVersionNumber = syntheticVersionNumber;
   }
 
-  public void addRuntime(R8TestBuilder<?> testBuilder) {
+  public void addRuntime(R8TestBuilder<?, ?, ?> testBuilder) {
     Path runtimeDir = Paths.get(ToolHelper.PROTO_RUNTIME_DIR, runtimeName);
     testBuilder
         .addProgramFiles(runtimeDir.resolve("libprotobuf_lite.jar"))
@@ -58,7 +58,7 @@
   }
 
   // The class com.google.protobuf.ProtoMessage is not present in newer proto lite runtimes.
-  public void workaroundProtoMessageRemoval(R8TestBuilder<?> testBuilder) {
+  public void workaroundProtoMessageRemoval(R8TestBuilder<?, ?, ?> testBuilder) {
     if (isNewerThanOrEqualTo(ProtoRuntime.EDITION2023)) {
       testBuilder.addDontWarn("com.google.protobuf.ProtoMessage");
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMappingOnSameLineTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMappingOnSameLineTest.java
index d26643f..f4a55f5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMappingOnSameLineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMappingOnSameLineTest.java
@@ -47,7 +47,7 @@
   }
 
   @Override
-  public void configure(R8TestBuilder<?> builder) {
+  public void configure(R8TestBuilder<?, ?, ?> builder) {
     builder.enableInliningAnnotations();
   }
 
diff --git a/src/test/java/com/android/tools/r8/java_language/pattern_matching_for_instenceof/PattternMatchingForInstanceOfTest.java b/src/test/java/com/android/tools/r8/java_language/pattern_matching_for_instenceof/PattternMatchingForInstanceOfTest.java
index feb4819..135fc1f 100644
--- a/src/test/java/com/android/tools/r8/java_language/pattern_matching_for_instenceof/PattternMatchingForInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/java_language/pattern_matching_for_instenceof/PattternMatchingForInstanceOfTest.java
@@ -60,7 +60,7 @@
   @Test
   public void testR8() throws Exception {
     parameters.assumeR8TestParameters();
-    R8TestBuilder<?> builder =
+    R8TestBuilder<?, ?, ?> builder =
         testForR8(parameters.getBackend())
             .addProgramFiles(JAR)
             .setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
index cf7edc4..5a548bc 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
@@ -108,7 +108,8 @@
     return this;
   }
 
-  public KeepAnnoTestBuilder applyIfR8Current(ThrowableConsumer<R8TestBuilder<?>> builderConsumer) {
+  public KeepAnnoTestBuilder applyIfR8Current(
+      ThrowableConsumer<R8TestBuilder<?, ?, ?>> builderConsumer) {
     return this;
   }
 
@@ -229,7 +230,7 @@
 
     @Override
     public KeepAnnoTestBuilder applyIfR8Current(
-        ThrowableConsumer<R8TestBuilder<?>> builderConsumer) {
+        ThrowableConsumer<R8TestBuilder<?, ?, ?>> builderConsumer) {
       builderConsumer.acceptWithRuntimeException(builder);
       return this;
     }
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
index b307545..6142fb7 100644
--- a/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileRetraceTest.java
@@ -12,6 +12,7 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.graph.DexClass;
@@ -62,7 +63,7 @@
   @Test
   public void testR8()
       throws ExecutionException, CompilationFailedException, IOException, NoSuchMethodException {
-    R8TestBuilder<? extends R8TestBuilder<?>> r8TestBuilder =
+    R8TestBuilder<?, R8TestRunResult, ?> r8TestBuilder =
         isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend());
     if (keepSourceFile) {
       r8TestBuilder.addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE);
@@ -85,7 +86,7 @@
   @Test
   public void testRenameSourceFileR8()
       throws ExecutionException, CompilationFailedException, IOException, NoSuchMethodException {
-    R8TestBuilder<? extends R8TestBuilder<?>> r8TestBuilder =
+    R8TestBuilder<?, R8TestRunResult, ?> r8TestBuilder =
         isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend());
     if (keepSourceFile) {
       r8TestBuilder.addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE);
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodDirectRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodDirectRetraceTest.java
index 2439b3b..758fd3e 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodDirectRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodDirectRetraceTest.java
@@ -46,7 +46,7 @@
   }
 
   @Override
-  public void configure(R8TestBuilder<?> builder) {
+  public void configure(R8TestBuilder<?, ?, ?> builder) {
     builder.enableInliningAnnotations();
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
index 21ea804..b82ab15 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
@@ -37,7 +37,7 @@
                   .assertFailure()
                   .map(StackTrace::extractFromJvm));
 
-  public void configure(R8TestBuilder<?> builder) {}
+  public void configure(R8TestBuilder<?, ?, ?> builder) {}
 
   public void inspect(CodeInspector inspector) {}
 
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java
index ebf25d9..8bd3581 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/VerticalClassMergingRetraceTest.java
@@ -48,7 +48,7 @@
   }
 
   @Override
-  public void configure(R8TestBuilder<?> builder) {
+  public void configure(R8TestBuilder<?, ?, ?> builder) {
     builder
         .addOptionsModification(
             options ->
diff --git a/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java b/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java
index f08c178..6a32a4c 100644
--- a/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java
@@ -81,7 +81,7 @@
                     internalOptions.testing.assertConsistentRenamingOfSignature = true));
   }
 
-  private void test(R8TestBuilder<?> builder) throws Exception {
+  private void test(R8TestBuilder<?, ?, ?> builder) throws Exception {
     builder
         .addKeepRules("-dontoptimize")
         .addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
diff --git a/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderDesugaredLibraryTest.java b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderDesugaredLibraryTest.java
index a3bc5b4..22389b4 100644
--- a/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderDesugaredLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/serviceloader/ServiceLoaderDesugaredLibraryTest.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
 import com.google.common.collect.ImmutableList;
@@ -79,7 +78,7 @@
   private static final String servicesFile =
       StringUtils.lines(SimpleChronology.class.getTypeName());
 
-  private void configureR8(R8TestBuilder<?> builder) {
+  private void configureR8(R8TestBuilder<?, ?, ?> builder) {
     // When testing R8 add the META-INF/services to the input to apply rewriting.
     builder
         .addDataEntryResources(
diff --git a/src/test/java/com/android/tools/r8/partial/ClassHierarchyInterleavedD8AndR8Test.java b/src/test/java/com/android/tools/r8/partial/ClassHierarchyInterleavedD8AndR8Test.java
index 0af90b1..db45859 100644
--- a/src/test/java/com/android/tools/r8/partial/ClassHierarchyInterleavedD8AndR8Test.java
+++ b/src/test/java/com/android/tools/r8/partial/ClassHierarchyInterleavedD8AndR8Test.java
@@ -42,15 +42,12 @@
   private void runTest(
       Predicate<String> isR8, ThrowingConsumer<CodeInspector, RuntimeException> inspector)
       throws Exception {
-    testForR8(parameters.getBackend())
-        .addOptionsModification(
-            options -> {
-              options.r8PartialCompilationOptions.enabled = true;
-              options.r8PartialCompilationOptions.isR8 = isR8;
-            })
+    // Path tempDir = temp.newFolder().toPath();
+    testForR8Partial(parameters.getBackend())
         .setMinApi(parameters)
         .addProgramClasses(A.class, B.class, C.class, Main.class)
         .addKeepMainRule(Main.class)
+        .setR8PartialConfigurationPredicate(isR8)
         .compile()
         .inspect(inspector)
         .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationBasicTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationBasicTest.java
index 929c0e2..662aa21 100644
--- a/src/test/java/com/android/tools/r8/partial/PartialCompilationBasicTest.java
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationBasicTest.java
@@ -35,17 +35,11 @@
 
   @Test
   public void runTestClassAIsCompiledWithD8() throws Exception {
-    testForR8(parameters.getBackend())
+    testForR8Partial(parameters.getBackend())
         .setMinApi(parameters)
         .addProgramClasses(A.class, B.class, Main.class)
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options -> {
-              options.r8PartialCompilationOptions.enabled = true;
-              // Run R8 on all classes except class A.
-              options.r8PartialCompilationOptions.isR8 =
-                  name -> !name.equals(A.class.getTypeName());
-            })
+        .setR8PartialConfiguration(builder -> builder.includeAll().excludeClasses(A.class).build())
         .compile()
         .inspect(
             inspector -> {
@@ -58,17 +52,11 @@
 
   @Test
   public void runTestClassBIsCompiledWithD8() throws Exception {
-    testForR8(parameters.getBackend())
+    testForR8Partial(parameters.getBackend())
         .setMinApi(parameters)
         .addProgramClasses(A.class, B.class, Main.class)
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options -> {
-              options.r8PartialCompilationOptions.enabled = true;
-              // Run R8 on all classes except class A.
-              options.r8PartialCompilationOptions.isR8 =
-                  name -> !name.equals(B.class.getTypeName());
-            })
+        .setR8PartialConfiguration(builder -> builder.includeAll().excludeClasses(B.class).build())
         .compile()
         .inspect(
             inspector -> {
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationDemoTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationDemoTest.java
index f527e25..0ccd8ce 100644
--- a/src/test/java/com/android/tools/r8/partial/PartialCompilationDemoTest.java
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationDemoTest.java
@@ -235,12 +235,11 @@
 
   private void runR8Partial(Path tempDir, CompilerDump dump, Path output, Predicate<String> isR8)
       throws IOException, CompilationFailedException {
-    testForR8(parameters.getBackend())
+    testForR8Partial(parameters.getBackend())
+        .setR8PartialConfigurationPredicate(isR8)
         .addOptionsModification(
             options -> {
-              options.r8PartialCompilationOptions.enabled = true;
               options.r8PartialCompilationOptions.tempDir = tempDir;
-              options.r8PartialCompilationOptions.isR8 = isR8;
 
               // For compiling nowonandroid.
               options.testing.allowUnnecessaryDontWarnWildcards = true;
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageDontObfuscateTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageDontObfuscateTest.java
index 677fe21..030966f 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageDontObfuscateTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageDontObfuscateTest.java
@@ -99,7 +99,8 @@
         .assertSuccessWithOutputLines(EXPECTED);
   }
 
-  private R8TestCompileResult setup(R8TestBuilder<?> r8TestBuilder) throws Exception {
+  private R8TestCompileResult setup(R8TestBuilder<R8TestCompileResult, ?, ?> r8TestBuilder)
+      throws Exception {
     return r8TestBuilder
         .addInnerClasses(getClass())
         .setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageInnerAndOuterClassTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageInnerAndOuterClassTest.java
index a026793..003b2e5 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageInnerAndOuterClassTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageInnerAndOuterClassTest.java
@@ -39,7 +39,8 @@
     test(testForR8Compat(parameters.getBackend()).addProgramClasses(TestClass.class), false);
   }
 
-  private void test(R8TestBuilder<?> testBuilder, boolean eligibleForRepackaging) throws Exception {
+  private void test(R8TestBuilder<?, ?, ?> testBuilder, boolean eligibleForRepackaging)
+      throws Exception {
     testBuilder
         .addProgramClasses(Outer.class, Inner.class)
         .addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateInnerClassTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateInnerClassTest.java
index 70bd738..6234266 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateInnerClassTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateInnerClassTest.java
@@ -39,7 +39,7 @@
     test(testForR8(parameters.getBackend()).addKeepClassRules(NonPublicKeptClass.class), true);
   }
 
-  private void test(R8TestBuilder<?> builder, boolean expectRepackaged) throws Exception {
+  private void test(R8TestBuilder<?, ?, ?> builder, boolean expectRepackaged) throws Exception {
     builder
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationAssertionHandlerAssertionInClinitOnlyTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationAssertionHandlerAssertionInClinitOnlyTest.java
index f2de8f4..160d344 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationAssertionHandlerAssertionInClinitOnlyTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationAssertionHandlerAssertionInClinitOnlyTest.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  protected void configure(R8TestBuilder<?> builder) {
+  protected void configure(R8TestBuilder<?, ?, ?> builder) {
     builder.allowUnusedProguardConfigurationRules();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationAssertionHandlerTestBase.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationAssertionHandlerTestBase.java
index c3a4eeb..004e756 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationAssertionHandlerTestBase.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationAssertionHandlerTestBase.java
@@ -40,7 +40,7 @@
     return ImmutableList.of(AssertionHandlers.class);
   }
 
-  protected void configure(R8TestBuilder<?> builder) {}
+  protected void configure(R8TestBuilder<?, ?, ?> builder) {}
 
   protected void inspect(CodeInspector inspector) {}
 
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 b7c0d9a..e523554 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -343,7 +343,8 @@
     }
   }
 
-  private void suppressZipFileAssignmentsToJavaLangAutoCloseable(R8TestBuilder<?> testBuilder) {
+  private void suppressZipFileAssignmentsToJavaLangAutoCloseable(
+      R8TestBuilder<?, ?, ?> testBuilder) {
     testBuilder.addOptionsModification(
         options ->
             options
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryClassExtendingProgramClassSuperTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryClassExtendingProgramClassSuperTest.java
index e856027..f3db1be 100644
--- a/src/test/java/com/android/tools/r8/shaking/LibraryClassExtendingProgramClassSuperTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryClassExtendingProgramClassSuperTest.java
@@ -59,7 +59,7 @@
 
   @Test
   public void testR8() throws Exception {
-    R8TestBuilder<? extends R8TestBuilder<?>> r8TestBuilder =
+    R8TestBuilder<?, ?, ?> r8TestBuilder =
         (proguardCompatibility
                 ? testForR8Compat(parameters.getBackend(), true)
                 : testForR8(parameters.getBackend()))
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepEnclosingMethodForKeptMethodTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepEnclosingMethodForKeptMethodTest.java
index 9a66258..f6d7ea4 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/KeepEnclosingMethodForKeptMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepEnclosingMethodForKeptMethodTest.java
@@ -61,7 +61,8 @@
     runTest(testForR8Compat(parameters.getBackend())).assertSuccessWithOutputLines(EXPECTED);
   }
 
-  private R8TestRunResult runTest(R8TestBuilder<?> testBuilder) throws Exception {
+  private R8TestRunResult runTest(R8TestBuilder<?, R8TestRunResult, ?> testBuilder)
+      throws Exception {
     return testBuilder
         .addInnerClasses(KeptClass.class)
         .addProgramClassFileData(
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java
index 56e7b82..57a904f 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java
@@ -66,7 +66,8 @@
     runTest(testForR8Compat(parameters.getBackend()), true);
   }
 
-  private void runTest(R8TestBuilder<?> testBuilder, boolean keptForNotKept) throws Exception {
+  private void runTest(R8TestBuilder<?, ?, ?> testBuilder, boolean keptForNotKept)
+      throws Exception {
     testBuilder
         .addProgramClassFileData(transformer(KeptClass.class).removeInnerClasses().transform())
         .addProgramClassFileData(transformer(NotKeptClass.class).removeInnerClasses().transform())
diff --git a/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
index 41c6730..2670bee 100644
--- a/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
+++ b/src/test/java/com/android/tools/r8/startup/utils/StartupTestingUtils.java
@@ -194,7 +194,7 @@
     }
   }
 
-  public static <B extends R8TestBuilder<B>> ThrowableConsumer<B> addStartupProfile(
+  public static <B extends R8TestBuilder<?, ?, ?>> ThrowableConsumer<B> addStartupProfile(
       Collection<ExternalStartupItem> startupItems) {
     return testBuilder -> addStartupProfile(testBuilder, startupItems);
   }
diff --git a/src/test/testbase/java/com/android/tools/r8/R8CompatTestBuilder.java b/src/test/testbase/java/com/android/tools/r8/R8CompatTestBuilder.java
index 8074250..bc904c7 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8CompatTestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8CompatTestBuilder.java
@@ -5,8 +5,17 @@
 
 import com.android.tools.r8.R8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.benchmarks.BenchmarkResults;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
 
-public class R8CompatTestBuilder extends R8TestBuilder<R8CompatTestBuilder> {
+public class R8CompatTestBuilder
+    extends R8TestBuilder<R8TestCompileResult, R8TestRunResult, R8CompatTestBuilder> {
 
   private R8CompatTestBuilder(TestState state, Builder builder, Backend backend) {
     super(state, builder, backend);
@@ -28,4 +37,31 @@
   R8CompatTestBuilder self() {
     return this;
   }
+
+  R8TestCompileResult internalCompileR8(
+      Builder builder,
+      Consumer<InternalOptions> optionsConsumer,
+      Supplier<AndroidApp> app,
+      BenchmarkResults benchmarkResults,
+      StringBuilder pgConfOutput,
+      Box<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer,
+      StringBuilder proguardMapBuilder)
+      throws CompilationFailedException {
+    ToolHelper.runAndBenchmarkR8WithoutResult(builder, optionsConsumer, benchmarkResults);
+    return new R8TestCompileResult(
+        getState(),
+        getOutputMode(),
+        libraryDesugaringTestConfiguration,
+        app.get(),
+        pgConfOutput.toString(),
+        syntheticProguardRulesConsumer.get(),
+        proguardMapBuilder.toString(),
+        graphConsumer,
+        getMinApiLevel(),
+        features,
+        residualArtProfiles,
+        resourceShrinkerOutput,
+        resourceShrinkerOutputForFeatures,
+        buildMetadata != null ? buildMetadata.get() : null);
+  }
 }
diff --git a/src/test/testbase/java/com/android/tools/r8/R8FullTestBuilder.java b/src/test/testbase/java/com/android/tools/r8/R8FullTestBuilder.java
index a8f6d60..3caab28 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8FullTestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8FullTestBuilder.java
@@ -5,9 +5,17 @@
 
 import com.android.tools.r8.R8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.benchmarks.BenchmarkResults;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
 
-public class R8FullTestBuilder extends R8TestBuilder<R8FullTestBuilder> {
+public class R8FullTestBuilder
+    extends R8TestBuilder<R8TestCompileResult, R8TestRunResult, R8FullTestBuilder> {
 
   private R8FullTestBuilder(TestState state, Builder builder, Backend backend) {
     super(state, builder, backend);
@@ -32,4 +40,31 @@
   R8FullTestBuilder self() {
     return this;
   }
+
+  R8TestCompileResult internalCompileR8(
+      Builder builder,
+      Consumer<InternalOptions> optionsConsumer,
+      Supplier<AndroidApp> app,
+      BenchmarkResults benchmarkResults,
+      StringBuilder pgConfOutput,
+      Box<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer,
+      StringBuilder proguardMapBuilder)
+      throws CompilationFailedException {
+    ToolHelper.runAndBenchmarkR8WithoutResult(builder, optionsConsumer, benchmarkResults);
+    return new R8TestCompileResult(
+        getState(),
+        getOutputMode(),
+        libraryDesugaringTestConfiguration,
+        app.get(),
+        pgConfOutput.toString(),
+        syntheticProguardRulesConsumer.get(),
+        proguardMapBuilder.toString(),
+        graphConsumer,
+        getMinApiLevel(),
+        features,
+        residualArtProfiles,
+        resourceShrinkerOutput,
+        resourceShrinkerOutputForFeatures,
+        buildMetadata != null ? buildMetadata.get() : null);
+  }
 }
diff --git a/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java b/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java
new file mode 100644
index 0000000..319b633
--- /dev/null
+++ b/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java
@@ -0,0 +1,191 @@
+// 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;
+
+import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.benchmarks.BenchmarkResults;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+public class R8PartialTestBuilder
+    extends R8TestBuilder<R8PartialTestCompileResult, R8TestRunResult, R8PartialTestBuilder> {
+
+  private R8PartialConfiguration r8PartialConfiguration =
+      R8PartialConfiguration.defaultConfiguration();
+
+  private R8PartialTestBuilder(TestState state, Builder builder, Backend backend) {
+    super(state, builder, backend);
+  }
+
+  public static R8PartialTestBuilder create(TestState state, Backend backend) {
+    Builder builder = R8Command.builder(state.getDiagnosticsHandler());
+    return new R8PartialTestBuilder(state, builder, backend);
+  }
+
+  public static R8PartialTestBuilder create(
+      TestState state, AndroidApp.Builder appBuilder, Backend backend) {
+    return new R8PartialTestBuilder(state, R8Command.builder(appBuilder.build()), backend);
+  }
+
+  @Override
+  public boolean isR8TestBuilder() {
+    return true;
+  }
+
+  @Override
+  R8PartialTestBuilder self() {
+    return this;
+  }
+
+  public static class R8PartialConfiguration implements Predicate<String> {
+    private static final R8PartialConfiguration defaultConfiguration =
+        new R8PartialConfiguration(ImmutableList.of(), ImmutableList.of());
+    private final List<Predicate<String>> includePredicates;
+    private final List<Predicate<String>> excludePredicates;
+
+    public R8PartialConfiguration(
+        List<Predicate<String>> includePredicates, List<Predicate<String>> excludePredicates) {
+      this.includePredicates = includePredicates;
+      this.excludePredicates = excludePredicates;
+    }
+
+    private static R8PartialConfiguration defaultConfiguration() {
+      return defaultConfiguration;
+    }
+
+    public static Builder builder() {
+      return new Builder();
+    }
+
+    public boolean test(String name) {
+      for (Predicate<String> isR8ClassPredicate : includePredicates) {
+        if (isR8ClassPredicate.test(name)) {
+          for (Predicate<String> isD8ClassPredicate : excludePredicates) {
+            if (isD8ClassPredicate.test(name)) {
+              return false;
+            }
+          }
+          return true;
+        }
+      }
+      return false;
+    }
+
+    public static class Builder {
+      private final List<Predicate<String>> includePredicates = new ArrayList<>();
+      private final List<Predicate<String>> excludePredicates = new ArrayList<>();
+
+      public R8PartialConfiguration build() {
+        return new R8PartialConfiguration(includePredicates, excludePredicates);
+      }
+
+      public Builder includeAll() {
+        includePredicates.add(Predicates.alwaysTrue());
+        return this;
+      }
+
+      public Builder includeClasses(Class<?>... classes) {
+        return includeClasses(Arrays.asList(classes));
+      }
+
+      public Builder includeClasses(Collection<Class<?>> classes) {
+        Collection<String> typeNames =
+            classes.stream().map(Class::getTypeName).collect(Collectors.toList());
+        includePredicates.add(typeNames::contains);
+        return this;
+      }
+
+      public Builder include(Predicate<String> include) {
+        includePredicates.add(include);
+        return this;
+      }
+
+      public Builder excludeClasses(Class<?>... classes) {
+        return excludeClasses(Arrays.asList(classes));
+      }
+
+      public Builder excludeClasses(Collection<Class<?>> classes) {
+        Collection<String> typeNames =
+            classes.stream().map(Class::getTypeName).collect(Collectors.toList());
+        excludePredicates.add(typeNames::contains);
+        return this;
+      }
+
+      public Builder exclude(Predicate<String> exclude) {
+        excludePredicates.add(exclude);
+        return this;
+      }
+    }
+  }
+
+  public R8PartialTestBuilder setR8PartialConfigurationPredicate(Predicate<String> include) {
+    assert r8PartialConfiguration == R8PartialConfiguration.defaultConfiguration()
+        : "Overwriting configuration...?";
+    r8PartialConfiguration = R8PartialConfiguration.builder().include(include).build();
+    return self();
+  }
+
+  public R8PartialTestBuilder setR8PartialConfiguration(R8PartialConfiguration configuration) {
+    assert r8PartialConfiguration == R8PartialConfiguration.defaultConfiguration()
+        : "Overwriting configuration...?";
+    r8PartialConfiguration = configuration;
+    return self();
+  }
+
+  public R8PartialTestBuilder setR8PartialConfiguration(
+      Function<R8PartialConfiguration.Builder, R8PartialConfiguration> fn) {
+    assert r8PartialConfiguration == R8PartialConfiguration.defaultConfiguration()
+        : "Overwriting configuration...?";
+    r8PartialConfiguration = fn.apply(R8PartialConfiguration.builder());
+    return self();
+  }
+
+  @Override
+  R8PartialTestCompileResult internalCompileR8(
+      Builder builder,
+      Consumer<InternalOptions> optionsConsumer,
+      Supplier<AndroidApp> app,
+      BenchmarkResults benchmarkResults,
+      StringBuilder pgConfOutput,
+      Box<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer,
+      StringBuilder proguardMapBuilder)
+      throws CompilationFailedException {
+    Consumer<InternalOptions> configureR8PartialCompilation =
+        options -> {
+          options.r8PartialCompilationOptions.enabled = true;
+          options.r8PartialCompilationOptions.isR8 = r8PartialConfiguration;
+        };
+    ToolHelper.runAndBenchmarkR8WithoutResult(
+        builder, configureR8PartialCompilation.andThen(optionsConsumer), benchmarkResults);
+    return new R8PartialTestCompileResult(
+        getState(),
+        getOutputMode(),
+        libraryDesugaringTestConfiguration,
+        app.get(),
+        pgConfOutput.toString(),
+        syntheticProguardRulesConsumer.get(),
+        proguardMapBuilder.toString(),
+        graphConsumer,
+        getMinApiLevel(),
+        features,
+        residualArtProfiles,
+        resourceShrinkerOutput,
+        resourceShrinkerOutputForFeatures,
+        buildMetadata != null ? buildMetadata.get() : null);
+  }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/R8PartialTestCompileResult.java b/src/test/testbase/java/com/android/tools/r8/R8PartialTestCompileResult.java
new file mode 100644
index 0000000..18f75fb
--- /dev/null
+++ b/src/test/testbase/java/com/android/tools/r8/R8PartialTestCompileResult.java
@@ -0,0 +1,54 @@
+// 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;
+
+import com.android.tools.r8.metadata.R8BuildMetadata;
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.shaking.CollectingGraphConsumer;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.utils.AndroidApp;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.List;
+
+public class R8PartialTestCompileResult
+    extends R8TestCompileResultBase<R8PartialTestCompileResult> {
+
+  R8PartialTestCompileResult(
+      TestState state,
+      OutputMode outputMode,
+      LibraryDesugaringTestConfiguration libraryDesugaringTestConfiguration,
+      AndroidApp app,
+      String proguardConfiguration,
+      List<ProguardConfigurationRule> syntheticProguardRules,
+      String proguardMap,
+      CollectingGraphConsumer graphConsumer,
+      int minApiLevel,
+      List<Path> features,
+      List<ExternalArtProfile> residualArtProfiles,
+      Path resourceShrinkerOutput,
+      HashMap<String, Path> resourceShrinkerOutputForFeatures,
+      R8BuildMetadata buildMetadata) {
+    super(
+        state,
+        outputMode,
+        libraryDesugaringTestConfiguration,
+        app,
+        proguardConfiguration,
+        syntheticProguardRules,
+        proguardMap,
+        graphConsumer,
+        minApiLevel,
+        features,
+        residualArtProfiles,
+        resourceShrinkerOutput,
+        resourceShrinkerOutputForFeatures,
+        buildMetadata);
+  }
+
+  @Override
+  public R8PartialTestCompileResult self() {
+    return this;
+  }
+}
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 066e1ff..2301f6a 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8TestBuilder.java
@@ -65,8 +65,11 @@
 import java.util.stream.Collectors;
 import org.hamcrest.core.IsAnything;
 
-public abstract class R8TestBuilder<T extends R8TestBuilder<T>>
-    extends TestShrinkerBuilder<R8Command, Builder, R8TestCompileResult, R8TestRunResult, T> {
+public abstract class R8TestBuilder<
+        CR extends TestCompileResult<CR, RR>,
+        RR extends TestRunResult<RR>,
+        T extends R8TestBuilder<CR, RR, T>>
+    extends TestShrinkerBuilder<R8Command, Builder, CR, RR, T> {
 
   enum AllowedDiagnosticMessages {
     ALL,
@@ -86,15 +89,15 @@
   private boolean enableIsolatedSplits = false;
   private boolean enableMissingLibraryApiModeling = true;
   private boolean enableStartupLayoutOptimization = true;
-  private CollectingGraphConsumer graphConsumer = null;
-  private final List<ExternalArtProfile> residualArtProfiles = new ArrayList<>();
+  CollectingGraphConsumer graphConsumer = null;
+  final List<ExternalArtProfile> residualArtProfiles = new ArrayList<>();
   private final List<String> keepRules = new ArrayList<>();
   private final List<Path> mainDexRulesFiles = new ArrayList<>();
   private final List<String> applyMappingMaps = new ArrayList<>();
-  private final List<Path> features = new ArrayList<>();
-  private Path resourceShrinkerOutput = null;
-  private HashMap<String, Path> resourceShrinkerOutputForFeatures = new HashMap<>();
-  private Box<R8BuildMetadata> buildMetadata;
+  final List<Path> features = new ArrayList<>();
+  Path resourceShrinkerOutput = null;
+  HashMap<String, Path> resourceShrinkerOutputForFeatures = new HashMap<>();
+  Box<R8BuildMetadata> buildMetadata;
   private boolean androidPlatformBuild = false;
 
   @Override
@@ -103,12 +106,22 @@
   }
 
   @Override
-  public R8TestBuilder<?> asR8TestBuilder() {
+  public R8TestBuilder<?, ?, ?> asR8TestBuilder() {
     return this;
   }
 
+  abstract CR internalCompileR8(
+      Builder builder,
+      Consumer<InternalOptions> optionsConsumer,
+      Supplier<AndroidApp> app,
+      BenchmarkResults benchmarkResults,
+      StringBuilder pgConfOutput,
+      Box<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer,
+      StringBuilder proguardMapBuilder)
+      throws CompilationFailedException;
+
   @Override
-  R8TestCompileResult internalCompile(
+  CR internalCompile(
       Builder builder,
       Consumer<InternalOptions> optionsConsumer,
       Supplier<AndroidApp> app,
@@ -134,13 +147,9 @@
       }
     }
 
-    class Box {
-
-      private List<ProguardConfigurationRule> syntheticProguardRules;
-    }
-    Box box = new Box();
+    Box<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer = new Box<>();
     ToolHelper.addSyntheticProguardRulesConsumerForTesting(
-        builder, rules -> box.syntheticProguardRules = rules);
+        builder, syntheticProguardRulesConsumer::set);
     libraryDesugaringTestConfiguration.configure(builder);
     builder.setAndroidPlatformBuild(androidPlatformBuild);
     if (!enableEmptyMemberRulesToDefaultInitRuleConversion.isUnknown()) {
@@ -154,23 +163,15 @@
       builder.setBuildMetadataConsumer(buildMetadata::set);
     }
     StringBuilder pgConfOutput = wrapProguardConfigConsumer(builder);
-    ToolHelper.runAndBenchmarkR8WithoutResult(builder, optionsConsumer, benchmarkResults);
-    R8TestCompileResult compileResult =
-        new R8TestCompileResult(
-            getState(),
-            getOutputMode(),
-            libraryDesugaringTestConfiguration,
-            app.get(),
-            pgConfOutput.toString(),
-            box.syntheticProguardRules,
-            proguardMapBuilder.toString(),
-            graphConsumer,
-            getMinApiLevel(),
-            features,
-            residualArtProfiles,
-            resourceShrinkerOutput,
-            resourceShrinkerOutputForFeatures,
-            buildMetadata != null ? buildMetadata.get() : null);
+    CR compileResult =
+        internalCompileR8(
+            builder,
+            optionsConsumer,
+            app,
+            benchmarkResults,
+            pgConfOutput,
+            syntheticProguardRulesConsumer,
+            proguardMapBuilder);
     switch (allowedDiagnosticMessages) {
       case ALL:
         compileResult.getDiagnosticMessages().assertAllDiagnosticsMatch(new IsAnything<>());
@@ -238,6 +239,38 @@
     return pgConfOutput;
   }
 
+  private static StringBuilder wrapProguardMapConsumer(Builder builder, StringBuilder pgMapOutput) {
+    StringConsumer pgMapConsumer = builder.getProguardMapConsumer();
+    builder.setProguardMapConsumer(
+        new StringConsumer.ForwardingConsumer(pgMapConsumer) {
+          @Override
+          public void accept(String string, DiagnosticsHandler handler) {
+            super.accept(string, handler);
+            pgMapOutput.append(string);
+          }
+
+          @Override
+          public void finished(DiagnosticsHandler handler) {
+            super.finished(handler);
+          }
+        });
+    return pgMapOutput;
+  }
+
+  private static StringBuilder wrapProguardConfigConsumer(
+      Builder builder, StringBuilder pgConfOutput) {
+    StringConsumer pgConfConsumer = builder.getProguardConfigurationConsumer();
+    builder.setProguardConfigurationConsumer(
+        new StringConsumer.ForwardingConsumer(pgConfConsumer) {
+          @Override
+          public void accept(String string, DiagnosticsHandler handler) {
+            super.accept(string, handler);
+            pgConfOutput.append(string);
+          }
+        });
+    return pgConfOutput;
+  }
+
   public Builder getBuilder() {
     return builder;
   }
diff --git a/src/test/testbase/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/testbase/java/com/android/tools/r8/R8TestCompileResult.java
index e9956ad..9354c62 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8TestCompileResult.java
@@ -3,61 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import com.android.tools.r8.DexSegments.SegmentInfo;
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.androidresources.AndroidResourceTestingUtils;
-import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.ResourceTableInspector;
-import com.android.tools.r8.benchmarks.BenchmarkResults;
-import com.android.tools.r8.benchmarks.InstructionCodeSizeResult;
-import com.android.tools.r8.dexsplitter.SplitterTestBase.SplitRunner;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.metadata.R8BuildMetadata;
 import com.android.tools.r8.profile.art.model.ExternalArtProfile;
-import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
 import com.android.tools.r8.shaking.CollectingGraphConsumer;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.ThrowingBiConsumer;
-import com.android.tools.r8.utils.ThrowingConsumer;
-import com.android.tools.r8.utils.ZipUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
-import com.android.tools.r8.utils.graphinspector.GraphInspector;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceLinkedOpenHashMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Consumer;
 
-public class R8TestCompileResult extends TestCompileResult<R8TestCompileResult, R8TestRunResult> {
-
-  private final String proguardConfiguration;
-  private final List<ProguardConfigurationRule> syntheticProguardRules;
-  private final String proguardMap;
-  private final CollectingGraphConsumer graphConsumer;
-  private final List<Path> features;
-  private final List<ExternalArtProfile> residualArtProfiles;
-  private final Path resourceShrinkerOutput;
-  private final Map<String, Path> resourceShrinkerOutputForFeatures;
-  private final R8BuildMetadata buildMetadata;
+public class R8TestCompileResult extends R8TestCompileResultBase<R8TestCompileResult> {
 
   R8TestCompileResult(
       TestState state,
@@ -74,318 +29,25 @@
       Path resourceShrinkerOutput,
       HashMap<String, Path> resourceShrinkerOutputForFeatures,
       R8BuildMetadata buildMetadata) {
-    super(state, app, minApiLevel, outputMode, libraryDesugaringTestConfiguration);
-    this.proguardConfiguration = proguardConfiguration;
-    this.syntheticProguardRules = syntheticProguardRules;
-    this.proguardMap = proguardMap;
-    this.graphConsumer = graphConsumer;
-    this.features = features;
-    this.residualArtProfiles = residualArtProfiles;
-    this.resourceShrinkerOutput = resourceShrinkerOutput;
-    this.resourceShrinkerOutputForFeatures = resourceShrinkerOutputForFeatures;
-    this.buildMetadata = buildMetadata;
-  }
-
-  public R8TestCompileResult benchmarkResourceSize(BenchmarkResults results) throws IOException {
-    results.addResourceSizeResult(Files.size(resourceShrinkerOutput));
-    return self();
+    super(
+        state,
+        outputMode,
+        libraryDesugaringTestConfiguration,
+        app,
+        proguardConfiguration,
+        syntheticProguardRules,
+        proguardMap,
+        graphConsumer,
+        minApiLevel,
+        features,
+        residualArtProfiles,
+        resourceShrinkerOutput,
+        resourceShrinkerOutputForFeatures,
+        buildMetadata);
   }
 
   @Override
   public R8TestCompileResult self() {
     return this;
   }
-
-  public R8BuildMetadata getBuildMetadata() {
-    assert buildMetadata != null;
-    return buildMetadata;
-  }
-
-  @Override
-  public TestDiagnosticMessages getDiagnosticMessages() {
-    return state.getDiagnosticsMessages();
-  }
-
-  @Override
-  public R8TestCompileResult inspectDiagnosticMessages(Consumer<TestDiagnosticMessages> consumer) {
-    consumer.accept(state.getDiagnosticsMessages());
-    return self();
-  }
-
-  public Path getFeature(int index) {
-    return features.get(index);
-  }
-
-  public List<Path> getFeatures() {
-    return features;
-  }
-
-  @Override
-  public Set<String> getMainDexClasses() {
-    return state.getMainDexClasses();
-  }
-
-  @Override
-  public String getStdout() {
-    return state.getStdout();
-  }
-
-  @Override
-  public String getStderr() {
-    return state.getStderr();
-  }
-
-  @Override
-  public CodeInspector inspector() throws IOException {
-    return inspector(null);
-  }
-
-  public CodeInspector inspector(Consumer<InternalOptions> debugOptionsConsumer)
-      throws IOException {
-    return new CodeInspector(app, proguardMap, debugOptionsConsumer);
-  }
-
-  private CodeInspector featureInspector(Path feature) throws IOException {
-    return new CodeInspector(
-        AndroidApp.builder().addProgramFile(feature).setProguardMapOutputData(proguardMap).build());
-  }
-
-  public CodeInspector featureInspector() throws IOException {
-    assert features.size() == 1;
-    return featureInspector(features.get(0));
-  }
-
-  @SafeVarargs
-  public final <E extends Throwable> R8TestCompileResult inspect(
-      ThrowingConsumer<CodeInspector, E>... consumers) throws IOException, E {
-    assertEquals(1 + features.size(), consumers.length);
-    consumers[0].accept(inspector());
-    for (int i = 0; i < features.size(); i++) {
-      consumers[i + 1].accept(featureInspector(features.get(i)));
-    }
-    return self();
-  }
-
-  @SafeVarargs
-  @Override
-  public final <E extends Throwable> R8TestCompileResult inspectMultiDex(
-      ThrowingConsumer<CodeInspector, E>... consumers) throws E {
-    try {
-      return inspectMultiDex(writeProguardMap(), consumers);
-    } catch (IOException e) {
-      throw new UncheckedIOException(e);
-    }
-  }
-
-  public final <E extends Throwable> R8TestCompileResult inspectGraph(
-      ThrowingConsumer<GraphInspector, E> consumer) throws IOException, E {
-    consumer.accept(graphInspector());
-    return self();
-  }
-
-  public <E extends Throwable> R8TestCompileResult inspectResidualArtProfile(
-      ThrowingConsumer<ArtProfileInspector, E> consumer) throws E, IOException {
-    return inspectResidualArtProfile(
-        (rewrittenArtProfile, inspector) -> consumer.accept(rewrittenArtProfile));
-  }
-
-  public <E extends Throwable> R8TestCompileResult inspectResidualArtProfile(
-      ThrowingBiConsumer<ArtProfileInspector, CodeInspector, E> consumer) throws E, IOException {
-    assertEquals(1, residualArtProfiles.size());
-    consumer.accept(new ArtProfileInspector(residualArtProfiles.iterator().next()), inspector());
-    return self();
-  }
-
-  public <E extends Throwable> R8TestCompileResult inspectShrunkenResources(
-      Consumer<ResourceTableInspector> consumer) throws IOException {
-    assertNotNull(resourceShrinkerOutput);
-    consumer.accept(
-        new ResourceTableInspector(
-            ZipUtils.readSingleEntry(resourceShrinkerOutput, "resources.pb")));
-    return self();
-  }
-
-  public <E extends Throwable> R8TestCompileResult assertResourceFile(String name, boolean present)
-      throws IOException {
-    assertNotNull(resourceShrinkerOutput);
-    assertEquals(ZipUtils.containsEntry(resourceShrinkerOutput, name), present);
-    return self();
-  }
-
-  public <E extends Throwable> R8TestCompileResult assertFeatureResourceFile(
-      String name, boolean present, String featureName) throws IOException {
-    Path path = resourceShrinkerOutputForFeatures.get(featureName);
-    assertEquals(ZipUtils.containsEntry(path, name), present);
-    return self();
-  }
-
-  public String dumpResources() throws IOException {
-    ProcessResult processResult = AndroidResourceTestingUtils.dumpWithAapt2(resourceShrinkerOutput);
-    assert processResult.exitCode == 0;
-    return processResult.stdout;
-  }
-
-  public <E extends Throwable> R8TestCompileResult inspectShrunkenResourcesForFeature(
-      Consumer<ResourceTableInspector> consumer, String featureName) throws IOException {
-    Path path = resourceShrinkerOutputForFeatures.get(featureName);
-    assertNotNull(path);
-    consumer.accept(new ResourceTableInspector(ZipUtils.readSingleEntry(path, "resources.pb")));
-    return self();
-  }
-
-  public GraphInspector graphInspector() throws IOException {
-    assert graphConsumer != null;
-    return new GraphInspector(graphConsumer, inspector());
-  }
-
-  public String getProguardConfiguration() {
-    return proguardConfiguration;
-  }
-
-  public R8TestCompileResult inspectProguardConfiguration(Consumer<String> consumer) {
-    consumer.accept(getProguardConfiguration());
-    return self();
-  }
-
-  public List<ProguardConfigurationRule> getSyntheticProguardRules() {
-    return syntheticProguardRules;
-  }
-
-  public R8TestCompileResult inspectSyntheticProguardRules(
-      Consumer<List<ProguardConfigurationRule>> consumer) {
-    consumer.accept(getSyntheticProguardRules());
-    return self();
-  }
-
-  @Override
-  public R8TestRunResult createRunResult(TestRuntime runtime, ProcessResult result) {
-    return new R8TestRunResult(app, runtime, result, proguardMap, this::graphInspector, state);
-  }
-
-  public R8TestCompileResult addFeatureSplitsToRunClasspathFiles() {
-    return addRunClasspathFiles(features);
-  }
-
-  public R8TestRunResult runFeature(TestRuntime runtime, Class<?> mainFeatureClass)
-      throws IOException {
-    return runFeature(runtime, mainFeatureClass, features.get(0));
-  }
-
-  public R8TestRunResult runFeature(
-      TestRuntime runtime, Class<?> mainFeatureClass, Path feature, Path... featureDependencies)
-      throws IOException {
-    assert getBackend() == runtime.getBackend();
-    ClassSubject mainClassSubject = inspector().clazz(SplitRunner.class);
-    assertThat("Did you forget a keep rule for the main method?", mainClassSubject, isPresent());
-    assertThat(
-        "Did you forget a keep rule for the main method?",
-        mainClassSubject.mainMethod(),
-        isPresent());
-    ClassSubject mainFeatureClassSubject = featureInspector(feature).clazz(mainFeatureClass);
-    assertThat(
-        "Did you forget a keep rule for the run method?", mainFeatureClassSubject, isPresent());
-    assertThat(
-        "Did you forget a keep rule for the run method?",
-        mainFeatureClassSubject.uniqueMethodWithOriginalName("run"),
-        isPresent());
-    String[] args = new String[2 + featureDependencies.length];
-    args[0] = mainFeatureClassSubject.getFinalName();
-    args[1] = feature.toString();
-    for (int i = 2; i < args.length; i++) {
-      args[i] = featureDependencies[i - 2].toString();
-    }
-    return runArt(runtime, mainClassSubject.getFinalName(), args);
-  }
-
-  public String getProguardMap() {
-    return proguardMap;
-  }
-
-  public R8TestCompileResult inspectProguardMap(ThrowableConsumer<String> consumer)
-      throws Throwable {
-    consumer.accept(getProguardMap());
-    return this;
-  }
-
-  public Path writeProguardMap() throws IOException {
-    Path file = state.getNewTempFolder().resolve("out.zip");
-    writeProguardMap(file);
-    return file;
-  }
-
-  public R8TestCompileResult writeProguardMap(Path path) throws IOException {
-    FileUtils.writeTextFile(path, getProguardMap());
-    return self();
-  }
-
-  @Override
-  public R8TestCompileResult benchmarkCodeSize(BenchmarkResults results)
-      throws IOException, ResourceException {
-    if (results.isBenchmarkingCodeSize()) {
-      int applicationSizeWithFeatures =
-          AndroidApp.builder(app).addProgramFiles(features).build().applicationSize();
-      results.addCodeSizeResult(applicationSizeWithFeatures);
-    }
-    return self();
-  }
-
-  @Override
-  public R8TestCompileResult benchmarkInstructionCodeSize(BenchmarkResults results)
-      throws IOException {
-    if (results.isBenchmarkingCodeSize()) {
-      InstructionCodeSizeResult result = getComposableCodeSize(inspector());
-      for (Path feature : features) {
-        result.add(getComposableCodeSize(featureInspector(feature)));
-      }
-      results.addInstructionCodeSizeResult(result.instructionCodeSize);
-      results.addComposableInstructionCodeSizeResult(result.composableInstructionCodeSize);
-    }
-    return self();
-  }
-
-  private InstructionCodeSizeResult getComposableCodeSize(CodeInspector inspector) {
-    DexType composableType =
-        inspector.getFactory().createType("Landroidx/compose/runtime/Composable;");
-    InstructionCodeSizeResult result = new InstructionCodeSizeResult();
-    for (FoundClassSubject classSubject : inspector.allClasses()) {
-      DexProgramClass clazz = classSubject.getDexProgramClass();
-      for (DexEncodedMethod method : clazz.methods(DexEncodedMethod::hasCode)) {
-        int instructionCodeSize = method.getCode().asDexCode().codeSizeInBytes();
-        result.instructionCodeSize += instructionCodeSize;
-        if (method.annotations().hasAnnotation(composableType)) {
-          result.composableInstructionCodeSize += instructionCodeSize;
-        }
-      }
-    }
-    return result;
-  }
-
-  @Override
-  public R8TestCompileResult benchmarkDexSegmentsCodeSize(BenchmarkResults results)
-      throws IOException, ResourceException {
-    if (results.isBenchmarkingCodeSize()) {
-      AndroidApp appWithFeatures =
-          features.isEmpty() ? app : AndroidApp.builder(app).addProgramFiles(features).build();
-      results.addDexSegmentsSizeResult(runDexSegments(appWithFeatures));
-    }
-    return self();
-  }
-
-  private Int2ReferenceMap<SegmentInfo> runDexSegments(AndroidApp app)
-      throws IOException, ResourceException {
-    Map<Integer, SegmentInfo> result = DexSegments.runForTesting(app);
-    Int2ReferenceMap<SegmentInfo> rewrittenResult = new Int2ReferenceLinkedOpenHashMap<>();
-    rewrittenResult.putAll(result);
-    return rewrittenResult;
-  }
-
-  @Override
-  public R8TestCompileResult benchmarkDex2OatCodeSize(BenchmarkResults results) throws IOException {
-    if (results.isBenchmarkingCodeSize()) {
-      Dex2OatTestRunResult dex2OatTestRunResult =
-          runDex2Oat(new DexRuntime(DexVm.Version.LATEST_DEX2OAT));
-      results.addDex2OatSizeResult(dex2OatTestRunResult.getOatSizeOrDefault(0));
-    }
-    return self();
-  }
 }
diff --git a/src/test/testbase/java/com/android/tools/r8/R8TestCompileResultBase.java b/src/test/testbase/java/com/android/tools/r8/R8TestCompileResultBase.java
new file mode 100644
index 0000000..b38c63c
--- /dev/null
+++ b/src/test/testbase/java/com/android/tools/r8/R8TestCompileResultBase.java
@@ -0,0 +1,403 @@
+// 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;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.DexSegments.SegmentInfo;
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.ResourceTableInspector;
+import com.android.tools.r8.benchmarks.BenchmarkResults;
+import com.android.tools.r8.benchmarks.InstructionCodeSizeResult;
+import com.android.tools.r8.dexsplitter.SplitterTestBase.SplitRunner;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.metadata.R8BuildMetadata;
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
+import com.android.tools.r8.shaking.CollectingGraphConsumer;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThrowingBiConsumer;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+
+public abstract class R8TestCompileResultBase<CR extends R8TestCompileResultBase<CR>>
+    extends TestCompileResult<CR, R8TestRunResult> {
+
+  private final String proguardConfiguration;
+  private final List<ProguardConfigurationRule> syntheticProguardRules;
+  private final String proguardMap;
+  private final CollectingGraphConsumer graphConsumer;
+  private final List<Path> features;
+  private final List<ExternalArtProfile> residualArtProfiles;
+  private final Path resourceShrinkerOutput;
+  private final Map<String, Path> resourceShrinkerOutputForFeatures;
+  private final R8BuildMetadata buildMetadata;
+
+  R8TestCompileResultBase(
+      TestState state,
+      OutputMode outputMode,
+      LibraryDesugaringTestConfiguration libraryDesugaringTestConfiguration,
+      AndroidApp app,
+      String proguardConfiguration,
+      List<ProguardConfigurationRule> syntheticProguardRules,
+      String proguardMap,
+      CollectingGraphConsumer graphConsumer,
+      int minApiLevel,
+      List<Path> features,
+      List<ExternalArtProfile> residualArtProfiles,
+      Path resourceShrinkerOutput,
+      HashMap<String, Path> resourceShrinkerOutputForFeatures,
+      R8BuildMetadata buildMetadata) {
+    super(state, app, minApiLevel, outputMode, libraryDesugaringTestConfiguration);
+    this.proguardConfiguration = proguardConfiguration;
+    this.syntheticProguardRules = syntheticProguardRules;
+    this.proguardMap = proguardMap;
+    this.graphConsumer = graphConsumer;
+    this.features = features;
+    this.residualArtProfiles = residualArtProfiles;
+    this.resourceShrinkerOutput = resourceShrinkerOutput;
+    this.resourceShrinkerOutputForFeatures = resourceShrinkerOutputForFeatures;
+    this.buildMetadata = buildMetadata;
+  }
+
+  public CR benchmarkResourceSize(BenchmarkResults results) throws IOException {
+    results.addResourceSizeResult(Files.size(resourceShrinkerOutput));
+    return self();
+  }
+
+  public R8BuildMetadata getBuildMetadata() {
+    assert buildMetadata != null;
+    return buildMetadata;
+  }
+
+  @Override
+  public TestDiagnosticMessages getDiagnosticMessages() {
+    return state.getDiagnosticsMessages();
+  }
+
+  @Override
+  public CR inspectDiagnosticMessages(Consumer<TestDiagnosticMessages> consumer) {
+    consumer.accept(state.getDiagnosticsMessages());
+    return self();
+  }
+
+  public Path getFeature(int index) {
+    return features.get(index);
+  }
+
+  public List<Path> getFeatures() {
+    return features;
+  }
+
+  @Override
+  public Set<String> getMainDexClasses() {
+    return state.getMainDexClasses();
+  }
+
+  @Override
+  public String getStdout() {
+    return state.getStdout();
+  }
+
+  @Override
+  public String getStderr() {
+    return state.getStderr();
+  }
+
+  @Override
+  public CodeInspector inspector() throws IOException {
+    return inspector(null);
+  }
+
+  public CodeInspector inspector(Consumer<InternalOptions> debugOptionsConsumer)
+      throws IOException {
+    return new CodeInspector(app, proguardMap, debugOptionsConsumer);
+  }
+
+  private CodeInspector featureInspector(Path feature) throws IOException {
+    return new CodeInspector(
+        AndroidApp.builder().addProgramFile(feature).setProguardMapOutputData(proguardMap).build());
+  }
+
+  public CodeInspector featureInspector() throws IOException {
+    assert features.size() == 1;
+    return featureInspector(features.get(0));
+  }
+
+  @SafeVarargs
+  public final <E extends Throwable> CR inspect(ThrowingConsumer<CodeInspector, E>... consumers)
+      throws IOException, E {
+    assertEquals(1 + features.size(), consumers.length);
+    consumers[0].accept(inspector());
+    for (int i = 0; i < features.size(); i++) {
+      consumers[i + 1].accept(featureInspector(features.get(i)));
+    }
+    return self();
+  }
+
+  @SafeVarargs
+  @Override
+  public final <E extends Throwable> CR inspectMultiDex(
+      ThrowingConsumer<CodeInspector, E>... consumers) throws E {
+    try {
+      return inspectMultiDex(writeProguardMap(), consumers);
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    }
+  }
+
+  public final <E extends Throwable> CR inspectGraph(ThrowingConsumer<GraphInspector, E> consumer)
+      throws IOException, E {
+    consumer.accept(graphInspector());
+    return self();
+  }
+
+  public <E extends Throwable> CR inspectResidualArtProfile(
+      ThrowingConsumer<ArtProfileInspector, E> consumer) throws E, IOException {
+    return inspectResidualArtProfile(
+        (rewrittenArtProfile, inspector) -> consumer.accept(rewrittenArtProfile));
+  }
+
+  public <E extends Throwable> CR inspectResidualArtProfile(
+      ThrowingBiConsumer<ArtProfileInspector, CodeInspector, E> consumer) throws E, IOException {
+    assertEquals(1, residualArtProfiles.size());
+    consumer.accept(new ArtProfileInspector(residualArtProfiles.iterator().next()), inspector());
+    return self();
+  }
+
+  public <E extends Throwable> CR inspectShrunkenResources(
+      Consumer<ResourceTableInspector> consumer) throws IOException {
+    assertNotNull(resourceShrinkerOutput);
+    consumer.accept(
+        new ResourceTableInspector(
+            ZipUtils.readSingleEntry(resourceShrinkerOutput, "resources.pb")));
+    return self();
+  }
+
+  public <E extends Throwable> CR assertResourceFile(String name, boolean present)
+      throws IOException {
+    assertNotNull(resourceShrinkerOutput);
+    assertEquals(ZipUtils.containsEntry(resourceShrinkerOutput, name), present);
+    return self();
+  }
+
+  public <E extends Throwable> CR assertFeatureResourceFile(
+      String name, boolean present, String featureName) throws IOException {
+    Path path = resourceShrinkerOutputForFeatures.get(featureName);
+    assertEquals(ZipUtils.containsEntry(path, name), present);
+    return self();
+  }
+
+  public String dumpResources() throws IOException {
+    ProcessResult processResult = AndroidResourceTestingUtils.dumpWithAapt2(resourceShrinkerOutput);
+    assert processResult.exitCode == 0;
+    return processResult.stdout;
+  }
+
+  public <E extends Throwable> CR inspectShrunkenResourcesForFeature(
+      Consumer<ResourceTableInspector> consumer, String featureName) throws IOException {
+    Path path = resourceShrinkerOutputForFeatures.get(featureName);
+    assertNotNull(path);
+    consumer.accept(new ResourceTableInspector(ZipUtils.readSingleEntry(path, "resources.pb")));
+    return self();
+  }
+
+  public GraphInspector graphInspector() throws IOException {
+    assert graphConsumer != null;
+    return new GraphInspector(graphConsumer, inspector());
+  }
+
+  public String getProguardConfiguration() {
+    return proguardConfiguration;
+  }
+
+  public CR inspectProguardConfiguration(Consumer<String> consumer) {
+    consumer.accept(getProguardConfiguration());
+    return self();
+  }
+
+  public List<ProguardConfigurationRule> getSyntheticProguardRules() {
+    return syntheticProguardRules;
+  }
+
+  public CR inspectSyntheticProguardRules(Consumer<List<ProguardConfigurationRule>> consumer) {
+    consumer.accept(getSyntheticProguardRules());
+    return self();
+  }
+
+  @Override
+  public R8TestRunResult createRunResult(TestRuntime runtime, ProcessResult result) {
+    return new R8TestRunResult(app, runtime, result, proguardMap, this::graphInspector, state);
+  }
+
+  public CR addFeatureSplitsToRunClasspathFiles() {
+    return addRunClasspathFiles(features);
+  }
+
+  public R8TestRunResult runFeature(TestRuntime runtime, Class<?> mainFeatureClass)
+      throws IOException {
+    return runFeature(runtime, mainFeatureClass, features.get(0));
+  }
+
+  public R8TestRunResult runFeature(
+      TestRuntime runtime, Class<?> mainFeatureClass, Path feature, Path... featureDependencies)
+      throws IOException {
+    assert getBackend() == runtime.getBackend();
+    ClassSubject mainClassSubject = inspector().clazz(SplitRunner.class);
+    assertThat("Did you forget a keep rule for the main method?", mainClassSubject, isPresent());
+    assertThat(
+        "Did you forget a keep rule for the main method?",
+        mainClassSubject.mainMethod(),
+        isPresent());
+    ClassSubject mainFeatureClassSubject = featureInspector(feature).clazz(mainFeatureClass);
+    assertThat(
+        "Did you forget a keep rule for the run method?", mainFeatureClassSubject, isPresent());
+    assertThat(
+        "Did you forget a keep rule for the run method?",
+        mainFeatureClassSubject.uniqueMethodWithOriginalName("run"),
+        isPresent());
+    String[] args = new String[2 + featureDependencies.length];
+    args[0] = mainFeatureClassSubject.getFinalName();
+    args[1] = feature.toString();
+    for (int i = 2; i < args.length; i++) {
+      args[i] = featureDependencies[i - 2].toString();
+    }
+    return runArt(runtime, mainClassSubject.getFinalName(), args);
+  }
+
+  public CodeInspector inspectorForBase() throws IOException {
+    return new CodeInspector(writeToZip());
+  }
+
+  public CodeInspector inspectorForFeature(int index) throws IOException {
+    return new CodeInspector(getFeature(index));
+  }
+
+  public <E extends Throwable> CR inspectBase(ThrowingConsumer<CodeInspector, E> consumer)
+      throws IOException, E {
+    consumer.accept(inspectorForBase());
+    return self();
+  }
+
+  public <E extends Throwable> CR inspectFeature(
+      int index, ThrowingConsumer<CodeInspector, E> consumer) throws IOException, E {
+    consumer.accept(inspectorForFeature(index));
+    return self();
+  }
+
+  public String getProguardMap() {
+    return proguardMap;
+  }
+
+  public CR inspectProguardMap(ThrowableConsumer<String> consumer) throws Throwable {
+    consumer.accept(getProguardMap());
+    return self();
+  }
+
+  public Path writeProguardMap() throws IOException {
+    Path file = state.getNewTempFolder().resolve("out.zip");
+    writeProguardMap(file);
+    return file;
+  }
+
+  public CR writeProguardMap(Path path) throws IOException {
+    FileUtils.writeTextFile(path, getProguardMap());
+    return self();
+  }
+
+  @Override
+  public CR benchmarkCodeSize(BenchmarkResults results) throws IOException, ResourceException {
+    if (results.isBenchmarkingCodeSize()) {
+      int applicationSizeWithFeatures =
+          AndroidApp.builder(app).addProgramFiles(features).build().applicationSize();
+      results.addCodeSizeResult(applicationSizeWithFeatures);
+    }
+    return self();
+  }
+
+  @Override
+  public CR benchmarkInstructionCodeSize(BenchmarkResults results) throws IOException {
+    if (results.isBenchmarkingCodeSize()) {
+      InstructionCodeSizeResult result = getComposableCodeSize(inspector());
+      for (Path feature : features) {
+        result.add(getComposableCodeSize(featureInspector(feature)));
+      }
+      results.addInstructionCodeSizeResult(result.instructionCodeSize);
+      results.addComposableInstructionCodeSizeResult(result.composableInstructionCodeSize);
+    }
+    return self();
+  }
+
+  private InstructionCodeSizeResult getComposableCodeSize(CodeInspector inspector) {
+    DexType composableType =
+        inspector.getFactory().createType("Landroidx/compose/runtime/Composable;");
+    InstructionCodeSizeResult result = new InstructionCodeSizeResult();
+    for (FoundClassSubject classSubject : inspector.allClasses()) {
+      DexProgramClass clazz = classSubject.getDexProgramClass();
+      for (DexEncodedMethod method : clazz.methods(DexEncodedMethod::hasCode)) {
+        int instructionCodeSize = method.getCode().asDexCode().codeSizeInBytes();
+        result.instructionCodeSize += instructionCodeSize;
+        if (method.annotations().hasAnnotation(composableType)) {
+          result.composableInstructionCodeSize += instructionCodeSize;
+        }
+      }
+    }
+    return result;
+  }
+
+  @Override
+  public CR benchmarkDexSegmentsCodeSize(BenchmarkResults results)
+      throws IOException, ResourceException {
+    if (results.isBenchmarkingCodeSize()) {
+      AndroidApp appWithFeatures =
+          features.isEmpty() ? app : AndroidApp.builder(app).addProgramFiles(features).build();
+      results.addDexSegmentsSizeResult(runDexSegments(appWithFeatures));
+    }
+    return self();
+  }
+
+  private Int2ReferenceMap<SegmentInfo> runDexSegments(AndroidApp app)
+      throws IOException, ResourceException {
+    Map<Integer, SegmentInfo> result = DexSegments.runForTesting(app);
+    Int2ReferenceMap<SegmentInfo> rewrittenResult = new Int2ReferenceLinkedOpenHashMap<>();
+    rewrittenResult.putAll(result);
+    return rewrittenResult;
+  }
+
+  @Override
+  public CR benchmarkDex2OatCodeSize(BenchmarkResults results) throws IOException {
+    if (results.isBenchmarkingCodeSize()) {
+      Dex2OatTestRunResult dex2OatTestRunResult =
+          runDex2Oat(new DexRuntime(DexVm.Version.LATEST_DEX2OAT));
+      results.addDex2OatSizeResult(dex2OatTestRunResult.getOatSizeOrDefault(0));
+    }
+    return self();
+  }
+}
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 e3cc4c8..8cdcbc0 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestBase.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestBase.java
@@ -167,6 +167,10 @@
     return R8FullTestBuilder.create(new TestState(temp), backend);
   }
 
+  public static R8PartialTestBuilder testForR8Partial(TemporaryFolder temp, Backend backend) {
+    return R8PartialTestBuilder.create(new TestState(temp), backend);
+  }
+
   public static R8CompatTestBuilder testForR8Compat(
       TemporaryFolder temp, Backend backend, boolean forceProguardCompatibility) {
     return R8CompatTestBuilder.create(new TestState(temp), backend, forceProguardCompatibility);
@@ -206,6 +210,10 @@
     return testForR8(temp, backend);
   }
 
+  public R8PartialTestBuilder testForR8Partial(Backend backend) {
+    return testForR8Partial(temp, backend);
+  }
+
   public R8CompatTestBuilder testForR8Compat(Backend backend) {
     return testForR8Compat(backend, true);
   }
diff --git a/src/test/testbase/java/com/android/tools/r8/TestBuilder.java b/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
index 4b18fcb..ea40b20 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestBuilder.java
@@ -88,10 +88,10 @@
     return self;
   }
 
-  public T applyIfR8(ThrowableConsumer<? super R8TestBuilder<?>> consumer) {
+  public T applyIfR8(ThrowableConsumer<? super R8TestBuilder<?, ?, ?>> consumer) {
     T self = self();
-    if (this instanceof R8TestBuilder<?>) {
-      consumer.acceptWithRuntimeException((R8TestBuilder<?>) self);
+    if (this instanceof R8TestBuilder<?, ?, ?>) {
+      consumer.acceptWithRuntimeException((R8TestBuilder<?, ?, ?>) self);
     }
     return self;
   }
diff --git a/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java
index 0e28732..c9cc10d 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -118,7 +118,7 @@
     return false;
   }
 
-  public R8TestBuilder<?> asR8TestBuilder() {
+  public R8TestBuilder<?, ?, ?> asR8TestBuilder() {
     return null;
   }
 
diff --git a/src/test/testbase/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java b/src/test/testbase/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
index d9a4981..7aab984 100644
--- a/src/test/testbase/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
@@ -230,11 +230,11 @@
     consumer.accept((D8TestBuilder) builder);
   }
 
-  private void withR8TestBuilder(Consumer<R8TestBuilder<?>> consumer) {
+  private void withR8TestBuilder(Consumer<R8TestBuilder<?, ?, ?>> consumer) {
     if (!builder.isTestShrinkerBuilder()) {
       return;
     }
-    consumer.accept((R8TestBuilder<?>) builder);
+    consumer.accept((R8TestBuilder<?, ?, ?>) builder);
   }
 
   public DesugaredLibraryTestBuilder<T> allowUnusedDontWarnPatterns() {
@@ -267,7 +267,8 @@
     return this;
   }
 
-  public DesugaredLibraryTestBuilder<T> applyIfR8TestBuilder(Consumer<R8TestBuilder<?>> consumer) {
+  public DesugaredLibraryTestBuilder<T> applyIfR8TestBuilder(
+      Consumer<R8TestBuilder<?, ?, ?>> consumer) {
     withR8TestBuilder(consumer);
     return this;
   }