// 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 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.FailureStrategy; import com.google.common.truth.SubjectFactory; 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.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.Assert; 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(root.resolve("assets/bin/boojum")); DataSource otherAssetSource = DataSource.of(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(assetSource), DataValueFile.of(otherAssetSource))), ImmutableMap.<DataKey, DataResource>of(), ImmutableMap.<DataKey, DataResource>of(), ImmutableMap.<DataKey, DataAsset>of( key, DataValueFile.of(otherAssetSource.overwrite(assetSource))))); } @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(root.resolve("res/drawable/menu.png")); DataSource otherRootDrawableMenuPath = DataSource.of(otherRoot.resolve("res/drawable/menu.png")); DataSource rootValuesPath = DataSource.of(root.resolve("res/values/attr.xml")); DataSource otherRootValuesPath = DataSource.of(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(rootDrawableMenuPath), DataValueFile.of(otherRootDrawableMenuPath)), 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( otherRootDrawableMenuPath.overwrite(rootDrawableMenuPath)), // 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, "way out")) // value ), 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", ""); try { builder.buildParsed(); Assert.fail("expected MergingException"); } catch (MergingException e) { assertThat(e).hasMessageThat().isEqualTo("3 Parse Error(s)"); String combinedSuberrors = Joiner.on('\n').join(e.getSuppressed()); assertThat(combinedSuberrors) .contains("values/unique_strings.xml: ParseError at [row,col]:[3,35]"); assertThat(combinedSuberrors) .contains("unrecognized resource type: <not_string name='invalid_string'>"); assertThat(combinedSuberrors) .contains("layout/unique_layout.xml: ParseError at [row,col]:[6,3]"); assertThat(combinedSuberrors) .contains("must be terminated by the matching end-tag"); assertThat(combinedSuberrors) .contains("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", ""); try { builder.buildParsed(); Assert.fail("expected exception"); } catch (MergingException e) { assertThat(e).hasMessageThat().isEqualTo("5 Parse Error(s)"); String combinedSuberrors = Joiner.on('\n').join(e.getSuppressed()); assertThat(combinedSuberrors).contains("values/missing_name.xml"); assertThat(combinedSuberrors).contains("resource name is required for public"); assertThat(combinedSuberrors).contains("values/missing_type.xml"); assertThat(combinedSuberrors).contains("missing type attribute"); assertThat(combinedSuberrors).contains("values/bad_type.xml"); assertThat(combinedSuberrors).contains("has invalid type attribute"); assertThat(combinedSuberrors).contains("values/invalid_id.xml"); assertThat(combinedSuberrors).contains("has invalid id number"); assertThat(combinedSuberrors).contains("values/overflow_id.xml"); assertThat(combinedSuberrors).contains("has invalid id number"); } } final SubjectFactory<ParsedAndroidDataSubject, ParsedAndroidData> parsedAndroidData = new SubjectFactory<ParsedAndroidDataSubject, ParsedAndroidData>() { @Override public ParsedAndroidDataSubject getSubject(FailureStrategy fs, ParsedAndroidData that) { return new ParsedAndroidDataSubject(fs, that); } }; }