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, ParseNull) { 195 std::string input = "<integer name=\"foo\">@null</integer>"; 196 ASSERT_TRUE(testParse(input)); 197 198 // The Android runtime treats a value of android::Res_value::TYPE_NULL as 199 // a non-existing value, and this causes problems in styles when trying to resolve 200 // an attribute. Null values must be encoded as android::Res_value::TYPE_REFERENCE 201 // with a data value of 0. 202 const BinaryPrimitive* integer = findResource<BinaryPrimitive>(ResourceName{ 203 u"android", ResourceType::kInteger, u"foo" }); 204 ASSERT_NE(nullptr, integer); 205 EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE), integer->value.dataType); 206 EXPECT_EQ(0u, integer->value.data); 207} 208 209TEST_F(ResourceParserTest, ParseEmpty) { 210 std::string input = "<integer name=\"foo\">@empty</integer>"; 211 ASSERT_TRUE(testParse(input)); 212 213 const BinaryPrimitive* integer = findResource<BinaryPrimitive>(ResourceName{ 214 u"android", ResourceType::kInteger, u"foo" }); 215 ASSERT_NE(nullptr, integer); 216 EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType); 217 EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data); 218} 219 220TEST_F(ResourceParserTest, ParseAttr) { 221 std::string input = "<attr name=\"foo\" format=\"string\"/>\n" 222 "<attr name=\"bar\"/>"; 223 ASSERT_TRUE(testParse(input)); 224 225 const Attribute* attr = findResource<Attribute>(ResourceName{ 226 u"android", ResourceType::kAttr, u"foo"}); 227 EXPECT_NE(nullptr, attr); 228 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); 229 230 attr = findResource<Attribute>(ResourceName{ 231 u"android", ResourceType::kAttr, u"bar"}); 232 EXPECT_NE(nullptr, attr); 233 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask); 234} 235 236TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) { 237 std::string input = "<declare-styleable name=\"Styleable\">\n" 238 " <attr name=\"foo\" />\n" 239 "</declare-styleable>\n" 240 "<attr name=\"foo\" format=\"string\"/>"; 241 ASSERT_TRUE(testParse(input)); 242 243 const Attribute* attr = findResource<Attribute>(ResourceName{ 244 u"android", ResourceType::kAttr, u"foo"}); 245 ASSERT_NE(nullptr, attr); 246 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); 247} 248 249TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) { 250 std::string input = "<declare-styleable name=\"Theme\">" 251 " <attr name=\"foo\" />\n" 252 "</declare-styleable>\n" 253 "<declare-styleable name=\"Window\">\n" 254 " <attr name=\"foo\" format=\"boolean\"/>\n" 255 "</declare-styleable>"; 256 ASSERT_TRUE(testParse(input)); 257 258 const Attribute* attr = findResource<Attribute>(ResourceName{ 259 u"android", ResourceType::kAttr, u"foo"}); 260 ASSERT_NE(nullptr, attr); 261 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask); 262} 263 264TEST_F(ResourceParserTest, ParseEnumAttr) { 265 std::string input = "<attr name=\"foo\">\n" 266 " <enum name=\"bar\" value=\"0\"/>\n" 267 " <enum name=\"bat\" value=\"1\"/>\n" 268 " <enum name=\"baz\" value=\"2\"/>\n" 269 "</attr>"; 270 ASSERT_TRUE(testParse(input)); 271 272 const Attribute* enumAttr = findResource<Attribute>(ResourceName{ 273 u"android", ResourceType::kAttr, u"foo"}); 274 ASSERT_NE(enumAttr, nullptr); 275 EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM); 276 ASSERT_EQ(enumAttr->symbols.size(), 3u); 277 278 EXPECT_EQ(enumAttr->symbols[0].symbol.name.entry, u"bar"); 279 EXPECT_EQ(enumAttr->symbols[0].value, 0u); 280 281 EXPECT_EQ(enumAttr->symbols[1].symbol.name.entry, u"bat"); 282 EXPECT_EQ(enumAttr->symbols[1].value, 1u); 283 284 EXPECT_EQ(enumAttr->symbols[2].symbol.name.entry, u"baz"); 285 EXPECT_EQ(enumAttr->symbols[2].value, 2u); 286} 287 288TEST_F(ResourceParserTest, ParseFlagAttr) { 289 std::string input = "<attr name=\"foo\">\n" 290 " <flag name=\"bar\" value=\"0\"/>\n" 291 " <flag name=\"bat\" value=\"1\"/>\n" 292 " <flag name=\"baz\" value=\"2\"/>\n" 293 "</attr>"; 294 ASSERT_TRUE(testParse(input)); 295 296 const Attribute* flagAttr = findResource<Attribute>(ResourceName{ 297 u"android", ResourceType::kAttr, u"foo"}); 298 ASSERT_NE(flagAttr, nullptr); 299 EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS); 300 ASSERT_EQ(flagAttr->symbols.size(), 3u); 301 302 EXPECT_EQ(flagAttr->symbols[0].symbol.name.entry, u"bar"); 303 EXPECT_EQ(flagAttr->symbols[0].value, 0u); 304 305 EXPECT_EQ(flagAttr->symbols[1].symbol.name.entry, u"bat"); 306 EXPECT_EQ(flagAttr->symbols[1].value, 1u); 307 308 EXPECT_EQ(flagAttr->symbols[2].symbol.name.entry, u"baz"); 309 EXPECT_EQ(flagAttr->symbols[2].value, 2u); 310 311 std::unique_ptr<BinaryPrimitive> flagValue = 312 ResourceParser::tryParseFlagSymbol(*flagAttr, u"baz|bat"); 313 ASSERT_NE(flagValue, nullptr); 314 EXPECT_EQ(flagValue->value.data, 1u | 2u); 315} 316 317TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) { 318 std::string input = "<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 = "<style name=\"foo\" parent=\"@style/fu\">\n" 328 " <item name=\"bar\">#ffffffff</item>\n" 329 " <item name=\"bat\">@string/hey</item>\n" 330 " <item name=\"baz\"><b>hey</b></item>\n" 331 "</style>"; 332 ASSERT_TRUE(testParse(input)); 333 334 const Style* style = findResource<Style>(ResourceName{ 335 u"android", ResourceType::kStyle, u"foo"}); 336 ASSERT_NE(style, nullptr); 337 EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"fu"), style->parent.name); 338 ASSERT_EQ(style->entries.size(), 3u); 339 340 EXPECT_EQ(style->entries[0].key.name, 341 (ResourceName{ u"android", ResourceType::kAttr, u"bar" })); 342 EXPECT_EQ(style->entries[1].key.name, 343 (ResourceName{ u"android", ResourceType::kAttr, u"bat" })); 344 EXPECT_EQ(style->entries[2].key.name, 345 (ResourceName{ u"android", ResourceType::kAttr, u"baz" })); 346} 347 348TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) { 349 std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>"; 350 ASSERT_TRUE(testParse(input)); 351 352 const Style* style = findResource<Style>( 353 ResourceName{ u"android", ResourceType::kStyle, u"foo" }); 354 ASSERT_NE(style, nullptr); 355 EXPECT_EQ(ResourceNameRef(u"com.app", ResourceType::kStyle, u"Theme"), style->parent.name); 356} 357 358TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) { 359 std::string input = "<style xmlns:app=\"http://schemas.android.com/apk/res/android\"\n" 360 " name=\"foo\" parent=\"app:Theme\"/>"; 361 ASSERT_TRUE(testParse(input)); 362 363 const Style* style = findResource<Style>(ResourceName{ 364 u"android", ResourceType::kStyle, u"foo" }); 365 ASSERT_NE(style, nullptr); 366 EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"Theme"), style->parent.name); 367} 368 369TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) { 370 std::string input = 371 "<style xmlns:app=\"http://schemas.android.com/apk/res/android\" name=\"foo\">\n" 372 " <item name=\"app:bar\">0</item>\n" 373 "</style>"; 374 ASSERT_TRUE(testParse(input)); 375 376 const Style* style = findResource<Style>(ResourceName{ 377 u"android", ResourceType::kStyle, u"foo" }); 378 ASSERT_NE(style, nullptr); 379 ASSERT_EQ(1u, style->entries.size()); 380 EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kAttr, u"bar"), 381 style->entries[0].key.name); 382} 383 384TEST_F(ResourceParserTest, ParseStyleWithInferredParent) { 385 std::string input = "<style name=\"foo.bar\"/>"; 386 ASSERT_TRUE(testParse(input)); 387 388 const Style* style = findResource<Style>(ResourceName{ 389 u"android", ResourceType::kStyle, u"foo.bar" }); 390 ASSERT_NE(style, nullptr); 391 EXPECT_EQ(style->parent.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); 392 EXPECT_TRUE(style->parentInferred); 393} 394 395TEST_F(ResourceParserTest, ParseStyleWithInferredParentOverridenByEmptyParentAttribute) { 396 std::string input = "<style name=\"foo.bar\" parent=\"\"/>"; 397 ASSERT_TRUE(testParse(input)); 398 399 const Style* style = findResource<Style>(ResourceName{ 400 u"android", ResourceType::kStyle, u"foo.bar" }); 401 ASSERT_NE(style, nullptr); 402 EXPECT_FALSE(style->parent.name.isValid()); 403 EXPECT_FALSE(style->parentInferred); 404} 405 406TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) { 407 std::string input = "<string name=\"foo\">@+id/bar</string>"; 408 ASSERT_TRUE(testParse(input)); 409 410 const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"bar"}); 411 ASSERT_NE(id, nullptr); 412} 413 414TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) { 415 std::string input = "<declare-styleable name=\"foo\">\n" 416 " <attr name=\"bar\" />\n" 417 " <attr name=\"bat\" format=\"string|reference\"/>\n" 418 "</declare-styleable>"; 419 ASSERT_TRUE(testParse(input)); 420 421 const Attribute* attr = findResource<Attribute>(ResourceName{ 422 u"android", ResourceType::kAttr, u"bar"}); 423 ASSERT_NE(attr, nullptr); 424 EXPECT_TRUE(attr->isWeak()); 425 426 attr = findResource<Attribute>(ResourceName{ u"android", ResourceType::kAttr, u"bat"}); 427 ASSERT_NE(attr, nullptr); 428 EXPECT_TRUE(attr->isWeak()); 429 430 const Styleable* styleable = findResource<Styleable>(ResourceName{ 431 u"android", ResourceType::kStyleable, u"foo" }); 432 ASSERT_NE(styleable, nullptr); 433 ASSERT_EQ(2u, styleable->entries.size()); 434 435 EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bar"}), styleable->entries[0].name); 436 EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bat"}), styleable->entries[1].name); 437} 438 439TEST_F(ResourceParserTest, ParseArray) { 440 std::string input = "<array name=\"foo\">\n" 441 " <item>@string/ref</item>\n" 442 " <item>hey</item>\n" 443 " <item>23</item>\n" 444 "</array>"; 445 ASSERT_TRUE(testParse(input)); 446 447 const Array* array = findResource<Array>(ResourceName{ 448 u"android", ResourceType::kArray, u"foo" }); 449 ASSERT_NE(array, nullptr); 450 ASSERT_EQ(3u, array->items.size()); 451 452 EXPECT_NE(nullptr, dynamic_cast<const Reference*>(array->items[0].get())); 453 EXPECT_NE(nullptr, dynamic_cast<const String*>(array->items[1].get())); 454 EXPECT_NE(nullptr, dynamic_cast<const BinaryPrimitive*>(array->items[2].get())); 455} 456 457TEST_F(ResourceParserTest, ParsePlural) { 458 std::string input = "<plurals name=\"foo\">\n" 459 " <item quantity=\"other\">apples</item>\n" 460 " <item quantity=\"one\">apple</item>\n" 461 "</plurals>"; 462 ASSERT_TRUE(testParse(input)); 463} 464 465TEST_F(ResourceParserTest, ParseCommentsWithResource) { 466 std::string input = "<!-- This is a comment -->\n" 467 "<string name=\"foo\">Hi</string>"; 468 ASSERT_TRUE(testParse(input)); 469 470 const ResourceTableType* type; 471 const ResourceEntry* entry; 472 std::tie(type, entry) = mTable->findResource(ResourceName{ 473 u"android", ResourceType::kString, u"foo"}); 474 ASSERT_NE(type, nullptr); 475 ASSERT_NE(entry, nullptr); 476 ASSERT_FALSE(entry->values.empty()); 477 EXPECT_EQ(entry->values.front().comment, u"This is a comment"); 478} 479 480/* 481 * Declaring an ID as public should not require a separate definition 482 * (as an ID has no value). 483 */ 484TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) { 485 std::string input = "<public type=\"id\" name=\"foo\"/>"; 486 ASSERT_TRUE(testParse(input)); 487 488 const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"foo" }); 489 ASSERT_NE(nullptr, id); 490} 491 492} // namespace aapt 493