blob: 92fc7be48542375b3653461c41bd1624c9087ff7 [file] [log] [blame]
Ian Zerny65914052020-06-19 13:26:29 +02001// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
2// for details. All rights reserved. Use of this source code is governed by a
3// BSD-style license that can be found in the LICENSE file.
4package com.android.tools.r8.desugar.desugaredlibrary;
5
6import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
7import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
8import static org.hamcrest.CoreMatchers.allOf;
9import static org.hamcrest.CoreMatchers.containsString;
10import static org.junit.Assert.assertEquals;
11import static org.junit.Assert.fail;
12
13import com.android.tools.r8.DiagnosticsHandler;
14import com.android.tools.r8.StringResource;
15import com.android.tools.r8.TestBase;
16import com.android.tools.r8.TestDiagnosticMessages;
17import com.android.tools.r8.TestDiagnosticMessagesImpl;
18import com.android.tools.r8.TestParameters;
19import com.android.tools.r8.TestParametersCollection;
20import com.android.tools.r8.TestRuntime.NoneRuntime;
21import com.android.tools.r8.ToolHelper;
22import com.android.tools.r8.graph.DexItemFactory;
23import com.android.tools.r8.graph.DexType;
24import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
25import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
26import com.android.tools.r8.origin.Origin;
27import com.android.tools.r8.utils.AbortException;
28import com.android.tools.r8.utils.AndroidApiLevel;
29import com.android.tools.r8.utils.ListUtils;
30import com.android.tools.r8.utils.Reporter;
31import com.android.tools.r8.utils.StringUtils;
32import com.android.tools.r8.utils.StringUtils.BraceType;
33import com.google.common.collect.ImmutableList;
34import com.google.common.collect.ImmutableMap;
35import java.util.Collections;
36import java.util.LinkedHashMap;
37import java.util.List;
38import java.util.Map;
39import java.util.function.Consumer;
40import java.util.stream.Collectors;
41import org.junit.Test;
42import org.junit.runner.RunWith;
43import org.junit.runners.Parameterized;
44
45@RunWith(Parameterized.class)
46public class DesugaredLibraryConfigurationParsingTest extends TestBase {
47
48 @Parameterized.Parameters(name = "{0}")
49 public static TestParametersCollection data() {
50 return getTestParameters().withNoneRuntime().build();
51 }
52
53 public DesugaredLibraryConfigurationParsingTest(TestParameters parameters) {
54 assertEquals(NoneRuntime.getInstance(), parameters.getRuntime());
55 }
56
57 final AndroidApiLevel minApi = AndroidApiLevel.B;
58 final boolean libraryCompilation = true;
59
60 final DexItemFactory factory = new DexItemFactory();
61 final Origin origin =
62 new Origin(Origin.root()) {
63 @Override
64 public String part() {
65 return "Test Origin";
66 }
67 };
68
69 final Map<String, Object> TEMPLATE =
70 ImmutableMap.<String, Object>builder()
71 .put(
72 "configuration_format_version",
73 DesugaredLibraryConfigurationParser.MAX_SUPPORTED_VERSION)
74 .put("group_id", "com.tools.android")
75 .put("artifact_id", "desugar_jdk_libs")
76 .put("version", "0.0.0")
77 .put("required_compilation_api_level", 1)
78 .put("synthesized_library_classes_package_prefix", "j$.")
79 .put("common_flags", Collections.emptyList())
80 .put("program_flags", Collections.emptyList())
81 .put("library_flags", Collections.emptyList())
82 .build();
83
84 private LinkedHashMap<String, Object> template() {
85 return new LinkedHashMap<>(TEMPLATE);
86 }
87
88 private DesugaredLibraryConfigurationParser parser(DiagnosticsHandler handler) {
89 return new DesugaredLibraryConfigurationParser(
90 factory, new Reporter(handler), libraryCompilation, minApi.getLevel());
91 }
92
93 private DesugaredLibraryConfiguration runPassing(String resource) {
94 return runPassing(StringResource.fromString(resource, origin));
95 }
96
97 private DesugaredLibraryConfiguration runPassing(StringResource resource) {
98 TestDiagnosticMessagesImpl handler = new TestDiagnosticMessagesImpl();
99 DesugaredLibraryConfiguration config = parser(handler).parse(resource);
100 handler.assertNoMessages();
101 return config;
102 }
103
104 private void runFailing(String json, Consumer<TestDiagnosticMessages> checker) {
105 TestDiagnosticMessagesImpl handler = new TestDiagnosticMessagesImpl();
106 try {
107 parser(handler).parse(StringResource.fromString(json, origin));
108 fail("Expected failure");
109 } catch (AbortException e) {
110 checker.accept(handler);
111 }
112 }
113
114 @Test
115 public void testReference() throws Exception {
116 // Just test that the reference file parses without issues.
117 DesugaredLibraryConfiguration config =
118 runPassing(StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
119 assertEquals(libraryCompilation, config.isLibraryCompilation());
120 }
121
122 @Test
123 public void testEmpty() {
124 runFailing(
125 "",
126 diagnostics -> {
127 diagnostics.assertErrorsMatch(
128 allOf(
129 diagnosticMessage(containsString("Not a JSON Object")),
130 diagnosticOrigin(origin)));
131 });
132 }
133
134 @Test
135 public void testRequiredKeys() {
136 ImmutableList<String> requiredKeys =
137 ImmutableList.of(
138 "configuration_format_version",
139 "group_id",
140 "artifact_id",
141 "version",
142 "required_compilation_api_level",
143 "synthesized_library_classes_package_prefix",
Ian Zerny65914052020-06-19 13:26:29 +0200144 "program_flags",
145 "library_flags");
146 for (String key : requiredKeys) {
147 Map<String, Object> data = template();
148 data.remove(key);
149 runFailing(
150 toJson(data),
151 diagnostics ->
152 diagnostics.assertErrorsMatch(
153 allOf(
154 diagnosticMessage(containsString("Invalid desugared library configuration")),
155 diagnosticMessage(containsString("Expected required key '" + key + "'")),
156 diagnosticOrigin(origin))));
157 }
158 }
159
160 @Test
Ian Zernye0fd0242020-06-23 13:46:14 +0200161 public void testUnsupportedFormatMissingFlags() {
Ian Zerny65914052020-06-19 13:26:29 +0200162 LinkedHashMap<String, Object> data = template();
Ian Zernye0fd0242020-06-23 13:46:14 +0200163 data.remove("common_flags");
Ian Zerny65914052020-06-19 13:26:29 +0200164 runFailing(
165 toJson(data),
166 diagnostics ->
167 diagnostics.assertErrorsMatch(
168 allOf(
Ian Zerny4be71572020-06-19 21:43:38 +0200169 diagnosticMessage(containsString("upgrade the desugared library")),
Ian Zerny65914052020-06-19 13:26:29 +0200170 diagnosticOrigin(origin))));
171 }
172
173 @Test
174 public void testUnsupportedAbove() {
175 LinkedHashMap<String, Object> data = template();
176 data.put("configuration_format_version", 100000);
177 runFailing(
178 toJson(data),
179 diagnostics ->
180 diagnostics.assertErrorsMatch(
181 allOf(
182 diagnosticMessage(containsString("upgrade the D8/R8 compiler")),
183 diagnosticOrigin(origin))));
184 }
185
186 @Test
187 public void testCustomAndWrapperOverlap() {
188 LinkedHashMap<String, Object> data = template();
189 data.put(
190 "common_flags",
191 ImmutableList.of(
192 ImmutableMap.of(
193 "api_level_below_or_equal",
194 100000,
195 "custom_conversion",
196 ImmutableMap.of("java.util.Foo", "j$.util.FooConv"),
197 "wrapper_conversion",
198 ImmutableList.of("java.util.Foo"))));
199 runFailing(
200 toJson(data),
201 diagnostics ->
202 diagnostics.assertErrorsMatch(
203 allOf(
204 diagnosticMessage(containsString("Duplicate types")),
205 diagnosticMessage(containsString("java.util.Foo")),
206 diagnosticOrigin(origin))));
207 }
208
209 @Test
210 public void testRedefinition() {
211 LinkedHashMap<String, Object> data = template();
212 data.put(
213 "common_flags",
214 ImmutableList.of(
215 ImmutableMap.of(
216 "api_level_below_or_equal",
217 100000,
218 "custom_conversion",
219 ImmutableMap.of("java.util.Foo", "j$.util.FooConv1"))));
220 data.put(
221 "library_flags",
222 ImmutableList.of(
223 ImmutableMap.of(
224 "api_level_below_or_equal",
225 100000,
226 "custom_conversion",
227 ImmutableMap.of("java.util.Foo", "j$.util.FooConv2"))));
228 runFailing(
229 toJson(data),
230 diagnostics ->
231 diagnostics.assertErrorsMatch(
232 allOf(
233 diagnosticMessage(containsString("Duplicate assignment of key")),
234 diagnosticMessage(containsString("java.util.Foo")),
235 diagnosticMessage(containsString("custom_conversion")),
236 diagnosticOrigin(origin))));
237 }
238
239 @Test
240 public void testDuplicate() {
241 LinkedHashMap<String, Object> data = template();
242 data.put(
243 "common_flags",
244 ImmutableList.of(
245 ImmutableMap.of(
246 "api_level_below_or_equal",
247 100000,
248 "custom_conversion",
249 ImmutableMap.of(
250 "java.util.Foo", "j$.util.FooConv1",
251 "java.util.Foo2", "j$.util.FooConv2"))));
252 // The gson parser will overwrite the key in order during parsing, thus hiding potential issues.
253 DesugaredLibraryConfiguration config = runPassing(toJson(data).replace("Foo2", "Foo"));
254 assertEquals(
255 Collections.singletonList("java.util.Foo"),
256 config.getCustomConversions().keySet().stream()
257 .map(DexType::toString)
258 .collect(Collectors.toList()));
259 assertEquals(
260 Collections.singletonList("j$.util.FooConv2"),
261 config.getCustomConversions().values().stream()
262 .map(DexType::toString)
263 .collect(Collectors.toList()));
264 }
265
266 // JSON building helpers.
267 // This does not use gson to make the text input construction independent of gson.
268
269 private static String toJson(Map<String, Object> data) {
270 StringBuilder builder = new StringBuilder();
271 toJsonObject(data, builder);
272 return builder.toString();
273 }
274
275 private static void toJsonObject(Map<String, Object> data, StringBuilder builder) {
276 StringUtils.append(
277 builder,
278 ListUtils.map(
279 data.entrySet(),
280 entry -> "\n " + quote(entry.getKey()) + ": " + toJsonElement(entry.getValue())),
281 ", ",
282 BraceType.TUBORG);
283 }
284
285 private static String toJsonElement(Object element) {
286 StringBuilder builder = new StringBuilder();
287 toJsonElement(element, builder);
288 return builder.toString();
289 }
290
291 private static void toJsonElement(Object element, StringBuilder builder) {
292 if (element instanceof String) {
293 builder.append(quote((String) element));
294 } else if (element instanceof Integer) {
295 builder.append(element);
296 } else if (element instanceof List) {
297 toJsonList((List<Object>) element, builder);
298 } else if (element instanceof Map) {
299 toJsonObject((Map<String, Object>) element, builder);
300 } else {
301 throw new IllegalStateException("Unexpected object type: " + element.getClass());
302 }
303 }
304
305 private static void toJsonList(List<Object> element, StringBuilder builder) {
306 StringUtils.append(
307 builder, ListUtils.map(element, o -> "\n " + toJsonElement(o)), ", ", BraceType.SQUARE);
308 }
309
310 private static String quote(String str) {
311 return "\"" + str + "\"";
312 }
313}