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