| /* | |
| * Copyright (C) 2015 The Android Open Source Project | |
| * | |
| * 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. | |
| */ | |
| #include "ResourceParser.h" | |
| #include <sstream> | |
| #include <string> | |
| #include "ResourceTable.h" | |
| #include "ResourceUtils.h" | |
| #include "ResourceValues.h" | |
| #include "io/StringStream.h" | |
| #include "test/Test.h" | |
| #include "xml/XmlPullParser.h" | |
| using ::aapt::io::StringInputStream; | |
| using ::aapt::test::StrValueEq; | |
| using ::aapt::test::ValueEq; | |
| using ::android::ConfigDescription; | |
| using ::android::Res_value; | |
| using ::android::ResTable_map; | |
| using ::android::StringPiece; | |
| using ::testing::Eq; | |
| using ::testing::IsEmpty; | |
| using ::testing::IsNull; | |
| using ::testing::NotNull; | |
| using ::testing::Pointee; | |
| using ::testing::SizeIs; | |
| using ::testing::StrEq; | |
| using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; | |
| namespace aapt { | |
| constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; | |
| TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) { | |
| std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); | |
| ResourceTable table; | |
| ResourceParser parser(context->GetDiagnostics(), &table, android::Source{"test"}, {}); | |
| std::string input = kXmlPreamble; | |
| input += R"(<attr name="foo"/>)"; | |
| StringInputStream in(input); | |
| xml::XmlPullParser xml_parser(&in); | |
| ASSERT_FALSE(parser.Parse(&xml_parser)); | |
| } | |
| class ResourceParserTest : public ::testing::Test { | |
| public: | |
| void SetUp() override { | |
| context_ = test::ContextBuilder().Build(); | |
| } | |
| ::testing::AssertionResult TestParse(StringPiece str) { | |
| return TestParse(str, ConfigDescription{}); | |
| } | |
| ::testing::AssertionResult TestParse(StringPiece str, const ConfigDescription& config) { | |
| ResourceParserOptions parserOptions; | |
| ResourceParser parser(context_->GetDiagnostics(), &table_, android::Source{"test"}, config, | |
| parserOptions); | |
| std::string input = kXmlPreamble; | |
| input += "<resources>\n"; | |
| input.append(str.data(), str.size()); | |
| input += "\n</resources>"; | |
| StringInputStream in(input); | |
| xml::XmlPullParser xmlParser(&in); | |
| if (parser.Parse(&xmlParser)) { | |
| return ::testing::AssertionSuccess(); | |
| } | |
| return ::testing::AssertionFailure(); | |
| } | |
| protected: | |
| ResourceTable table_; | |
| std::unique_ptr<IAaptContext> context_; | |
| }; | |
| TEST_F(ResourceParserTest, ParseQuotedString) { | |
| ASSERT_TRUE(TestParse(R"(<string name="foo"> " hey there " </string>)")); | |
| String* str = test::GetValue<String>(&table_, "string/foo"); | |
| ASSERT_THAT(str, NotNull()); | |
| EXPECT_THAT(*str, StrValueEq(" hey there ")); | |
| EXPECT_THAT(str->untranslatable_sections, IsEmpty()); | |
| ASSERT_TRUE(TestParse(R"(<string name="bar">Isn\'t it cool?</string>)")); | |
| str = test::GetValue<String>(&table_, "string/bar"); | |
| ASSERT_THAT(str, NotNull()); | |
| EXPECT_THAT(*str, StrValueEq("Isn't it cool?")); | |
| ASSERT_TRUE(TestParse(R"(<string name="baz">"Isn't it cool?"</string>)")); | |
| str = test::GetValue<String>(&table_, "string/baz"); | |
| ASSERT_THAT(str, NotNull()); | |
| EXPECT_THAT(*str, StrValueEq("Isn't it cool?")); | |
| } | |
| TEST_F(ResourceParserTest, ParseEscapedString) { | |
| ASSERT_TRUE(TestParse(R"(<string name="foo">\?123</string>)")); | |
| String* str = test::GetValue<String>(&table_, "string/foo"); | |
| ASSERT_THAT(str, NotNull()); | |
| EXPECT_THAT(*str, StrValueEq("?123")); | |
| EXPECT_THAT(str->untranslatable_sections, IsEmpty()); | |
| ASSERT_TRUE(TestParse(R"(<string name="bar">This isn\’t a bad string</string>)")); | |
| str = test::GetValue<String>(&table_, "string/bar"); | |
| ASSERT_THAT(str, NotNull()); | |
| EXPECT_THAT(*str, StrValueEq("This isn’t a bad string")); | |
| } | |
| TEST_F(ResourceParserTest, ParseFormattedString) { | |
| ASSERT_FALSE(TestParse(R"(<string name="foo">%d %s</string>)")); | |
| ASSERT_TRUE(TestParse(R"(<string name="foo">%1$d %2$s</string>)")); | |
| } | |
| TEST_F(ResourceParserTest, ParseStyledString) { | |
| // Use a surrogate pair unicode point so that we can verify that the span | |
| // indices use UTF-16 length and not UTF-8 length. | |
| std::string input = | |
| "<string name=\"foo\">This is my aunt\u2019s <b>fickle <small>string</small></b></string>"; | |
| ASSERT_TRUE(TestParse(input)); | |
| StyledString* str = test::GetValue<StyledString>(&table_, "string/foo"); | |
| ASSERT_THAT(str, NotNull()); | |
| EXPECT_THAT(str->value->value, StrEq("This is my aunt\u2019s fickle string")); | |
| EXPECT_THAT(str->value->spans, SizeIs(2)); | |
| EXPECT_THAT(str->untranslatable_sections, IsEmpty()); | |
| EXPECT_THAT(*str->value->spans[0].name, StrEq("b")); | |
| EXPECT_THAT(str->value->spans[0].first_char, Eq(18u)); | |
| EXPECT_THAT(str->value->spans[0].last_char, Eq(30u)); | |
| EXPECT_THAT(*str->value->spans[1].name, StrEq("small")); | |
| EXPECT_THAT(str->value->spans[1].first_char, Eq(25u)); | |
| EXPECT_THAT(str->value->spans[1].last_char, Eq(30u)); | |
| } | |
| TEST_F(ResourceParserTest, ParseStringWithWhitespace) { | |
| ASSERT_TRUE(TestParse(R"(<string name="foo"> This is what I think </string>)")); | |
| String* str = test::GetValue<String>(&table_, "string/foo"); | |
| ASSERT_THAT(str, NotNull()); | |
| EXPECT_THAT(*str->value, StrEq("This is what I think")); | |
| EXPECT_THAT(str->untranslatable_sections, IsEmpty()); | |
| ASSERT_TRUE(TestParse(R"(<string name="foo2">" This is what I think "</string>)")); | |
| str = test::GetValue<String>(&table_, "string/foo2"); | |
| ASSERT_THAT(str, NotNull()); | |
| EXPECT_THAT(*str, StrValueEq(" This is what I think ")); | |
| } | |
| TEST_F(ResourceParserTest, ParseStringTruncateASCII) { | |
| // Tuncate leading and trailing whitespace | |
| EXPECT_TRUE(TestParse(R"(<string name="foo"> Hello </string>)")); | |
| String* str = test::GetValue<String>(&table_, "string/foo"); | |
| ASSERT_THAT(str, NotNull()); | |
| EXPECT_THAT(*str->value, StrEq("Hello")); | |
| EXPECT_THAT(str->untranslatable_sections, IsEmpty()); | |
| // AAPT does not truncate unicode whitespace | |
| EXPECT_TRUE(TestParse(R"(<string name="foo2">\u0020\Hello\u0020</string>)")); | |
| str = test::GetValue<String>(&table_, "string/foo2"); | |
| ASSERT_THAT(str, NotNull()); | |
| EXPECT_THAT(*str->value, StrEq(" Hello ")); | |
| EXPECT_THAT(str->untranslatable_sections, IsEmpty()); | |
| // Preserve non-ASCII whitespace including extended ASCII characters | |
| EXPECT_TRUE(TestParse(R"(<string name="foo3"> Hello World </string>)")); | |
| str = test::GetValue<String>(&table_, "string/foo3"); | |
| ASSERT_THAT(str, NotNull()); | |
| EXPECT_THAT(*str->value, StrEq("\xC2\xA0Hello\xE2\x80\xAFWorld\xC2\xA0")); | |
| EXPECT_THAT(str->untranslatable_sections, IsEmpty()); | |
| EXPECT_TRUE(TestParse(R"(<string name="foo4">2005年6月1日</string>)")); | |
| str = test::GetValue<String>(&table_, "string/foo4"); | |
| ASSERT_THAT(str, NotNull()); | |
| EXPECT_THAT(*str->value, StrEq("2005年6月1日")); | |
| EXPECT_THAT(str->untranslatable_sections, IsEmpty()); | |
| } | |
| TEST_F(ResourceParserTest, ParseStyledStringWithWhitespace) { | |
| std::string input = R"(<string name="foo"> <b> My <i> favorite</i> string </b> </string>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| StyledString* str = test::GetValue<StyledString>(&table_, "string/foo"); | |
| ASSERT_THAT(str, NotNull()); | |
| EXPECT_THAT(str->value->value, StrEq(" My favorite string ")); | |
| EXPECT_THAT(str->untranslatable_sections, IsEmpty()); | |
| ASSERT_THAT(str->value->spans, SizeIs(2u)); | |
| EXPECT_THAT(*str->value->spans[0].name, StrEq("b")); | |
| EXPECT_THAT(str->value->spans[0].first_char, Eq(1u)); | |
| EXPECT_THAT(str->value->spans[0].last_char, Eq(21u)); | |
| EXPECT_THAT(*str->value->spans[1].name, StrEq("i")); | |
| EXPECT_THAT(str->value->spans[1].first_char, Eq(5u)); | |
| EXPECT_THAT(str->value->spans[1].last_char, Eq(13u)); | |
| } | |
| TEST_F(ResourceParserTest, ParseStringTranslatableAttribute) { | |
| // If there is no translate attribute the default is 'true' | |
| EXPECT_TRUE(TestParse(R"(<string name="foo1">Translate</string>)")); | |
| String* str = test::GetValue<String>(&table_, "string/foo1"); | |
| ASSERT_THAT(str, NotNull()); | |
| ASSERT_TRUE(str->IsTranslatable()); | |
| // Explicit 'true' translate attribute | |
| EXPECT_TRUE(TestParse(R"(<string name="foo2" translatable="true">Translate</string>)")); | |
| str = test::GetValue<String>(&table_, "string/foo2"); | |
| ASSERT_THAT(str, NotNull()); | |
| ASSERT_TRUE(str->IsTranslatable()); | |
| // Explicit 'false' translate attribute | |
| EXPECT_TRUE(TestParse(R"(<string name="foo3" translatable="false">Do not translate</string>)")); | |
| str = test::GetValue<String>(&table_, "string/foo3"); | |
| ASSERT_THAT(str, NotNull()); | |
| ASSERT_FALSE(str->IsTranslatable()); | |
| // Invalid value for the translate attribute, should be boolean ('true' or 'false') | |
| EXPECT_FALSE(TestParse(R"(<string name="foo4" translatable="yes">Translate</string>)")); | |
| } | |
| TEST_F(ResourceParserTest, IgnoreXliffTagsOtherThanG) { | |
| std::string input = R"( | |
| <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> | |
| There are <xliff:source>no</xliff:source> apples</string>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| String* str = test::GetValue<String>(&table_, "string/foo"); | |
| ASSERT_THAT(str, NotNull()); | |
| EXPECT_THAT(*str, StrValueEq("There are no apples")); | |
| EXPECT_THAT(str->untranslatable_sections, IsEmpty()); | |
| } | |
| TEST_F(ResourceParserTest, NestedXliffGTagsAreIllegal) { | |
| std::string input = R"( | |
| <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> | |
| Do not <xliff:g>translate <xliff:g>this</xliff:g></xliff:g></string>)"; | |
| EXPECT_FALSE(TestParse(input)); | |
| } | |
| TEST_F(ResourceParserTest, RecordUntranslateableXliffSectionsInString) { | |
| std::string input = R"( | |
| <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> | |
| There are <xliff:g id="count">%1$d</xliff:g> apples</string>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| String* str = test::GetValue<String>(&table_, "string/foo"); | |
| ASSERT_THAT(str, NotNull()); | |
| EXPECT_THAT(*str, StrValueEq("There are %1$d apples")); | |
| ASSERT_THAT(str->untranslatable_sections, SizeIs(1)); | |
| EXPECT_THAT(str->untranslatable_sections[0].start, Eq(10u)); | |
| EXPECT_THAT(str->untranslatable_sections[0].end, Eq(14u)); | |
| } | |
| TEST_F(ResourceParserTest, RecordUntranslateableXliffSectionsInStyledString) { | |
| std::string input = R"( | |
| <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> | |
| There are <b><xliff:g id="count">%1$d</xliff:g></b> apples</string>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| StyledString* str = test::GetValue<StyledString>(&table_, "string/foo"); | |
| ASSERT_THAT(str, NotNull()); | |
| EXPECT_THAT(str->value->value, Eq(" There are %1$d apples")); | |
| ASSERT_THAT(str->untranslatable_sections, SizeIs(1)); | |
| EXPECT_THAT(str->untranslatable_sections[0].start, Eq(11u)); | |
| EXPECT_THAT(str->untranslatable_sections[0].end, Eq(15u)); | |
| ASSERT_THAT(str->value->spans, SizeIs(1u)); | |
| EXPECT_THAT(*str->value->spans[0].name, StrEq("b")); | |
| EXPECT_THAT(str->value->spans[0].first_char, Eq(11u)); | |
| EXPECT_THAT(str->value->spans[0].last_char, Eq(14u)); | |
| } | |
| TEST_F(ResourceParserTest, ParseNull) { | |
| std::string input = R"(<integer name="foo">@null</integer>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| // The Android runtime treats a value of android::Res_value::TYPE_NULL as | |
| // a non-existing value, and this causes problems in styles when trying to | |
| // resolve an attribute. Null values must be encoded as android::Res_value::TYPE_REFERENCE | |
| // with a data value of 0. | |
| Reference* null_ref = test::GetValue<Reference>(&table_, "integer/foo"); | |
| ASSERT_THAT(null_ref, NotNull()); | |
| EXPECT_FALSE(null_ref->name); | |
| EXPECT_FALSE(null_ref->id); | |
| EXPECT_THAT(null_ref->reference_type, Eq(Reference::Type::kResource)); | |
| } | |
| TEST_F(ResourceParserTest, ParseEmpty) { | |
| std::string input = R"(<integer name="foo">@empty</integer>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| BinaryPrimitive* integer = test::GetValue<BinaryPrimitive>(&table_, "integer/foo"); | |
| ASSERT_THAT(integer, NotNull()); | |
| EXPECT_THAT(integer->value.dataType, Eq(Res_value::TYPE_NULL)); | |
| EXPECT_THAT(integer->value.data, Eq(Res_value::DATA_NULL_EMPTY)); | |
| } | |
| TEST_F(ResourceParserTest, ParseAttr) { | |
| std::string input = R"( | |
| <attr name="foo" format="string"/> | |
| <attr name="bar"/>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo"); | |
| ASSERT_THAT(attr, NotNull()); | |
| EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_STRING)); | |
| attr = test::GetValue<Attribute>(&table_, "attr/bar"); | |
| ASSERT_THAT(attr, NotNull()); | |
| EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_ANY)); | |
| } | |
| TEST_F(ResourceParserTest, ParseMacro) { | |
| std::string input = R"(<macro name="foo">12345</macro>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| Macro* macro = test::GetValue<Macro>(&table_, "macro/foo"); | |
| ASSERT_THAT(macro, NotNull()); | |
| EXPECT_THAT(macro->raw_value, Eq("12345")); | |
| EXPECT_THAT(macro->style_string.str, Eq("12345")); | |
| EXPECT_THAT(macro->style_string.spans, IsEmpty()); | |
| EXPECT_THAT(macro->untranslatable_sections, IsEmpty()); | |
| EXPECT_THAT(macro->alias_namespaces, IsEmpty()); | |
| } | |
| TEST_F(ResourceParserTest, ParseMacroUntranslatableSection) { | |
| std::string input = R"(<macro name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> | |
| This being <b><xliff:g>human</xliff:g></b> is a guest house.</macro>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| Macro* macro = test::GetValue<Macro>(&table_, "macro/foo"); | |
| ASSERT_THAT(macro, NotNull()); | |
| EXPECT_THAT(macro->raw_value, Eq("\nThis being human is a guest house.")); | |
| EXPECT_THAT(macro->style_string.str, Eq(" This being human is a guest house.")); | |
| EXPECT_THAT(macro->style_string.spans.size(), Eq(1)); | |
| EXPECT_THAT(macro->style_string.spans[0].name, Eq("b")); | |
| EXPECT_THAT(macro->style_string.spans[0].first_char, Eq(12)); | |
| EXPECT_THAT(macro->style_string.spans[0].last_char, Eq(16)); | |
| ASSERT_THAT(macro->untranslatable_sections.size(), Eq(1)); | |
| EXPECT_THAT(macro->untranslatable_sections[0].start, Eq(12)); | |
| EXPECT_THAT(macro->untranslatable_sections[0].end, Eq(17)); | |
| EXPECT_THAT(macro->alias_namespaces, IsEmpty()); | |
| } | |
| TEST_F(ResourceParserTest, ParseMacroNamespaces) { | |
| std::string input = R"(<macro name="foo" xmlns:app="http://schemas.android.com/apk/res/android"> | |
| @app:string/foo</macro>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| Macro* macro = test::GetValue<Macro>(&table_, "macro/foo"); | |
| ASSERT_THAT(macro, NotNull()); | |
| EXPECT_THAT(macro->raw_value, Eq("\n@app:string/foo")); | |
| EXPECT_THAT(macro->style_string.str, Eq("@app:string/foo")); | |
| EXPECT_THAT(macro->style_string.spans, IsEmpty()); | |
| EXPECT_THAT(macro->untranslatable_sections, IsEmpty()); | |
| EXPECT_THAT(macro->alias_namespaces.size(), Eq(1)); | |
| EXPECT_THAT(macro->alias_namespaces[0].alias, Eq("app")); | |
| EXPECT_THAT(macro->alias_namespaces[0].package_name, Eq("android")); | |
| EXPECT_THAT(macro->alias_namespaces[0].is_private, Eq(false)); | |
| } | |
| TEST_F(ResourceParserTest, ParseMacroReference) { | |
| std::string input = R"(<string name="res_string">@macro/foo</string>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| Reference* macro = test::GetValue<Reference>(&table_, "string/res_string"); | |
| ASSERT_THAT(macro, NotNull()); | |
| EXPECT_THAT(macro->type_flags, Eq(ResTable_map::TYPE_STRING)); | |
| EXPECT_THAT(macro->allow_raw, Eq(false)); | |
| input = R"(<style name="foo"> | |
| <item name="bar">@macro/foo</item> | |
| </style>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| Style* style = test::GetValue<Style>(&table_, "style/foo"); | |
| ASSERT_THAT(style, NotNull()); | |
| EXPECT_THAT(style->entries.size(), Eq(1)); | |
| macro = ValueCast<Reference>(style->entries[0].value.get()); | |
| ASSERT_THAT(macro, NotNull()); | |
| EXPECT_THAT(macro->type_flags, Eq(0U)); | |
| EXPECT_THAT(macro->allow_raw, Eq(true)); | |
| } | |
| TEST_F(ResourceParserTest, ParseMacroNoNameFail) { | |
| std::string input = R"(<macro>12345</macro>)"; | |
| ASSERT_FALSE(TestParse(input)); | |
| } | |
| TEST_F(ResourceParserTest, ParseMacroNonDefaultConfigurationFail) { | |
| const ConfigDescription watch_config = test::ParseConfigOrDie("watch"); | |
| std::string input = R"(<macro name="foo">12345</macro>)"; | |
| ASSERT_FALSE(TestParse(input, watch_config)); | |
| } | |
| // Old AAPT allowed attributes to be defined under different configurations, but ultimately | |
| // stored them with the default configuration. Check that we have the same behavior. | |
| TEST_F(ResourceParserTest, ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig) { | |
| const ConfigDescription watch_config = test::ParseConfigOrDie("watch"); | |
| std::string input = R"( | |
| <attr name="foo" /> | |
| <declare-styleable name="bar"> | |
| <attr name="baz" format="reference"/> | |
| </declare-styleable>)"; | |
| ASSERT_TRUE(TestParse(input, watch_config)); | |
| EXPECT_THAT(test::GetValueForConfig<Attribute>(&table_, "attr/foo", watch_config), IsNull()); | |
| EXPECT_THAT(test::GetValueForConfig<Attribute>(&table_, "attr/baz", watch_config), IsNull()); | |
| EXPECT_THAT(test::GetValueForConfig<Styleable>(&table_, "styleable/bar", watch_config), IsNull()); | |
| EXPECT_THAT(test::GetValue<Attribute>(&table_, "attr/foo"), NotNull()); | |
| EXPECT_THAT(test::GetValue<Attribute>(&table_, "attr/baz"), NotNull()); | |
| EXPECT_THAT(test::GetValue<Styleable>(&table_, "styleable/bar"), NotNull()); | |
| } | |
| TEST_F(ResourceParserTest, ParseAttrWithMinMax) { | |
| std::string input = R"(<attr name="foo" min="10" max="23" format="integer"/>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo"); | |
| ASSERT_THAT(attr, NotNull()); | |
| EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_INTEGER)); | |
| EXPECT_THAT(attr->min_int, Eq(10)); | |
| EXPECT_THAT(attr->max_int, Eq(23)); | |
| } | |
| TEST_F(ResourceParserTest, FailParseAttrWithMinMaxButNotInteger) { | |
| ASSERT_FALSE(TestParse(R"(<attr name="foo" min="10" max="23" format="string"/>)")); | |
| } | |
| TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) { | |
| std::string input = R"( | |
| <declare-styleable name="Styleable"> | |
| <attr name="foo" /> | |
| </declare-styleable> | |
| <attr name="foo" format="string"/>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo"); | |
| ASSERT_THAT(attr, NotNull()); | |
| EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_STRING)); | |
| } | |
| TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) { | |
| std::string input = R"( | |
| <declare-styleable name="Theme"> | |
| <attr name="foo" /> | |
| </declare-styleable> | |
| <declare-styleable name="Window"> | |
| <attr name="foo" format="boolean"/> | |
| </declare-styleable>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo"); | |
| ASSERT_THAT(attr, NotNull()); | |
| EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_BOOLEAN)); | |
| } | |
| TEST_F(ResourceParserTest, ParseEnumAttr) { | |
| std::string input = R"( | |
| <attr name="foo"> | |
| <enum name="bar" value="0"/> | |
| <enum name="bat" value="0x1"/> | |
| <enum name="baz" value="2"/> | |
| </attr>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| Attribute* enum_attr = test::GetValue<Attribute>(&table_, "attr/foo"); | |
| ASSERT_THAT(enum_attr, NotNull()); | |
| EXPECT_THAT(enum_attr->type_mask, Eq(ResTable_map::TYPE_ENUM)); | |
| ASSERT_THAT(enum_attr->symbols, SizeIs(3)); | |
| ASSERT_TRUE(enum_attr->symbols[0].symbol.name); | |
| EXPECT_THAT(enum_attr->symbols[0].symbol.name.value().entry, Eq("bar")); | |
| EXPECT_THAT(enum_attr->symbols[0].value, Eq(0u)); | |
| EXPECT_THAT(enum_attr->symbols[0].type, Eq(Res_value::TYPE_INT_DEC)); | |
| ASSERT_TRUE(enum_attr->symbols[1].symbol.name); | |
| EXPECT_THAT(enum_attr->symbols[1].symbol.name.value().entry, Eq("bat")); | |
| EXPECT_THAT(enum_attr->symbols[1].value, Eq(1u)); | |
| EXPECT_THAT(enum_attr->symbols[1].type, Eq(Res_value::TYPE_INT_HEX)); | |
| ASSERT_TRUE(enum_attr->symbols[2].symbol.name); | |
| EXPECT_THAT(enum_attr->symbols[2].symbol.name.value().entry, Eq("baz")); | |
| EXPECT_THAT(enum_attr->symbols[2].value, Eq(2u)); | |
| EXPECT_THAT(enum_attr->symbols[2].type, Eq(Res_value::TYPE_INT_DEC)); | |
| } | |
| TEST_F(ResourceParserTest, ParseFlagAttr) { | |
| std::string input = R"( | |
| <attr name="foo"> | |
| <flag name="bar" value="0"/> | |
| <flag name="bat" value="1"/> | |
| <flag name="baz" value="2"/> | |
| </attr>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| Attribute* flag_attr = test::GetValue<Attribute>(&table_, "attr/foo"); | |
| ASSERT_THAT(flag_attr, NotNull()); | |
| EXPECT_THAT(flag_attr->type_mask, Eq(ResTable_map::TYPE_FLAGS)); | |
| ASSERT_THAT(flag_attr->symbols, SizeIs(3)); | |
| ASSERT_TRUE(flag_attr->symbols[0].symbol.name); | |
| EXPECT_THAT(flag_attr->symbols[0].symbol.name.value().entry, Eq("bar")); | |
| EXPECT_THAT(flag_attr->symbols[0].value, Eq(0u)); | |
| ASSERT_TRUE(flag_attr->symbols[1].symbol.name); | |
| EXPECT_THAT(flag_attr->symbols[1].symbol.name.value().entry, Eq("bat")); | |
| EXPECT_THAT(flag_attr->symbols[1].value, Eq(1u)); | |
| ASSERT_TRUE(flag_attr->symbols[2].symbol.name); | |
| EXPECT_THAT(flag_attr->symbols[2].symbol.name.value().entry, Eq("baz")); | |
| EXPECT_THAT(flag_attr->symbols[2].value, Eq(2u)); | |
| std::unique_ptr<BinaryPrimitive> flag_value = | |
| ResourceUtils::TryParseFlagSymbol(flag_attr, "baz|bat"); | |
| ASSERT_THAT(flag_value, NotNull()); | |
| EXPECT_THAT(flag_value->value.data, Eq(1u | 2u)); | |
| } | |
| TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) { | |
| std::string input = R"( | |
| <attr name="foo"> | |
| <enum name="bar" value="0"/> | |
| <enum name="bat" value="1"/> | |
| <enum name="bat" value="2"/> | |
| </attr>)"; | |
| ASSERT_FALSE(TestParse(input)); | |
| } | |
| TEST_F(ResourceParserTest, ParseStyle) { | |
| std::string input = R"( | |
| <style name="foo" parent="@style/fu"> | |
| <item name="bar">#ffffffff</item> | |
| <item name="bat">@string/hey</item> | |
| <item name="baz"><b>hey</b></item> | |
| </style>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| Style* style = test::GetValue<Style>(&table_, "style/foo"); | |
| ASSERT_THAT(style, NotNull()); | |
| ASSERT_TRUE(style->parent); | |
| EXPECT_THAT(style->parent.value().name, Eq(test::ParseNameOrDie("style/fu"))); | |
| ASSERT_THAT(style->entries, SizeIs(3)); | |
| EXPECT_THAT(style->entries[0].key.name, Eq(test::ParseNameOrDie("attr/bar"))); | |
| EXPECT_THAT(style->entries[1].key.name, Eq(test::ParseNameOrDie("attr/bat"))); | |
| EXPECT_THAT(style->entries[2].key.name, Eq(test::ParseNameOrDie("attr/baz"))); | |
| } | |
| TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) { | |
| ASSERT_TRUE(TestParse(R"(<style name="foo" parent="com.app:Theme"/>)")); | |
| Style* style = test::GetValue<Style>(&table_, "style/foo"); | |
| ASSERT_THAT(style, NotNull()); | |
| ASSERT_TRUE(style->parent); | |
| EXPECT_THAT(style->parent.value().name, Eq(test::ParseNameOrDie("com.app:style/Theme"))); | |
| } | |
| TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) { | |
| std::string input = R"( | |
| <style xmlns:app="http://schemas.android.com/apk/res/android" | |
| name="foo" parent="app:Theme"/>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| Style* style = test::GetValue<Style>(&table_, "style/foo"); | |
| ASSERT_THAT(style, NotNull()); | |
| ASSERT_TRUE(style->parent); | |
| ASSERT_TRUE(style->parent.value().name); | |
| EXPECT_THAT(style->parent.value().name, Eq(test::ParseNameOrDie("android:style/Theme"))); | |
| } | |
| TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) { | |
| std::string input = R"( | |
| <style xmlns:app="http://schemas.android.com/apk/res/android" name="foo"> | |
| <item name="app:bar">0</item> | |
| </style>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| Style* style = test::GetValue<Style>(&table_, "style/foo"); | |
| ASSERT_THAT(style, NotNull()); | |
| ASSERT_THAT(style->entries, SizeIs(1)); | |
| EXPECT_THAT(style->entries[0].key.name, Eq(test::ParseNameOrDie("android:attr/bar"))); | |
| } | |
| TEST_F(ResourceParserTest, ParseStyleWithRawStringItem) { | |
| std::string input = R"( | |
| <style name="foo"> | |
| <item name="bar"> | |
| com.helloworld.AppClass | |
| </item> | |
| </style>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| Style* style = test::GetValue<Style>(&table_, "style/foo"); | |
| ASSERT_THAT(style, NotNull()); | |
| EXPECT_THAT(style->entries[0].value, NotNull()); | |
| RawString* value = ValueCast<RawString>(style->entries[0].value.get()); | |
| EXPECT_THAT(value, NotNull()); | |
| EXPECT_THAT(*value->value, StrEq(R"(com.helloworld.AppClass)")); | |
| } | |
| TEST_F(ResourceParserTest, ParseStyleWithInferredParent) { | |
| ASSERT_TRUE(TestParse(R"(<style name="foo.bar"/>)")); | |
| Style* style = test::GetValue<Style>(&table_, "style/foo.bar"); | |
| ASSERT_THAT(style, NotNull()); | |
| ASSERT_TRUE(style->parent); | |
| EXPECT_THAT(style->parent.value().name, Eq(test::ParseNameOrDie("style/foo"))); | |
| EXPECT_TRUE(style->parent_inferred); | |
| } | |
| TEST_F(ResourceParserTest, ParseStyleWithInferredParentOverridenByEmptyParentAttribute) { | |
| ASSERT_TRUE(TestParse(R"(<style name="foo.bar" parent=""/>)")); | |
| Style* style = test::GetValue<Style>(&table_, "style/foo.bar"); | |
| ASSERT_THAT(style, NotNull()); | |
| EXPECT_FALSE(style->parent); | |
| EXPECT_FALSE(style->parent_inferred); | |
| } | |
| TEST_F(ResourceParserTest, ParseStyleWithPrivateParentReference) { | |
| ASSERT_TRUE(TestParse(R"(<style name="foo" parent="*android:style/bar" />)")); | |
| Style* style = test::GetValue<Style>(&table_, "style/foo"); | |
| ASSERT_THAT(style, NotNull()); | |
| ASSERT_TRUE(style->parent); | |
| EXPECT_TRUE(style->parent.value().private_reference); | |
| } | |
| TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) { | |
| ASSERT_TRUE(TestParse(R"(<string name="foo">@+id/bar</string>)")); | |
| ASSERT_THAT(test::GetValue<Id>(&table_, "id/bar"), NotNull()); | |
| } | |
| TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) { | |
| std::string input = R"( | |
| <declare-styleable name="foo"> | |
| <attr name="bar" /> | |
| <attr name="bat" format="string|reference"/> | |
| <attr name="baz"> | |
| <enum name="foo" value="1"/> | |
| </attr> | |
| </declare-styleable>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| std::optional<ResourceTable::SearchResult> result = | |
| table_.FindResource(test::ParseNameOrDie("styleable/foo")); | |
| ASSERT_TRUE(result); | |
| EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic)); | |
| Attribute* attr = test::GetValue<Attribute>(&table_, "attr/bar"); | |
| ASSERT_THAT(attr, IsNull()); | |
| attr = test::GetValue<Attribute>(&table_, "attr/bat"); | |
| ASSERT_THAT(attr, NotNull()); | |
| EXPECT_TRUE(attr->IsWeak()); | |
| attr = test::GetValue<Attribute>(&table_, "attr/baz"); | |
| ASSERT_THAT(attr, NotNull()); | |
| EXPECT_TRUE(attr->IsWeak()); | |
| EXPECT_THAT(attr->symbols, SizeIs(1)); | |
| EXPECT_THAT(test::GetValue<Id>(&table_, "id/foo"), NotNull()); | |
| Styleable* styleable = test::GetValue<Styleable>(&table_, "styleable/foo"); | |
| ASSERT_THAT(styleable, NotNull()); | |
| ASSERT_THAT(styleable->entries, SizeIs(3)); | |
| EXPECT_THAT(styleable->entries[0].name, Eq(test::ParseNameOrDie("attr/bar"))); | |
| EXPECT_THAT(styleable->entries[1].name, Eq(test::ParseNameOrDie("attr/bat"))); | |
| EXPECT_THAT(styleable->entries[2].name, Eq(test::ParseNameOrDie("attr/baz"))); | |
| } | |
| TEST_F(ResourceParserTest, ParseDeclareStyleablePreservingVisibility) { | |
| StringInputStream input(R"( | |
| <resources> | |
| <declare-styleable name="foo"> | |
| <attr name="myattr" /> | |
| </declare-styleable> | |
| <declare-styleable name="bar"> | |
| <attr name="myattr" /> | |
| </declare-styleable> | |
| <public type="styleable" name="bar" /> | |
| </resources>)"); | |
| ResourceParser parser(context_->GetDiagnostics(), &table_, android::Source{"test"}, | |
| ConfigDescription::DefaultConfig(), | |
| ResourceParserOptions{.preserve_visibility_of_styleables = true}); | |
| xml::XmlPullParser xml_parser(&input); | |
| ASSERT_TRUE(parser.Parse(&xml_parser)); | |
| EXPECT_EQ( | |
| table_.FindResource(test::ParseNameOrDie("styleable/foo")).value().entry->visibility.level, | |
| Visibility::Level::kUndefined); | |
| EXPECT_EQ( | |
| table_.FindResource(test::ParseNameOrDie("styleable/bar")).value().entry->visibility.level, | |
| Visibility::Level::kPublic); | |
| } | |
| TEST_F(ResourceParserTest, ParsePrivateAttributesDeclareStyleable) { | |
| std::string input = R"( | |
| <declare-styleable xmlns:privAndroid="http://schemas.android.com/apk/prv/res/android" | |
| name="foo"> | |
| <attr name="*android:bar" /> | |
| <attr name="privAndroid:bat" /> | |
| </declare-styleable>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| Styleable* styleable = test::GetValue<Styleable>(&table_, "styleable/foo"); | |
| ASSERT_THAT(styleable, NotNull()); | |
| ASSERT_THAT(styleable->entries, SizeIs(2)); | |
| EXPECT_TRUE(styleable->entries[0].private_reference); | |
| ASSERT_TRUE(styleable->entries[0].name); | |
| EXPECT_THAT(styleable->entries[0].name.value().package, Eq("android")); | |
| EXPECT_TRUE(styleable->entries[1].private_reference); | |
| ASSERT_TRUE(styleable->entries[1].name); | |
| EXPECT_THAT(styleable->entries[1].name.value().package, Eq("android")); | |
| } | |
| TEST_F(ResourceParserTest, ParseArray) { | |
| std::string input = R"( | |
| <array name="foo"> | |
| <item>@string/ref</item> | |
| <item>hey</item> | |
| <item>23</item> | |
| </array>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| Array* array = test::GetValue<Array>(&table_, "array/foo"); | |
| ASSERT_THAT(array, NotNull()); | |
| ASSERT_THAT(array->elements, SizeIs(3)); | |
| EXPECT_THAT(ValueCast<Reference>(array->elements[0].get()), NotNull()); | |
| EXPECT_THAT(ValueCast<String>(array->elements[1].get()), NotNull()); | |
| EXPECT_THAT(ValueCast<BinaryPrimitive>(array->elements[2].get()), NotNull()); | |
| } | |
| TEST_F(ResourceParserTest, ParseStringArray) { | |
| std::string input = R"( | |
| <string-array name="foo"> | |
| <item>"Werk"</item>" | |
| </string-array>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| EXPECT_THAT(test::GetValue<Array>(&table_, "array/foo"), NotNull()); | |
| } | |
| TEST_F(ResourceParserTest, ParseArrayWithFormat) { | |
| std::string input = R"( | |
| <array name="foo" format="string"> | |
| <item>100</item> | |
| </array>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| Array* array = test::GetValue<Array>(&table_, "array/foo"); | |
| ASSERT_THAT(array, NotNull()); | |
| ASSERT_THAT(array->elements, SizeIs(1)); | |
| String* str = ValueCast<String>(array->elements[0].get()); | |
| ASSERT_THAT(str, NotNull()); | |
| EXPECT_THAT(*str, StrValueEq("100")); | |
| } | |
| TEST_F(ResourceParserTest, ParseArrayWithBadFormat) { | |
| std::string input = R"( | |
| <array name="foo" format="integer"> | |
| <item>Hi</item> | |
| </array>)"; | |
| ASSERT_FALSE(TestParse(input)); | |
| } | |
| TEST_F(ResourceParserTest, ParsePlural) { | |
| std::string input = R"( | |
| <plurals name="foo"> | |
| <item quantity="other">apples</item> | |
| <item quantity="one">apple</item> | |
| </plurals>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| Plural* plural = test::GetValue<Plural>(&table_, "plurals/foo"); | |
| ASSERT_THAT(plural, NotNull()); | |
| EXPECT_THAT(plural->values[Plural::Zero], IsNull()); | |
| EXPECT_THAT(plural->values[Plural::Two], IsNull()); | |
| EXPECT_THAT(plural->values[Plural::Few], IsNull()); | |
| EXPECT_THAT(plural->values[Plural::Many], IsNull()); | |
| EXPECT_THAT(plural->values[Plural::One], NotNull()); | |
| EXPECT_THAT(plural->values[Plural::Other], NotNull()); | |
| } | |
| TEST_F(ResourceParserTest, ParseCommentsWithResource) { | |
| std::string input = R"( | |
| <!--This is a comment--> | |
| <string name="foo">Hi</string>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| String* value = test::GetValue<String>(&table_, "string/foo"); | |
| ASSERT_THAT(value, NotNull()); | |
| EXPECT_THAT(value->GetComment(), Eq("This is a comment")); | |
| } | |
| TEST_F(ResourceParserTest, DoNotCombineMultipleComments) { | |
| std::string input = R"( | |
| <!--One--> | |
| <!--Two--> | |
| <string name="foo">Hi</string>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| String* value = test::GetValue<String>(&table_, "string/foo"); | |
| ASSERT_THAT(value, NotNull()); | |
| EXPECT_THAT(value->GetComment(), Eq("Two")); | |
| } | |
| TEST_F(ResourceParserTest, IgnoreCommentBeforeEndTag) { | |
| std::string input = R"( | |
| <!--One--> | |
| <string name="foo"> | |
| Hi | |
| <!--Two--> | |
| </string>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| String* value = test::GetValue<String>(&table_, "string/foo"); | |
| ASSERT_THAT(value, NotNull()); | |
| EXPECT_THAT(value->GetComment(), Eq("One")); | |
| } | |
| TEST_F(ResourceParserTest, ParseNestedComments) { | |
| // We only care about declare-styleable and enum/flag attributes because | |
| // comments from those end up in R.java | |
| std::string input = R"( | |
| <declare-styleable name="foo"> | |
| <!-- The name of the bar --> | |
| <attr name="barName" format="string|reference" /> | |
| </declare-styleable> | |
| <attr name="foo"> | |
| <!-- The very first --> | |
| <enum name="one" value="1" /> | |
| </attr>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| Styleable* styleable = test::GetValue<Styleable>(&table_, "styleable/foo"); | |
| ASSERT_THAT(styleable, NotNull()); | |
| ASSERT_THAT(styleable->entries, SizeIs(1)); | |
| EXPECT_THAT(styleable->entries[0].GetComment(), Eq("The name of the bar")); | |
| Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo"); | |
| ASSERT_THAT(attr, NotNull()); | |
| ASSERT_THAT(attr->symbols, SizeIs(1)); | |
| EXPECT_THAT(attr->symbols[0].symbol.GetComment(), Eq("The very first")); | |
| } | |
| // Declaring an ID as public should not require a separate definition (as an ID has no value). | |
| TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) { | |
| ASSERT_TRUE(TestParse(R"(<public type="id" name="foo"/>)")); | |
| ASSERT_THAT(test::GetValue<Id>(&table_, "id/foo"), NotNull()); | |
| } | |
| TEST_F(ResourceParserTest, KeepAllProducts) { | |
| std::string input = R"( | |
| <string name="foo" product="phone">hi</string> | |
| <string name="foo" product="no-sdcard">ho</string> | |
| <string name="bar" product="">wee</string> | |
| <string name="baz">woo</string> | |
| <string name="bit" product="phablet">hoot</string> | |
| <string name="bot" product="default">yes</string>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| ASSERT_THAT(test::GetValueForConfigAndProduct<String>(&table_, "string/foo", ConfigDescription::DefaultConfig(), "phone"), NotNull()); | |
| ASSERT_THAT(test::GetValueForConfigAndProduct<String>(&table_, "string/foo",ConfigDescription::DefaultConfig(), "no-sdcard"), NotNull()); | |
| ASSERT_THAT(test::GetValueForConfigAndProduct<String>(&table_, "string/bar", ConfigDescription::DefaultConfig(), ""), NotNull()); | |
| ASSERT_THAT(test::GetValueForConfigAndProduct<String>(&table_, "string/baz", ConfigDescription::DefaultConfig(), ""), NotNull()); | |
| ASSERT_THAT(test::GetValueForConfigAndProduct<String>(&table_, "string/bit", ConfigDescription::DefaultConfig(), "phablet"), NotNull()); | |
| ASSERT_THAT(test::GetValueForConfigAndProduct<String>(&table_, "string/bot", ConfigDescription::DefaultConfig(), "default"), NotNull()); | |
| } | |
| TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) { | |
| std::string input = R"( | |
| <public-group type="attr" first-id="0x01010040"> | |
| <public name="foo" /> | |
| <public name="bar" /> | |
| </public-group>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| std::optional<ResourceTable::SearchResult> result = | |
| table_.FindResource(test::ParseNameOrDie("attr/foo")); | |
| ASSERT_TRUE(result); | |
| ASSERT_TRUE(result.value().entry->id); | |
| EXPECT_THAT(result.value().entry->id.value(), Eq(ResourceId(0x01010040))); | |
| result = table_.FindResource(test::ParseNameOrDie("attr/bar")); | |
| ASSERT_TRUE(result); | |
| ASSERT_TRUE(result.value().entry->id); | |
| EXPECT_THAT(result.value().entry->id.value(), Eq(ResourceId(0x01010041))); | |
| } | |
| TEST_F(ResourceParserTest, StagingPublicGroup) { | |
| std::string input = R"( | |
| <staging-public-group type="attr" first-id="0x01ff0049"> | |
| <public name="foo" /> | |
| <public name="bar" /> | |
| </staging-public-group>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| std::optional<ResourceTable::SearchResult> result = | |
| table_.FindResource(test::ParseNameOrDie("attr/foo")); | |
| ASSERT_TRUE(result); | |
| ASSERT_TRUE(result.value().entry->id); | |
| EXPECT_THAT(result.value().entry->id.value(), Eq(ResourceId(0x01ff0049))); | |
| EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic)); | |
| EXPECT_TRUE(result.value().entry->visibility.staged_api); | |
| result = table_.FindResource(test::ParseNameOrDie("attr/bar")); | |
| ASSERT_TRUE(result); | |
| ASSERT_TRUE(result.value().entry->id); | |
| EXPECT_THAT(result.value().entry->id.value(), Eq(ResourceId(0x01ff004a))); | |
| EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic)); | |
| EXPECT_TRUE(result.value().entry->visibility.staged_api); | |
| } | |
| TEST_F(ResourceParserTest, StrongestSymbolVisibilityWins) { | |
| std::string input = R"( | |
| <!-- private --> | |
| <java-symbol type="string" name="foo" /> | |
| <!-- public --> | |
| <public type="string" name="foo" id="0x01020000" /> | |
| <!-- private2 --> | |
| <java-symbol type="string" name="foo" />)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| std::optional<ResourceTable::SearchResult> result = | |
| table_.FindResource(test::ParseNameOrDie("string/foo")); | |
| ASSERT_TRUE(result); | |
| ResourceEntry* entry = result.value().entry; | |
| ASSERT_THAT(entry, NotNull()); | |
| EXPECT_THAT(entry->visibility.level, Eq(Visibility::Level::kPublic)); | |
| EXPECT_THAT(entry->visibility.comment, StrEq("public")); | |
| } | |
| TEST_F(ResourceParserTest, ExternalTypesShouldOnlyBeReferences) { | |
| ASSERT_TRUE(TestParse(R"(<item type="layout" name="foo">@layout/bar</item>)")); | |
| ASSERT_FALSE(TestParse(R"(<item type="layout" name="bar">"this is a string"</item>)")); | |
| } | |
| TEST_F(ResourceParserTest, AddResourcesElementShouldAddEntryWithUndefinedSymbol) { | |
| ASSERT_TRUE(TestParse(R"(<add-resource name="bar" type="string" />)")); | |
| std::optional<ResourceTable::SearchResult> result = | |
| table_.FindResource(test::ParseNameOrDie("string/bar")); | |
| ASSERT_TRUE(result); | |
| const ResourceEntry* entry = result.value().entry; | |
| ASSERT_THAT(entry, NotNull()); | |
| EXPECT_THAT(entry->visibility.level, Eq(Visibility::Level::kUndefined)); | |
| EXPECT_TRUE(entry->allow_new); | |
| } | |
| TEST_F(ResourceParserTest, ParseItemElementWithFormat) { | |
| ASSERT_TRUE(TestParse(R"(<item name="foo" type="integer" format="float">0.3</item>)")); | |
| BinaryPrimitive* val = test::GetValue<BinaryPrimitive>(&table_, "integer/foo"); | |
| ASSERT_THAT(val, NotNull()); | |
| EXPECT_THAT(val->value.dataType, Eq(Res_value::TYPE_FLOAT)); | |
| ASSERT_FALSE(TestParse(R"(<item name="bar" type="integer" format="fraction">100</item>)")); | |
| } | |
| // An <item> without a format specifier accepts all types of values. | |
| TEST_F(ResourceParserTest, ParseItemElementWithoutFormat) { | |
| ASSERT_TRUE(TestParse(R"(<item name="foo" type="integer">100%p</item>)")); | |
| BinaryPrimitive* val = test::GetValue<BinaryPrimitive>(&table_, "integer/foo"); | |
| ASSERT_THAT(val, NotNull()); | |
| EXPECT_THAT(val->value.dataType, Eq(Res_value::TYPE_FRACTION)); | |
| } | |
| TEST_F(ResourceParserTest, ParseConfigVaryingItem) { | |
| ASSERT_TRUE(TestParse(R"(<item name="foo" type="configVarying">Hey</item>)")); | |
| ASSERT_THAT(test::GetValue<String>(&table_, "configVarying/foo"), NotNull()); | |
| } | |
| TEST_F(ResourceParserTest, ParseBagElement) { | |
| std::string input = R"( | |
| <bag name="bag" type="configVarying"> | |
| <item name="test">Hello!</item> | |
| </bag>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| Style* val = test::GetValue<Style>(&table_, "configVarying/bag"); | |
| ASSERT_THAT(val, NotNull()); | |
| ASSERT_THAT(val->entries, SizeIs(1)); | |
| EXPECT_THAT(val->entries[0].key, Eq(Reference(test::ParseNameOrDie("attr/test")))); | |
| EXPECT_THAT(ValueCast<RawString>(val->entries[0].value.get()), NotNull()); | |
| } | |
| TEST_F(ResourceParserTest, ParseElementWithNoValue) { | |
| std::string input = R"( | |
| <item type="drawable" format="reference" name="foo" /> | |
| <string name="foo" />)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| ASSERT_THAT(test::GetValue(&table_, "drawable/foo"), Pointee(ValueEq(Reference()))); | |
| String* str = test::GetValue<String>(&table_, "string/foo"); | |
| ASSERT_THAT(str, NotNull()); | |
| EXPECT_THAT(*str, StrValueEq("")); | |
| } | |
| TEST_F(ResourceParserTest, ParsePlatformIndependentNewline) { | |
| ASSERT_TRUE(TestParse(R"(<string name="foo">%1$s %n %2$s</string>)")); | |
| } | |
| TEST_F(ResourceParserTest, ParseOverlayable) { | |
| std::string input = R"( | |
| <overlayable name="Name" actor="overlay://theme"> | |
| <policy type="signature"> | |
| <item type="string" name="foo" /> | |
| <item type="drawable" name="bar" /> | |
| </policy> | |
| </overlayable>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo")); | |
| ASSERT_TRUE(search_result); | |
| ASSERT_THAT(search_result.value().entry, NotNull()); | |
| ASSERT_TRUE(search_result.value().entry->overlayable_item); | |
| OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value(); | |
| EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); | |
| EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme")); | |
| EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::SIGNATURE)); | |
| search_result = table_.FindResource(test::ParseNameOrDie("drawable/bar")); | |
| ASSERT_TRUE(search_result); | |
| ASSERT_THAT(search_result.value().entry, NotNull()); | |
| ASSERT_TRUE(search_result.value().entry->overlayable_item); | |
| result_overlayable_item = search_result.value().entry->overlayable_item.value(); | |
| EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); | |
| EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme")); | |
| EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::SIGNATURE)); | |
| } | |
| TEST_F(ResourceParserTest, ParseOverlayableRequiresName) { | |
| EXPECT_FALSE(TestParse(R"(<overlayable actor="overlay://theme" />)")); | |
| EXPECT_TRUE(TestParse(R"(<overlayable name="Name" />)")); | |
| EXPECT_TRUE(TestParse(R"(<overlayable name="Name" actor="overlay://theme" />)")); | |
| } | |
| TEST_F(ResourceParserTest, ParseOverlayableBadActorFail) { | |
| EXPECT_FALSE(TestParse(R"(<overlayable name="Name" actor="overley://theme" />)")); | |
| } | |
| TEST_F(ResourceParserTest, ParseOverlayablePolicy) { | |
| std::string input = R"( | |
| <overlayable name="Name"> | |
| <policy type="product"> | |
| <item type="string" name="bar" /> | |
| </policy> | |
| <policy type="system"> | |
| <item type="string" name="fiz" /> | |
| </policy> | |
| <policy type="vendor"> | |
| <item type="string" name="fuz" /> | |
| </policy> | |
| <policy type="public"> | |
| <item type="string" name="faz" /> | |
| </policy> | |
| <policy type="signature"> | |
| <item type="string" name="foz" /> | |
| </policy> | |
| <policy type="odm"> | |
| <item type="string" name="biz" /> | |
| </policy> | |
| <policy type="oem"> | |
| <item type="string" name="buz" /> | |
| </policy> | |
| <policy type="actor"> | |
| <item type="string" name="actor" /> | |
| </policy> | |
| </overlayable>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| auto search_result = table_.FindResource(test::ParseNameOrDie("string/bar")); | |
| ASSERT_TRUE(search_result); | |
| ASSERT_THAT(search_result.value().entry, NotNull()); | |
| ASSERT_TRUE(search_result.value().entry->overlayable_item); | |
| OverlayableItem result_overlayable_item = search_result.value().entry->overlayable_item.value(); | |
| EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); | |
| EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::PRODUCT_PARTITION)); | |
| search_result = table_.FindResource(test::ParseNameOrDie("string/fiz")); | |
| ASSERT_TRUE(search_result); | |
| ASSERT_THAT(search_result.value().entry, NotNull()); | |
| ASSERT_TRUE(search_result.value().entry->overlayable_item); | |
| result_overlayable_item = search_result.value().entry->overlayable_item.value(); | |
| EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); | |
| EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::SYSTEM_PARTITION)); | |
| search_result = table_.FindResource(test::ParseNameOrDie("string/fuz")); | |
| ASSERT_TRUE(search_result); | |
| ASSERT_THAT(search_result.value().entry, NotNull()); | |
| ASSERT_TRUE(search_result.value().entry->overlayable_item); | |
| result_overlayable_item = search_result.value().entry->overlayable_item.value(); | |
| EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); | |
| EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::VENDOR_PARTITION)); | |
| search_result = table_.FindResource(test::ParseNameOrDie("string/faz")); | |
| ASSERT_TRUE(search_result); | |
| ASSERT_THAT(search_result.value().entry, NotNull()); | |
| ASSERT_TRUE(search_result.value().entry->overlayable_item); | |
| result_overlayable_item = search_result.value().entry->overlayable_item.value(); | |
| EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); | |
| EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::PUBLIC)); | |
| search_result = table_.FindResource(test::ParseNameOrDie("string/foz")); | |
| ASSERT_TRUE(search_result); | |
| ASSERT_THAT(search_result.value().entry, NotNull()); | |
| ASSERT_TRUE(search_result.value().entry->overlayable_item); | |
| result_overlayable_item = search_result.value().entry->overlayable_item.value(); | |
| EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); | |
| EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::SIGNATURE)); | |
| search_result = table_.FindResource(test::ParseNameOrDie("string/biz")); | |
| ASSERT_TRUE(search_result); | |
| ASSERT_THAT(search_result.value().entry, NotNull()); | |
| ASSERT_TRUE(search_result.value().entry->overlayable_item); | |
| result_overlayable_item = search_result.value().entry->overlayable_item.value(); | |
| EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); | |
| EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::ODM_PARTITION)); | |
| search_result = table_.FindResource(test::ParseNameOrDie("string/buz")); | |
| ASSERT_TRUE(search_result); | |
| ASSERT_THAT(search_result.value().entry, NotNull()); | |
| ASSERT_TRUE(search_result.value().entry->overlayable_item); | |
| result_overlayable_item = search_result.value().entry->overlayable_item.value(); | |
| EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); | |
| EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::OEM_PARTITION)); | |
| search_result = table_.FindResource(test::ParseNameOrDie("string/actor")); | |
| ASSERT_TRUE(search_result); | |
| ASSERT_THAT(search_result.value().entry, NotNull()); | |
| ASSERT_TRUE(search_result.value().entry->overlayable_item); | |
| result_overlayable_item = search_result.value().entry->overlayable_item.value(); | |
| EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); | |
| EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::ACTOR_SIGNATURE)); | |
| } | |
| TEST_F(ResourceParserTest, ParseOverlayableNoPolicyError) { | |
| std::string input = R"( | |
| <overlayable name="Name"> | |
| <item type="string" name="foo" /> | |
| </overlayable>)"; | |
| EXPECT_FALSE(TestParse(input)); | |
| input = R"( | |
| <overlayable name="Name"> | |
| <policy> | |
| <item name="foo" /> | |
| </policy> | |
| </overlayable>)"; | |
| EXPECT_FALSE(TestParse(input)); | |
| } | |
| TEST_F(ResourceParserTest, ParseOverlayableBadPolicyError) { | |
| std::string input = R"( | |
| <overlayable name="Name"> | |
| <policy type="illegal_policy"> | |
| <item type="string" name="foo" /> | |
| </policy> | |
| </overlayable>)"; | |
| EXPECT_FALSE(TestParse(input)); | |
| input = R"( | |
| <overlayable name="Name"> | |
| <policy type="product"> | |
| <item name="foo" /> | |
| </policy> | |
| </overlayable>)"; | |
| EXPECT_FALSE(TestParse(input)); | |
| input = R"( | |
| <overlayable name="Name"> | |
| <policy type="vendor"> | |
| <item type="string" /> | |
| </policy> | |
| </overlayable>)"; | |
| EXPECT_FALSE(TestParse(input)); | |
| } | |
| TEST_F(ResourceParserTest, ParseOverlayableMultiplePolicy) { | |
| std::string input = R"( | |
| <overlayable name="Name"> | |
| <policy type="vendor|public"> | |
| <item type="string" name="foo" /> | |
| </policy> | |
| <policy type="product|system"> | |
| <item type="string" name="bar" /> | |
| </policy> | |
| </overlayable>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo")); | |
| ASSERT_TRUE(search_result); | |
| ASSERT_THAT(search_result.value().entry, NotNull()); | |
| ASSERT_TRUE(search_result.value().entry->overlayable_item); | |
| OverlayableItem result_overlayable_item = search_result.value().entry->overlayable_item.value(); | |
| EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); | |
| EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::VENDOR_PARTITION | |
| | PolicyFlags::PUBLIC)); | |
| search_result = table_.FindResource(test::ParseNameOrDie("string/bar")); | |
| ASSERT_TRUE(search_result); | |
| ASSERT_THAT(search_result.value().entry, NotNull()); | |
| ASSERT_TRUE(search_result.value().entry->overlayable_item); | |
| result_overlayable_item = search_result.value().entry->overlayable_item.value(); | |
| EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); | |
| EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::PRODUCT_PARTITION | |
| | PolicyFlags::SYSTEM_PARTITION)); | |
| } | |
| TEST_F(ResourceParserTest, DuplicateOverlayableIsError) { | |
| std::string input = R"( | |
| <overlayable name="Name"> | |
| <item type="string" name="foo" /> | |
| <item type="string" name="foo" /> | |
| </overlayable>)"; | |
| EXPECT_FALSE(TestParse(input)); | |
| input = R"( | |
| <overlayable name="Name"> | |
| <item type="string" name="foo" /> | |
| </overlayable> | |
| <overlayable name="Name"> | |
| <item type="string" name="foo" /> | |
| </overlayable>)"; | |
| EXPECT_FALSE(TestParse(input)); | |
| input = R"( | |
| <overlayable name="Name"> | |
| <item type="string" name="foo" /> | |
| </overlayable> | |
| <overlayable name="Other"> | |
| <item type="string" name="foo" /> | |
| </overlayable>)"; | |
| EXPECT_FALSE(TestParse(input)); | |
| input = R"( | |
| <overlayable name="Name" actor="overlay://my.actor.one"> | |
| <item type="string" name="foo" /> | |
| </overlayable> | |
| <overlayable name="Other" actor="overlay://my.actor.two"> | |
| <item type="string" name="foo" /> | |
| </overlayable>)"; | |
| EXPECT_FALSE(TestParse(input)); | |
| input = R"( | |
| <overlayable name="Name"> | |
| <policy type="product"> | |
| <item type="string" name="foo" /> | |
| <item type="string" name="foo" /> | |
| </policy> | |
| </overlayable>)"; | |
| EXPECT_FALSE(TestParse(input)); | |
| input = R"( | |
| <overlayable name="Name"> | |
| <policy type="product"> | |
| <item type="string" name="foo" /> | |
| </policy> | |
| <item type="string" name="foo" /> | |
| </overlayable>)"; | |
| EXPECT_FALSE(TestParse(input)); | |
| input = R"( | |
| <overlayable name="Name"> | |
| <policy type="product"> | |
| <item type="string" name="foo" /> | |
| </policy> | |
| <policy type="vendor"> | |
| <item type="string" name="foo" /> | |
| </policy> | |
| </overlayable>)"; | |
| EXPECT_FALSE(TestParse(input)); | |
| input = R"( | |
| <overlayable name="Name"> | |
| <policy type="product"> | |
| <item type="string" name="foo" /> | |
| </policy> | |
| </overlayable> | |
| <overlayable name="Name"> | |
| <policy type="product"> | |
| <item type="string" name="foo" /> | |
| </policy> | |
| </overlayable>)"; | |
| EXPECT_FALSE(TestParse(input)); | |
| } | |
| TEST_F(ResourceParserTest, NestPolicyInOverlayableError) { | |
| std::string input = R"( | |
| <overlayable name="Name"> | |
| <policy type="vendor|product"> | |
| <policy type="public"> | |
| <item type="string" name="foo" /> | |
| </policy> | |
| </policy> | |
| </overlayable>)"; | |
| EXPECT_FALSE(TestParse(input)); | |
| } | |
| TEST_F(ResourceParserTest, ParseIdItem) { | |
| std::string input = R"( | |
| <item name="foo" type="id">@id/bar</item> | |
| <item name="bar" type="id"/> | |
| <item name="baz" type="id"></item>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| ASSERT_THAT(test::GetValue<Reference>(&table_, "id/foo"), NotNull()); | |
| ASSERT_THAT(test::GetValue<Id>(&table_, "id/bar"), NotNull()); | |
| ASSERT_THAT(test::GetValue<Id>(&table_, "id/baz"), NotNull()); | |
| input = R"( | |
| <id name="foo2">@id/bar</id> | |
| <id name="bar2"/> | |
| <id name="baz2"></id>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| ASSERT_THAT(test::GetValue<Reference>(&table_, "id/foo2"), NotNull()); | |
| ASSERT_THAT(test::GetValue<Id>(&table_, "id/bar2"), NotNull()); | |
| ASSERT_THAT(test::GetValue<Id>(&table_, "id/baz2"), NotNull()); | |
| // Reject attribute references | |
| input = R"(<item name="foo3" type="id">?attr/bar"</item>)"; | |
| ASSERT_FALSE(TestParse(input)); | |
| // Reject non-references | |
| input = R"(<item name="foo4" type="id">0x7f010001</item>)"; | |
| ASSERT_FALSE(TestParse(input)); | |
| input = R"(<item name="foo5" type="id">@drawable/my_image</item>)"; | |
| ASSERT_FALSE(TestParse(input)); | |
| input = R"(<item name="foo6" type="id"><string name="biz"></string></item>)"; | |
| ASSERT_FALSE(TestParse(input)); | |
| // Ids that reference other resource ids cannot be public | |
| input = R"(<public name="foo7" type="id">@id/bar7</item>)"; | |
| ASSERT_FALSE(TestParse(input)); | |
| } | |
| TEST_F(ResourceParserTest, ParseCData) { | |
| // Double quotes should still change the state of whitespace processing | |
| std::string input = R"(<string name="foo">Hello<![CDATA[ "</string>' ]]> World</string>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| auto output = test::GetValue<String>(&table_, "string/foo"); | |
| ASSERT_THAT(output, NotNull()); | |
| EXPECT_THAT(*output, StrValueEq(std::string("Hello </string>' World").data())); | |
| input = R"(<string name="foo2"><![CDATA[Hello | |
| World]]></string>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| output = test::GetValue<String>(&table_, "string/foo2"); | |
| ASSERT_THAT(output, NotNull()); | |
| EXPECT_THAT(*output, StrValueEq(std::string("Hello World").data())); | |
| // Cdata blocks should have their whitespace trimmed | |
| input = R"(<string name="foo3"> <![CDATA[ text ]]> </string>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| output = test::GetValue<String>(&table_, "string/foo3"); | |
| ASSERT_THAT(output, NotNull()); | |
| EXPECT_THAT(*output, StrValueEq(std::string("text").data())); | |
| input = R"(<string name="foo4"> <![CDATA[]]> </string>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| output = test::GetValue<String>(&table_, "string/foo4"); | |
| ASSERT_THAT(output, NotNull()); | |
| EXPECT_THAT(*output, StrValueEq(std::string("").data())); | |
| input = R"(<string name="foo5"> <![CDATA[ ]]> </string>)"; | |
| ASSERT_TRUE(TestParse(input)); | |
| output = test::GetValue<String>(&table_, "string/foo5"); | |
| ASSERT_THAT(output, NotNull()); | |
| EXPECT_THAT(*output, StrValueEq(std::string("").data())); | |
| // Single quotes must still be escaped | |
| input = R"(<string name="foo6"><![CDATA[some text and ' apostrophe]]></string>)"; | |
| ASSERT_FALSE(TestParse(input)); | |
| } | |
| } // namespace aapt |