Add inspection of D8 and R8 input and output for R8 partial compilation

Bug: b/309743298
Change-Id: I6eb8b64d990c429892f0529c57d059a242cefa3b
diff --git a/src/main/java/com/android/tools/r8/R8Partial.java b/src/main/java/com/android/tools/r8/R8Partial.java
index e9cb9b8..ff1d8a6 100644
--- a/src/main/java/com/android/tools/r8/R8Partial.java
+++ b/src/main/java/com/android/tools/r8/R8Partial.java
@@ -21,9 +21,12 @@
 import com.android.tools.r8.tracereferences.TraceReferencesKeepRules;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.AndroidAppConsumers;
 import com.android.tools.r8.utils.DumpInputFlags;
+import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.ZipUtils;
 import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
@@ -34,15 +37,38 @@
 import java.util.HashSet;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 class R8Partial {
 
   private final InternalOptions options;
+  private final Consumer<AndroidApp> r8InputApp;
+  private final Consumer<AndroidApp> d8InputApp;
+  private final Consumer<AndroidApp> r8OutputApp;
+  private final Consumer<AndroidApp> d8OutputApp;
 
   R8Partial(InternalOptions options) {
     this.options = options;
+    this.r8InputApp = options.r8PartialCompilationOptions.r8InputApp;
+    this.d8InputApp = options.r8PartialCompilationOptions.d8InputApp;
+    this.r8OutputApp = options.r8PartialCompilationOptions.r8OutputApp;
+    this.d8OutputApp = options.r8PartialCompilationOptions.d8OutputApp;
+  }
+
+  static void runForTesting(AndroidApp app, InternalOptions options)
+      throws CompilationFailedException {
+    ExecutorService executor = ThreadUtils.getExecutorService(options);
+    ExceptionUtils.withR8CompilationHandler(
+        options.reporter,
+        () -> {
+          try {
+            new R8Partial(options).runInternal(app, executor);
+          } finally {
+            executor.shutdown();
+          }
+        });
   }
 
   void runInternal(AndroidApp app, ExecutorService executor) throws IOException, ResourceException {
@@ -151,13 +177,23 @@
       d8Builder.addDesugaredLibraryConfiguration(
           Files.readString(dump.getDesugaredLibraryFile(), UTF_8));
     }
+    AndroidAppConsumers d8OutputAppSink = null;
+    if (d8OutputApp != null) {
+      d8OutputAppSink = new AndroidAppConsumers(d8Builder);
+    }
     d8Builder.validate();
     D8Command d8command = d8Builder.makeCommand();
     AndroidApp d8App = d8command.getInputApp();
+    if (d8InputApp != null) {
+      d8InputApp.accept(d8App);
+    }
     InternalOptions d8Options = d8command.getInternalOptions();
     assert d8Options.getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)
         : "Default interface methods not yet supported";
     D8.runInternal(d8App, d8Options, executor);
+    if (d8OutputApp != null) {
+      d8OutputApp.accept(d8OutputAppSink.build());
+    }
 
     // Run trace references to produce keep rules for the D8 compiled part.
     // TODO(b/309743298): Do not emit keep rules into a file.
@@ -192,12 +228,23 @@
       r8Builder.addDesugaredLibraryConfiguration(
           Files.readString(dump.getDesugaredLibraryFile(), UTF_8));
     }
+    AndroidAppConsumers r8OutputAppSink = null;
+    if (r8Builder != null) {
+      r8OutputAppSink = new AndroidAppConsumers(r8Builder);
+    }
+    r8Builder.validate();
     R8Command r8Command = r8Builder.makeCommand();
     AndroidApp r8App = r8Command.getInputApp();
+    if (r8InputApp != null) {
+      r8InputApp.accept(r8App);
+    }
     InternalOptions r8Options = r8Command.getInternalOptions();
     r8Options.mapConsumer = originalMapConsumer;
     r8Options.quiet = true; // Don't write the R8 version.
     R8.runInternal(r8App, r8Options, executor);
+    if (r8OutputApp != null) {
+      r8OutputApp.accept(r8OutputAppSink.build());
+    }
 
     // Emit resources and merged DEX to the output consumer.
     // TODO(b/309743298): Consider passing the DataResourceConsumer to the R8 invocation above.
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 7d9439b..7b907e9 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2286,6 +2286,10 @@
     public boolean enabled;
     public Path tempDir = null;
     public Predicate<String> isR8 = null;
