| // Copyright 2017 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package com.google.devtools.build.android; | |
| import static com.google.common.truth.Truth.assertAbout; | |
| import static com.google.common.truth.Truth.assertThat; | |
| import com.android.ide.common.internal.PngCruncher; | |
| import com.android.ide.common.internal.PngException; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.common.jimfs.Jimfs; | |
| import com.google.common.truth.Subject; | |
| import com.google.common.util.concurrent.MoreExecutors; | |
| import java.io.File; | |
| import java.io.IOException; | |
| import java.nio.file.FileSystem; | |
| import java.nio.file.Files; | |
| import java.nio.file.Path; | |
| import org.junit.Before; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| /** Tests for the AndroidDataWriter. */ | |
| @RunWith(JUnit4.class) | |
| public class AndroidDataWriterTest { | |
| private static final String START_RESOURCES = | |
| new String(AndroidDataWriter.PRELUDE) + "<resources"; | |
| private static final String END_RESOURCES = new String(AndroidDataWriter.END_RESOURCES); | |
| private FileSystem fs; | |
| @Before | |
| public void createCleanEnvironment() { | |
| fs = Jimfs.newFileSystem(); | |
| } | |
| @Test | |
| public void writeResourceFile() throws Exception { | |
| Path target = fs.getPath("target"); | |
| Path source = fs.getPath("source"); | |
| AndroidDataWriter mergedDataWriter = AndroidDataWriter.createWithDefaults(target); | |
| String drawable = "drawable/menu.gif"; | |
| String layout = "layout/foo.xml"; | |
| ParsedAndroidData direct = | |
| AndroidDataBuilder.of(source) | |
| .addResource(layout, AndroidDataBuilder.ResourceType.LAYOUT, "") | |
| .addResourceBinary(drawable, Files.createFile(fs.getPath("menu.gif"))) | |
| .createManifest("AndroidManifest.xml", "com.carroll.lewis", "") | |
| .buildParsed(); | |
| MergedAndroidData actual = | |
| UnwrittenMergedAndroidData.of( | |
| source.resolve("AndroidManifest.xml"), direct, ParsedAndroidDataBuilder.empty()) | |
| .write(mergedDataWriter); | |
| assertAbout(paths).that(actual.getManifest()).exists(); | |
| assertAbout(paths).that(actual.getResourceDir().resolve(drawable)).exists(); | |
| assertAbout(paths).that(actual.getResourceDir().resolve(layout)).exists(); | |
| } | |
| @Test | |
| public void writePngInRawAndNotInRaw() throws Exception { | |
| Path tmpDir = Files.createTempDirectory(this.toString()); | |
| final Path target = tmpDir.resolve("target"); | |
| final Path source = tmpDir.resolve("source"); | |
| final String drawable = "drawable/crunch.png"; | |
| final String raw = "raw/nocrunch.png"; | |
| final String layout = "layout/foo.xml"; | |
| AndroidDataWriter mergedDataWriter = | |
| AndroidDataWriter.createWith( | |
| target, | |
| target.resolve("res"), | |
| target.resolve("assets"), | |
| new PngCruncher() { | |
| @Override | |
| public int start() { | |
| return 0; | |
| } | |
| @Override | |
| public void end(int key) throws InterruptedException { | |
| } | |
| @Override | |
| public void crunchPng(int key, File from, File to) | |
| throws PngException { | |
| assertThat(from.toString()).doesNotContain(raw); | |
| try { | |
| Files.copy(from.toPath(), to.toPath()); | |
| } catch (IOException e) { | |
| throw new PngException(e); | |
| } | |
| } | |
| }, | |
| MoreExecutors.newDirectExecutorService()); | |
| ParsedAndroidData direct = | |
| AndroidDataBuilder.of(source) | |
| .addResource(layout, AndroidDataBuilder.ResourceType.LAYOUT, "") | |
| .addResourceBinary(drawable, Files.createFile(tmpDir.resolve("crunch.png"))) | |
| .addResourceBinary(raw, Files.createFile(tmpDir.resolve("nocrunch.png"))) | |
| .createManifest("AndroidManifest.xml", "com.carroll.lewis", "") | |
| .buildParsed(); | |
| MergedAndroidData actual = | |
| UnwrittenMergedAndroidData.of( | |
| source.resolve("AndroidManifest.xml"), direct, ParsedAndroidDataBuilder.empty()) | |
| .write(mergedDataWriter); | |
| assertAbout(paths).that(actual.getManifest()).exists(); | |
| assertAbout(paths).that(actual.getResourceDir().resolve(drawable)).exists(); | |
| assertAbout(paths).that(actual.getResourceDir().resolve(raw)).exists(); | |
| assertAbout(paths).that(actual.getResourceDir().resolve(layout)).exists(); | |
| } | |
| @Test | |
| public void writeNinepatchResourceFile() throws Exception { | |
| Path tmpDir = Files.createTempDirectory(this.toString()); | |
| Path target = tmpDir.resolve("target"); | |
| Path source = tmpDir.resolve("source"); | |
| AndroidDataWriter mergedDataWriter = AndroidDataWriter.createWithDefaults(target); | |
| String drawable = "drawable-hdpi-v4/seven_eight.9.png"; | |
| ParsedAndroidData direct = | |
| AndroidDataBuilder.of(source) | |
| .addResourceBinary(drawable, Files.createFile(fs.getPath("seven_eight.9.png"))) | |
| .createManifest("AndroidManifest.xml", "com.carroll.lewis", "") | |
| .buildParsed(); | |
| MergedAndroidData actual = | |
| UnwrittenMergedAndroidData.of( | |
| source.resolve("AndroidManifest.xml"), direct, ParsedAndroidDataBuilder.empty()) | |
| .write(mergedDataWriter); | |
| assertAbout(paths).that(actual.getManifest()).exists(); | |
| assertAbout(paths).that(actual.getResourceDir().resolve(drawable)).exists(); | |
| } | |
| @Test | |
| public void writeResourceXml() throws Exception { | |
| Path target = fs.getPath("target"); | |
| Path source = fs.getPath("source"); | |
| AndroidDataWriter mergedDataWriter = AndroidDataWriter.createWithDefaults(target); | |
| ParsedAndroidData direct = | |
| AndroidDataBuilder.of(source) | |
| .addResource( | |
| "values/ids.xml", | |
| AndroidDataBuilder.ResourceType.VALUE, | |
| "<item name=\"id1\" type=\"id\"/>", | |
| "<item name=\"id\" type=\"id\"/>") | |
| .addResource( | |
| "values/stubs.xml", | |
| AndroidDataBuilder.ResourceType.VALUE, | |
| "<item name=\"walrus\" type=\"drawable\"/>") | |
| .createManifest("AndroidManifest.xml", "com.carroll.lewis", "") | |
| .buildParsed(); | |
| MergedAndroidData actual = | |
| UnwrittenMergedAndroidData.of( | |
| source.resolve("AndroidManifest.xml"), direct, ParsedAndroidDataBuilder.empty()) | |
| .write(mergedDataWriter); | |
| assertAbout(paths).that(actual.getManifest()).exists(); | |
| assertAbout(paths).that(actual.getResourceDir().resolve("values/values.xml")).exists(); | |
| assertAbout(paths) | |
| .that(actual.getResourceDir().resolve("values/values.xml")) | |
| .xmlContentsIsEqualTo( | |
| START_RESOURCES + ">", | |
| "<!-- " + fs.getPath("source/res/values/stubs.xml") + " --><eat-comment/>", | |
| "<item name='walrus' type='drawable'/>", | |
| "<!-- " + fs.getPath("source/res/values/ids.xml") + " --><eat-comment/>", | |
| "<item name='id' type='id'/>", | |
| "<item name='id1' type='id'/>", | |
| END_RESOURCES); | |
| } | |
| @Test | |
| public void writeResourceXmlWithQualfiers() throws Exception { | |
| Path target = fs.getPath("target"); | |
| Path source = fs.getPath("source"); | |
| AndroidDataWriter mergedDataWriter = AndroidDataWriter.createWithDefaults(target); | |
| ParsedAndroidData direct = | |
| AndroidDataBuilder.of(source) | |
| .addResource( | |
| "values/ids.xml", | |
| AndroidDataBuilder.ResourceType.VALUE, | |
| "<item name=\"id1\" type=\"id\"/>") | |
| .addResource( | |
| "values-en/ids.xml", | |
| AndroidDataBuilder.ResourceType.VALUE, | |
| "<item name=\"id1\" type=\"id\"/>") | |
| .createManifest("AndroidManifest.xml", "com.carroll.lewis", "") | |
| .buildParsed(); | |
| MergedAndroidData actual = | |
| UnwrittenMergedAndroidData.of( | |
| source.resolve("AndroidManifest.xml"), direct, ParsedAndroidDataBuilder.empty()) | |
| .write(mergedDataWriter); | |
| assertAbout(paths).that(actual.getManifest()).exists(); | |
| assertAbout(paths).that(actual.getResourceDir().resolve("values/values.xml")).exists(); | |
| assertAbout(paths) | |
| .that(actual.getResourceDir().resolve("values/values.xml")) | |
| .xmlContentsIsEqualTo( | |
| START_RESOURCES + ">", | |
| "<!-- " | |
| + fs.getPath("source/res/values/ids.xml") | |
| + " --><eat-comment/><item name='id1' type='id'/>", | |
| END_RESOURCES); | |
| assertAbout(paths).that(actual.getResourceDir().resolve("values-en/values.xml")).exists(); | |
| assertAbout(paths) | |
| .that(actual.getResourceDir().resolve("values-en/values.xml")) | |
| .xmlContentsIsEqualTo( | |
| START_RESOURCES + ">", | |
| "<!-- " | |
| + fs.getPath("source/res/values-en/ids.xml") | |
| + " --><eat-comment/><item name='id1' type='id'/>", | |
| END_RESOURCES); | |
| } | |
| @Test | |
| public void writePublicResourceSameNameDifferentType() throws Exception { | |
| Path target = fs.getPath("target"); | |
| Path source = fs.getPath("source"); | |
| AndroidDataWriter mergedDataWriter = AndroidDataWriter.createWithDefaults(target); | |
| ParsedAndroidData direct = | |
| AndroidDataBuilder.of(source) | |
| .addResource( | |
| "values/integers.xml", | |
| AndroidDataBuilder.ResourceType.VALUE, | |
| "<integer name=\"foo\">12345</integer>", | |
| "<public name=\"foo\" type=\"integer\" id=\"0x7f040000\"/>", | |
| "<integer name=\"zoo\">54321</integer>", | |
| "<public name=\"zoo\" type=\"integer\" />") | |
| .addResource( | |
| "values/strings.xml", | |
| AndroidDataBuilder.ResourceType.VALUE, | |
| "<string name=\"foo\">meow</string>", | |
| "<public name=\"foo\" type=\"string\" id=\"0x7f050000\"/>") | |
| .createManifest("AndroidManifest.xml", "com.carroll.lewis", "") | |
| .buildParsed(); | |
| MergedAndroidData actual = | |
| UnwrittenMergedAndroidData.of( | |
| source.resolve("AndroidManifest.xml"), direct, ParsedAndroidDataBuilder.empty()) | |
| .write(mergedDataWriter); | |
| assertAbout(paths).that(actual.getManifest()).exists(); | |
| assertAbout(paths).that(actual.getResourceDir().resolve("values/values.xml")).exists(); | |
| assertAbout(paths) | |
| .that(actual.getResourceDir().resolve("values/values.xml")) | |
| .xmlContentsIsEqualTo( | |
| START_RESOURCES + ">", | |
| "<!-- " + fs.getPath("source/res/values/integers.xml") + " --><eat-comment/>", | |
| "<integer name='foo'>12345</integer>", | |
| "<integer name='zoo'>54321</integer>", | |
| "<!-- " + fs.getPath("source/res/values/strings.xml") + " --><eat-comment/>", | |
| "<string name='foo'>meow</string>", | |
| "<!-- " + fs.getPath("source/res/values/integers.xml") + " --><eat-comment/>", | |
| "<public name='foo' type='integer' id='0x7f040000'/>", | |
| "<public name='foo' type='string' id='0x7f050000'/>", | |
| "<public name='zoo' type='integer' />", | |
| END_RESOURCES); | |
| } | |
| @Test | |
| public void writeWithIDDuplicates() throws Exception { | |
| // We parse IDs from layout, etc. XML. We can include it in the merged values.xml redundantly | |
| // (redundant because we also give aapt the original layout xml, which it can parse for IDs | |
| // too), but make sure we don't accidentally put multiple copies in the merged values.xml file. | |
| // Otherwise, aapt will throw an error if there are duplicates in the same values.xml file. | |
| Path target = fs.getPath("target"); | |
| Path source = fs.getPath("source"); | |
| AndroidDataWriter mergedDataWriter = AndroidDataWriter.createWithDefaults(target); | |
| ParsedAndroidData direct = | |
| AndroidDataBuilder.of(source) | |
| .addResource( | |
| "layout/some_layout.xml", | |
| AndroidDataBuilder.ResourceType.LAYOUT, | |
| "<TextView android:id=\"@+id/MyTextView\"", | |
| " android:text=\"@string/walrus\"", | |
| " android:layout_above=\"@+id/AnotherTextView\"", | |
| " android:layout_width=\"wrap_content\"", | |
| " android:layout_height=\"wrap_content\" />", | |
| // Test redundantly having a "+id/MyTextView" in a different attribute. | |
| "<TextView android:id=\"@id/AnotherTextView\"", | |
| " android:text=\"@string/walrus\"", | |
| " android:layout_below=\"@+id/MyTextView\"", | |
| " android:layout_width=\"wrap_content\"", | |
| " android:layout_height=\"wrap_content\" />") | |
| // Test what happens if a user accidentally uses the same ID in multiple layouts too. | |
| .addResource( | |
| "layout/another_layout.xml", | |
| AndroidDataBuilder.ResourceType.LAYOUT, | |
| "<TextView android:id=\"@+id/MyTextView\"", | |
| " android:text=\"@string/walrus\"", | |
| " android:layout_width=\"wrap_content\"", | |
| " android:layout_height=\"wrap_content\" />") | |
| // Also check what happens if a value XML file also contains the same ID. | |
| .addResource( | |
| "values/ids.xml", | |
| AndroidDataBuilder.ResourceType.VALUE, | |
| "<item name=\"MyTextView\" type=\"id\"/>", | |
| "<item name=\"OtherId\" type=\"id\"/>") | |
| .addResource( | |
| "values/strings.xml", | |
| AndroidDataBuilder.ResourceType.VALUE, | |
| "<string name=\"walrus\">I has a bucket</string>") | |
| .createManifest("AndroidManifest.xml", "com.carroll.lewis", "") | |
| .buildParsed(); | |
| MergedAndroidData actual = | |
| UnwrittenMergedAndroidData.of( | |
| source.resolve("AndroidManifest.xml"), direct, ParsedAndroidDataBuilder.empty()) | |
| .write(mergedDataWriter); | |
| assertAbout(paths).that(actual.getManifest()).exists(); | |
| assertAbout(paths).that(actual.getResourceDir().resolve("layout/some_layout.xml")).exists(); | |
| assertAbout(paths).that(actual.getResourceDir().resolve("values/values.xml")).exists(); | |
| assertAbout(paths) | |
| .that(actual.getResourceDir().resolve("values/values.xml")) | |
| .xmlContentsIsEqualTo( | |
| START_RESOURCES + ">", | |
| "<!-- " + fs.getPath("source/res/values/ids.xml") + " --><eat-comment/>", | |
| "<item name='MyTextView' type='id'/>", | |
| "<item name='OtherId' type='id'/>", | |
| "<!-- " + fs.getPath("source/res/values/strings.xml") + " --><eat-comment/>", | |
| "<string name='walrus'>I has a bucket</string>", | |
| END_RESOURCES); | |
| } | |
| @Test | |
| public void writeResourceXmlWithAttributes() throws Exception { | |
| Path target = fs.getPath("target"); | |
| Path source = fs.getPath("source"); | |
| AndroidDataWriter mergedDataWriter = AndroidDataWriter.createWithDefaults(target); | |
| ParsedAndroidData direct = | |
| AndroidDataBuilder.of(source) | |
| .addValuesWithAttributes( | |
| "values/ids.xml", | |
| ImmutableMap.of("foo", "fooVal", "bar", "barVal"), | |
| "<item name=\"id1\" type=\"id\"/>", | |
| "<item name=\"id\" type=\"id\"/>") | |
| .addValuesWithAttributes( | |
| "values/stubs.xml", | |
| ImmutableMap.of("baz", "bazVal"), | |
| "<item name=\"walrus\" type=\"drawable\"/>") | |
| .createManifest("AndroidManifest.xml", "com.carroll.lewis", "") | |
| .buildParsed(); | |
| MergedAndroidData actual = | |
| UnwrittenMergedAndroidData.of( | |
| source.resolve("AndroidManifest.xml"), direct, ParsedAndroidDataBuilder.empty()) | |
| .write(mergedDataWriter); | |
| assertAbout(paths).that(actual.getManifest()).exists(); | |
| assertAbout(paths).that(actual.getResourceDir().resolve("values/values.xml")).exists(); | |
| assertAbout(paths) | |
| .that(actual.getResourceDir().resolve("values/values.xml")) | |
| .xmlContentsIsEqualTo( | |
| START_RESOURCES + " foo=\"fooVal\" bar=\"barVal\" baz=\"bazVal\">", | |
| "<!-- " + fs.getPath("source/res/values/stubs.xml") + " --><eat-comment/>", | |
| "<item name='walrus' type='drawable'/>", | |
| "<!-- " + fs.getPath("source/res/values/ids.xml") + " --><eat-comment/>", | |
| "<item name='id' type='id'/>", | |
| "<item name='id1' type='id'/>", | |
| END_RESOURCES); | |
| } | |
| @Test | |
| public void writeAssetFile() throws Exception { | |
| Path target = fs.getPath("target"); | |
| Path source = fs.getPath("source"); | |
| AndroidDataWriter mergedDataWriter = AndroidDataWriter.createWithDefaults(target); | |
| String asset = "hunting/of/the/boojum"; | |
| ParsedAndroidData direct = | |
| AndroidDataBuilder.of(source) | |
| .addAsset(asset, "not a snark!") | |
| .createManifest("AndroidManifest.xml", "com.carroll.lewis", "") | |
| .buildParsed(); | |
| MergedAndroidData actual = | |
| UnwrittenMergedAndroidData.of( | |
| source.resolve("AndroidManifest.xml"), direct, ParsedAndroidDataBuilder.empty()) | |
| .write(mergedDataWriter); | |
| assertAbout(paths).that(actual.getManifest()).exists(); | |
| assertAbout(paths).that(actual.getAssetDir().resolve(asset)).exists(); | |
| } | |
| private static final Subject.Factory<PathsSubject, Path> paths = PathsSubject::new; | |
| } |