Support custom map id in R8.

Bug: 201269335
Change-Id: I29934b25c291d8b33e9ff4599dc44c6890959e43
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index c292f33..51c1d2f 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -50,6 +50,7 @@
   private final List<Consumer<Inspector>> outputInspections;
   private final int threadCount;
   private final DumpInputFlags dumpInputFlags;
+  private final MapIdProvider mapIdProvider;
 
   BaseCompilerCommand(boolean printHelp, boolean printVersion) {
     super(printHelp, printVersion);
@@ -66,6 +67,7 @@
     outputInspections = null;
     threadCount = ThreadUtils.NOT_SPECIFIED;
     dumpInputFlags = DumpInputFlags.noDump();
+    mapIdProvider = null;
   }
 
   BaseCompilerCommand(
@@ -82,7 +84,8 @@
       List<AssertionsConfiguration> assertionsConfiguration,
       List<Consumer<Inspector>> outputInspections,
       int threadCount,
-      DumpInputFlags dumpInputFlags) {
+      DumpInputFlags dumpInputFlags,
+      MapIdProvider mapIdProvider) {
     super(app);
     assert minApiLevel > 0;
     assert mode != null;
@@ -99,6 +102,7 @@
     this.outputInspections = outputInspections;
     this.threadCount = threadCount;
     this.dumpInputFlags = dumpInputFlags;
+    this.mapIdProvider = mapIdProvider;
   }
 
   /**
@@ -148,6 +152,10 @@
     return desugarState;
   }
 
+  public MapIdProvider getMapIdProvider() {
+    return mapIdProvider;
+  }
+
   /** True if the output dex files has checksum information encoded in it. False otherwise. */
   public boolean getIncludeClassesChecksum() {
     return includeClassesChecksum;
@@ -217,6 +225,7 @@
     private List<Consumer<Inspector>> outputInspections = new ArrayList<>();
     protected StringConsumer proguardMapConsumer = null;
     private DumpInputFlags dumpInputFlags = DumpInputFlags.noDump();
+    private MapIdProvider mapIdProvider = null;
 
     abstract CompilationMode defaultCompilationMode();
 
@@ -516,6 +525,16 @@
       return desugarState;
     }
 
+    /** Set a custom provider for defining the map id for the build. */
+    public B setMapIdProvider(MapIdProvider mapIdProvider) {
+      this.mapIdProvider = mapIdProvider;
+      return self();
+    }
+
+    public MapIdProvider getMapIdProvider() {
+      return mapIdProvider;
+    }
+
     @Deprecated
     public B addSpecialLibraryConfiguration(String configuration) {
       return addDesugaredLibraryConfiguration(configuration);
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 671fb6b..0a7bcd1 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -318,6 +318,7 @@
           mainDexKeepRules,
           getThreadCount(),
           getDumpInputFlags(),
+          getMapIdProvider(),
           factory);
     }
   }
@@ -399,6 +400,7 @@
       ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
       int threadCount,
       DumpInputFlags dumpInputFlags,
+      MapIdProvider mapIdProvider,
       DexItemFactory factory) {
     super(
         inputApp,
@@ -414,7 +416,8 @@
         assertionsConfiguration,
         outputInspections,
         threadCount,
-        dumpInputFlags);
+        dumpInputFlags,
+        mapIdProvider);
     this.intermediate = intermediate;
     this.desugarGraphConsumer = desugarGraphConsumer;
     this.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 7cd95e5..fc11da4 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -100,6 +100,7 @@
       List<Consumer<Inspector>> outputInspections,
       int threadCount,
       DumpInputFlags dumpInputFlags,
+      MapIdProvider mapIdProvider,
       DexItemFactory factory) {
     super(
         inputApp,
@@ -115,7 +116,8 @@
         assertionsConfiguration,
         outputInspections,
         threadCount,
-        dumpInputFlags);
+        dumpInputFlags,
+        mapIdProvider);
     this.d8Command = d8Command;
     this.r8Command = r8Command;
     this.libraryConfiguration = libraryConfiguration;
@@ -409,6 +411,7 @@
           getOutputInspections(),
           getThreadCount(),
           getDumpInputFlags(),
+          getMapIdProvider(),
           factory);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/MapIdEnvironment.java b/src/main/java/com/android/tools/r8/MapIdEnvironment.java
