blob: 5b8fe3e3a2698c732743bf1680950d6dd312e45e [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.assertThat;
import static org.junit.Assert.assertThrows;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.jimfs.Jimfs;
import com.google.common.truth.Subject;
import com.google.common.truth.Truth;
import com.google.devtools.build.android.AndroidResourceMerger.MergingException;
import com.google.devtools.build.android.FullyQualifiedName.Factory;
import com.google.devtools.build.android.resources.Visibility;
import com.google.devtools.build.android.xml.AttrXmlResourceValue;
import com.google.devtools.build.android.xml.IdXmlResourceValue;
import com.google.devtools.build.android.xml.ResourcesAttribute;
import com.google.devtools.build.android.xml.SimpleXmlResourceValue;
import com.google.devtools.build.android.xml.StyleableXmlResourceValue;
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 ParsedAndroidData */
@RunWith(JUnit4.class)
public class ParsedAndroidDataTest {
private FileSystem fs;
private Factory fqnFactory;
@Before
public void createCleanEnvironment() {
fs = Jimfs.newFileSystem();
fqnFactory = FullyQualifiedName.Factory.from(ImmutableList.<String>of());
}
@Test
public void assets() throws Exception {
Path root = fs.getPath("transDep");
ParsedAndroidData dataSet =
ParsedAndroidData.from(
ImmutableList.of(
AndroidDataBuilder.of(root)
.addAsset("bin/boojum")
.createManifest("AndroidManifest.xml", "com.google.foo", "")
.buildDependency()));
Path assetPath = root.resolve("assets/bin/boojum");
RelativeAssetPath key = RelativeAssetPath.Factory.of(root.resolve("assets")).create(assetPath);
assertThat(dataSet.getAssets()).containsEntry(key, DataValueFile.of(assetPath));
assertThat(dataSet.getCombiningResources()).isEmpty();
assertThat(dataSet.getOverwritingResources()).isEmpty();
}
@Test
public void assetsConflict() throws Exception {
Path root = fs.getPath("root");
Path otherRoot = fs.getPath("otherRoot");
ParsedAndroidData dataSet =
ParsedAndroidData.from(
ImmutableList.of(
AndroidDataBuilder.of(root)
.addAsset("bin/boojum")
.createManifest("AndroidManifest.xml", "com.google.foo", "")
.buildDependency(),
AndroidDataBuilder.of(otherRoot)
.addAsset("bin/boojum")
.createManifest("AndroidManifest.xml", "com.google.foo", "")
.buildDependency()));
DataSource assetSource =
DataSource.of(DependencyInfo.UNKNOWN, root.resolve("assets/bin/boojum"));
DataSource otherAssetSource =
DataSource.of(DependencyInfo.UNKNOWN, otherRoot.resolve("assets/bin/boojum"));
RelativeAssetPath key =
RelativeAssetPath.Factory.of(root.resolve("assets")).create(assetSource.getPath());
Truth.assertAbout(parsedAndroidData)
.that(dataSet)
.isEqualTo(
ParsedAndroidData.of(
ImmutableSet.of(
MergeConflict.of(
key,
DataValueFile.of(
Visibility.UNKNOWN,
assetSource,
/*fingerprint=*/ null,
/*rootXmlNode=*/ null),
DataValueFile.of(
Visibility.UNKNOWN,
otherAssetSource,
/*fingerprint=*/ null,
/*rootXmlNode=*/ null))),
ImmutableMap.<DataKey, DataResource>of(),
ImmutableMap.<DataKey, DataResource>of(),
ImmutableMap.<DataKey, DataAsset>of(
key,
DataValueFile.of(
Visibility.UNKNOWN,
otherAssetSource.overwrite(assetSource),
/*fingerprint=*/ null,
/*rootXmlNode=*/ null))));
}
@Test
public void fileResources() throws Exception {
Path root = fs.getPath("transDep");
ParsedAndroidData dataSet =
ParsedAndroidData.from(
ImmutableList.of(
AndroidDataBuilder.of(root)
.addResource("layout/foo.xml", AndroidDataBuilder.ResourceType.LAYOUT, "")
.addResourceBinary(
"drawable/menu.png", Files.createFile(fs.getPath("menu.png")))
.createManifest("AndroidManifest.xml", "com.google.foo", "")
.buildDependency()));
FullyQualifiedName layoutFoo = fqnFactory.parse("layout/foo");
FullyQualifiedName drawableMenu = fqnFactory.parse("drawable/menu");
assertThat(dataSet.getOverwritingResources())
.containsExactly(
layoutFoo, DataValueFile.of(root.resolve("res/layout/foo.xml")),
drawableMenu, DataValueFile.of(root.resolve("res/drawable/menu.png")));
assertThat(dataSet.getCombiningResources()).isEmpty();
}
@Test
public void ninePatchResourceName() throws Exception {
// drawable/foo.9.png should give a resource named "R.drawable.foo"
Path root = fs.getPath("transDep");
ParsedAndroidData dataSet =
ParsedAndroidData.from(
ImmutableList.of(
AndroidDataBuilder.of(root)
.addResourceBinary(
"drawable-hdpi/seven_eight.9.png",
Files.createFile(fs.getPath("seven_eight.9.png")))
.createManifest("AndroidManifest.xml", "com.google.foo", "")
.buildDependency()));
FullyQualifiedName.Factory fqnFactory =
FullyQualifiedName.Factory.from(ImmutableList.<String>of("hdpi", "v4"));
FullyQualifiedName drawable = fqnFactory.parse("drawable/seven_eight");
assertThat(dataSet.getOverwritingResources())
.containsExactly(
drawable, DataValueFile.of(root.resolve("res/drawable-hdpi/seven_eight.9.png")));
assertThat(dataSet.getCombiningResources()).isEmpty();
}
@Test
public void fileResourcesSkipInvalidQualifier() throws Exception {
Path root = fs.getPath("transDep");
ParsedAndroidData dataSet =
ParsedAndroidData.from(
ImmutableList.of(
AndroidDataBuilder.of(root)
.addResource("layout/foo.xml", AndroidDataBuilder.ResourceType.LAYOUT, "")
.addResourceBinary(
"drawable-scooby-doo/menu.png", Files.createFile(fs.getPath("menu.png")))
.createManifest("AndroidManifest.xml", "com.google.foo", "")
.buildDependency()));
FullyQualifiedName layoutFoo = fqnFactory.parse("layout/foo");
assertThat(dataSet.getOverwritingResources())
.containsExactly(layoutFoo, DataValueFile.of(root.resolve("res/layout/foo.xml")));
assertThat(dataSet.getCombiningResources()).isEmpty();
}
@Test
public void nestedFileResources() throws Exception {
Path parent = fs.getPath("parent");
Path root = parent.resolve("transDep");
AndroidDataBuilder.of(root)
.addResource("layout/foo.xml", AndroidDataBuilder.ResourceType.LAYOUT, "")
.addResourceBinary("drawable/menu.png", Files.createFile(fs.getPath("menu.png")))
.createManifest("AndroidManifest.xml", "com.google.foo", "")
.buildDependency();
ParsedAndroidData dataSet =
ParsedAndroidData.from(
new UnvalidatedAndroidData(
ImmutableList.of(root),
ImmutableList.<Path>of(),
root.resolve("AndroidManifest.xml")));
FullyQualifiedName layoutFoo = fqnFactory.parse("layout/foo");
FullyQualifiedName drawableMenu = fqnFactory.parse("drawable/menu");
assertThat(dataSet.getOverwritingResources())
.containsExactly(
layoutFoo, DataValueFile.of(root.resolve("res/layout/foo.xml")),
drawableMenu, DataValueFile.of(root.resolve("res/drawable/menu.png")));
assertThat(dataSet.getCombiningResources()).isEmpty();
}
@Test
public void xmlAndFileResources() throws Exception {
Path root = fs.getPath("root");
DependencyAndroidData dep =
AndroidDataBuilder.of(root)
.addResource("layout/foo.xml", AndroidDataBuilder.ResourceType.LAYOUT, "")
.addResourceBinary("drawable/menu.png", Files.createFile(fs.getPath("menu.png")))
.addValuesWithAttributes(
"values/attr.xml",
ImmutableMap.of("foo", "fooVal"),
"<string name='exit'>way out</string>",
"<declare-styleable name='Theme'>",
" <attr name=\"labelPosition\" format=\"enum\">",
" <enum name=\"left\" value=\"0\"/>",
" <enum name=\"right\" value=\"1\"/>",
" </attr>",
"</declare-styleable>",
"<item name='some_id' type='id'/>")
.createManifest("AndroidManifest.xml", "com.google.foo", "")
.buildDependency();
ParsedAndroidData dataSet = ParsedAndroidData.from(ImmutableList.of(dep));
FullyQualifiedName layoutFoo = fqnFactory.parse("layout/foo");
FullyQualifiedName drawableMenu = fqnFactory.parse("drawable/menu");
FullyQualifiedName attributeFoo = fqnFactory.parse("<resources>/foo");
FullyQualifiedName stringExit = fqnFactory.parse("string/exit");
FullyQualifiedName attrLabelPosition = fqnFactory.parse("attr/labelPosition");
assertThat(dataSet.getOverwritingResources())
.containsExactly(
layoutFoo, // key
DataValueFile.of(root.resolve("res/layout/foo.xml")), // value
drawableMenu, // key
DataValueFile.of(root.resolve("res/drawable/menu.png")), // value
attributeFoo, // key
DataResourceXml.createWithNoNamespace(
root.resolve("res/values/attr.xml"),
ResourcesAttribute.of(attributeFoo, "foo", "fooVal")), // value
stringExit, // key
DataResourceXml.createWithNoNamespace(
root.resolve("res/values/attr.xml"),
SimpleXmlResourceValue.createWithValue(
SimpleXmlResourceValue.Type.STRING, "way out")), // value
attrLabelPosition, // key
DataResourceXml.createWithNoNamespace(
root.resolve("res/values/attr.xml"), // value
AttrXmlResourceValue.of(
ImmutableMap.of(
"enum",
AttrXmlResourceValue.EnumResourceXmlAttrValue.of(
ImmutableMap.of("left", "0", "right", "1"))))));
FullyQualifiedName styleableTheme = fqnFactory.parse("styleable/Theme");
FullyQualifiedName idSomeId = fqnFactory.parse("id/some_id");
assertThat(dataSet.getCombiningResources())
.containsExactly(
styleableTheme, // key
DataResourceXml.createWithNoNamespace(
root.resolve("res/values/attr.xml"),
StyleableXmlResourceValue.createAllAttrAsDefinitions(
fqnFactory.parse("attr/labelPosition"))), // value
idSomeId, // key
DataResourceXml.createWithNoNamespace(
root.resolve("res/values/attr.xml"), IdXmlResourceValue.of())); // value
assertThat(dataSet.getAssets()).isEmpty();
}
@Test
public void combiningResources() throws Exception {
Path root = fs.getPath("root");
DependencyAndroidData dep =
AndroidDataBuilder.of(root)
.addResource(
"values/attr.xml",
AndroidDataBuilder.ResourceType.VALUE,
"<declare-styleable name='Doors'>",
" <attr name='egress' />",
"</declare-styleable>",
"<declare-styleable name='Doors'>",
" <attr name='exit' />",
"</declare-styleable>",
"<item name='exit_id' type='id'/>")
.createManifest("AndroidManifest.xml", "com.google.foo", "")
.buildDependency();
ParsedAndroidData dataSet = ParsedAndroidData.from(ImmutableList.of(dep));
assertThat(dataSet.getOverwritingResources()).isEmpty();
FullyQualifiedName styleableDoors = fqnFactory.parse("styleable/Doors");
FullyQualifiedName idExitId = fqnFactory.parse("id/exit_id");
assertThat(dataSet.getCombiningResources())
.containsExactly(
styleableDoors, // key
DataResourceXml.createWithNoNamespace(
root.resolve("res/values/attr.xml"),
StyleableXmlResourceValue.createAllAttrAsReferences(
fqnFactory.parse("attr/egress"), fqnFactory.parse("attr/exit"))), // value
idExitId, // key
DataResourceXml.createWithNoNamespace(
root.resolve("res/values/attr.xml"), IdXmlResourceValue.of())); // value
assertThat(dataSet.getAssets()).isEmpty();
}
@Test
public void xmlAndFileResourcesConflict() throws Exception {
Path root = fs.getPath("root");
Path otherRoot = fs.getPath("otherRoot");
DependencyAndroidData dep =
AndroidDataBuilder.of(root)
.addResourceBinary("drawable/menu.png", Files.createFile(fs.getPath("menu1.png")))
.addValuesWithAttributes(
"values/attr.xml",
ImmutableMap.of("foo", "fooVal"),
"<string name='exit'>way out</string>",
"<item name='some_id' type='id'/>")
.createManifest("AndroidManifest.xml", "com.google.foo", "")
.buildDependency();
DependencyAndroidData otherDep =
AndroidDataBuilder.of(otherRoot)
.addResourceBinary("drawable/menu.png", Files.createFile(fs.getPath("menu2.png")))
.addValuesWithAttributes(
"values/attr.xml",
ImmutableMap.of("foo", "fooVal"),
"<string name='exit'>way out</string>",
"<item name='some_id' type='id'/>")
.createManifest("AndroidManifest.xml", "com.google.foo", "")
.buildDependency();
ParsedAndroidData dataSet = ParsedAndroidData.from(ImmutableList.of(dep, otherDep));
FullyQualifiedName drawableMenu = fqnFactory.parse("drawable/menu");
FullyQualifiedName stringExit = fqnFactory.parse("string/exit");
FullyQualifiedName attributeFoo = fqnFactory.parse("<resources>/foo");
DataSource rootDrawableMenuPath =
DataSource.of(DependencyInfo.UNKNOWN, root.resolve("res/drawable/menu.png"));
DataSource otherRootDrawableMenuPath =
DataSource.of(DependencyInfo.UNKNOWN, otherRoot.resolve("res/drawable/menu.png"));
DataSource rootValuesPath =
DataSource.of(DependencyInfo.UNKNOWN, root.resolve("res/values/attr.xml"));
DataSource otherRootValuesPath =
DataSource.of(DependencyInfo.UNKNOWN, otherRoot.resolve("res/values/attr.xml"));
FullyQualifiedName idSomeId = fqnFactory.parse("id/some_id");
Truth.assertAbout(parsedAndroidData)
.that(dataSet)
.isEqualTo(
ParsedAndroidData.of(
ImmutableSet.of(
MergeConflict.of(
drawableMenu,
DataValueFile.of(
Visibility.UNKNOWN,
rootDrawableMenuPath,
/*fingerprint=*/ null,
/*rootXmlNode=*/ null),
DataValueFile.of(
Visibility.UNKNOWN,
otherRootDrawableMenuPath,
/*fingerprint=*/ null,
/*rootXmlNode=*/ null)),
MergeConflict.of(
stringExit,
DataResourceXml.createWithNoNamespace(
rootValuesPath,
SimpleXmlResourceValue.createWithValue(
SimpleXmlResourceValue.Type.STRING, "way out")),
DataResourceXml.createWithNoNamespace(
otherRootValuesPath,
SimpleXmlResourceValue.createWithValue(
SimpleXmlResourceValue.Type.STRING, "way out"))),
MergeConflict.of(
attributeFoo,
DataResourceXml.createWithNoNamespace(
rootValuesPath, ResourcesAttribute.of(attributeFoo, "foo", "fooVal")),
DataResourceXml.createWithNoNamespace(
otherRootValuesPath,
ResourcesAttribute.of(attributeFoo, "foo", "fooVal")))),
ImmutableMap.<DataKey, DataResource>of(
drawableMenu, // key
DataValueFile.of(
Visibility.UNKNOWN,
otherRootDrawableMenuPath.overwrite(rootDrawableMenuPath),
/*fingerprint=*/ null,
/*rootXmlNode=*/ null), // value
attributeFoo, // key
DataResourceXml.createWithNoNamespace(
otherRootValuesPath.overwrite(rootValuesPath),
ResourcesAttribute.of(attributeFoo, "foo", "fooVal")), // value
stringExit, // key
DataResourceXml.createWithNoNamespace(
otherRootValuesPath.overwrite(rootValuesPath),
SimpleXmlResourceValue.createWithValue(
SimpleXmlResourceValue.Type.STRING, /* value= */ "way out"))),
ImmutableMap.<DataKey, DataResource>of(
idSomeId, // key
DataResourceXml.createWithNoNamespace(
rootValuesPath, IdXmlResourceValue.of()) // value
),
ImmutableMap.<DataKey, DataAsset>of()));
}
@Test
public void layoutWithIDsForwardDeclared() throws Exception {
Path root = fs.getPath("root");
ParsedAndroidData dataSet =
AndroidDataBuilder.of(root)
.addResource(
"layout/some_layout.xml",
AndroidDataBuilder.ResourceType.LAYOUT,
"<TextView android:id=\"@+id/MyTextView\"",
" android:text=\"@string/walrus\"",
// Test forward declaration in an attribute other than android:id
" android:layout_above=\"@+id/AnotherTextView\"",
" android:layout_width=\"wrap_content\"",
" android:layout_height=\"wrap_content\" />",
"<TextView android:id=\"@id/AnotherTextView\"",
" android:text=\"@string/walrus\"",
// Test redundantly having a "+id/MyTextView" in a different attribute.
" android:layout_below=\"@+id/MyTextView\"",
" android:layout_width=\"wrap_content\"",
" android:layout_height=\"wrap_content\" />")
.addResource(
"values/strings.xml",
AndroidDataBuilder.ResourceType.VALUE,
"<string name=\"walrus\">I has a bucket</string>")
.createManifest("AndroidManifest.xml", "com.carroll.lewis", "")
.buildParsed();
FullyQualifiedName layoutKey = fqnFactory.parse("layout/some_layout");
FullyQualifiedName stringKey = fqnFactory.parse("string/walrus");
Path layoutPath = root.resolve("res/layout/some_layout.xml");
Path valuesPath = root.resolve("res/values/strings.xml");
assertThat(dataSet.getOverwritingResources())
.containsExactly(
layoutKey, DataValueFile.of(layoutPath),
stringKey,
DataResourceXml.createWithNoNamespace(
valuesPath,
SimpleXmlResourceValue.createWithValue(
SimpleXmlResourceValue.Type.STRING, "I has a bucket")));
FullyQualifiedName idTextView = fqnFactory.parse("id/MyTextView");
FullyQualifiedName idTextView2 = fqnFactory.parse("id/AnotherTextView");
assertThat(dataSet.getCombiningResources())
.containsExactly(
idTextView, DataResourceXml.createWithNoNamespace(layoutPath, IdXmlResourceValue.of()),
idTextView2,
DataResourceXml.createWithNoNamespace(layoutPath, IdXmlResourceValue.of()));
}
@Test
public void databindingWithIDs() throws Exception {
Path root = fs.getPath("root");
ParsedAndroidData dataSet =
AndroidDataBuilder.of(root)
.addResource(
"layout/databinding_layout.xml",
AndroidDataBuilder.ResourceType.UNFORMATTED,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>",
"<layout xmlns:android=\"http://schemas.android.com/apk/res/android\">",
" <data>",
" <variable name=\"handlers\" type=\"com.xyz.Handlers\"/>",
" <variable name=\"user\" type=\"com.xyz.User\"/>",
" </data>",
" <LinearLayout",
" android:orientation=\"vertical\"",
" android:layout_width=\"match_parent\"",
" android:layout_height=\"match_parent\">",
" <TextView android:id=\"@+id/MyTextView\"",
" android:text=\"@{user.id}\"",
" android:layout_width=\"wrap_content\"",
" android:layout_height=\"wrap_content\"",
" android:onClick=\"@{user.isX ? handlers.onClickX : handlers.onClickY}\"",
" />",
" <TextView android:id=\"@+id/AnotherTextView\"",
" android:text=\"@{user.nickName}\"",
" android:layout_width=\"wrap_content\"",
" android:layout_height=\"wrap_content\" />",
" </LinearLayout>",
"</layout>")
.createManifest("AndroidManifest.xml", "com.xyz", "")
.buildParsed();
FullyQualifiedName layoutKey = fqnFactory.parse("layout/databinding_layout");
Path layoutPath = root.resolve("res/layout/databinding_layout.xml");
assertThat(dataSet.getOverwritingResources())
.containsExactly(layoutKey, DataValueFile.of(layoutPath));
FullyQualifiedName idTextView = fqnFactory.parse("id/MyTextView");
FullyQualifiedName idTextView2 = fqnFactory.parse("id/AnotherTextView");
assertThat(dataSet.getCombiningResources())
.containsExactly(
idTextView, DataResourceXml.createWithNoNamespace(layoutPath, IdXmlResourceValue.of()),
idTextView2,
DataResourceXml.createWithNoNamespace(layoutPath, IdXmlResourceValue.of()));
}
@Test
public void menuWithByteOrderMark() throws Exception {
Path root = fs.getPath("root");
ParsedAndroidData dataSet =
AndroidDataBuilder.of(root)
.addResource(
"menu/some_menu.xml",
AndroidDataBuilder.ResourceType.UNFORMATTED,
"\uFEFF<?xml version=\"1.0\" encoding=\"utf-8\"?>",
"<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">",
" <group android:id=\"@+id/some_group\"",
" android:checkableBehavior=\"none\"",
" android:visible=\"true\"",
" android:enabled=\"true\"",
" android:menuCategory=\"container\"",
" android:orderInCategory=\"50\" >",
" <item",
" android:orderInCategory=\"100\"",
" android:showAsAction=\"never\"",
" android:id=\"@+id/action_settings\"",
" android:title=\"foo\"",
" />",
" </group>",
"</menu>")
.createManifest("AndroidManifest.xml", "com.xyz", "")
.buildParsed();
FullyQualifiedName menuKey = fqnFactory.parse("menu/some_menu");
Path menuPath = root.resolve("res/menu/some_menu.xml");
assertThat(dataSet.getOverwritingResources())
.containsExactly(menuKey, DataValueFile.of(menuPath));
FullyQualifiedName groupIdKey = fqnFactory.parse("id/some_group");
FullyQualifiedName itemIdKey = fqnFactory.parse("id/action_settings");
assertThat(dataSet.getCombiningResources())
.containsExactly(
groupIdKey, DataResourceXml.createWithNoNamespace(menuPath, IdXmlResourceValue.of()),
itemIdKey, DataResourceXml.createWithNoNamespace(menuPath, IdXmlResourceValue.of()));
}
@Test
public void drawableWithIDs() throws Exception {
Path root = fs.getPath("root");
ParsedAndroidData dataSet =
AndroidDataBuilder.of(root)
.addResource(
"drawable-v21/some_drawable.xml",
AndroidDataBuilder.ResourceType.UNFORMATTED,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>",
"<animated-selector",
" xmlns:android=\"http://schemas.android.com/apk/res/android\">",
" <item android:id=\"@+id/focused_state\"",
" android:drawable=\"@drawable/a_drawable\"",
" android:state_focused=\"true\"/>",
" <item android:id=\"@+id/default_state\"",
" android:drawable=\"@drawable/a_drawable\"/>",
" <!-- specify a transition -->",
" <transition",
" android:fromId=\"@id/default_state\"",
// test forward decl
" android:toId=\"@+id/pressed_state\">",
" <animation-list>",
" <item android:duration=\"15\" android:drawable=\"@drawable/a_drawable\"/>",
" <item android:duration=\"15\" android:drawable=\"@drawable/a_drawable\"/>",
" </animation-list>",
" </transition>",
" <item android:id=\"@id/pressed_state\"",
" android:drawable=\"@drawable/a_drawable\"",
" android:state_pressed=\"true\"/>",
"</animated-selector>")
.createManifest("AndroidManifest.xml", "com.carroll.lewis", "")
.buildParsed();
FullyQualifiedName.Factory fqnV21Factory =
FullyQualifiedName.Factory.from(ImmutableList.<String>of("v21"));
FullyQualifiedName drawableKey = fqnV21Factory.parse("drawable/some_drawable");
Path drawablePath = root.resolve("res/drawable-v21/some_drawable.xml");
assertThat(dataSet.getOverwritingResources())
.containsExactly(drawableKey, DataValueFile.of(drawablePath));
FullyQualifiedName focusedState = fqnV21Factory.parse("id/focused_state");
FullyQualifiedName defaultState = fqnV21Factory.parse("id/default_state");
FullyQualifiedName pressedState = fqnV21Factory.parse("id/pressed_state");
assertThat(dataSet.getCombiningResources())
.containsExactly(
focusedState,
DataResourceXml.createWithNoNamespace(drawablePath, IdXmlResourceValue.of()),
defaultState,
DataResourceXml.createWithNoNamespace(drawablePath, IdXmlResourceValue.of()),
pressedState,
DataResourceXml.createWithNoNamespace(drawablePath, IdXmlResourceValue.of()));
}
@Test
public void testParseErrorsHaveContext() throws Exception {
Path root = fs.getPath("root");
AndroidDataBuilder builder =
AndroidDataBuilder.of(root)
.addResource(
"values/unique_strings.xml",
AndroidDataBuilder.ResourceType.VALUE,
"<string name=\"hello\">Hello</string>",
"<not_string name=\"invalid_string\">Unrecognized tag</not_string>")
.addResource(
"layout/unique_layout.xml",
AndroidDataBuilder.ResourceType.UNFORMATTED,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>",
"<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"",
" android:orientation=\"vertical\"",
" android:layout_width=\"match_parent\"",
" android:layout_height=\"match_parent\">",
"</WrongCloseTag>")
.addResource(
"menu/unique_menu.xml",
AndroidDataBuilder.ResourceType.UNFORMATTED,
"<?xml version=\"not_a_version\" encoding=\"utf-8\"?>",
"<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">",
"</menu>")
.createManifest("AndroidManifest.xml", "com.xyz", "");
MergingException e = assertThrows(MergingException.class, () -> builder.buildParsed());
assertThat(e)
.hasMessageThat()
.isEqualTo(MergingException.withMessage("3 Parse Error(s)").getMessage());
String combinedSuberrors = Joiner.on('\n').join(e.getSuppressed());
assertThat(combinedSuberrors)
.contains(fs.getPath("values/unique_strings.xml") + ": ParseError at [row,col]:[3,35]");
assertThat(combinedSuberrors)
.contains("unrecognized resource type: <not_string name='invalid_string'>");
assertThat(combinedSuberrors)
.contains(fs.getPath("layout/unique_layout.xml") + ": ParseError at [row,col]:[6,3]");
assertThat(combinedSuberrors).contains("must be terminated by the matching end-tag");
assertThat(combinedSuberrors)
.contains(fs.getPath("menu/unique_menu.xml") + ": ParseError at [row,col]:[1,30]");
assertThat(combinedSuberrors)
.contains("XML version \"not_a_version\" is not supported, only XML 1.0 is supported");
}
@Test
public void publicResourceValidation() throws Exception {
Path root = fs.getPath("root");
AndroidDataBuilder builder =
AndroidDataBuilder.of(root)
.addResource(
"values/missing_name.xml",
AndroidDataBuilder.ResourceType.VALUE,
"<public flame=\"not_name\" type=\"string\" id=\"0x7f050000\" />")
.addResource(
"values/missing_type.xml",
AndroidDataBuilder.ResourceType.VALUE,
"<public name=\"yarn\" id=\"0x7f050000\" />")
.addResource(
"values/bad_type.xml",
AndroidDataBuilder.ResourceType.VALUE,
"<public name=\"twine\" type=\"tring\" id=\"0x7f050000\" />")
.addResource(
"values/invalid_id.xml",
AndroidDataBuilder.ResourceType.VALUE,
"<public name=\"wire\" type=\"string\" id=\"0z7f050000\" />")
.addResource(
"values/overflow_id.xml",
AndroidDataBuilder.ResourceType.VALUE,
"<public name=\"hemp\" type=\"string\" id=\"0x7f0500000\" />")
.createManifest("AndroidManifest.xml", "com.carroll.lewis", "");
MergingException e = assertThrows(MergingException.class, () -> builder.buildParsed());
assertThat(e)
.hasMessageThat()
.isEqualTo(MergingException.withMessage("5 Parse Error(s)").getMessage());
String combinedSuberrors = Joiner.on('\n').join(e.getSuppressed());
assertThat(combinedSuberrors).contains(fs.getPath("values/missing_name.xml").toString());
assertThat(combinedSuberrors).contains("resource name is required for public");
assertThat(combinedSuberrors).contains(fs.getPath("values/missing_type.xml").toString());
assertThat(combinedSuberrors).contains("missing type attribute");
assertThat(combinedSuberrors).contains(fs.getPath("values/bad_type.xml").toString());
assertThat(combinedSuberrors).contains("has invalid type attribute");
assertThat(combinedSuberrors).contains(fs.getPath("values/invalid_id.xml").toString());
assertThat(combinedSuberrors).contains("has invalid id number");
assertThat(combinedSuberrors).contains(fs.getPath("values/overflow_id.xml").toString());
assertThat(combinedSuberrors).contains("has invalid id number");
}
final Subject.Factory<ParsedAndroidDataSubject, ParsedAndroidData> parsedAndroidData =
ParsedAndroidDataSubject::new;
}