blob: 12ed82087dbce9ebc5a5da53a18cf10ae159e14a [file] [log] [blame]
// Copyright (c) 2020, 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.desugar.desugaredlibrary;
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.StringResource;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestDiagnosticMessages;
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.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AbortException;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.SemanticVersion;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.StringUtils.BraceType;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class DesugaredLibraryConfigurationParsingTest extends TestBase {
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withNoneRuntime().build();
}
public DesugaredLibraryConfigurationParsingTest(TestParameters parameters) {
parameters.assertNoneRuntime();
}
final AndroidApiLevel minApi = AndroidApiLevel.B;
final boolean libraryCompilation = true;
final DexItemFactory factory = new DexItemFactory();
final Origin origin =
new Origin(Origin.root()) {
@Override
public String part() {
return "Test Origin";
}
};
final Map<String, Object> TEMPLATE =
ImmutableMap.<String, Object>builder()
.put(
"configuration_format_version",
DesugaredLibraryConfigurationParser.MAX_SUPPORTED_VERSION)
.put("group_id", "com.tools.android")
.put("artifact_id", "desugar_jdk_libs")
.put("version", DesugaredLibraryConfigurationParser.MIN_SUPPORTED_VERSION.toString())
.put("required_compilation_api_level", 1)
.put("synthesized_library_classes_package_prefix", "j$.")
.put("common_flags", Collections.emptyList())
.put("program_flags", Collections.emptyList())
.put("library_flags", Collections.emptyList())
.build();
private LinkedHashMap<String, Object> template() {
return new LinkedHashMap<>(TEMPLATE);
}
private DesugaredLibraryConfigurationParser parser(DiagnosticsHandler handler) {
return new DesugaredLibraryConfigurationParser(
factory, new Reporter(handler), libraryCompilation, minApi.getLevel());
}
private DesugaredLibraryConfiguration runPassing(String resource) {
return runPassing(StringResource.fromString(resource, origin));
}
private DesugaredLibraryConfiguration runPassing(StringResource resource) {
TestDiagnosticMessagesImpl handler = new TestDiagnosticMessagesImpl();
DesugaredLibraryConfiguration config = parser(handler).parse(resource);
handler.assertNoMessages();
return config;
}
private void runFailing(String json, Consumer<TestDiagnosticMessages> checker) {
TestDiagnosticMessagesImpl handler = new TestDiagnosticMessagesImpl();
try {
parser(handler).parse(StringResource.fromString(json, origin));
fail("Expected failure");
} catch (AbortException e) {
checker.accept(handler);
}
}
@Test
public void testReference() throws Exception {
// Just test that the reference file parses without issues.
DesugaredLibraryConfiguration config =
runPassing(StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
assertEquals(libraryCompilation, config.isLibraryCompilation());
}
@Test
public void testEmpty() {
runFailing(
"",
diagnostics -> {
diagnostics.assertErrorsMatch(
allOf(
diagnosticMessage(containsString("Not a JSON Object")),
diagnosticOrigin(origin)));
});
}
@Test
public void testRequiredKeys() {
ImmutableList<String> requiredKeys =
ImmutableList.of(
"configuration_format_version",
"group_id",
"artifact_id",
"version",
"required_compilation_api_level",
"synthesized_library_classes_package_prefix",
"common_flags",
"program_flags",
"library_flags");
for (String key : requiredKeys) {
Map<String, Object> data = template();
data.remove(key);
runFailing(
toJson(data),
diagnostics ->
diagnostics.assertErrorsMatch(
allOf(
diagnosticMessage(containsString("Invalid desugared library configuration")),
diagnosticMessage(containsString("Expected required key '" + key + "'")),
diagnosticOrigin(origin))));
}
}
@Test
public void testUnsupportedVersion() {
LinkedHashMap<String, Object> data = template();
SemanticVersion minVersion = DesugaredLibraryConfigurationParser.MIN_SUPPORTED_VERSION;
data.put(
"version",
new SemanticVersion(minVersion.getMajor(), minVersion.getMinor(), minVersion.getPatch() - 1)
.toString());
runFailing(
toJson(data),
diagnostics ->
diagnostics.assertErrorsMatch(
allOf(
diagnosticMessage(containsString("upgrade the desugared library")),
diagnosticOrigin(origin))));
}
@Test
public void testUnsupportedAbove() {
LinkedHashMap<String, Object> data = template();
data.put("configuration_format_version", 100000);
runFailing(
toJson(data),
diagnostics ->
diagnostics.assertErrorsMatch(
allOf(
diagnosticMessage(containsString("upgrade the D8/R8 compiler")),
diagnosticOrigin(origin))));
}
@Test
public void testCustomAndWrapperOverlap() {
LinkedHashMap<String, Object> data = template();
data.put(
"common_flags",
ImmutableList.of(
ImmutableMap.of(
"api_level_below_or_equal",
100000,
"custom_conversion",
ImmutableMap.of("java.util.Foo", "j$.util.FooConv"),
"wrapper_conversion",
ImmutableList.of("java.util.Foo"))));
runFailing(
toJson(data),
diagnostics ->
diagnostics.assertErrorsMatch(
allOf(
diagnosticMessage(containsString("Duplicate types")),
diagnosticMessage(containsString("java.util.Foo")),
diagnosticOrigin(origin))));
}
@Test
public void testRedefinition() {
LinkedHashMap<String, Object> data = template();
data.put(
"common_flags",
ImmutableList.of(
ImmutableMap.of(
"api_level_below_or_equal",
100000,
"custom_conversion",
ImmutableMap.of("java.util.Foo", "j$.util.FooConv1"))));
data.put(
"library_flags",
ImmutableList.of(
ImmutableMap.of(
"api_level_below_or_equal",
100000,
"custom_conversion",
ImmutableMap.of("java.util.Foo", "j$.util.FooConv2"))));
runFailing(
toJson(data),
diagnostics ->
diagnostics.assertErrorsMatch(
allOf(
diagnosticMessage(containsString("Duplicate assignment of key")),
diagnosticMessage(containsString("java.util.Foo")),
diagnosticMessage(containsString("custom_conversion")),
diagnosticOrigin(origin))));
}
@Test
public void testDuplicate() {
LinkedHashMap<String, Object> data = template();
data.put(
"common_flags",
ImmutableList.of(
ImmutableMap.of(
"api_level_below_or_equal",
100000,
"custom_conversion",
ImmutableMap.of(
"java.util.Foo", "j$.util.FooConv1",
"java.util.Foo2", "j$.util.FooConv2"))));
// The gson parser will overwrite the key in order during parsing, thus hiding potential issues.
DesugaredLibraryConfiguration config = runPassing(toJson(data).replace("Foo2", "Foo"));
assertEquals(
Collections.singletonList("java.util.Foo"),
config.getCustomConversions().keySet().stream()
.map(DexType::toString)
.collect(Collectors.toList()));
assertEquals(
Collections.singletonList("j$.util.FooConv2"),
config.getCustomConversions().values().stream()
.map(DexType::toString)
.collect(Collectors.toList()));
}
// JSON building helpers.
// This does not use gson to make the text input construction independent of gson.
private static String toJson(Map<String, Object> data) {
StringBuilder builder = new StringBuilder();
toJsonObject(data, builder);
return builder.toString();
}
private static void toJsonObject(Map<String, Object> data, StringBuilder builder) {
StringUtils.append(
builder,
ListUtils.map(
data.entrySet(),
entry -> "\n " + quote(entry.getKey()) + ": " + toJsonElement(entry.getValue())),
", ",
BraceType.TUBORG);
}
private static String toJsonElement(Object element) {
StringBuilder builder = new StringBuilder();
toJsonElement(element, builder);
return builder.toString();
}
private static void toJsonElement(Object element, StringBuilder builder) {
if (element instanceof String) {
builder.append(quote((String) element));
} else if (element instanceof Integer) {
builder.append(element);
} else if (element instanceof List) {
@SuppressWarnings("unchecked")
List<Object> elements = (List<Object>) element;
toJsonList(elements, builder);
} else if (element instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> elements = (Map<String, Object>) element;
toJsonObject(elements, builder);
} else {
throw new IllegalStateException("Unexpected object type: " + element.getClass());
}
}
private static void toJsonList(List<Object> element, StringBuilder builder) {
StringUtils.append(
builder, ListUtils.map(element, o -> "\n " + toJsonElement(o)), ", ", BraceType.SQUARE);
}
private static String quote(String str) {
return "\"" + str + "\"";
}
}