ResourceParser_test.cpp revision bdaa092a193d8ddccbd9ad8434be97878e6ded59
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include "ResourceParser.h" 18#include "ResourceTable.h" 19#include "ResourceValues.h" 20#include "SourceXmlPullParser.h" 21 22#include <gtest/gtest.h> 23#include <sstream> 24#include <string> 25 26namespace aapt { 27 28constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; 29 30TEST(ResourceParserReferenceTest, ParseReferenceWithNoPackage) { 31 ResourceNameRef expected = { {}, ResourceType::kColor, u"foo" }; 32 ResourceNameRef actual; 33 bool create = false; 34 bool privateRef = false; 35 EXPECT_TRUE(ResourceParser::tryParseReference(u"@color/foo", &actual, &create, &privateRef)); 36 EXPECT_EQ(expected, actual); 37 EXPECT_FALSE(create); 38 EXPECT_FALSE(privateRef); 39} 40 41TEST(ResourceParserReferenceTest, ParseReferenceWithPackage) { 42 ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" }; 43 ResourceNameRef actual; 44 bool create = false; 45 bool privateRef = false; 46 EXPECT_TRUE(ResourceParser::tryParseReference(u"@android:color/foo", &actual, &create, 47 &privateRef)); 48 EXPECT_EQ(expected, actual); 49 EXPECT_FALSE(create); 50 EXPECT_FALSE(privateRef); 51} 52 53TEST(ResourceParserReferenceTest, ParseReferenceWithSurroundingWhitespace) { 54 ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" }; 55 ResourceNameRef actual; 56 bool create = false; 57 bool privateRef = false; 58 EXPECT_TRUE(ResourceParser::tryParseReference(u"\t @android:color/foo\n \n\t", &actual, 59 &create, &privateRef)); 60 EXPECT_EQ(expected, actual); 61 EXPECT_FALSE(create); 62 EXPECT_FALSE(privateRef); 63} 64 65TEST(ResourceParserReferenceTest, ParseAutoCreateIdReference) { 66 ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" }; 67 ResourceNameRef actual; 68 bool create = false; 69 bool privateRef = false; 70 EXPECT_TRUE(ResourceParser::tryParseReference(u"@+android:id/foo", &actual, &create, 71 &privateRef)); 72 EXPECT_EQ(expected, actual); 73 EXPECT_TRUE(create); 74 EXPECT_FALSE(privateRef); 75} 76 77TEST(ResourceParserReferenceTest, ParsePrivateReference) { 78 ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" }; 79 ResourceNameRef actual; 80 bool create = false; 81 bool privateRef = false; 82 EXPECT_TRUE(ResourceParser::tryParseReference(u"@*android:id/foo", &actual, &create, 83 &privateRef)); 84 EXPECT_EQ(expected, actual); 85 EXPECT_FALSE(create); 86 EXPECT_TRUE(privateRef); 87} 88 89TEST(ResourceParserReferenceTest, FailToParseAutoCreateNonIdReference) { 90 bool create = false; 91 bool privateRef = false; 92 ResourceNameRef actual; 93 EXPECT_FALSE(ResourceParser::tryParseReference(u"@+android:color/foo", &actual, &create, 94 &privateRef)); 95} 96 97TEST(ResourceParserReferenceTest, ParseStyleParentReference) { 98 Reference ref; 99 std::string errStr; 100 EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@android:style/foo", &ref, &errStr)); 101 EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); 102 103 EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@style/foo", &ref, &errStr)); 104 EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" })); 105 106 EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?android:style/foo", &ref, &errStr)); 107 EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); 108 109 EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?style/foo", &ref, &errStr)); 110 EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" })); 111 112 EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:style/foo", &ref, &errStr)); 113 EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); 114 115 EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:foo", &ref, &errStr)); 116 EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); 117 118 EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"foo", &ref, &errStr)); 119 EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" })); 120} 121 122struct ResourceParserTest : public ::testing::Test { 123 virtual void SetUp() override { 124 mTable = std::make_shared<ResourceTable>(); 125 mTable->setPackage(u"android"); 126 } 127 128 ::testing::AssertionResult testParse(const StringPiece& str) { 129 std::stringstream input(kXmlPreamble); 130 input << "<resources>\n" << str << "\n</resources>" << std::endl; 131 ResourceParser parser(mTable, Source{ "test" }, {}, 132 std::make_shared<SourceXmlPullParser>(input)); 133 if (parser.parse()) { 134 return ::testing::AssertionSuccess(); 135 } 136 return ::testing::AssertionFailure(); 137 } 138 139 template <typename T> 140 const T* findResource(const ResourceNameRef& name, const ConfigDescription& config) { 141 using std::begin; 142 using std::end; 143 144 const ResourceTableType* type; 145 const ResourceEntry* entry; 146 std::tie(type, entry) = mTable->findResource(name); 147 if (!type || !entry) { 148 return nullptr; 149 } 150 151 for (const auto& configValue : entry->values) { 152 if (configValue.config == config) { 153 return dynamic_cast<const T*>(configValue.value.get()); 154 } 155 } 156 return nullptr; 157 } 158 159 template <typename T> 160 const T* findResource(const ResourceNameRef& name) { 161 return findResource<T>(name, {}); 162 } 163 164 std::shared_ptr<ResourceTable> mTable; 165}; 166 167TEST_F(ResourceParserTest, FailToParseWithNoRootResourcesElement) { 168 std::stringstream input(kXmlPreamble); 169 input << "<attr name=\"foo\"/>" << std::endl; 170 ResourceParser parser(mTable, {}, {}, std::make_shared<SourceXmlPullParser>(input)); 171 ASSERT_FALSE(parser.parse()); 172} 173 174TEST_F(ResourceParserTest, ParseQuotedString) { 175 std::string input = "<string name=\"foo\"> \" hey there \" </string>"; 176 ASSERT_TRUE(testParse(input)); 177 178 const String* str = findResource<String>(ResourceName{ 179 u"android", ResourceType::kString, u"foo"}); 180 ASSERT_NE(nullptr, str); 181 EXPECT_EQ(std::u16string(u" hey there "), *str->value); 182} 183 184TEST_F(ResourceParserTest, ParseEscapedString) { 185 std::string input = "<string name=\"foo\">\\?123</string>"; 186 ASSERT_TRUE(testParse(input)); 187 188 const String* str = findResource<String>(ResourceName{ 189 u"android", ResourceType::kString, u"foo" }); 190 ASSERT_NE(nullptr, str); 191 EXPECT_EQ(std::u16string(u"?123"), *str->value); 192} 193 194TEST_F(ResourceParserTest, ParseAttr) { 195 std::string input = "<attr name=\"foo\" format=\"string\"/>\n" 196 "<attr name=\"bar\"/>"; 197 ASSERT_TRUE(testParse(input)); 198 199 const Attribute* attr = findResource<Attribute>(ResourceName{ 200 u"android", ResourceType::kAttr, u"foo"}); 201 EXPECT_NE(nullptr, attr); 202 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); 203 204 attr = findResource<Attribute>(ResourceName{ 205 u"android", ResourceType::kAttr, u"bar"}); 206 EXPECT_NE(nullptr, attr); 207 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask); 208} 209 210TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) { 211 std::string input = "<declare-styleable name=\"Styleable\">\n" 212 " <attr name=\"foo\" />\n" 213 "</declare-styleable>\n" 214 "<attr name=\"foo\" format=\"string\"/>"; 215 ASSERT_TRUE(testParse(input)); 216 217 const Attribute* attr = findResource<Attribute>(ResourceName{ 218 u"android", ResourceType::kAttr, u"foo"}); 219 ASSERT_NE(nullptr, attr); 220 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); 221} 222 223TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) { 224 std::string input = "<declare-styleable name=\"Theme\">" 225 " <attr name=\"foo\" />\n" 226 "</declare-styleable>\n" 227 "<declare-styleable name=\"Window\">\n" 228 " <attr name=\"foo\" format=\"boolean\"/>\n" 229 "</declare-styleable>"; 230 ASSERT_TRUE(testParse(input)); 231 232 const Attribute* attr = findResource<Attribute>(ResourceName{ 233 u"android", ResourceType::kAttr, u"foo"}); 234 ASSERT_NE(nullptr, attr); 235 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask); 236} 237 238TEST_F(ResourceParserTest, ParseEnumAttr) { 239 std::string input = "<attr name=\"foo\">\n" 240 " <enum name=\"bar\" value=\"0\"/>\n" 241 " <enum name=\"bat\" value=\"1\"/>\n" 242 " <enum name=\"baz\" value=\"2\"/>\n" 243 "</attr>"; 244 ASSERT_TRUE(testParse(input)); 245 246 const Attribute* enumAttr = findResource<Attribute>(ResourceName{ 247 u"android", ResourceType::kAttr, u"foo"}); 248 ASSERT_NE(enumAttr, nullptr); 249 EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM); 250 ASSERT_EQ(enumAttr->symbols.size(), 3u); 251 252 EXPECT_EQ(enumAttr->symbols[0].symbol.name.entry, u"bar"); 253 EXPECT_EQ(enumAttr->symbols[0].value, 0u); 254 255 EXPECT_EQ(enumAttr->symbols[1].symbol.name.entry, u"bat"); 256 EXPECT_EQ(enumAttr->symbols[1].value, 1u); 257 258 EXPECT_EQ(enumAttr->symbols[2].symbol.name.entry, u"baz"); 259 EXPECT_EQ(enumAttr->symbols[2].value, 2u); 260} 261 262TEST_F(ResourceParserTest, ParseFlagAttr) { 263 std::string input = "<attr name=\"foo\">\n" 264 " <flag name=\"bar\" value=\"0\"/>\n" 265 " <flag name=\"bat\" value=\"1\"/>\n" 266 " <flag name=\"baz\" value=\"2\"/>\n" 267 "</attr>"; 268 ASSERT_TRUE(testParse(input)); 269 270 const Attribute* flagAttr = findResource<Attribute>(ResourceName{ 271 u"android", ResourceType::kAttr, u"foo"}); 272 ASSERT_NE(flagAttr, nullptr); 273 EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS); 274 ASSERT_EQ(flagAttr->symbols.size(), 3u); 275 276 EXPECT_EQ(flagAttr->symbols[0].symbol.name.entry, u"bar"); 277 EXPECT_EQ(flagAttr->symbols[0].value, 0u); 278 279 EXPECT_EQ(flagAttr->symbols[1].symbol.name.entry, u"bat"); 280 EXPECT_EQ(flagAttr->symbols[1].value, 1u); 281 282 EXPECT_EQ(flagAttr->symbols[2].symbol.name.entry, u"baz"); 283 EXPECT_EQ(flagAttr->symbols[2].value, 2u); 284 285 std::unique_ptr<BinaryPrimitive> flagValue = 286 ResourceParser::tryParseFlagSymbol(*flagAttr, u"baz|bat"); 287 ASSERT_NE(flagValue, nullptr); 288 EXPECT_EQ(flagValue->value.data, 1u | 2u); 289} 290 291TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) { 292 std::string input = "<attr name=\"foo\">\n" 293 " <enum name=\"bar\" value=\"0\"/>\n" 294 " <enum name=\"bat\" value=\"1\"/>\n" 295 " <enum name=\"bat\" value=\"2\"/>\n" 296 "</attr>"; 297 ASSERT_FALSE(testParse(input)); 298} 299 300TEST_F(ResourceParserTest, ParseStyle) { 301 std::string input = "<style name=\"foo\" parent=\"@style/fu\">\n" 302 " <item name=\"bar\">#ffffffff</item>\n" 303 " <item name=\"bat\">@string/hey</item>\n" 304 " <item name=\"baz\"><b>hey</b></item>\n" 305 "</style>"; 306 ASSERT_TRUE(testParse(input)); 307 308 const Style* style = findResource<Style>(ResourceName{ 309 u"android", ResourceType::kStyle, u"foo"}); 310 ASSERT_NE(style, nullptr); 311 EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"fu"), style->parent.name); 312 ASSERT_EQ(style->entries.size(), 3u); 313 314 EXPECT_EQ(style->entries[0].key.name, 315 (ResourceName{ u"android", ResourceType::kAttr, u"bar" })); 316 EXPECT_EQ(style->entries[1].key.name, 317 (ResourceName{ u"android", ResourceType::kAttr, u"bat" })); 318 EXPECT_EQ(style->entries[2].key.name, 319 (ResourceName{ u"android", ResourceType::kAttr, u"baz" })); 320} 321 322TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) { 323 std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>"; 324 ASSERT_TRUE(testParse(input)); 325 326 const Style* style = findResource<Style>( 327 ResourceName{ u"android", ResourceType::kStyle, u"foo" }); 328 ASSERT_NE(style, nullptr); 329 EXPECT_EQ(ResourceNameRef(u"com.app", ResourceType::kStyle, u"Theme"), style->parent.name); 330} 331 332TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) { 333 std::string input = "<style xmlns:app=\"http://schemas.android.com/apk/res/android\"\n" 334 " name=\"foo\" parent=\"app:Theme\"/>"; 335 ASSERT_TRUE(testParse(input)); 336 337 const Style* style = findResource<Style>(ResourceName{ 338 u"android", ResourceType::kStyle, u"foo" }); 339 ASSERT_NE(style, nullptr); 340 EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"Theme"), style->parent.name); 341} 342 343TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) { 344 std::string input = 345 "<style xmlns:app=\"http://schemas.android.com/apk/res/android\" name=\"foo\">\n" 346 " <item name=\"app:bar\">0</item>\n" 347 "</style>"; 348 ASSERT_TRUE(testParse(input)); 349 350 const Style* style = findResource<Style>(ResourceName{ 351 u"android", ResourceType::kStyle, u"foo" }); 352 ASSERT_NE(style, nullptr); 353 ASSERT_EQ(1u, style->entries.size()); 354 EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kAttr, u"bar"), 355 style->entries[0].key.name); 356} 357 358TEST_F(ResourceParserTest, ParseStyleWithInferredParent) { 359 std::string input = "<style name=\"foo.bar\"/>"; 360 ASSERT_TRUE(testParse(input)); 361 362 const Style* style = findResource<Style>(ResourceName{ 363 u"android", ResourceType::kStyle, u"foo.bar" }); 364 ASSERT_NE(style, nullptr); 365 EXPECT_EQ(style->parent.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); 366 EXPECT_TRUE(style->parentInferred); 367} 368 369TEST_F(ResourceParserTest, ParseStyleWithInferredParentOverridenByEmptyParentAttribute) { 370 std::string input = "<style name=\"foo.bar\" parent=\"\"/>"; 371 ASSERT_TRUE(testParse(input)); 372 373 const Style* style = findResource<Style>(ResourceName{ 374 u"android", ResourceType::kStyle, u"foo.bar" }); 375 ASSERT_NE(style, nullptr); 376 EXPECT_FALSE(style->parent.name.isValid()); 377 EXPECT_FALSE(style->parentInferred); 378} 379 380TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) { 381 std::string input = "<string name=\"foo\">@+id/bar</string>"; 382 ASSERT_TRUE(testParse(input)); 383 384 const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"bar"}); 385 ASSERT_NE(id, nullptr); 386} 387 388TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) { 389 std::string input = "<declare-styleable name=\"foo\">\n" 390 " <attr name=\"bar\" />\n" 391 " <attr name=\"bat\" format=\"string|reference\"/>\n" 392 "</declare-styleable>"; 393 ASSERT_TRUE(testParse(input)); 394 395 const Attribute* attr = findResource<Attribute>(ResourceName{ 396 u"android", ResourceType::kAttr, u"bar"}); 397 ASSERT_NE(attr, nullptr); 398 EXPECT_TRUE(attr->isWeak()); 399 400 attr = findResource<Attribute>(ResourceName{ u"android", ResourceType::kAttr, u"bat"}); 401 ASSERT_NE(attr, nullptr); 402 EXPECT_TRUE(attr->isWeak()); 403 404 const Styleable* styleable = findResource<Styleable>(ResourceName{ 405 u"android", ResourceType::kStyleable, u"foo" }); 406 ASSERT_NE(styleable, nullptr); 407 ASSERT_EQ(2u, styleable->entries.size()); 408 409 EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bar"}), styleable->entries[0].name); 410 EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bat"}), styleable->entries[1].name); 411} 412 413TEST_F(ResourceParserTest, ParseArray) { 414 std::string input = "<array name=\"foo\">\n" 415 " <item>@string/ref</item>\n" 416 " <item>hey</item>\n" 417 " <item>23</item>\n" 418 "</array>"; 419 ASSERT_TRUE(testParse(input)); 420 421 const Array* array = findResource<Array>(ResourceName{ 422 u"android", ResourceType::kArray, u"foo" }); 423 ASSERT_NE(array, nullptr); 424 ASSERT_EQ(3u, array->items.size()); 425 426 EXPECT_NE(nullptr, dynamic_cast<const Reference*>(array->items[0].get())); 427 EXPECT_NE(nullptr, dynamic_cast<const String*>(array->items[1].get())); 428 EXPECT_NE(nullptr, dynamic_cast<const BinaryPrimitive*>(array->items[2].get())); 429} 430 431TEST_F(ResourceParserTest, ParsePlural) { 432 std::string input = "<plurals name=\"foo\">\n" 433 " <item quantity=\"other\">apples</item>\n" 434 " <item quantity=\"one\">apple</item>\n" 435 "</plurals>"; 436 ASSERT_TRUE(testParse(input)); 437} 438 439TEST_F(ResourceParserTest, ParseCommentsWithResource) { 440 std::string input = "<!-- This is a comment -->\n" 441 "<string name=\"foo\">Hi</string>"; 442 ASSERT_TRUE(testParse(input)); 443 444 const ResourceTableType* type; 445 const ResourceEntry* entry; 446 std::tie(type, entry) = mTable->findResource(ResourceName{ 447 u"android", ResourceType::kString, u"foo"}); 448 ASSERT_NE(type, nullptr); 449 ASSERT_NE(entry, nullptr); 450 ASSERT_FALSE(entry->values.empty()); 451 EXPECT_EQ(entry->values.front().comment, u"This is a comment"); 452} 453 454/* 455 * Declaring an ID as public should not require a separate definition 456 * (as an ID has no value). 457 */ 458TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) { 459 std::string input = "<public type=\"id\" name=\"foo\"/>"; 460 ASSERT_TRUE(testParse(input)); 461 462 const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"foo" }); 463 ASSERT_NE(nullptr, id); 464} 465 466} // namespace aapt 467