/* * 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 "ResourceTable.h" #include "ResourceValues.h" #include "SourceXmlPullParser.h" #include #include #include namespace aapt { constexpr const char* kXmlPreamble = "\n"; TEST(ResourceParserReferenceTest, ParseReferenceWithNoPackage) { ResourceNameRef expected = { {}, ResourceType::kColor, u"foo" }; ResourceNameRef actual; bool create = false; bool privateRef = false; EXPECT_TRUE(ResourceParser::tryParseReference(u"@color/foo", &actual, &create, &privateRef)); EXPECT_EQ(expected, actual); EXPECT_FALSE(create); EXPECT_FALSE(privateRef); } TEST(ResourceParserReferenceTest, ParseReferenceWithPackage) { ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" }; ResourceNameRef actual; bool create = false; bool privateRef = false; EXPECT_TRUE(ResourceParser::tryParseReference(u"@android:color/foo", &actual, &create, &privateRef)); EXPECT_EQ(expected, actual); EXPECT_FALSE(create); EXPECT_FALSE(privateRef); } TEST(ResourceParserReferenceTest, ParseReferenceWithSurroundingWhitespace) { ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" }; ResourceNameRef actual; bool create = false; bool privateRef = false; EXPECT_TRUE(ResourceParser::tryParseReference(u"\t @android:color/foo\n \n\t", &actual, &create, &privateRef)); EXPECT_EQ(expected, actual); EXPECT_FALSE(create); EXPECT_FALSE(privateRef); } TEST(ResourceParserReferenceTest, ParseAutoCreateIdReference) { ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" }; ResourceNameRef actual; bool create = false; bool privateRef = false; EXPECT_TRUE(ResourceParser::tryParseReference(u"@+android:id/foo", &actual, &create, &privateRef)); EXPECT_EQ(expected, actual); EXPECT_TRUE(create); EXPECT_FALSE(privateRef); } TEST(ResourceParserReferenceTest, ParsePrivateReference) { ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" }; ResourceNameRef actual; bool create = false; bool privateRef = false; EXPECT_TRUE(ResourceParser::tryParseReference(u"@*android:id/foo", &actual, &create, &privateRef)); EXPECT_EQ(expected, actual); EXPECT_FALSE(create); EXPECT_TRUE(privateRef); } TEST(ResourceParserReferenceTest, FailToParseAutoCreateNonIdReference) { bool create = false; bool privateRef = false; ResourceNameRef actual; EXPECT_FALSE(ResourceParser::tryParseReference(u"@+android:color/foo", &actual, &create, &privateRef)); } TEST(ResourceParserReferenceTest, ParseStyleParentReference) { Reference ref; std::string errStr; EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@android:style/foo", &ref, &errStr)); EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@style/foo", &ref, &errStr)); EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" })); EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?android:style/foo", &ref, &errStr)); EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?style/foo", &ref, &errStr)); EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" })); EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:style/foo", &ref, &errStr)); EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:foo", &ref, &errStr)); EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"foo", &ref, &errStr)); EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" })); } struct ResourceParserTest : public ::testing::Test { virtual void SetUp() override { mTable = std::make_shared(); mTable->setPackage(u"android"); } ::testing::AssertionResult testParse(const StringPiece& str) { std::stringstream input(kXmlPreamble); input << "\n" << str << "\n" << std::endl; ResourceParser parser(mTable, Source{ "test" }, {}, std::make_shared(input)); if (parser.parse()) { return ::testing::AssertionSuccess(); } return ::testing::AssertionFailure(); } template const T* findResource(const ResourceNameRef& name, const ConfigDescription& config) { using std::begin; using std::end; const ResourceTableType* type; const ResourceEntry* entry; std::tie(type, entry) = mTable->findResource(name); if (!type || !entry) { return nullptr; } for (const auto& configValue : entry->values) { if (configValue.config == config) { return dynamic_cast(configValue.value.get()); } } return nullptr; } template const T* findResource(const ResourceNameRef& name) { return findResource(name, {}); } std::shared_ptr mTable; }; TEST_F(ResourceParserTest, FailToParseWithNoRootResourcesElement) { std::stringstream input(kXmlPreamble); input << "" << std::endl; ResourceParser parser(mTable, {}, {}, std::make_shared(input)); ASSERT_FALSE(parser.parse()); } TEST_F(ResourceParserTest, ParseQuotedString) { std::string input = " \" hey there \" "; ASSERT_TRUE(testParse(input)); const String* str = findResource(ResourceName{ u"android", ResourceType::kString, u"foo"}); ASSERT_NE(nullptr, str); EXPECT_EQ(std::u16string(u" hey there "), *str->value); } TEST_F(ResourceParserTest, ParseEscapedString) { std::string input = "\\?123"; ASSERT_TRUE(testParse(input)); const String* str = findResource(ResourceName{ u"android", ResourceType::kString, u"foo" }); ASSERT_NE(nullptr, str); EXPECT_EQ(std::u16string(u"?123"), *str->value); } TEST_F(ResourceParserTest, ParseNull) { std::string input = "@null"; 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. const BinaryPrimitive* integer = findResource(ResourceName{ u"android", ResourceType::kInteger, u"foo" }); ASSERT_NE(nullptr, integer); EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE), integer->value.dataType); EXPECT_EQ(0u, integer->value.data); } TEST_F(ResourceParserTest, ParseEmpty) { std::string input = "@empty"; ASSERT_TRUE(testParse(input)); const BinaryPrimitive* integer = findResource(ResourceName{ u"android", ResourceType::kInteger, u"foo" }); ASSERT_NE(nullptr, integer); EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType); EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data); } TEST_F(ResourceParserTest, ParseAttr) { std::string input = "\n" ""; ASSERT_TRUE(testParse(input)); const Attribute* attr = findResource(ResourceName{ u"android", ResourceType::kAttr, u"foo"}); EXPECT_NE(nullptr, attr); EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); attr = findResource(ResourceName{ u"android", ResourceType::kAttr, u"bar"}); EXPECT_NE(nullptr, attr); EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask); } TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) { std::string input = "\n" " \n" "\n" ""; ASSERT_TRUE(testParse(input)); const Attribute* attr = findResource(ResourceName{ u"android", ResourceType::kAttr, u"foo"}); ASSERT_NE(nullptr, attr); EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); } TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) { std::string input = "" " \n" "\n" "\n" " \n" ""; ASSERT_TRUE(testParse(input)); const Attribute* attr = findResource(ResourceName{ u"android", ResourceType::kAttr, u"foo"}); ASSERT_NE(nullptr, attr); EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask); } TEST_F(ResourceParserTest, ParseEnumAttr) { std::string input = "\n" " \n" " \n" " \n" ""; ASSERT_TRUE(testParse(input)); const Attribute* enumAttr = findResource(ResourceName{ u"android", ResourceType::kAttr, u"foo"}); ASSERT_NE(enumAttr, nullptr); EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM); ASSERT_EQ(enumAttr->symbols.size(), 3u); EXPECT_EQ(enumAttr->symbols[0].symbol.name.entry, u"bar"); EXPECT_EQ(enumAttr->symbols[0].value, 0u); EXPECT_EQ(enumAttr->symbols[1].symbol.name.entry, u"bat"); EXPECT_EQ(enumAttr->symbols[1].value, 1u); EXPECT_EQ(enumAttr->symbols[2].symbol.name.entry, u"baz"); EXPECT_EQ(enumAttr->symbols[2].value, 2u); } TEST_F(ResourceParserTest, ParseFlagAttr) { std::string input = "\n" " \n" " \n" " \n" ""; ASSERT_TRUE(testParse(input)); const Attribute* flagAttr = findResource(ResourceName{ u"android", ResourceType::kAttr, u"foo"}); ASSERT_NE(flagAttr, nullptr); EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS); ASSERT_EQ(flagAttr->symbols.size(), 3u); EXPECT_EQ(flagAttr->symbols[0].symbol.name.entry, u"bar"); EXPECT_EQ(flagAttr->symbols[0].value, 0u); EXPECT_EQ(flagAttr->symbols[1].symbol.name.entry, u"bat"); EXPECT_EQ(flagAttr->symbols[1].value, 1u); EXPECT_EQ(flagAttr->symbols[2].symbol.name.entry, u"baz"); EXPECT_EQ(flagAttr->symbols[2].value, 2u); std::unique_ptr flagValue = ResourceParser::tryParseFlagSymbol(*flagAttr, u"baz|bat"); ASSERT_NE(flagValue, nullptr); EXPECT_EQ(flagValue->value.data, 1u | 2u); } TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) { std::string input = "\n" " \n" " \n" " \n" ""; ASSERT_FALSE(testParse(input)); } TEST_F(ResourceParserTest, ParseStyle) { std::string input = ""; ASSERT_TRUE(testParse(input)); const Style* style = findResource"; ASSERT_TRUE(testParse(input)); const Style* style = findResource