ResourceParser_test.cpp revision cacb28f2d60858106e2819cc7d95a65e8bda890b
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 "ResourceUtils.h" 20#include "ResourceValues.h" 21#include "test/Test.h" 22#include "xml/XmlPullParser.h" 23 24#include <sstream> 25#include <string> 26 27namespace aapt { 28 29constexpr const char* kXmlPreamble = 30 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; 31 32TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) { 33 std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); 34 std::stringstream input(kXmlPreamble); 35 input << "<attr name=\"foo\"/>" << std::endl; 36 ResourceTable table; 37 ResourceParser parser(context->getDiagnostics(), &table, Source{"test"}, {}); 38 xml::XmlPullParser xmlParser(input); 39 ASSERT_FALSE(parser.parse(&xmlParser)); 40} 41 42struct ResourceParserTest : public ::testing::Test { 43 ResourceTable mTable; 44 std::unique_ptr<IAaptContext> mContext; 45 46 void SetUp() override { mContext = test::ContextBuilder().build(); } 47 48 ::testing::AssertionResult testParse(const StringPiece& str) { 49 return testParse(str, ConfigDescription{}); 50 } 51 52 ::testing::AssertionResult testParse(const StringPiece& str, 53 const ConfigDescription& config) { 54 std::stringstream input(kXmlPreamble); 55 input << "<resources>\n" << str << "\n</resources>" << std::endl; 56 ResourceParserOptions parserOptions; 57 ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{"test"}, 58 config, parserOptions); 59 xml::XmlPullParser xmlParser(input); 60 if (parser.parse(&xmlParser)) { 61 return ::testing::AssertionSuccess(); 62 } 63 return ::testing::AssertionFailure(); 64 } 65}; 66 67TEST_F(ResourceParserTest, ParseQuotedString) { 68 std::string input = "<string name=\"foo\"> \" hey there \" </string>"; 69 ASSERT_TRUE(testParse(input)); 70 71 String* str = test::getValue<String>(&mTable, "string/foo"); 72 ASSERT_NE(nullptr, str); 73 EXPECT_EQ(std::string(" hey there "), *str->value); 74} 75 76TEST_F(ResourceParserTest, ParseEscapedString) { 77 std::string input = "<string name=\"foo\">\\?123</string>"; 78 ASSERT_TRUE(testParse(input)); 79 80 String* str = test::getValue<String>(&mTable, "string/foo"); 81 ASSERT_NE(nullptr, str); 82 EXPECT_EQ(std::string("?123"), *str->value); 83} 84 85TEST_F(ResourceParserTest, ParseFormattedString) { 86 std::string input = "<string name=\"foo\">%d %s</string>"; 87 ASSERT_FALSE(testParse(input)); 88 89 input = "<string name=\"foo\">%1$d %2$s</string>"; 90 ASSERT_TRUE(testParse(input)); 91} 92 93TEST_F(ResourceParserTest, ParseStyledString) { 94 // Use a surrogate pair unicode point so that we can verify that the span 95 // indices 96 // use UTF-16 length and not UTF-18 length. 97 std::string input = 98 "<string name=\"foo\">This is my aunt\u2019s <b>string</b></string>"; 99 ASSERT_TRUE(testParse(input)); 100 101 StyledString* str = test::getValue<StyledString>(&mTable, "string/foo"); 102 ASSERT_NE(nullptr, str); 103 104 const std::string expectedStr = "This is my aunt\u2019s string"; 105 EXPECT_EQ(expectedStr, *str->value->str); 106 EXPECT_EQ(1u, str->value->spans.size()); 107 108 EXPECT_EQ(std::string("b"), *str->value->spans[0].name); 109 EXPECT_EQ(17u, str->value->spans[0].firstChar); 110 EXPECT_EQ(23u, str->value->spans[0].lastChar); 111} 112 113TEST_F(ResourceParserTest, ParseStringWithWhitespace) { 114 std::string input = "<string name=\"foo\"> This is what I think </string>"; 115 ASSERT_TRUE(testParse(input)); 116 117 String* str = test::getValue<String>(&mTable, "string/foo"); 118 ASSERT_NE(nullptr, str); 119 EXPECT_EQ(std::string("This is what I think"), *str->value); 120 121 input = "<string name=\"foo2\">\" This is what I think \"</string>"; 122 ASSERT_TRUE(testParse(input)); 123 124 str = test::getValue<String>(&mTable, "string/foo2"); 125 ASSERT_NE(nullptr, str); 126 EXPECT_EQ(std::string(" This is what I think "), *str->value); 127} 128 129TEST_F(ResourceParserTest, IgnoreXliffTags) { 130 std::string input = 131 "<string name=\"foo\" \n" 132 " xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n" 133 " There are <xliff:g id=\"count\">%1$d</xliff:g> apples</string>"; 134 ASSERT_TRUE(testParse(input)); 135 136 String* str = test::getValue<String>(&mTable, "string/foo"); 137 ASSERT_NE(nullptr, str); 138 EXPECT_EQ(StringPiece("There are %1$d apples"), StringPiece(*str->value)); 139} 140 141TEST_F(ResourceParserTest, ParseNull) { 142 std::string input = "<integer name=\"foo\">@null</integer>"; 143 ASSERT_TRUE(testParse(input)); 144 145 // The Android runtime treats a value of android::Res_value::TYPE_NULL as 146 // a non-existing value, and this causes problems in styles when trying to 147 // resolve 148 // an attribute. Null values must be encoded as 149 // android::Res_value::TYPE_REFERENCE 150 // with a data value of 0. 151 BinaryPrimitive* integer = 152 test::getValue<BinaryPrimitive>(&mTable, "integer/foo"); 153 ASSERT_NE(nullptr, integer); 154 EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE), 155 integer->value.dataType); 156 EXPECT_EQ(0u, integer->value.data); 157} 158 159TEST_F(ResourceParserTest, ParseEmpty) { 160 std::string input = "<integer name=\"foo\">@empty</integer>"; 161 ASSERT_TRUE(testParse(input)); 162 163 BinaryPrimitive* integer = 164 test::getValue<BinaryPrimitive>(&mTable, "integer/foo"); 165 ASSERT_NE(nullptr, integer); 166 EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType); 167 EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data); 168} 169 170TEST_F(ResourceParserTest, ParseAttr) { 171 std::string input = 172 "<attr name=\"foo\" format=\"string\"/>\n" 173 "<attr name=\"bar\"/>"; 174 ASSERT_TRUE(testParse(input)); 175 176 Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo"); 177 ASSERT_NE(nullptr, attr); 178 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); 179 180 attr = test::getValue<Attribute>(&mTable, "attr/bar"); 181 ASSERT_NE(nullptr, attr); 182 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask); 183} 184 185// Old AAPT allowed attributes to be defined under different configurations, but 186// ultimately 187// stored them with the default configuration. Check that we have the same 188// behavior. 189TEST_F(ResourceParserTest, 190 ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig) { 191 const ConfigDescription watchConfig = test::parseConfigOrDie("watch"); 192 std::string input = R"EOF( 193 <attr name="foo" /> 194 <declare-styleable name="bar"> 195 <attr name="baz" /> 196 </declare-styleable>)EOF"; 197 ASSERT_TRUE(testParse(input, watchConfig)); 198 199 EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, "attr/foo", 200 watchConfig)); 201 EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, "attr/baz", 202 watchConfig)); 203 EXPECT_EQ(nullptr, test::getValueForConfig<Styleable>( 204 &mTable, "styleable/bar", watchConfig)); 205 206 EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, "attr/foo")); 207 EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, "attr/baz")); 208 EXPECT_NE(nullptr, test::getValue<Styleable>(&mTable, "styleable/bar")); 209} 210 211TEST_F(ResourceParserTest, ParseAttrWithMinMax) { 212 std::string input = 213 "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"integer\"/>"; 214 ASSERT_TRUE(testParse(input)); 215 216 Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo"); 217 ASSERT_NE(nullptr, attr); 218 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->typeMask); 219 EXPECT_EQ(10, attr->minInt); 220 EXPECT_EQ(23, attr->maxInt); 221} 222 223TEST_F(ResourceParserTest, FailParseAttrWithMinMaxButNotInteger) { 224 std::string input = 225 "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"string\"/>"; 226 ASSERT_FALSE(testParse(input)); 227} 228 229TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) { 230 std::string input = 231 "<declare-styleable name=\"Styleable\">\n" 232 " <attr name=\"foo\" />\n" 233 "</declare-styleable>\n" 234 "<attr name=\"foo\" format=\"string\"/>"; 235 ASSERT_TRUE(testParse(input)); 236 237 Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo"); 238 ASSERT_NE(nullptr, attr); 239 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); 240} 241 242TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) { 243 std::string input = 244 "<declare-styleable name=\"Theme\">" 245 " <attr name=\"foo\" />\n" 246 "</declare-styleable>\n" 247 "<declare-styleable name=\"Window\">\n" 248 " <attr name=\"foo\" format=\"boolean\"/>\n" 249 "</declare-styleable>"; 250 ASSERT_TRUE(testParse(input)); 251 252 Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo"); 253 ASSERT_NE(nullptr, attr); 254 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask); 255} 256 257TEST_F(ResourceParserTest, ParseEnumAttr) { 258 std::string input = 259 "<attr name=\"foo\">\n" 260 " <enum name=\"bar\" value=\"0\"/>\n" 261 " <enum name=\"bat\" value=\"1\"/>\n" 262 " <enum name=\"baz\" value=\"2\"/>\n" 263 "</attr>"; 264 ASSERT_TRUE(testParse(input)); 265 266 Attribute* enumAttr = test::getValue<Attribute>(&mTable, "attr/foo"); 267 ASSERT_NE(enumAttr, nullptr); 268 EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM); 269 ASSERT_EQ(enumAttr->symbols.size(), 3u); 270 271 AAPT_ASSERT_TRUE(enumAttr->symbols[0].symbol.name); 272 EXPECT_EQ(enumAttr->symbols[0].symbol.name.value().entry, "bar"); 273 EXPECT_EQ(enumAttr->symbols[0].value, 0u); 274 275 AAPT_ASSERT_TRUE(enumAttr->symbols[1].symbol.name); 276 EXPECT_EQ(enumAttr->symbols[1].symbol.name.value().entry, "bat"); 277 EXPECT_EQ(enumAttr->symbols[1].value, 1u); 278 279 AAPT_ASSERT_TRUE(enumAttr->symbols[2].symbol.name); 280 EXPECT_EQ(enumAttr->symbols[2].symbol.name.value().entry, "baz"); 281 EXPECT_EQ(enumAttr->symbols[2].value, 2u); 282} 283 284TEST_F(ResourceParserTest, ParseFlagAttr) { 285 std::string input = 286 "<attr name=\"foo\">\n" 287 " <flag name=\"bar\" value=\"0\"/>\n" 288 " <flag name=\"bat\" value=\"1\"/>\n" 289 " <flag name=\"baz\" value=\"2\"/>\n" 290 "</attr>"; 291 ASSERT_TRUE(testParse(input)); 292 293 Attribute* flagAttr = test::getValue<Attribute>(&mTable, "attr/foo"); 294 ASSERT_NE(nullptr, flagAttr); 295 EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS); 296 ASSERT_EQ(flagAttr->symbols.size(), 3u); 297 298 AAPT_ASSERT_TRUE(flagAttr->symbols[0].symbol.name); 299 EXPECT_EQ(flagAttr->symbols[0].symbol.name.value().entry, "bar"); 300 EXPECT_EQ(flagAttr->symbols[0].value, 0u); 301 302 AAPT_ASSERT_TRUE(flagAttr->symbols[1].symbol.name); 303 EXPECT_EQ(flagAttr->symbols[1].symbol.name.value().entry, "bat"); 304 EXPECT_EQ(flagAttr->symbols[1].value, 1u); 305 306 AAPT_ASSERT_TRUE(flagAttr->symbols[2].symbol.name); 307 EXPECT_EQ(flagAttr->symbols[2].symbol.name.value().entry, "baz"); 308 EXPECT_EQ(flagAttr->symbols[2].value, 2u); 309 310 std::unique_ptr<BinaryPrimitive> flagValue = 311 ResourceUtils::tryParseFlagSymbol(flagAttr, "baz|bat"); 312 ASSERT_NE(nullptr, flagValue); 313 EXPECT_EQ(flagValue->value.data, 1u | 2u); 314} 315 316TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) { 317 std::string input = 318 "<attr name=\"foo\">\n" 319 " <enum name=\"bar\" value=\"0\"/>\n" 320 " <enum name=\"bat\" value=\"1\"/>\n" 321 " <enum name=\"bat\" value=\"2\"/>\n" 322 "</attr>"; 323 ASSERT_FALSE(testParse(input)); 324} 325 326TEST_F(ResourceParserTest, ParseStyle) { 327 std::string input = 328 "<style name=\"foo\" parent=\"@style/fu\">\n" 329 " <item name=\"bar\">#ffffffff</item>\n" 330 " <item name=\"bat\">@string/hey</item>\n" 331 " <item name=\"baz\"><b>hey</b></item>\n" 332 "</style>"; 333 ASSERT_TRUE(testParse(input)); 334 335 Style* style = test::getValue<Style>(&mTable, "style/foo"); 336 ASSERT_NE(nullptr, style); 337 AAPT_ASSERT_TRUE(style->parent); 338 AAPT_ASSERT_TRUE(style->parent.value().name); 339 EXPECT_EQ(test::parseNameOrDie("style/fu"), 340 style->parent.value().name.value()); 341 ASSERT_EQ(3u, style->entries.size()); 342 343 AAPT_ASSERT_TRUE(style->entries[0].key.name); 344 EXPECT_EQ(test::parseNameOrDie("attr/bar"), 345 style->entries[0].key.name.value()); 346 347 AAPT_ASSERT_TRUE(style->entries[1].key.name); 348 EXPECT_EQ(test::parseNameOrDie("attr/bat"), 349 style->entries[1].key.name.value()); 350 351 AAPT_ASSERT_TRUE(style->entries[2].key.name); 352 EXPECT_EQ(test::parseNameOrDie("attr/baz"), 353 style->entries[2].key.name.value()); 354} 355 356TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) { 357 std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>"; 358 ASSERT_TRUE(testParse(input)); 359 360 Style* style = test::getValue<Style>(&mTable, "style/foo"); 361 ASSERT_NE(nullptr, style); 362 AAPT_ASSERT_TRUE(style->parent); 363 AAPT_ASSERT_TRUE(style->parent.value().name); 364 EXPECT_EQ(test::parseNameOrDie("com.app:style/Theme"), 365 style->parent.value().name.value()); 366} 367 368TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) { 369 std::string input = 370 "<style xmlns:app=\"http://schemas.android.com/apk/res/android\"\n" 371 " name=\"foo\" parent=\"app:Theme\"/>"; 372 ASSERT_TRUE(testParse(input)); 373 374 Style* style = test::getValue<Style>(&mTable, "style/foo"); 375 ASSERT_NE(nullptr, style); 376 AAPT_ASSERT_TRUE(style->parent); 377 AAPT_ASSERT_TRUE(style->parent.value().name); 378 EXPECT_EQ(test::parseNameOrDie("android:style/Theme"), 379 style->parent.value().name.value()); 380} 381 382TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) { 383 std::string input = 384 "<style xmlns:app=\"http://schemas.android.com/apk/res/android\" " 385 "name=\"foo\">\n" 386 " <item name=\"app:bar\">0</item>\n" 387 "</style>"; 388 ASSERT_TRUE(testParse(input)); 389 390 Style* style = test::getValue<Style>(&mTable, "style/foo"); 391 ASSERT_NE(nullptr, style); 392 ASSERT_EQ(1u, style->entries.size()); 393 EXPECT_EQ(test::parseNameOrDie("android:attr/bar"), 394 style->entries[0].key.name.value()); 395} 396 397TEST_F(ResourceParserTest, ParseStyleWithInferredParent) { 398 std::string input = "<style name=\"foo.bar\"/>"; 399 ASSERT_TRUE(testParse(input)); 400 401 Style* style = test::getValue<Style>(&mTable, "style/foo.bar"); 402 ASSERT_NE(nullptr, style); 403 AAPT_ASSERT_TRUE(style->parent); 404 AAPT_ASSERT_TRUE(style->parent.value().name); 405 EXPECT_EQ(style->parent.value().name.value(), 406 test::parseNameOrDie("style/foo")); 407 EXPECT_TRUE(style->parentInferred); 408} 409 410TEST_F(ResourceParserTest, 411 ParseStyleWithInferredParentOverridenByEmptyParentAttribute) { 412 std::string input = "<style name=\"foo.bar\" parent=\"\"/>"; 413 ASSERT_TRUE(testParse(input)); 414 415 Style* style = test::getValue<Style>(&mTable, "style/foo.bar"); 416 ASSERT_NE(nullptr, style); 417 AAPT_EXPECT_FALSE(style->parent); 418 EXPECT_FALSE(style->parentInferred); 419} 420 421TEST_F(ResourceParserTest, ParseStyleWithPrivateParentReference) { 422 std::string input = 423 R"EOF(<style name="foo" parent="*android:style/bar" />)EOF"; 424 ASSERT_TRUE(testParse(input)); 425 426 Style* style = test::getValue<Style>(&mTable, "style/foo"); 427 ASSERT_NE(nullptr, style); 428 AAPT_ASSERT_TRUE(style->parent); 429 EXPECT_TRUE(style->parent.value().privateReference); 430} 431 432TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) { 433 std::string input = "<string name=\"foo\">@+id/bar</string>"; 434 ASSERT_TRUE(testParse(input)); 435 436 Id* id = test::getValue<Id>(&mTable, "id/bar"); 437 ASSERT_NE(id, nullptr); 438} 439 440TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) { 441 std::string input = 442 "<declare-styleable name=\"foo\">\n" 443 " <attr name=\"bar\" />\n" 444 " <attr name=\"bat\" format=\"string|reference\"/>\n" 445 " <attr name=\"baz\">\n" 446 " <enum name=\"foo\" value=\"1\"/>\n" 447 " </attr>\n" 448 "</declare-styleable>"; 449 ASSERT_TRUE(testParse(input)); 450 451 Maybe<ResourceTable::SearchResult> result = 452 mTable.findResource(test::parseNameOrDie("styleable/foo")); 453 AAPT_ASSERT_TRUE(result); 454 EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbolStatus.state); 455 456 Attribute* attr = test::getValue<Attribute>(&mTable, "attr/bar"); 457 ASSERT_NE(attr, nullptr); 458 EXPECT_TRUE(attr->isWeak()); 459 460 attr = test::getValue<Attribute>(&mTable, "attr/bat"); 461 ASSERT_NE(attr, nullptr); 462 EXPECT_TRUE(attr->isWeak()); 463 464 attr = test::getValue<Attribute>(&mTable, "attr/baz"); 465 ASSERT_NE(attr, nullptr); 466 EXPECT_TRUE(attr->isWeak()); 467 EXPECT_EQ(1u, attr->symbols.size()); 468 469 EXPECT_NE(nullptr, test::getValue<Id>(&mTable, "id/foo")); 470 471 Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo"); 472 ASSERT_NE(styleable, nullptr); 473 ASSERT_EQ(3u, styleable->entries.size()); 474 475 EXPECT_EQ(test::parseNameOrDie("attr/bar"), 476 styleable->entries[0].name.value()); 477 EXPECT_EQ(test::parseNameOrDie("attr/bat"), 478 styleable->entries[1].name.value()); 479} 480 481TEST_F(ResourceParserTest, ParsePrivateAttributesDeclareStyleable) { 482 std::string input = 483 "<declare-styleable name=\"foo\" " 484 "xmlns:privAndroid=\"http://schemas.android.com/apk/prv/res/android\">\n" 485 " <attr name=\"*android:bar\" />\n" 486 " <attr name=\"privAndroid:bat\" />\n" 487 "</declare-styleable>"; 488 ASSERT_TRUE(testParse(input)); 489 Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo"); 490 ASSERT_NE(nullptr, styleable); 491 ASSERT_EQ(2u, styleable->entries.size()); 492 493 EXPECT_TRUE(styleable->entries[0].privateReference); 494 AAPT_ASSERT_TRUE(styleable->entries[0].name); 495 EXPECT_EQ(std::string("android"), styleable->entries[0].name.value().package); 496 497 EXPECT_TRUE(styleable->entries[1].privateReference); 498 AAPT_ASSERT_TRUE(styleable->entries[1].name); 499 EXPECT_EQ(std::string("android"), styleable->entries[1].name.value().package); 500} 501 502TEST_F(ResourceParserTest, ParseArray) { 503 std::string input = 504 "<array name=\"foo\">\n" 505 " <item>@string/ref</item>\n" 506 " <item>hey</item>\n" 507 " <item>23</item>\n" 508 "</array>"; 509 ASSERT_TRUE(testParse(input)); 510 511 Array* array = test::getValue<Array>(&mTable, "array/foo"); 512 ASSERT_NE(array, nullptr); 513 ASSERT_EQ(3u, array->items.size()); 514 515 EXPECT_NE(nullptr, valueCast<Reference>(array->items[0].get())); 516 EXPECT_NE(nullptr, valueCast<String>(array->items[1].get())); 517 EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(array->items[2].get())); 518} 519 520TEST_F(ResourceParserTest, ParseStringArray) { 521 std::string input = 522 "<string-array name=\"foo\">\n" 523 " <item>\"Werk\"</item>\n" 524 "</string-array>\n"; 525 ASSERT_TRUE(testParse(input)); 526 EXPECT_NE(nullptr, test::getValue<Array>(&mTable, "array/foo")); 527} 528 529TEST_F(ResourceParserTest, ParsePlural) { 530 std::string input = 531 "<plurals name=\"foo\">\n" 532 " <item quantity=\"other\">apples</item>\n" 533 " <item quantity=\"one\">apple</item>\n" 534 "</plurals>"; 535 ASSERT_TRUE(testParse(input)); 536} 537 538TEST_F(ResourceParserTest, ParseCommentsWithResource) { 539 std::string input = 540 "<!--This is a comment-->\n" 541 "<string name=\"foo\">Hi</string>"; 542 ASSERT_TRUE(testParse(input)); 543 544 String* value = test::getValue<String>(&mTable, "string/foo"); 545 ASSERT_NE(nullptr, value); 546 EXPECT_EQ(value->getComment(), "This is a comment"); 547} 548 549TEST_F(ResourceParserTest, DoNotCombineMultipleComments) { 550 std::string input = 551 "<!--One-->\n" 552 "<!--Two-->\n" 553 "<string name=\"foo\">Hi</string>"; 554 555 ASSERT_TRUE(testParse(input)); 556 557 String* value = test::getValue<String>(&mTable, "string/foo"); 558 ASSERT_NE(nullptr, value); 559 EXPECT_EQ(value->getComment(), "Two"); 560} 561 562TEST_F(ResourceParserTest, IgnoreCommentBeforeEndTag) { 563 std::string input = 564 "<!--One-->\n" 565 "<string name=\"foo\">\n" 566 " Hi\n" 567 "<!--Two-->\n" 568 "</string>"; 569 570 ASSERT_TRUE(testParse(input)); 571 572 String* value = test::getValue<String>(&mTable, "string/foo"); 573 ASSERT_NE(nullptr, value); 574 EXPECT_EQ(value->getComment(), "One"); 575} 576 577TEST_F(ResourceParserTest, ParseNestedComments) { 578 // We only care about declare-styleable and enum/flag attributes because 579 // comments 580 // from those end up in R.java 581 std::string input = R"EOF( 582 <declare-styleable name="foo"> 583 <!-- The name of the bar --> 584 <attr name="barName" format="string|reference" /> 585 </declare-styleable> 586 587 <attr name="foo"> 588 <!-- The very first --> 589 <enum name="one" value="1" /> 590 </attr>)EOF"; 591 ASSERT_TRUE(testParse(input)); 592 593 Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo"); 594 ASSERT_NE(nullptr, styleable); 595 ASSERT_EQ(1u, styleable->entries.size()); 596 597 EXPECT_EQ(StringPiece("The name of the bar"), 598 styleable->entries.front().getComment()); 599 600 Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo"); 601 ASSERT_NE(nullptr, attr); 602 ASSERT_EQ(1u, attr->symbols.size()); 603 604 EXPECT_EQ(StringPiece("The very first"), 605 attr->symbols.front().symbol.getComment()); 606} 607 608/* 609 * Declaring an ID as public should not require a separate definition 610 * (as an ID has no value). 611 */ 612TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) { 613 std::string input = "<public type=\"id\" name=\"foo\"/>"; 614 ASSERT_TRUE(testParse(input)); 615 616 Id* id = test::getValue<Id>(&mTable, "id/foo"); 617 ASSERT_NE(nullptr, id); 618} 619 620TEST_F(ResourceParserTest, KeepAllProducts) { 621 std::string input = R"EOF( 622 <string name="foo" product="phone">hi</string> 623 <string name="foo" product="no-sdcard">ho</string> 624 <string name="bar" product="">wee</string> 625 <string name="baz">woo</string> 626 <string name="bit" product="phablet">hoot</string> 627 <string name="bot" product="default">yes</string> 628 )EOF"; 629 ASSERT_TRUE(testParse(input)); 630 631 EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>( 632 &mTable, "string/foo", 633 ConfigDescription::defaultConfig(), "phone")); 634 EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>( 635 &mTable, "string/foo", 636 ConfigDescription::defaultConfig(), "no-sdcard")); 637 EXPECT_NE(nullptr, 638 test::getValueForConfigAndProduct<String>( 639 &mTable, "string/bar", ConfigDescription::defaultConfig(), "")); 640 EXPECT_NE(nullptr, 641 test::getValueForConfigAndProduct<String>( 642 &mTable, "string/baz", ConfigDescription::defaultConfig(), "")); 643 EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>( 644 &mTable, "string/bit", 645 ConfigDescription::defaultConfig(), "phablet")); 646 EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>( 647 &mTable, "string/bot", 648 ConfigDescription::defaultConfig(), "default")); 649} 650 651TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) { 652 std::string input = R"EOF( 653 <public-group type="attr" first-id="0x01010040"> 654 <public name="foo" /> 655 <public name="bar" /> 656 </public-group>)EOF"; 657 ASSERT_TRUE(testParse(input)); 658 659 Maybe<ResourceTable::SearchResult> result = 660 mTable.findResource(test::parseNameOrDie("attr/foo")); 661 AAPT_ASSERT_TRUE(result); 662 663 AAPT_ASSERT_TRUE(result.value().package->id); 664 AAPT_ASSERT_TRUE(result.value().type->id); 665 AAPT_ASSERT_TRUE(result.value().entry->id); 666 ResourceId actualId(result.value().package->id.value(), 667 result.value().type->id.value(), 668 result.value().entry->id.value()); 669 EXPECT_EQ(ResourceId(0x01010040), actualId); 670 671 result = mTable.findResource(test::parseNameOrDie("attr/bar")); 672 AAPT_ASSERT_TRUE(result); 673 674 AAPT_ASSERT_TRUE(result.value().package->id); 675 AAPT_ASSERT_TRUE(result.value().type->id); 676 AAPT_ASSERT_TRUE(result.value().entry->id); 677 actualId = ResourceId(result.value().package->id.value(), 678 result.value().type->id.value(), 679 result.value().entry->id.value()); 680 EXPECT_EQ(ResourceId(0x01010041), actualId); 681} 682 683TEST_F(ResourceParserTest, ExternalTypesShouldOnlyBeReferences) { 684 std::string input = 685 R"EOF(<item type="layout" name="foo">@layout/bar</item>)EOF"; 686 ASSERT_TRUE(testParse(input)); 687 688 input = R"EOF(<item type="layout" name="bar">"this is a string"</item>)EOF"; 689 ASSERT_FALSE(testParse(input)); 690} 691 692TEST_F(ResourceParserTest, 693 AddResourcesElementShouldAddEntryWithUndefinedSymbol) { 694 std::string input = R"EOF(<add-resource name="bar" type="string" />)EOF"; 695 ASSERT_TRUE(testParse(input)); 696 697 Maybe<ResourceTable::SearchResult> result = 698 mTable.findResource(test::parseNameOrDie("string/bar")); 699 AAPT_ASSERT_TRUE(result); 700 const ResourceEntry* entry = result.value().entry; 701 ASSERT_NE(nullptr, entry); 702 EXPECT_EQ(SymbolState::kUndefined, entry->symbolStatus.state); 703} 704 705TEST_F(ResourceParserTest, ParseItemElementWithFormat) { 706 std::string input = 707 R"EOF(<item name="foo" type="integer" format="float">0.3</item>)EOF"; 708 ASSERT_TRUE(testParse(input)); 709 710 BinaryPrimitive* val = 711 test::getValue<BinaryPrimitive>(&mTable, "integer/foo"); 712 ASSERT_NE(nullptr, val); 713 714 EXPECT_EQ(uint32_t(android::Res_value::TYPE_FLOAT), val->value.dataType); 715} 716 717} // namespace aapt 718