[Partition] Add round-trip test for joining a partition

Bug: b/274893426
Bug: b/274735214
Change-Id: I05de70045f5721ce5d7cf917ede1479c1124f34c
diff --git a/src/main/java/com/android/tools/r8/retrace/PartitionedToProguardMappingConverter.java b/src/main/java/com/android/tools/r8/retrace/PartitionedToProguardMappingConverter.java
new file mode 100644
index 0000000..b6181dc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/PartitionedToProguardMappingConverter.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2023, 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.retrace;
+
+import static com.google.common.base.Predicates.alwaysTrue;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.LineReader;
+import com.android.tools.r8.naming.MapVersion;
+import com.android.tools.r8.retrace.internal.MappingPartitionMetadataInternal;
+import com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.ProguardMapReaderWithFilteringInputBuffer;
+import com.android.tools.r8.retrace.internal.RetracePartitionException;
+import com.android.tools.r8.utils.ChainableStringConsumer;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+public class PartitionedToProguardMappingConverter {
+
+  private final StringConsumer consumer;
+  private final MappingPartitionFromKeySupplier partitionSupplier;
+  private final byte[] metadata;
+  private final DiagnosticsHandler diagnosticsHandler;
+
+  private PartitionedToProguardMappingConverter(
+      StringConsumer consumer,
+      MappingPartitionFromKeySupplier partitionSupplier,
+      byte[] metadata,
+      DiagnosticsHandler diagnosticsHandler) {
+    this.consumer = consumer;
+    this.partitionSupplier = partitionSupplier;
+    this.metadata = metadata;
+    this.diagnosticsHandler = diagnosticsHandler;
+  }
+
+  public void run() throws RetracePartitionException {
+    MappingPartitionMetadataInternal metadataInternal =
+        MappingPartitionMetadataInternal.deserialize(
+            metadata, MapVersion.MAP_VERSION_UNKNOWN, diagnosticsHandler);
+    if (!metadataInternal.canGetPartitionKeys()) {
+      throw new RetracePartitionException("Cannot obtain all partition keys from metadata");
+    }
+    // TODO(b/274893426): Account for preamble.
+    ClassNameMapper classNameMapper = ClassNameMapper.builder().build();
+    for (String partitionKey : metadataInternal.getPartitionKeys()) {
+      LineReader reader =
+          new ProguardMapReaderWithFilteringInputBuffer(
+              new ByteArrayInputStream(partitionSupplier.get(partitionKey)), alwaysTrue(), true);
+      try {
+        classNameMapper =
+            ClassNameMapper.mapperFromLineReaderWithFiltering(
+                    reader, metadataInternal.getMapVersion(), diagnosticsHandler, true, true)
+                .combine(classNameMapper);
+      } catch (IOException e) {
+        throw new RetracePartitionException(e);
+      }
+    }
+    classNameMapper.sorted().write(new ProguardMapWriter(consumer, diagnosticsHandler));
+  }
+
+  private static class ProguardMapWriter implements ChainableStringConsumer {
+
+    private final StringConsumer consumer;
+    private final DiagnosticsHandler diagnosticsHandler;
+
+    private ProguardMapWriter(StringConsumer consumer, DiagnosticsHandler diagnosticsHandler) {
+      this.consumer = consumer;
+      this.diagnosticsHandler = diagnosticsHandler;
+    }
+
+    @Override
+    public ProguardMapWriter accept(String string) {
+      consumer.accept(string, diagnosticsHandler);
+      return this;
+    }
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+
+    private StringConsumer consumer;
+    private MappingPartitionFromKeySupplier partitionSupplier;
+    private byte[] metadata;
+    private DiagnosticsHandler diagnosticsHandler;
+
+    public Builder setConsumer(StringConsumer consumer) {
+      this.consumer = consumer;
+      return this;
+    }
+
+    public Builder setPartitionSupplier(MappingPartitionFromKeySupplier partitionSupplier) {
+      this.partitionSupplier = partitionSupplier;
+      return this;
+    }
+
+    public Builder setMetadata(byte[] metadata) {
+      this.metadata = metadata;
+      return this;
+    }
+
+    public Builder setDiagnosticsHandler(DiagnosticsHandler diagnosticsHandler) {
+      this.diagnosticsHandler = diagnosticsHandler;
+      return this;
+    }
+
+    public PartitionedToProguardMappingConverter build() {
+      return new PartitionedToProguardMappingConverter(
+          consumer, partitionSupplier, metadata, diagnosticsHandler);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java
index 8c8ee27..0cee013 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java
@@ -17,6 +17,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.DataOutputStream;
 import java.io.IOException;
+import java.util.Collection;
 
 public interface MappingPartitionMetadataInternal extends MappingPartitionMetadata {
 
@@ -24,12 +25,11 @@
 
   MapVersion getMapVersion();
 
-  default boolean isObfuscatedTypeNameAsKeyMetadataWithPartitionNames() {
+  default boolean canGetPartitionKeys() {
     return false;
   }
 
-  default ObfuscatedTypeNameAsKeyMetadataWithPartitionNames
-      asObfuscatedTypeNameAsKeyMetadataWithPartitionNames() {
+  default Collection<String> getPartitionKeys() {
     return null;
   }
 
@@ -150,14 +150,13 @@
     }
 
     @Override
-    public boolean isObfuscatedTypeNameAsKeyMetadataWithPartitionNames() {
+    public boolean canGetPartitionKeys() {
       return true;
     }
 
     @Override
-    public ObfuscatedTypeNameAsKeyMetadataWithPartitionNames
-        asObfuscatedTypeNameAsKeyMetadataWithPartitionNames() {
-      return this;
+    public Collection<String> getPartitionKeys() {
+      return metadataPartitionCollection.getPartitionKeys();
     }
 
     // The format is:
@@ -189,9 +188,5 @@
           mapVersion,
           new LazyMetadataPartitionCollection(bytes, partitionCollectionStartIndex, bytes.length));
     }
-
-    public MetadataPartitionCollection getMetadataPartitionCollection() {
-      return metadataPartitionCollection;
-    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java
index 68adc36..06ede47 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java
@@ -132,11 +132,16 @@
             (classNameMapper, classNamingForNameMapper, payload) -> {
               Set<String> seenMappings = new HashSet<>();
               StringBuilder payloadWithClassReferences = new StringBuilder();
+              Map<String, String> lookupMap =
+                  classNameMapper.getObfuscatedToOriginalMapping().inverse;
               classNamingForNameMapper.visitAllFullyQualifiedReferences(
                   holder -> {
                     if (seenMappings.add(holder)) {
                       payloadWithClassReferences.append(
-                          getSourceFileMapping(holder, classNameMapper.getSourceFile(holder)));
+                          getSourceFileMapping(
+                              holder,
+                              lookupMap.get(holder),
+                              classNameMapper.getSourceFile(holder)));
                     }
                   });
               payloadWithClassReferences.append(payload);
@@ -165,13 +170,14 @@
     }
   }
 
-  private String getSourceFileMapping(String className, String sourceFile) {
+  private String getSourceFileMapping(
+      String className, String obfuscatedClassName, String sourceFile) {
     if (sourceFile == null) {
       return "";
     }
     return className
         + " -> "
-        + className
+        + (obfuscatedClassName == null ? className : obfuscatedClassName)
         + ":"
         + "\n  # "
         + FileNameInformation.build(sourceFile).serialize()
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracePartitionException.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracePartitionException.java
index dd183f2..7e6e924 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracePartitionException.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracePartitionException.java
@@ -12,4 +12,8 @@
   public RetracePartitionException(String message) {
     super(message);
   }
+
+  public RetracePartitionException(Exception e) {
+    super(e);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 45322ff..74d850c 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -2026,4 +2026,15 @@
     }
     return byteRead1 == byteRead2;
   }
+
+  protected void assertListsAreEqual(List<String> expected, List<String> actual) {
+    int minLines = Math.min(expected.size(), actual.size());
+    for (int i = 0; i < minLines; i++) {
+      assertEquals(expected.get(i), actual.get(i));
+    }
+    if (expected.size() != actual.size()) {
+      assertEquals(
+          "", expected.size() > actual.size() ? expected.get(minLines) : actual.get(minLines));
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionAndJoinIdentityTest.java b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionAndJoinIdentityTest.java
new file mode 100644
index 0000000..1bd8f56
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionAndJoinIdentityTest.java
@@ -0,0 +1,88 @@
+// Copyright (c) 2023, 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.retrace.partition;
+
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.retrace.MappingPartitionMetadata;
+import com.android.tools.r8.retrace.PartitionedToProguardMappingConverter;
+import com.android.tools.r8.retrace.ProguardMapPartitioner;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RetracePartitionAndJoinIdentityTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public RetracePartitionAndJoinIdentityTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  @Test
+  public void testPartitionAndJoin() throws Exception {
+    Path mappingFile =
+        ToolHelper.RETRACE_MAPS_DIR.resolve(
+            "ad5c3e88ef2bae5ef324eb225fbc57345cd57863-r8lib.jar.map");
+    ProguardMapProducer proguardMapProducer = ProguardMapProducer.fromPath(mappingFile);
+    TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
+    Map<String, byte[]> partitions = new HashMap<>();
+    MappingPartitionMetadata metadataData =
+        ProguardMapPartitioner.builder(diagnosticsHandler)
+            .setProguardMapProducer(proguardMapProducer)
+            .setPartitionConsumer(
+                partition -> partitions.put(partition.getKey(), partition.getPayload()))
+            .build()
+            .run();
+    assertNotNull(metadataData);
+    diagnosticsHandler.assertNoMessages();
+
+    StringBuilder builder = new StringBuilder();
+    PartitionedToProguardMappingConverter.builder()
+        .setMetadata(metadataData.getBytes())
+        .setDiagnosticsHandler(diagnosticsHandler)
+        .setConsumer((string, handler) -> builder.append(string))
+        .setPartitionSupplier(partitions::get)
+        .build()
+        .run();
+    List<String> joinedMapLines = StringUtils.splitLines(builder.toString());
+    // TODO(b/274735214): Partitioning does not capture the preamble of the mapping file yet, so we
+    //  discard it before checking equality.
+    List<String> lines = Files.readAllLines(mappingFile);
+    List<String> filteredLines = lines.subList(computeFirstLine(lines), lines.size());
+    assertListsAreEqual(filteredLines, joinedMapLines);
+  }
+
+  private int computeFirstLine(List<String> lines) {
+    int firstLine = 0;
+    for (int i = 0; i < lines.size(); i++) {
+      String currentLine = lines.get(i).trim();
+      if (!currentLine.startsWith("#")
+          && currentLine.contains(" -> ")
+          && currentLine.endsWith(":")) {
+        firstLine = i;
+        break;
+      }
+    }
+    return firstLine;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionMetadataPartitionNamesTest.java b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionMetadataPartitionNamesTest.java
index 8d40215..6db000b 100644
--- a/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionMetadataPartitionNamesTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionMetadataPartitionNamesTest.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.retrace.ProguardMapProducer;
 import com.android.tools.r8.retrace.internal.MappingPartitionKeyStrategy;
 import com.android.tools.r8.retrace.internal.MappingPartitionMetadataInternal;
-import com.android.tools.r8.retrace.internal.MappingPartitionMetadataInternal.ObfuscatedTypeNameAsKeyMetadataWithPartitionNames;
 import com.android.tools.r8.retrace.internal.ProguardMapPartitionerOnClassNameToText.ProguardMapPartitionerBuilderImplInternal;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.ArrayList;
@@ -84,11 +83,7 @@
     MappingPartitionMetadataInternal mappingPartitionMetadata =
         MappingPartitionMetadataInternal.deserialize(
             bytes, MapVersion.MAP_VERSION_NONE, diagnosticsHandler);
-    assertTrue(mappingPartitionMetadata.isObfuscatedTypeNameAsKeyMetadataWithPartitionNames());
-    ObfuscatedTypeNameAsKeyMetadataWithPartitionNames obfuscatedTypeNameMetadata =
-        mappingPartitionMetadata.asObfuscatedTypeNameAsKeyMetadataWithPartitionNames();
-    assertEquals(
-        expectedPartitionKeys,
-        obfuscatedTypeNameMetadata.getMetadataPartitionCollection().getPartitionKeys());
+    assertTrue(mappingPartitionMetadata.canGetPartitionKeys());
+    assertEquals(expectedPartitionKeys, mappingPartitionMetadata.getPartitionKeys());
   }
 }
diff --git a/third_party/r8mappings.tar.gz.sha1 b/third_party/r8mappings.tar.gz.sha1
index a00ac52..424ff92 100644
--- a/third_party/r8mappings.tar.gz.sha1
+++ b/third_party/r8mappings.tar.gz.sha1
@@ -1 +1 @@
-baaf8e15c9f54677c30caf070a22319ccd46539c
\ No newline at end of file
+10daa67ab31114fd4649c996e1ac82c63a5b5e4e
\ No newline at end of file