blob: 8698750bac6f49fcebd0d63385d8503bbbfe4df6 [file] [log] [blame]
// 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;
}