Add testForAssistant support in test framework

This will d8 compile the input and then run this through the R8Assistant instrumentation.

Bug: b/393265921
Change-Id: Ieaf777cb3164f0a68129c3d81121f33de7c7e1ec
diff --git a/src/test/java/com/android/tools/r8/assistant/R8AssistentReflectiveInstrumentationTest.java b/src/test/java/com/android/tools/r8/assistant/R8AssistentReflectiveInstrumentationTest.java
index bb9b78c..3a578c3 100644
--- a/src/test/java/com/android/tools/r8/assistant/R8AssistentReflectiveInstrumentationTest.java
+++ b/src/test/java/com/android/tools/r8/assistant/R8AssistentReflectiveInstrumentationTest.java
@@ -4,15 +4,11 @@
 package com.android.tools.r8.assistant;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8Assistant;
-import com.android.tools.r8.R8AssistantCommand;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -24,7 +20,6 @@
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.io.IOException;
 import java.io.UncheckedIOException;
-import java.nio.file.Path;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -63,41 +58,16 @@
 
   @Test
   public void testInstrumentation() throws Exception {
-    Path d8Compilation =
-        testForD8(parameters.getBackend())
-            .addInnerClasses(getClass())
-            .setMinApi(parameters)
-            .compile()
-            .inspect(codeInspector -> inspectStaticCallsInReflectOn(0, codeInspector))
-            .writeToZip();
-    R8AssistantCommand.Builder builder = R8AssistantCommand.builder();
-    Path outputPath = temp.newFile("instrumented.jar").toPath();
-    // TODO(b/393265921): Add testForR8Assistant and avoid building up the command here
-    R8AssistantCommand command =
-        builder
-            .addProgramFiles(d8Compilation)
-            .setMinApiLevel(parameters.getApiLevel().getLevel())
-            .setOutput(outputPath, OutputMode.DexIndexed)
-            .build();
-    R8Assistant.run(command);
-    inspectStaticCallsInReflectOn(outputPath, 2);
-
-    String artOutput =
-        ToolHelper.runArtNoVerificationErrors(
-            outputPath.toString(),
-            TestClass.class
-                .getName()); // For now, just test that the printed logs are what we expect
-    String expectedNewInstanceString =
-        "Reflectively created new instance of " + Bar.class.getName();
-    assertThat(artOutput, containsString(expectedNewInstanceString));
-    String expectedGetDeclaredMethod =
-        "Reflectively got declared method callMe on " + Bar.class.getName();
-    assertThat(artOutput, containsString(expectedGetDeclaredMethod));
-  }
-
-  private static void inspectStaticCallsInReflectOn(Path outputPath, int count) throws IOException {
-    CodeInspector inspector = new CodeInspector(outputPath);
-    inspectStaticCallsInReflectOn(count, inspector);
+    testForAssistant()
+        .addInnerClasses(getClass())
+        .setMinApi(parameters)
+        .compile()
+        .inspectOriginalDex(inspector -> inspectStaticCallsInReflectOn(0, inspector))
+        .inspect(inspector -> inspectStaticCallsInReflectOn(2, inspector))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines(
+            "Reflectively created new instance of " + Bar.class.getName(),
+            "Reflectively got declared method callMe on " + Bar.class.getName());
   }
 
   private static void inspectStaticCallsInReflectOn(int count, CodeInspector inspector) {
diff --git a/src/test/testbase/java/com/android/tools/r8/AssistantTestBuilder.java b/src/test/testbase/java/com/android/tools/r8/AssistantTestBuilder.java
new file mode 100644
index 0000000..abecb6b
--- /dev/null
+++ b/src/test/testbase/java/com/android/tools/r8/AssistantTestBuilder.java
@@ -0,0 +1,89 @@
+// 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;
+
+import static com.android.tools.r8.TestBase.testForD8;
+
+import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.benchmarks.BenchmarkResults;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public class AssistantTestBuilder
+    extends TestCompilerBuilder<
+        R8AssistantCommand,
+        R8AssistantCommand.Builder,
+        AssistantTestCompileResult,
+        AssistantTestRunResult,
+        AssistantTestBuilder> {
+
+  private final D8TestBuilder initialCompileBuilder;
+  private Path output;
+
+  private AssistantTestBuilder(TestState state) {
+    super(state, R8AssistantCommand.builder(state.getDiagnosticsHandler()), Backend.DEX);
+    initialCompileBuilder = testForD8(state.getTempFolder());
+  }
+
+  public static AssistantTestBuilder create(TestState state) {
+    return new AssistantTestBuilder(state);
+  }
+
+  @Override
+  AssistantTestBuilder self() {
+    return this;
+  }
+
+  @Override
+  public AssistantTestBuilder addClasspathClasses(Collection<Class<?>> classes) {
+    throw new Unimplemented("No classpath for assistant");
+  }
+
+  @Override
+  public AssistantTestBuilder addClasspathFiles(Collection<Path> files) {
+    throw new Unimplemented("No classpath for assistant");
+  }
+
+  @Override
+  public AssistantTestBuilder addProgramFiles(Collection<Path> files) {
+    initialCompileBuilder.addProgramFiles(files);
+    return self();
+  }
+
+  @Override
+  AssistantTestCompileResult internalCompile(
+      R8AssistantCommand.Builder builder,
+      Consumer<InternalOptions> optionsConsumer,
+      Supplier<AndroidApp> app,
+      BenchmarkResults benchmarkResults)
+      throws CompilationFailedException {
+    Path initialCompilation;
+    try {
+      initialCompilation = initialCompileBuilder.setMinApi(getMinApiLevel()).compile().writeToZip();
+      if (output == null) {
+        output = getState().getNewTempFile("assistant_output.jar");
+      }
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    }
+    builder
+        .addProgramFiles(initialCompilation)
+        .setOutput(output, OutputMode.DexIndexed)
+        .setMinApiLevel(getMinApiLevel());
+
+    R8Assistant.run(builder.build());
+    return new AssistantTestCompileResult(
+        initialCompilation,
+        getState(),
+        AndroidApp.builder().addProgramFiles(output).build(),
+        getMinApiLevel());
+  }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/AssistantTestCompileResult.java b/src/test/testbase/java/com/android/tools/r8/AssistantTestCompileResult.java
new file mode 100644
index 0000000..b0b2c99
--- /dev/null
+++ b/src/test/testbase/java/com/android/tools/r8/AssistantTestCompileResult.java
@@ -0,0 +1,61 @@
+// 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;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Set;
+
+public class AssistantTestCompileResult
+    extends TestCompileResult<AssistantTestCompileResult, AssistantTestRunResult> {
+
+  private final Path initialOutput;
+
+  public AssistantTestCompileResult(
+      Path initialOutput, TestState testState, AndroidApp androidApp, int minApi) {
+    super(testState, androidApp, minApi, OutputMode.DexIndexed);
+    this.initialOutput = initialOutput;
+  }
+
+  @Override
+  public AssistantTestCompileResult self() {
+    return this;
+  }
+
+  @Override
+  public TestDiagnosticMessages getDiagnosticMessages() {
+    return state.getDiagnosticsMessages();
+  }
+
+  @Override
+  public Set<String> getMainDexClasses() {
+    throw new Unimplemented("No support for main dex in assistant");
+  }
+
+  @Override
+  public String getStdout() {
+    return state.getStdout();
+  }
+
+  @Override
+  public String getStderr() {
+    return state.getStderr();
+  }
+
+  @Override
+  protected AssistantTestRunResult createRunResult(TestRuntime runtime, ProcessResult result) {
+    return new AssistantTestRunResult(app, runtime, result, state);
+  }
+
+  public <E extends Throwable> AssistantTestCompileResult inspectOriginalDex(
+      ThrowingConsumer<CodeInspector, E> consumer) throws IOException, E {
+    consumer.accept(new CodeInspector(initialOutput));
+    return self();
+  }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/AssistantTestRunResult.java b/src/test/testbase/java/com/android/tools/r8/AssistantTestRunResult.java
new file mode 100644
index 0000000..f1054cf
--- /dev/null
+++ b/src/test/testbase/java/com/android/tools/r8/AssistantTestRunResult.java
@@ -0,0 +1,21 @@
+// 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;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApp;
+
+public class AssistantTestRunResult extends SingleTestRunResult<AssistantTestRunResult> {
+
+  public AssistantTestRunResult(
+      AndroidApp app, TestRuntime runtime, ProcessResult result, TestState state) {
+    super(app, runtime, result, state);
+  }
+
+  @Override
+  protected AssistantTestRunResult self() {
+    return this;
+  }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/RelocatorTestCompileResult.java b/src/test/testbase/java/com/android/tools/r8/RelocatorTestCompileResult.java
index f3c624c..635c37d 100644
--- a/src/test/testbase/java/com/android/tools/r8/RelocatorTestCompileResult.java
+++ b/src/test/testbase/java/com/android/tools/r8/RelocatorTestCompileResult.java
@@ -3,10 +3,8 @@
 // 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.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.utils.DescriptorUtils;
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 9722064..2f6a150 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestBase.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestBase.java
@@ -238,6 +238,10 @@
     return testForD8(temp, backend);
   }
 
+  public AssistantTestBuilder testForAssistant() {
+    return AssistantTestBuilder.create(new TestState(temp));
+  }
+
   public RelocatorTestBuilder testForRelocator(boolean external) {
     return RelocatorTestBuilder.create(new TestState(temp), external);
   }