new file mode 100644
index 0000000..f1e4260
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/MapIdEnvironment.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2021, 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;
+
+/** Environment made available when defining a custom map id for a build. */
+@Keep
+public interface MapIdEnvironment {
+
+  /** Get the computed hash for the mapping file content. */
+  String getMapHash();
+}
diff --git a/src/main/java/com/android/tools/r8/MapIdProvider.java b/src/main/java/com/android/tools/r8/MapIdProvider.java
new file mode 100644
index 0000000..9af775f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/MapIdProvider.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2021, 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;
+
+/**
+ * Interface for providing a custom map-id to the compiler.
+ *
+ * <p>The map-id is inserted in the mapping file output and included in the compiler marker
+ * information. The map-id can be used to provide an key/identifier to automate the lookup of
+ * mapping file information for builds. For example, by including it in the source-file part of
+ * program stacktraces. See {@code SourceFileProvider}.
+ */
+@Keep
+@FunctionalInterface
+public interface MapIdProvider {
+
+  /**
+   * Return the map-id content.
+   *
+   * @param environment An environment of values available for use when defining the map id.
+   * @return A non-null string that will be used as the map id.
+   */
+  String get(MapIdEnvironment environment);
+}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index fdf35d6..bb22da1 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -585,7 +585,8 @@
               synthesizedClassPrefix,
               skipDump,
               getThreadCount(),
-              getDumpInputFlags());
+              getDumpInputFlags(),
+              getMapIdProvider());
 
       return command;
     }
@@ -746,7 +747,8 @@
       String synthesizedClassPrefix,
       boolean skipDump,
       int threadCount,
-      DumpInputFlags dumpInputFlags) {
+      DumpInputFlags dumpInputFlags,
+      MapIdProvider mapIdProvider) {
     super(
         inputApp,
         mode,
@@ -761,7 +763,8 @@
         assertionsConfiguration,
         outputInspections,
         threadCount,
-        dumpInputFlags);
+        dumpInputFlags,
+        mapIdProvider);
     assert proguardConfiguration != null;
     assert mainDexKeepRules != null;
     this.mainDexKeepRules = mainDexKeepRules;
@@ -874,6 +877,8 @@
       internal.enableVerticalClassMerging = false;
     }
 