+    public Consumer<AndroidApp> r8InputApp;
+    public Consumer<AndroidApp> d8InputApp;
+    public Consumer<AndroidApp> r8OutputApp;
+    public Consumer<AndroidApp> d8OutputApp;
 
     R8PartialCompilationOptions(String partialR8) {
       this.enabled = partialR8 != null;
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 662aa21..4783aaa 100644
--- a/src/test/java/com/android/tools/r8/partial/PartialCompilationBasicTest.java
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationBasicTest.java
@@ -41,6 +41,34 @@
         .addKeepMainRule(Main.class)
         .setR8PartialConfiguration(builder -> builder.includeAll().excludeClasses(A.class).build())
         .compile()
+        .inspectR8Input(
+            inspector -> {
+              // TODO(b/309743298): These are all present as inspection currently also look at
+              //  classpath.
+              assertThat(inspector.clazz(A.class), isPresent());
+              assertThat(inspector.clazz(B.class), isPresent());
+              assertThat(inspector.clazz(Main.class), isPresent());
+            })
+        .inspectD8Input(
+            // TODO(b/309743298): These are all present as inspection currently also look at
+            //  classpath.
+            inspector -> {
+              assertThat(inspector.clazz(A.class), isPresent());
+              assertThat(inspector.clazz(B.class), isPresent());
+              assertThat(inspector.clazz(Main.class), isPresent());
+            })
+        .inspectR8Output(
+            inspector -> {
+              assertThat(inspector.clazz(A.class), isAbsent());
+              assertThat(inspector.clazz(B.class), isAbsent());
+              assertThat(inspector.clazz(Main.class), isPresent());
+            })
+        .inspectD8Output(
+            inspector -> {
+              assertThat(inspector.clazz(A.class), isPresent());
+              assertThat(inspector.clazz(B.class), isAbsent());
+              assertThat(inspector.clazz(Main.class), isAbsent());
+            })
         .inspect(
             inspector -> {
               assertThat(inspector.clazz(A.class), isPresent());
@@ -58,6 +86,34 @@
         .addKeepMainRule(Main.class)
         .setR8PartialConfiguration(builder -> builder.includeAll().excludeClasses(B.class).build())
         .compile()
+        .inspectR8Input(
+            inspector -> {
+              // TODO(b/309743298): These are all present as inspection currently also look at
+              //  classpath.
+              assertThat(inspector.clazz(A.class), isPresent());
+              assertThat(inspector.clazz(B.class), isPresent());
+              assertThat(inspector.clazz(Main.class), isPresent());
+            })
+        .inspectD8Input(
+            inspector -> {
+              // TODO(b/309743298): These are all present as inspection currently also look at
+              //  classpath.
+              assertThat(inspector.clazz(A.class), isPresent());
+              assertThat(inspector.clazz(B.class), isPresent());
+              assertThat(inspector.clazz(Main.class), isPresent());
+            })
+        .inspectR8Output(
+            inspector -> {
+              assertThat(inspector.clazz(A.class), isAbsent());
+              assertThat(inspector.clazz(B.class), isAbsent());
+              assertThat(inspector.clazz(Main.class), isPresent());
+            })
+        .inspectD8Output(
+            inspector -> {
+              assertThat(inspector.clazz(A.class), isAbsent());
+              assertThat(inspector.clazz(B.class), isPresent());
+              assertThat(inspector.clazz(Main.class), isAbsent());
+            })
         .inspect(
             inspector -> {
               assertThat(inspector.clazz(A.class), isAbsent());
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 319b633..9ea93eb 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8PartialTestBuilder.java
@@ -165,12 +165,20 @@
       Box<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer,
       StringBuilder proguardMapBuilder)
       throws CompilationFailedException {
+    Box<AndroidApp> r8InputApp = new Box<>();
+    Box<AndroidApp> d8InputApp = new Box<>();
+    Box<AndroidApp> r8OutputApp = new Box<>();
+    Box<AndroidApp> d8OutputApp = new Box<>();
     Consumer<InternalOptions> configureR8PartialCompilation =
         options -> {
           options.r8PartialCompilationOptions.enabled = true;
           options.r8PartialCompilationOptions.isR8 = r8PartialConfiguration;
+          options.r8PartialCompilationOptions.r8InputApp = r8InputApp::set;
+          options.r8PartialCompilationOptions.d8InputApp = d8InputApp::set;
+          options.r8PartialCompilationOptions.r8OutputApp = r8OutputApp::set;
+          options.r8PartialCompilationOptions.d8OutputApp = d8OutputApp::set;
         };
-    ToolHelper.runAndBenchmarkR8WithoutResult(
+    ToolHelper.runAndBenchmarkR8PartialWithoutResult(
         builder, configureR8PartialCompilation.andThen(optionsConsumer), benchmarkResults);
     return new R8PartialTestCompileResult(
         getState(),
@@ -186,6 +194,10 @@
         residualArtProfiles,
         resourceShrinkerOutput,
         resourceShrinkerOutputForFeatures,
-        buildMetadata != null ? buildMetadata.get() : null);
+        buildMetadata != null ? buildMetadata.get() : null,
+        r8InputApp.get(),
+        d8InputApp.get(),
+        r8OutputApp.get(),
+        d8OutputApp.get());
   }
 }
diff --git a/src/test/testbase/java/com/android/tools/r8/R8PartialTestCompileResult.java b/src/test/testbase/java/com/android/tools/r8/R8PartialTestCompileResult.java
index 18f75fb..8bdc393 100644
--- a/src/test/testbase/java/com/android/tools/r8/R8PartialTestCompileResult.java
+++ b/src/test/testbase/java/com/android/tools/r8/R8PartialTestCompileResult.java
@@ -8,13 +8,23 @@
 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.InternalOptions;
+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.HashMap;
 import java.util.List;
+import java.util.function.Consumer;
 
 public class R8PartialTestCompileResult
     extends R8TestCompileResultBase<R8PartialTestCompileResult> {
 
+  private final AndroidApp r8InputApp;
+  private final AndroidApp d8InputApp;
+  private final AndroidApp r8OutputApp;
+  private final AndroidApp d8OutputApp;
+
   R8PartialTestCompileResult(
       TestState state,
       OutputMode outputMode,
@@ -29,7 +39,11 @@
       List<ExternalArtProfile> residualArtProfiles,
       Path resourceShrinkerOutput,
       HashMap<String, Path> resourceShrinkerOutputForFeatures,
-      R8BuildMetadata buildMetadata) {
+      R8BuildMetadata buildMetadata,
+      AndroidApp r8InputApp,
+      AndroidApp d8InputApp,
+      AndroidApp r8OutputApp,
+      AndroidApp d8OutputApp) {
     super(
         state,
         outputMode,
@@ -45,10 +59,74 @@
         resourceShrinkerOutput,
         resourceShrinkerOutputForFeatures,
         buildMetadata);
+    this.r8InputApp = r8InputApp;
+    this.d8InputApp = d8InputApp;
+    this.r8OutputApp = r8OutputApp;
+    this.d8OutputApp = d8OutputApp;
   }
 
   @Override
   public R8PartialTestCompileResult self() {
     return this;
   }
+
+  public CodeInspector inspectorR8Input() throws IOException {
+    return new CodeInspector(r8InputApp);
+  }
+
+  public CodeInspector inspectorR8Input(Consumer<InternalOptions> debugOptionsConsumer)
+      throws IOException {
+    return new CodeInspector(r8InputApp, debugOptionsConsumer);
+  }
+
+  public <E extends Throwable> R8PartialTestCompileResult inspectR8Input(
+      ThrowingConsumer<CodeInspector, E> consumer) throws IOException, E {
+    consumer.accept(inspectorR8Input());
+    return self();
+  }
+
+  public CodeInspector inspectorD8Input() throws IOException {
+    return new CodeInspector(d8InputApp);
+  }
+
+  public CodeInspector inspectorD8Input(Consumer<InternalOptions> debugOptionsConsumer)
+      throws IOException {
+    return new CodeInspector(d8InputApp, debugOptionsConsumer);
+  }
+
+  public <E extends Throwable> R8PartialTestCompileResult inspectD8Input(
+      ThrowingConsumer<CodeInspector, E> consumer) throws IOException, E {
+    consumer.accept(inspectorR8Input());
+    return self();
+  }
+
+  public CodeInspector inspectorR8Output() throws IOException {
+    return new CodeInspector(r8OutputApp);
+  }
+
+  public CodeInspector inspectorR8Output(Consumer<InternalOptions> debugOptionsConsumer)
+      throws IOException {
+    return new CodeInspector(r8OutputApp, debugOptionsConsumer);
+  }
+
+  public <E extends Throwable> R8PartialTestCompileResult inspectR8Output(
+      ThrowingConsumer<CodeInspector, E> consumer) throws IOException, E {
+    consumer.accept(inspectorR8Output());
+    return self();
+  }
+
+  public CodeInspector inspectorD8Output() throws IOException {
+    return new CodeInspector(d8OutputApp);
+  }
+
+  public CodeInspector inspectorD8Output(Consumer<InternalOptions> debugOptionsConsumer)
+      throws IOException {
+    return new CodeInspector(d8OutputApp, debugOptionsConsumer);
+  }
+
+  public <E extends Throwable> R8PartialTestCompileResult inspectD8Output(
+      ThrowingConsumer<CodeInspector, E> consumer) throws IOException, E {
+    consumer.accept(inspectorD8Output());
+    return self();
+  }
 }
diff --git a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
index 1a8a049..0608acb 100644
--- a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
@@ -1807,6 +1807,28 @@
     }
   }
 
+  public static void runAndBenchmarkR8PartialWithoutResult(
+      R8Command.Builder commandBuilder,
+      Consumer<InternalOptions> optionsConsumer,
+      BenchmarkResults benchmarkResults)
+      throws CompilationFailedException {
+    long start = 0;
+    if (benchmarkResults != null) {
+      start = System.nanoTime();
+    }
+    R8Command command = commandBuilder.build();
+    InternalOptions internalOptions = command.getInternalOptions();
+    optionsConsumer.accept(internalOptions);
+    try {
+      R8Partial.runForTesting(command.getInputApp(), internalOptions);
+    } finally {
+      if (benchmarkResults != null) {
+        long end = System.nanoTime();
+        benchmarkResults.addRuntimeResult(end - start);
+      }
+    }
+  }
+
   public static AndroidApp runR8WithFullResult(
       R8Command command, Consumer<InternalOptions> optionsConsumer)
       throws CompilationFailedException {