[Partition] Test multiple partitions containing same class mapping
Change-Id: I39f24e19ffb2fcd0b2ca5e35f98ff90ea5438697
diff --git a/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionMultipleOutlineClassTest.java b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionMultipleOutlineClassTest.java
new file mode 100644
index 0000000..9175f41
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionMultipleOutlineClassTest.java
@@ -0,0 +1,149 @@
+// 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.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.MappingPartitionMetadata;
+import com.android.tools.r8.retrace.PartitionMappingSupplier;
+import com.android.tools.r8.retrace.ProguardMapPartitioner;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.RetraceFrameElement;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.Retracer;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.OptionalInt;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * This tests that we can join multiple class mappings with the same name if they are created by the
+ * partitioning scheme. To ensure proper retracing of filenames, the partitioner will ensure that a
+ * partition having inline frames to another class will contain a class-mapping with source-file
+ * mapping. Potentially the inlined class references will still have a mapping.
+ */
+@RunWith(Parameterized.class)
+public class RetracePartitionMultipleOutlineClassTest extends TestBase {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public RetracePartitionMultipleOutlineClassTest(TestParameters parameters) {
+ parameters.assertNoneRuntime();
+ }
+
+ private final ClassReference inlineOriginal = Reference.classFromTypeName("inlinee.Class");
+ private final ClassReference inlineRenamed = Reference.classFromTypeName("a");
+ private final ClassReference callerSomeClassOriginal = Reference.classFromTypeName("some.Class");
+ private final ClassReference callerSomeClassRenamed = Reference.classFromTypeName("b");
+ private final ClassReference callerOtherClassOriginal =
+ Reference.classFromTypeName("other.Class");
+ private final ClassReference callerOtherClassRenamed = Reference.classFromTypeName("c");
+ private final String mappingInline =
+ StringUtils.unixLines(
+ "# { id: 'com.android.tools.r8.mapping', version: '2.0' }",
+ inlineOriginal.getTypeName() + " -> " + inlineRenamed.getTypeName() + ":",
+ " # {'id':'sourceFile','fileName':'InlineeClass.kt'}",
+ " void foo() -> a");
+ private final String mappingSomeClass =
+ StringUtils.unixLines(
+ callerSomeClassOriginal.getTypeName()
+ + " -> "
+ + callerSomeClassRenamed.getTypeName()
+ + ":",
+ " # {'id':'sourceFile','fileName':'CallerClass.kt'}",
+ " 1:1:void inlinee.Class.bar():42:42 -> a",
+ " 1:1:void foo():43 -> a");
+ private final String mappingOtherClass =
+ StringUtils.unixLines(
+ callerOtherClassOriginal.getTypeName()
+ + " -> "
+ + callerOtherClassRenamed.getTypeName()
+ + ":",
+ " # {'id':'sourceFile','fileName':'OtherClass.kt'}",
+ " 1:1:void inlinee.Class.bar():42:42 -> a",
+ " 1:1:void foo():43 -> a");
+
+ private int prepareCounter = 0;
+
+ @Test
+ public void test() throws IOException {
+ ProguardMapProducer proguardMapProducer =
+ ProguardMapProducer.fromString(mappingInline + mappingSomeClass + mappingOtherClass);
+ DiagnosticsHandler diagnosticsHandler = new DiagnosticsHandler() {};
+ 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);
+ assertEquals(3, partitions.size());
+
+ Set<String> preFetchedKeys = new LinkedHashSet<>();
+ PartitionMappingSupplier mappingSupplier =
+ PartitionMappingSupplier.builder()
+ .setMetadata(metadataData.getBytes())
+ .setRegisterMappingPartitionCallback(preFetchedKeys::add)
+ .setPrepareMappingPartitionsCallback(() -> prepareCounter++)
+ .setMappingPartitionFromKeySupplier(
+ key -> {
+ assertTrue(preFetchedKeys.contains(key));
+ assertTrue(partitions.containsKey(key));
+ return partitions.get(key);
+ })
+ .build();
+ assertEquals(0, prepareCounter);
+ // Load all classes to ensure we build a mapping containing all.
+ mappingSupplier.registerClassUse(diagnosticsHandler, callerSomeClassRenamed);
+ mappingSupplier.registerClassUse(diagnosticsHandler, callerOtherClassRenamed);
+ mappingSupplier.registerClassUse(diagnosticsHandler, inlineRenamed);
+ // Add a redundant call to an existing mapping to ensure that will not incorrectly load the
+ // data twice.
+ mappingSupplier.registerClassUse(diagnosticsHandler, inlineRenamed);
+ assertEquals(3, preFetchedKeys.size());
+ Retracer retracer = mappingSupplier.createRetracer(diagnosticsHandler);
+ List<RetraceFrameElement> callerRetraced =
+ retracer
+ .retraceFrame(
+ RetraceStackTraceContext.empty(),
+ OptionalInt.of(1),
+ Reference.methodFromDescriptor(callerSomeClassRenamed, "a", "()V"))
+ .stream()
+ .collect(Collectors.toList());
+ // The retrace result should not be ambiguous or empty.
+ assertEquals(1, callerRetraced.size());
+ RetraceFrameElement retraceFrameElement = callerRetraced.get(0);
+
+ // Check that visiting all frames report all source files.
+ List<String> allSourceFiles =
+ retraceFrameElement.stream()
+ .map(x -> x.getSourceFile().getOrInferSourceFile())
+ .collect(Collectors.toList());
+ assertEquals(Arrays.asList("InlineeClass.kt", "CallerClass.kt"), allSourceFiles);
+ }
+}