+    internal.mapIdProvider = getMapIdProvider();
+
     // Amend the proguard-map consumer with options from the proguard configuration.
     internal.proguardMapConsumer =
         wrapStringConsumer(
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
index 85e7b1a..362f5f5 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
@@ -4,6 +4,8 @@
 package com.android.tools.r8.naming;
 
 import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.MapIdEnvironment;
+import com.android.tools.r8.MapIdProvider;
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.Version;
 import com.android.tools.r8.errors.Unreachable;
@@ -33,17 +35,19 @@
 
   // Hash of the Proguard map (excluding the header up to and including the hash marker).
   public static class ProguardMapId {
+    private final String id;
     private final String hash;
 
-    private ProguardMapId(String id) {
+    private ProguardMapId(String id, String hash) {
       assert id != null;
-      this.hash = id;
+      assert hash != null;
+      this.id = id;
+      this.hash = hash;
     }
 
-    /** Truncated prefix of the content hash. */
-    // TODO(b/201269335): Update this to be a full "source-file marker".
+    /** Id for the map file (user defined or a truncated prefix of the content hash). */
     public String getId() {
-      return hash.substring(0, PG_MAP_ID_LENGTH);
+      return id;
     }
 
     /** The actual content hash. */
@@ -84,7 +88,7 @@
   private ProguardMapId computeProguardMapId() {
     ProguardMapIdBuilder builder = new ProguardMapIdBuilder();
     classNameMapper.write(builder);
-    return builder.build();
+    return builder.build(options.mapIdProvider);
   }
 
   private void writeBody() {
@@ -138,14 +142,31 @@
 
     private final Hasher hasher = Hashing.sha256().newHasher();
 
+    private MapIdProvider getProviderOrDefault(MapIdProvider provider) {
+      return provider != null
+          ? provider
+          : environment -> environment.getMapHash().substring(0, PG_MAP_ID_LENGTH);
+    }
+
+    private MapIdEnvironment getEnvironment(String hash) {
+      return new MapIdEnvironment() {
+        @Override
+        public String getMapHash() {
+          return hash;
+        }
+      };
+    }
+
     @Override
     public ProguardMapIdBuilder accept(String string) {
       hasher.putString(string, StandardCharsets.UTF_8);
       return this;
     }
 
-    public ProguardMapId build() {
-      return new ProguardMapId(hasher.hash().toString());
+    public ProguardMapId build(MapIdProvider mapIdProvider) {
+      String hash = hasher.hash().toString();
+      String id = getProviderOrDefault(mapIdProvider).get(getEnvironment(hash));
+      return new ProguardMapId(id, hash);
     }
   }
 
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 9449f1b..fcb0502 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DumpOptions;
 import com.android.tools.r8.FeatureSplit;
+import com.android.tools.r8.MapIdProvider;
 import com.android.tools.r8.ProgramConsumer;
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.Version;
@@ -866,6 +867,8 @@
 
   public Consumer<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer = null;
 
+  public MapIdProvider mapIdProvider = null;
+
   public static boolean assertionsEnabled() {
     boolean assertionsEnabled = false;
     assert assertionsEnabled = true; // Intentional side-effect.
diff --git a/src/test/java/com/android/tools/r8/MarkerMatcher.java b/src/test/java/com/android/tools/r8/MarkerMatcher.java
index d766770..22610d3 100644
--- a/src/test/java/com/android/tools/r8/MarkerMatcher.java
+++ b/src/test/java/com/android/tools/r8/MarkerMatcher.java
@@ -179,6 +179,21 @@
     };
   }
 
+  public static Matcher<Marker> markerPgMapId(Matcher<String> predicate) {
+    return new MarkerMatcher() {
+      @Override
+      protected boolean eval(Marker marker) {
+        return predicate.matches(marker.getPgMapId());
+      }
+
+      @Override
+      protected void explain(Description description) {
+        description.appendText("with pg_map_id matching ");
+        predicate.describeTo(description);
+      }
+    };
+  }
+
   public static Matcher<Marker> markerR8Mode(String r8Mode) {
     return new MarkerMatcher() {
       @Override
diff --git a/src/test/java/com/android/tools/r8/compilerapi/BinaryCompatibilityTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/BinaryCompatibilityTestCollection.java
index 51a307b..db9f09e 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/BinaryCompatibilityTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/BinaryCompatibilityTestCollection.java
@@ -64,6 +64,9 @@
   /** Additional classes that should always be included together to run tests. */
   public abstract List<Class<?>> getAdditionalClassesForTests();
 
+  /** Additional classes that should always be included together to run the pending tests. */
+  public abstract List<Class<?>> getPendingAdditionalClassesForTests();
+
   /** Additional JVM args supplied to any external execution. */
   public abstract List<String> getVmArgs();
 
@@ -92,7 +95,8 @@
 
   public void runJunitOnTestClass(Class<? extends T> test) throws Exception {
     List<Class<? extends T>> testClasses = Collections.singletonList(test);
-    runJunitOnTestClasses(generateJarForTestClasses(testClasses), testClasses);
+    runJunitOnTestClasses(
+        generateJarForTestClasses(testClasses, getPendingAdditionalClassesForTests()), testClasses);
   }
 
   private void runJunitOnTestClasses(Path testJar, Collection<Class<? extends T>> tests)
@@ -140,10 +144,12 @@
   }
 
   public Path generateJarForCheckedInTestClasses() throws Exception {
-    return generateJarForTestClasses(getCheckedInTestClasses());
+    return generateJarForTestClasses(getCheckedInTestClasses(), Collections.emptyList());
   }
 
-  private Path generateJarForTestClasses(Collection<Class<? extends T>> classes) throws Exception {
+  private Path generateJarForTestClasses(
+      Collection<Class<? extends T>> classes, List<Class<?>> additionalPendingClassesForTest)
+      throws Exception {
     Path jar = getTemp().newFolder().toPath().resolve("test.jar");
     ZipBuilder zipBuilder = ZipBuilder.builder(jar);
     for (Class<? extends T> test : classes) {
@@ -162,6 +168,11 @@
         getAdditionalClassesForTests().stream()
             .map(ToolHelper::getClassFileForTestClass)
             .collect(Collectors.toList()));
+    zipBuilder.addFilesRelative(
+        ToolHelper.getClassPathForTests(),
+        additionalPendingClassesForTest.stream()
+            .map(ToolHelper::getClassFileForTestClass)
+            .collect(Collectors.toList()));
     return zipBuilder.build();
   }
 
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java
index 654eafd..100567d 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java
@@ -5,8 +5,15 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.compilerapi.mockdata.MockClass;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.Collections;
 import java.util.List;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
@@ -20,6 +27,8 @@
 @RunWith(Parameterized.class)
 public abstract class CompilerApiTest {
 
+  public static final Object PARAMETERS = "none";
+
   public static final String API_TEST_MODE_KEY = "API_TEST_MODE";
   public static final String API_TEST_MODE_EXTERNAL = "external";
 
@@ -34,11 +43,13 @@
     if (runtimes != null && !runtimes.contains("none")) {
       return Collections.emptyList();
     }
-    return Collections.singletonList("none");
+    return Collections.singletonList(PARAMETERS);
   }
 
+  @Rule public final TemporaryFolder temp = new TemporaryFolder();
+
   public CompilerApiTest(Object none) {
-    assertEquals("none", none);
+    assertEquals(PARAMETERS, none);
   }
 
   /** Predicate to determine if the test is being run externally. */
@@ -50,4 +61,30 @@
   public boolean isRunningR8Lib() {
     return API_TEST_LIB_YES.equals(System.getProperty(API_TEST_LIB_KEY));
   }
+
+  public Path getNewTempFolder() throws IOException {
+    return temp.newFolder().toPath();
+  }
+
+  public Class<?> getMockClass() {
+    return MockClass.class;
+  }
+
+  public Path getJava8RuntimeJar() {
+    return Paths.get("third_party", "openjdk", "openjdk-rt-1.8", "rt.jar");
+  }
+
+  public List<String> getKeepMainRules(Class<?> clazz) {
+    return Collections.singletonList(
+        "-keep class " + clazz.getName() + " { public static void main(java.lang.String[]); }");
+  }
+
+  public Path getPathForClass(Class<?> clazz) {
+    String file = clazz.getName().replace('.', '/') + ".class";
+    return Paths.get("build", "classes", "java", "test", file);
+  }
+
+  public byte[] getBytesForClass(Class<?> clazz) throws IOException {
+    return Files.readAllBytes(getPathForClass(clazz));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
index 8c1e8a5..09f46ba 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -8,6 +8,8 @@
 import static com.android.tools.r8.ToolHelper.isTestingR8Lib;
 
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.compilerapi.mapid.CustomMapIdTest;
+import com.android.tools.r8.compilerapi.mockdata.MockClass;
 import com.android.tools.r8.compilerapi.testsetup.ApiTestingSetUpTest;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
@@ -26,9 +28,7 @@
       ImmutableList.of(ApiTestingSetUpTest.ApiTest.class);
 
   private static final List<Class<? extends CompilerApiTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
-      ImmutableList.of(
-          // No pending APIs.
-          );
+      ImmutableList.of(CustomMapIdTest.ApiTest.class);
 
   private final TemporaryFolder temp;
 
@@ -57,6 +57,11 @@
   }
 
   @Override
+  public List<Class<?>> getPendingAdditionalClassesForTests() {
+    return ImmutableList.of(MockClass.class);
+  }
+
+  @Override
   public Path getCheckedInTestJar() {
     return BINARY_COMPATIBILITY_JAR;
   }
diff --git a/src/test/java/com/android/tools/r8/compilerapi/mapid/CustomMapIdTest.java b/src/test/java/com/android/tools/r8/compilerapi/mapid/CustomMapIdTest.java
new file mode 100644
index 0000000..ca7f819
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/mapid/CustomMapIdTest.java
@@ -0,0 +1,136 @@
+// Copyright (c) 2021, 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.compilerapi.mapid;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.MapIdEnvironment;
+import com.android.tools.r8.MarkerMatcher;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.ThrowingBiConsumer;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.function.Function;
+import org.junit.Test;
+
+public class CustomMapIdTest extends CompilerApiTestRunner {
+
+  public CustomMapIdTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<? extends CompilerApiTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  @Test
+  public void testDefaultMapId() throws Exception {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    runTest(test::runDefaultMapId, hash -> hash.substring(0, 7));
+  }
+
+  @Test
+  public void testCustomMapId() throws Exception {
+    ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+    runTest(test::runCustomMapId, hash -> hash);
+  }
+
+  private String getMapHash(String mapping) {
+    String lineHeader = "# pg_map_hash: SHA-256 ";
+    int i = mapping.indexOf(lineHeader);
+    assertTrue(i >= 0);
+    int start = i + lineHeader.length();
+    int end = mapping.indexOf('\n', start);
+    return mapping.substring(start, end);
+  }
+
+  private void runTest(
+      ThrowingBiConsumer<ProgramConsumer, StringConsumer, Exception> test,
+      Function<String, String> hashToId)
+      throws Exception {
+    Path output = temp.newFolder().toPath().resolve("out.jar");
+    StringBuilder mappingBuilder = new StringBuilder();
+    BooleanBox didGetMappingContent = new BooleanBox(false);
+    test.accept(
+        new DexIndexedConsumer.ArchiveConsumer(output),
+        (mappingContent, handler) -> {
+          mappingBuilder.append(mappingContent);
+          didGetMappingContent.set(true);
+        });
+    assertTrue(didGetMappingContent.get());
+
+    // Extract the map hash from the file. This is always set by R8 to a SHA 256 hash.
+    String mappingContent = mappingBuilder.toString();
+    String mapHash = getMapHash(mappingContent);
+    assertEquals(64, mapHash.length());
+
+    // Check the map id is also defined in the map file.
+    String mapId = hashToId.apply(mapHash);
+    assertThat(mappingContent, containsString("pg_map_id: " + mapId + "\n"));
+
+    // Check that the map id is also present in the markers.
+    CodeInspector inspector = new CodeInspector(output);
+    Collection<Marker> markers = inspector.getMarkers();
+    MarkerMatcher.assertMarkersMatch(markers, MarkerMatcher.markerPgMapId(equalTo(mapId)));
+    assertEquals(1, markers.size());
+  }
+
+  public static class ApiTest extends CompilerApiTest {
+
+    public ApiTest(Object parameters) {
+      super(parameters);
+    }
+
+    public void runDefaultMapId(ProgramConsumer programConsumer, StringConsumer mappingConsumer)
+        throws Exception {
+      R8.run(
+          R8Command.builder()
+              .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+              .addProguardConfiguration(getKeepMainRules(getMockClass()), Origin.unknown())
+              .addLibraryFiles(getJava8RuntimeJar())
+              .setProgramConsumer(programConsumer)
+              .setProguardMapConsumer(mappingConsumer)
+              .build());
+    }
+
+    public void runCustomMapId(ProgramConsumer programConsumer, StringConsumer mappingConsumer)
+        throws Exception {
+      R8.run(
+          R8Command.builder()
+              .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+              .addProguardConfiguration(getKeepMainRules(getMockClass()), Origin.unknown())
+              .addLibraryFiles(getJava8RuntimeJar())
+              .setMapIdProvider(MapIdEnvironment::getMapHash)
+              .setProgramConsumer(programConsumer)
+              .setProguardMapConsumer(mappingConsumer)
+              .build());
+    }
+
+    @Test
+    public void testDefaultMapId() throws Exception {
+      runDefaultMapId(DexIndexedConsumer.emptyConsumer(), StringConsumer.emptyConsumer());
+    }
+
+    @Test
+    public void testCustomMapId() throws Exception {
+      runCustomMapId(DexIndexedConsumer.emptyConsumer(), StringConsumer.emptyConsumer());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/mockdata/MockClass.java b/src/test/java/com/android/tools/r8/compilerapi/mockdata/MockClass.java
new file mode 100644
index 0000000..340e532
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/mockdata/MockClass.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2021, 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.compilerapi.mockdata;
+
+// Class to use as data for the compilation.
+public class MockClass {
+
+  public static void main(String[] args) {
+    System.out.println("Hello world!");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
index 77b8aea..4deb785 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
@@ -80,6 +80,11 @@
   }
 
   @Override
+  public List<Class<?>> getPendingAdditionalClassesForTests() {
+    return ImmutableList.of();
+  }
+
+  @Override
   public List<String> getVmArgs() {
     return ImmutableList.of();
   }