blob: ab928126bde5fe2197cbf9da2c224143f2491697 [file] [log] [blame]
// Copyright (c) 2024, 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.d8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.ByteDataView;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.DexFilePerClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.ExtractMarker;
import com.android.tools.r8.ExtractMarkerCommand;
import com.android.tools.r8.MarkerInfo;
import com.android.tools.r8.MarkerInfoConsumer;
import com.android.tools.r8.MarkerInfoConsumerData;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.StringUtils;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class IntermediateModeMarkerTest extends TestBase {
static final String EXPECTED = StringUtils.lines("Hello, world");
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withDefaultDexRuntime().build();
}
private final TestParameters parameters;
public IntermediateModeMarkerTest(TestParameters parameters) {
this.parameters = parameters;
}
@Test
public void test() throws Exception {
Map<String, byte[]> intermediatesA = new HashMap<>();
Map<String, byte[]> intermediatesB = new HashMap<>();
Map<String, byte[]> intermediatesC = new HashMap<>();
testForD8(Backend.DEX)
.addProgramClasses(A.class)
.setMinApi(1)
.setProgramConsumer(
new DexFilePerClassFileConsumer.ForwardingConsumer(null) {
@Override
public synchronized void accept(
String primaryClassDescriptor,
ByteDataView data,
Set<String> descriptors,
DiagnosticsHandler handler) {
byte[] old = intermediatesA.put(primaryClassDescriptor, data.copyByteData());
assertNull(old);
}
@Override
public boolean combineSyntheticClassesWithPrimaryClass() {
return false;
}
})
.compile();
testForD8(Backend.DEX)
.addProgramClasses(B.class)
.setMinApi(2)
.setProgramConsumer(
new DexFilePerClassFileConsumer.ForwardingConsumer(null) {
@Override
public synchronized void accept(
String primaryClassDescriptor,
ByteDataView data,
Set<String> descriptors,
DiagnosticsHandler handler) {
byte[] old = intermediatesB.put(primaryClassDescriptor, data.copyByteData());
assertNull(old);
}
@Override
public boolean combineSyntheticClassesWithPrimaryClass() {
return true;
}
})
.compile();
testForD8(Backend.DEX)
.addProgramClasses(C.class)
.setMinApi(3)
.setIntermediate(true)
.setProgramConsumer(
new DexIndexedConsumer.ForwardingConsumer(null) {
@Override
public synchronized void accept(
int fileIndex,
ByteDataView data,
Set<String> descriptors,
DiagnosticsHandler handler) {
assertEquals(2, descriptors.size());
byte[] old = intermediatesC.put("indexed", data.copyByteData());
assertNull(old);
}
})
.compile();
// The per-class output has two outputs. Each has min-api 1.
assertEquals(2, intermediatesA.size());
assertMinApiMarkers(1, intermediatesA.values());
// The per-input-class-file output has one output with min-api 2.
assertEquals(1, intermediatesB.size());
assertMinApiMarkers(2, intermediatesB.values());
// The indexed output has one output with min-api 3.
assertEquals(1, intermediatesC.size());
assertMinApiMarkers(3, intermediatesC.values());
testForD8(Backend.DEX)
.addProgramDexFileData(intermediatesA.values())
.addProgramDexFileData(intermediatesB.values())
.addProgramDexFileData(intermediatesC.values())
.setMinApi(4)
.compile()
.inspect(
inspector -> {
// Final merge has exactly three markers, one for each min-api intermediate.
// The merge does not add a new marker.
Collection<Marker> markers = inspector.getMarkers();
assertEquals(3, markers.size());
assertTrue(markers.stream().anyMatch(m -> m.getMinApi() == 1));
assertTrue(markers.stream().anyMatch(m -> m.getMinApi() == 2));
assertTrue(markers.stream().anyMatch(m -> m.getMinApi() == 3));
assertTrue(markers.stream().noneMatch(m -> m.getMinApi() == 4));
})
.run(parameters.getRuntime(), A.class)
.assertSuccessWithOutput(EXPECTED);
}
private static void assertMinApiMarkers(int expectedMinApi, Collection<byte[]> dexPayloads)
throws CompilationFailedException {
for (byte[] data : dexPayloads) {
ExtractMarker.run(
ExtractMarkerCommand.builder()
.addDexProgramData(data, Origin.unknown())
.setMarkerInfoConsumer(
new MarkerInfoConsumer() {
@Override
public void acceptMarkerInfo(MarkerInfoConsumerData data) {
assertTrue(data.hasMarkers());
for (MarkerInfo marker : data.getMarkers()) {
assertEquals(expectedMinApi, marker.getMinApi());
}
}
@Override
public void finished() {}
})
.build());
}
}
public static class A {
public static void main(String[] args) {
B.foo(() -> "Hello");
}
}
public static class B {
public static void foo(Supplier<String> fn) {
C.foo(() -> fn.get() + ", ");
}
}
public static class C {
public static void foo(Supplier<String> fn) {
bar(() -> fn.get() + "world");
}
public static void bar(Supplier<String> fn) {
System.out.println(fn.get());
}
}
}