blob: 8698750bac6f49fcebd0d63385d8503bbbfe4df6 [file] [log] [blame]
Adam Michael2587a6d2017-03-22 15:33:40 +00001// Copyright 2017 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.devtools.build.android;
15
16import static com.google.common.truth.Truth.assertAbout;
17import static com.google.common.truth.Truth.assertThat;
18
19import com.android.ide.common.internal.PngCruncher;
20import com.android.ide.common.internal.PngException;
21import com.google.common.collect.ImmutableMap;
22import com.google.common.jimfs.Jimfs;
Googler694105d2017-11-10 19:40:08 +010023import com.google.common.truth.Subject;
Adam Michael2587a6d2017-03-22 15:33:40 +000024import com.google.common.util.concurrent.MoreExecutors;
25import java.io.File;
26import java.io.IOException;
27import java.nio.file.FileSystem;
28import java.nio.file.Files;
29import java.nio.file.Path;
30import org.junit.Before;
31import org.junit.Test;
32import org.junit.runner.RunWith;
33import org.junit.runners.JUnit4;
34
35/** Tests for the AndroidDataWriter. */
36@RunWith(JUnit4.class)
37public class AndroidDataWriterTest {
38
39 private static final String START_RESOURCES =
40 new String(AndroidDataWriter.PRELUDE) + "<resources";
41 private static final String END_RESOURCES = new String(AndroidDataWriter.END_RESOURCES);
42
43 private FileSystem fs;
44
45 @Before
46 public void createCleanEnvironment() {
47 fs = Jimfs.newFileSystem();
48 }
49
50 @Test
51 public void writeResourceFile() throws Exception {
52 Path target = fs.getPath("target");
53 Path source = fs.getPath("source");
54 AndroidDataWriter mergedDataWriter = AndroidDataWriter.createWithDefaults(target);
55 String drawable = "drawable/menu.gif";
56 String layout = "layout/foo.xml";
57 ParsedAndroidData direct =
58 AndroidDataBuilder.of(source)
59 .addResource(layout, AndroidDataBuilder.ResourceType.LAYOUT, "")
60 .addResourceBinary(drawable, Files.createFile(fs.getPath("menu.gif")))
61 .createManifest("AndroidManifest.xml", "com.carroll.lewis", "")
62 .buildParsed();
63 MergedAndroidData actual =
64 UnwrittenMergedAndroidData.of(
65 source.resolve("AndroidManifest.xml"), direct, ParsedAndroidDataBuilder.empty())
66 .write(mergedDataWriter);
67
68 assertAbout(paths).that(actual.getManifest()).exists();
69 assertAbout(paths).that(actual.getResourceDir().resolve(drawable)).exists();
70 assertAbout(paths).that(actual.getResourceDir().resolve(layout)).exists();
71 }
72
73 @Test
74 public void writePngInRawAndNotInRaw() throws Exception {
75 Path tmpDir = Files.createTempDirectory(this.toString());
76 final Path target = tmpDir.resolve("target");
77 final Path source = tmpDir.resolve("source");
78 final String drawable = "drawable/crunch.png";
79 final String raw = "raw/nocrunch.png";
80 final String layout = "layout/foo.xml";
81 AndroidDataWriter mergedDataWriter =
82 AndroidDataWriter.createWith(
83 target,
84 target.resolve("res"),
85 target.resolve("assets"),
86 new PngCruncher() {
87 @Override
88 public int start() {
89 return 0;
90 }
91
92 @Override
93 public void end(int key) throws InterruptedException {
94 }
95
96 @Override
97 public void crunchPng(int key, File from, File to)
98 throws PngException {
99 assertThat(from.toString()).doesNotContain(raw);
100 try {
101 Files.copy(from.toPath(), to.toPath());
102 } catch (IOException e) {
103 throw new PngException(e);
104 }
105 }
106 },
107 MoreExecutors.newDirectExecutorService());
108 ParsedAndroidData direct =
109 AndroidDataBuilder.of(source)
110 .addResource(layout, AndroidDataBuilder.ResourceType.LAYOUT, "")
111 .addResourceBinary(drawable, Files.createFile(tmpDir.resolve("crunch.png")))
112 .addResourceBinary(raw, Files.createFile(tmpDir.resolve("nocrunch.png")))
113 .createManifest("AndroidManifest.xml", "com.carroll.lewis", "")
114 .buildParsed();
115 MergedAndroidData actual =
116 UnwrittenMergedAndroidData.of(
117 source.resolve("AndroidManifest.xml"), direct, ParsedAndroidDataBuilder.empty())
118 .write(mergedDataWriter);
119
120 assertAbout(paths).that(actual.getManifest()).exists();
121 assertAbout(paths).that(actual.getResourceDir().resolve(drawable)).exists();
122 assertAbout(paths).that(actual.getResourceDir().resolve(raw)).exists();
123 assertAbout(paths).that(actual.getResourceDir().resolve(layout)).exists();
124 }
125
126 @Test
127 public void writeNinepatchResourceFile() throws Exception {
128 Path tmpDir = Files.createTempDirectory(this.toString());
129 Path target = tmpDir.resolve("target");
130 Path source = tmpDir.resolve("source");
131 AndroidDataWriter mergedDataWriter = AndroidDataWriter.createWithDefaults(target);
132 String drawable = "drawable-hdpi-v4/seven_eight.9.png";
133 ParsedAndroidData direct =
134 AndroidDataBuilder.of(source)
135 .addResourceBinary(drawable, Files.createFile(fs.getPath("seven_eight.9.png")))
136 .createManifest("AndroidManifest.xml", "com.carroll.lewis", "")
137 .buildParsed();
138 MergedAndroidData actual =
139 UnwrittenMergedAndroidData.of(
140 source.resolve("AndroidManifest.xml"), direct, ParsedAndroidDataBuilder.empty())
141 .write(mergedDataWriter);
142
143 assertAbout(paths).that(actual.getManifest()).exists();
144 assertAbout(paths).that(actual.getResourceDir().resolve(drawable)).exists();
145 }
146
147 @Test
148 public void writeResourceXml() throws Exception {
149 Path target = fs.getPath("target");
150 Path source = fs.getPath("source");
151 AndroidDataWriter mergedDataWriter = AndroidDataWriter.createWithDefaults(target);
152 ParsedAndroidData direct =
153 AndroidDataBuilder.of(source)
154 .addResource(
155 "values/ids.xml",
156 AndroidDataBuilder.ResourceType.VALUE,
157 "<item name=\"id1\" type=\"id\"/>",
158 "<item name=\"id\" type=\"id\"/>")
159 .addResource(
160 "values/stubs.xml",
161 AndroidDataBuilder.ResourceType.VALUE,
162 "<item name=\"walrus\" type=\"drawable\"/>")
163 .createManifest("AndroidManifest.xml", "com.carroll.lewis", "")
164 .buildParsed();
165 MergedAndroidData actual =
166 UnwrittenMergedAndroidData.of(
167 source.resolve("AndroidManifest.xml"), direct, ParsedAndroidDataBuilder.empty())
168 .write(mergedDataWriter);
169
170 assertAbout(paths).that(actual.getManifest()).exists();
171 assertAbout(paths).that(actual.getResourceDir().resolve("values/values.xml")).exists();
172 assertAbout(paths)
173 .that(actual.getResourceDir().resolve("values/values.xml"))
174 .xmlContentsIsEqualTo(
175 START_RESOURCES + ">",
Laszlo Csomor9cce261b2017-07-10 12:17:22 +0200176 "<!-- " + fs.getPath("source/res/values/stubs.xml") + " --><eat-comment/>",
Adam Michael2587a6d2017-03-22 15:33:40 +0000177 "<item name='walrus' type='drawable'/>",
Laszlo Csomor9cce261b2017-07-10 12:17:22 +0200178 "<!-- " + fs.getPath("source/res/values/ids.xml") + " --><eat-comment/>",
Adam Michael2587a6d2017-03-22 15:33:40 +0000179 "<item name='id' type='id'/>",
180 "<item name='id1' type='id'/>",
181 END_RESOURCES);
182 }
183
184 @Test
185 public void writeResourceXmlWithQualfiers() throws Exception {
186 Path target = fs.getPath("target");
187 Path source = fs.getPath("source");
188 AndroidDataWriter mergedDataWriter = AndroidDataWriter.createWithDefaults(target);
189 ParsedAndroidData direct =
190 AndroidDataBuilder.of(source)
191 .addResource(
192 "values/ids.xml",
193 AndroidDataBuilder.ResourceType.VALUE,
194 "<item name=\"id1\" type=\"id\"/>")
195 .addResource(
196 "values-en/ids.xml",
197 AndroidDataBuilder.ResourceType.VALUE,
198 "<item name=\"id1\" type=\"id\"/>")
199 .createManifest("AndroidManifest.xml", "com.carroll.lewis", "")
200 .buildParsed();
201 MergedAndroidData actual =
202 UnwrittenMergedAndroidData.of(
203 source.resolve("AndroidManifest.xml"), direct, ParsedAndroidDataBuilder.empty())
204 .write(mergedDataWriter);
205
206 assertAbout(paths).that(actual.getManifest()).exists();
207 assertAbout(paths).that(actual.getResourceDir().resolve("values/values.xml")).exists();
208 assertAbout(paths)
209 .that(actual.getResourceDir().resolve("values/values.xml"))
210 .xmlContentsIsEqualTo(
211 START_RESOURCES + ">",
Laszlo Csomor9cce261b2017-07-10 12:17:22 +0200212 "<!-- "
213 + fs.getPath("source/res/values/ids.xml")
214 + " --><eat-comment/><item name='id1' type='id'/>",
Adam Michael2587a6d2017-03-22 15:33:40 +0000215 END_RESOURCES);
216 assertAbout(paths).that(actual.getResourceDir().resolve("values-en/values.xml")).exists();
217 assertAbout(paths)
218 .that(actual.getResourceDir().resolve("values-en/values.xml"))
219 .xmlContentsIsEqualTo(
220 START_RESOURCES + ">",
Laszlo Csomor9cce261b2017-07-10 12:17:22 +0200221 "<!-- "
222 + fs.getPath("source/res/values-en/ids.xml")
223 + " --><eat-comment/><item name='id1' type='id'/>",
Adam Michael2587a6d2017-03-22 15:33:40 +0000224 END_RESOURCES);
225 }
226
227 @Test
228 public void writePublicResourceSameNameDifferentType() throws Exception {
229 Path target = fs.getPath("target");
230 Path source = fs.getPath("source");
231 AndroidDataWriter mergedDataWriter = AndroidDataWriter.createWithDefaults(target);
232 ParsedAndroidData direct =
233 AndroidDataBuilder.of(source)
234 .addResource(
235 "values/integers.xml",
236 AndroidDataBuilder.ResourceType.VALUE,
237 "<integer name=\"foo\">12345</integer>",
238 "<public name=\"foo\" type=\"integer\" id=\"0x7f040000\"/>",
239 "<integer name=\"zoo\">54321</integer>",
240 "<public name=\"zoo\" type=\"integer\" />")
241 .addResource(
242 "values/strings.xml",
243 AndroidDataBuilder.ResourceType.VALUE,
244 "<string name=\"foo\">meow</string>",
245 "<public name=\"foo\" type=\"string\" id=\"0x7f050000\"/>")
246 .createManifest("AndroidManifest.xml", "com.carroll.lewis", "")
247 .buildParsed();
248 MergedAndroidData actual =
249 UnwrittenMergedAndroidData.of(
250 source.resolve("AndroidManifest.xml"), direct, ParsedAndroidDataBuilder.empty())
251 .write(mergedDataWriter);
252
253 assertAbout(paths).that(actual.getManifest()).exists();
254 assertAbout(paths).that(actual.getResourceDir().resolve("values/values.xml")).exists();
255 assertAbout(paths)
256 .that(actual.getResourceDir().resolve("values/values.xml"))
257 .xmlContentsIsEqualTo(
258 START_RESOURCES + ">",
Laszlo Csomor9cce261b2017-07-10 12:17:22 +0200259 "<!-- " + fs.getPath("source/res/values/integers.xml") + " --><eat-comment/>",
Adam Michael2587a6d2017-03-22 15:33:40 +0000260 "<integer name='foo'>12345</integer>",
261 "<integer name='zoo'>54321</integer>",
Laszlo Csomor9cce261b2017-07-10 12:17:22 +0200262 "<!-- " + fs.getPath("source/res/values/strings.xml") + " --><eat-comment/>",
Adam Michael2587a6d2017-03-22 15:33:40 +0000263 "<string name='foo'>meow</string>",
corysmithc28b4ce2017-11-03 21:04:40 +0100264 "<!-- " + fs.getPath("source/res/values/integers.xml") + " --><eat-comment/>",
Adam Michael2587a6d2017-03-22 15:33:40 +0000265 "<public name='foo' type='integer' id='0x7f040000'/>",
266 "<public name='foo' type='string' id='0x7f050000'/>",
267 "<public name='zoo' type='integer' />",
268 END_RESOURCES);
269 }
270
271 @Test
272 public void writeWithIDDuplicates() throws Exception {
273 // We parse IDs from layout, etc. XML. We can include it in the merged values.xml redundantly
274 // (redundant because we also give aapt the original layout xml, which it can parse for IDs
275 // too), but make sure we don't accidentally put multiple copies in the merged values.xml file.
276 // Otherwise, aapt will throw an error if there are duplicates in the same values.xml file.
277 Path target = fs.getPath("target");
278 Path source = fs.getPath("source");
279 AndroidDataWriter mergedDataWriter = AndroidDataWriter.createWithDefaults(target);
280 ParsedAndroidData direct =
281 AndroidDataBuilder.of(source)
282 .addResource(
283 "layout/some_layout.xml",
284 AndroidDataBuilder.ResourceType.LAYOUT,
285 "<TextView android:id=\"@+id/MyTextView\"",
286 " android:text=\"@string/walrus\"",
287 " android:layout_above=\"@+id/AnotherTextView\"",
288 " android:layout_width=\"wrap_content\"",
289 " android:layout_height=\"wrap_content\" />",
290 // Test redundantly having a "+id/MyTextView" in a different attribute.
291 "<TextView android:id=\"@id/AnotherTextView\"",
292 " android:text=\"@string/walrus\"",
293 " android:layout_below=\"@+id/MyTextView\"",
294 " android:layout_width=\"wrap_content\"",
295 " android:layout_height=\"wrap_content\" />")
296 // Test what happens if a user accidentally uses the same ID in multiple layouts too.
297 .addResource(
298 "layout/another_layout.xml",
299 AndroidDataBuilder.ResourceType.LAYOUT,
300 "<TextView android:id=\"@+id/MyTextView\"",
301 " android:text=\"@string/walrus\"",
302 " android:layout_width=\"wrap_content\"",
303 " android:layout_height=\"wrap_content\" />")
304 // Also check what happens if a value XML file also contains the same ID.
305 .addResource(
306 "values/ids.xml",
307 AndroidDataBuilder.ResourceType.VALUE,
308 "<item name=\"MyTextView\" type=\"id\"/>",
309 "<item name=\"OtherId\" type=\"id\"/>")
310 .addResource(
311 "values/strings.xml",
312 AndroidDataBuilder.ResourceType.VALUE,
313 "<string name=\"walrus\">I has a bucket</string>")
314 .createManifest("AndroidManifest.xml", "com.carroll.lewis", "")
315 .buildParsed();
316 MergedAndroidData actual =
317 UnwrittenMergedAndroidData.of(
318 source.resolve("AndroidManifest.xml"), direct, ParsedAndroidDataBuilder.empty())
319 .write(mergedDataWriter);
320
321 assertAbout(paths).that(actual.getManifest()).exists();
322 assertAbout(paths).that(actual.getResourceDir().resolve("layout/some_layout.xml")).exists();
323 assertAbout(paths).that(actual.getResourceDir().resolve("values/values.xml")).exists();
324 assertAbout(paths)
325 .that(actual.getResourceDir().resolve("values/values.xml"))
326 .xmlContentsIsEqualTo(
327 START_RESOURCES + ">",
Laszlo Csomor9cce261b2017-07-10 12:17:22 +0200328 "<!-- " + fs.getPath("source/res/values/ids.xml") + " --><eat-comment/>",
Adam Michael2587a6d2017-03-22 15:33:40 +0000329 "<item name='MyTextView' type='id'/>",
330 "<item name='OtherId' type='id'/>",
Laszlo Csomor9cce261b2017-07-10 12:17:22 +0200331 "<!-- " + fs.getPath("source/res/values/strings.xml") + " --><eat-comment/>",
Adam Michael2587a6d2017-03-22 15:33:40 +0000332 "<string name='walrus'>I has a bucket</string>",
333 END_RESOURCES);
334 }
335
336 @Test
337 public void writeResourceXmlWithAttributes() throws Exception {
338 Path target = fs.getPath("target");
339 Path source = fs.getPath("source");
340 AndroidDataWriter mergedDataWriter = AndroidDataWriter.createWithDefaults(target);
341 ParsedAndroidData direct =
342 AndroidDataBuilder.of(source)
343 .addValuesWithAttributes(
344 "values/ids.xml",
345 ImmutableMap.of("foo", "fooVal", "bar", "barVal"),
346 "<item name=\"id1\" type=\"id\"/>",
347 "<item name=\"id\" type=\"id\"/>")
348 .addValuesWithAttributes(
349 "values/stubs.xml",
350 ImmutableMap.of("baz", "bazVal"),
351 "<item name=\"walrus\" type=\"drawable\"/>")
352 .createManifest("AndroidManifest.xml", "com.carroll.lewis", "")
353 .buildParsed();
354 MergedAndroidData actual =
355 UnwrittenMergedAndroidData.of(
356 source.resolve("AndroidManifest.xml"), direct, ParsedAndroidDataBuilder.empty())
357 .write(mergedDataWriter);
358
359 assertAbout(paths).that(actual.getManifest()).exists();
360 assertAbout(paths).that(actual.getResourceDir().resolve("values/values.xml")).exists();
361 assertAbout(paths)
362 .that(actual.getResourceDir().resolve("values/values.xml"))
363 .xmlContentsIsEqualTo(
364 START_RESOURCES + " foo=\"fooVal\" bar=\"barVal\" baz=\"bazVal\">",
Laszlo Csomor9cce261b2017-07-10 12:17:22 +0200365 "<!-- " + fs.getPath("source/res/values/stubs.xml") + " --><eat-comment/>",
Adam Michael2587a6d2017-03-22 15:33:40 +0000366 "<item name='walrus' type='drawable'/>",
Laszlo Csomor9cce261b2017-07-10 12:17:22 +0200367 "<!-- " + fs.getPath("source/res/values/ids.xml") + " --><eat-comment/>",
Adam Michael2587a6d2017-03-22 15:33:40 +0000368 "<item name='id' type='id'/>",
369 "<item name='id1' type='id'/>",
370 END_RESOURCES);
371 }
372
373 @Test
374 public void writeAssetFile() throws Exception {
375 Path target = fs.getPath("target");
376 Path source = fs.getPath("source");
377 AndroidDataWriter mergedDataWriter = AndroidDataWriter.createWithDefaults(target);
378 String asset = "hunting/of/the/boojum";
379 ParsedAndroidData direct =
380 AndroidDataBuilder.of(source)
381 .addAsset(asset, "not a snark!")
382 .createManifest("AndroidManifest.xml", "com.carroll.lewis", "")
383 .buildParsed();
384 MergedAndroidData actual =
385 UnwrittenMergedAndroidData.of(
386 source.resolve("AndroidManifest.xml"), direct, ParsedAndroidDataBuilder.empty())
387 .write(mergedDataWriter);
388
389 assertAbout(paths).that(actual.getManifest()).exists();
390 assertAbout(paths).that(actual.getAssetDir().resolve(asset)).exists();
391 }
392
Googler694105d2017-11-10 19:40:08 +0100393 private static final Subject.Factory<PathsSubject, Path> paths = PathsSubject::new;
Adam Michael2587a6d2017-03-22 15:33:40 +0000394}