ResourceParser_test.cpp revision ca5638fd85098c3d0a699492751043545f75553a
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 "XmlPullParser.h" 22 23#include "test/Context.h" 24 25#include <gtest/gtest.h> 26#include <sstream> 27#include <string> 28 29namespace aapt { 30 31constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; 32 33TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) { 34 std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); 35 std::stringstream input(kXmlPreamble); 36 input << "<attr name=\"foo\"/>" << std::endl; 37 ResourceTable table; 38 ResourceParser parser(context->getDiagnostics(), &table, Source{ "test" }, {}); 39 XmlPullParser xmlParser(input); 40 ASSERT_FALSE(parser.parse(&xmlParser)); 41} 42 43struct ResourceParserTest : public ::testing::Test { 44 ResourceTable mTable; 45 std::unique_ptr<IAaptContext> mContext; 46 47 void SetUp() override { 48 mContext = test::ContextBuilder().build(); 49 } 50 51 ::testing::AssertionResult testParse(const StringPiece& str, 52 Maybe<std::u16string> product = {}) { 53 std::stringstream input(kXmlPreamble); 54 input << "<resources>\n" << str << "\n</resources>" << std::endl; 55 ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, {}, 56 ResourceParserOptions{ product }); 57 XmlPullParser xmlParser(input); 58 if (parser.parse(&xmlParser)) { 59 return ::testing::AssertionSuccess(); 60 } 61 return ::testing::AssertionFailure(); 62 } 63}; 64 65TEST_F(ResourceParserTest, ParseQuotedString) { 66 std::string input = "<string name=\"foo\"> \" hey there \" </string>"; 67 ASSERT_TRUE(testParse(input)); 68 69 String* str = test::getValue<String>(&mTable, u"@string/foo"); 70 ASSERT_NE(nullptr, str); 71 EXPECT_EQ(std::u16string(u" hey there "), *str->value); 72} 73 74TEST_F(ResourceParserTest, ParseEscapedString) { 75 std::string input = "<string name=\"foo\">\\?123</string>"; 76 ASSERT_TRUE(testParse(input)); 77 78 String* str = test::getValue<String>(&mTable, u"@string/foo"); 79 ASSERT_NE(nullptr, str); 80 EXPECT_EQ(std::u16string(u"?123"), *str->value); 81} 82 83TEST_F(ResourceParserTest, IgnoreXliffTags) { 84 std::string input = "<string name=\"foo\" \n" 85 " xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n" 86 " There are <xliff:g id=\"count\">%1$d</xliff:g> apples</string>"; 87 ASSERT_TRUE(testParse(input)); 88 89 String* str = test::getValue<String>(&mTable, u"@string/foo"); 90 ASSERT_NE(nullptr, str); 91 EXPECT_EQ(StringPiece16(u"There are %1$d apples"), StringPiece16(*str->value)); 92} 93 94TEST_F(ResourceParserTest, ParseNull) { 95 std::string input = "<integer name=\"foo\">@null</integer>"; 96 ASSERT_TRUE(testParse(input)); 97 98 // The Android runtime treats a value of android::Res_value::TYPE_NULL as 99 // a non-existing value, and this causes problems in styles when trying to resolve 100 // an attribute. Null values must be encoded as android::Res_value::TYPE_REFERENCE 101 // with a data value of 0. 102 BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, u"@integer/foo"); 103 ASSERT_NE(nullptr, integer); 104 EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE), integer->value.dataType); 105 EXPECT_EQ(0u, integer->value.data); 106} 107 108TEST_F(ResourceParserTest, ParseEmpty) { 109 std::string input = "<integer name=\"foo\">@empty</integer>"; 110 ASSERT_TRUE(testParse(input)); 111 112 BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, u"@integer/foo"); 113 ASSERT_NE(nullptr, integer); 114 EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType); 115 EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data); 116} 117 118TEST_F(ResourceParserTest, ParseAttr) { 119 std::string input = "<attr name=\"foo\" format=\"string\"/>\n" 120 "<attr name=\"bar\"/>"; 121 ASSERT_TRUE(testParse(input)); 122 123 Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo"); 124 ASSERT_NE(nullptr, attr); 125 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); 126 127 attr = test::getValue<Attribute>(&mTable, u"@attr/bar"); 128 ASSERT_NE(nullptr, attr); 129 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask); 130} 131 132TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) { 133 std::string input = "<declare-styleable name=\"Styleable\">\n" 134 " <attr name=\"foo\" />\n" 135 "</declare-styleable>\n" 136 "<attr name=\"foo\" format=\"string\"/>"; 137 ASSERT_TRUE(testParse(input)); 138 139 Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo"); 140 ASSERT_NE(nullptr, attr); 141 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); 142} 143 144TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) { 145 std::string input = "<declare-styleable name=\"Theme\">" 146 " <attr name=\"foo\" />\n" 147 "</declare-styleable>\n" 148 "<declare-styleable name=\"Window\">\n" 149 " <attr name=\"foo\" format=\"boolean\"/>\n" 150 "</declare-styleable>"; 151 ASSERT_TRUE(testParse(input)); 152 153 Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo"); 154 ASSERT_NE(nullptr, attr); 155 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask); 156} 157 158TEST_F(ResourceParserTest, ParseEnumAttr) { 159 std::string input = "<attr name=\"foo\">\n" 160 " <enum name=\"bar\" value=\"0\"/>\n" 161 " <enum name=\"bat\" value=\"1\"/>\n" 162 " <enum name=\"baz\" value=\"2\"/>\n" 163 "</attr>"; 164 ASSERT_TRUE(testParse(input)); 165 166 Attribute* enumAttr = test::getValue<Attribute>(&mTable, u"@attr/foo"); 167 ASSERT_NE(enumAttr, nullptr); 168 EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM); 169 ASSERT_EQ(enumAttr->symbols.size(), 3u); 170 171 AAPT_ASSERT_TRUE(enumAttr->symbols[0].symbol.name); 172 EXPECT_EQ(enumAttr->symbols[0].symbol.name.value().entry, u"bar"); 173 EXPECT_EQ(enumAttr->symbols[0].value, 0u); 174 175 AAPT_ASSERT_TRUE(enumAttr->symbols[1].symbol.name); 176 EXPECT_EQ(enumAttr->symbols[1].symbol.name.value().entry, u"bat"); 177 EXPECT_EQ(enumAttr->symbols[1].value, 1u); 178 179 AAPT_ASSERT_TRUE(enumAttr->symbols[2].symbol.name); 180 EXPECT_EQ(enumAttr->symbols[2].symbol.name.value().entry, u"baz"); 181 EXPECT_EQ(enumAttr->symbols[2].value, 2u); 182} 183 184TEST_F(ResourceParserTest, ParseFlagAttr) { 185 std::string input = "<attr name=\"foo\">\n" 186 " <flag name=\"bar\" value=\"0\"/>\n" 187 " <flag name=\"bat\" value=\"1\"/>\n" 188 " <flag name=\"baz\" value=\"2\"/>\n" 189 "</attr>"; 190 ASSERT_TRUE(testParse(input)); 191 192 Attribute* flagAttr = test::getValue<Attribute>(&mTable, u"@attr/foo"); 193 ASSERT_NE(flagAttr, nullptr); 194 EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS); 195 ASSERT_EQ(flagAttr->symbols.size(), 3u); 196 197 AAPT_ASSERT_TRUE(flagAttr->symbols[0].symbol.name); 198 EXPECT_EQ(flagAttr->symbols[0].symbol.name.value().entry, u"bar"); 199 EXPECT_EQ(flagAttr->symbols[0].value, 0u); 200 201 AAPT_ASSERT_TRUE(flagAttr->symbols[1].symbol.name); 202 EXPECT_EQ(flagAttr->symbols[1].symbol.name.value().entry, u"bat"); 203 EXPECT_EQ(flagAttr->symbols[1].value, 1u); 204 205 AAPT_ASSERT_TRUE(flagAttr->symbols[2].symbol.name); 206 EXPECT_EQ(flagAttr->symbols[2].symbol.name.value().entry, u"baz"); 207 EXPECT_EQ(flagAttr->symbols[2].value, 2u); 208 209 std::unique_ptr<BinaryPrimitive> flagValue = ResourceUtils::tryParseFlagSymbol(flagAttr, 210 u"baz|bat"); 211 ASSERT_NE(flagValue, nullptr); 212 EXPECT_EQ(flagValue->value.data, 1u | 2u); 213} 214 215TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) { 216 std::string input = "<attr name=\"foo\">\n" 217 " <enum name=\"bar\" value=\"0\"/>\n" 218 " <enum name=\"bat\" value=\"1\"/>\n" 219 " <enum name=\"bat\" value=\"2\"/>\n" 220 "</attr>"; 221 ASSERT_FALSE(testParse(input)); 222} 223 224TEST_F(ResourceParserTest, ParseStyle) { 225 std::string input = "<style name=\"foo\" parent=\"@style/fu\">\n" 226 " <item name=\"bar\">#ffffffff</item>\n" 227 " <item name=\"bat\">@string/hey</item>\n" 228 " <item name=\"baz\"><b>hey</b></item>\n" 229 "</style>"; 230 ASSERT_TRUE(testParse(input)); 231 232 Style* style = test::getValue<Style>(&mTable, u"@style/foo"); 233 ASSERT_NE(style, nullptr); 234 AAPT_ASSERT_TRUE(style->parent); 235 AAPT_ASSERT_TRUE(style->parent.value().name); 236 EXPECT_EQ(test::parseNameOrDie(u"@style/fu"), style->parent.value().name.value()); 237 ASSERT_EQ(3u, style->entries.size()); 238 239 AAPT_ASSERT_TRUE(style->entries[0].key.name); 240 EXPECT_EQ(test::parseNameOrDie(u"@attr/bar"), style->entries[0].key.name.value()); 241 242 AAPT_ASSERT_TRUE(style->entries[1].key.name); 243 EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), style->entries[1].key.name.value()); 244 245 AAPT_ASSERT_TRUE(style->entries[2].key.name); 246 EXPECT_EQ(test::parseNameOrDie(u"@attr/baz"), style->entries[2].key.name.value()); 247} 248 249TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) { 250 std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>"; 251 ASSERT_TRUE(testParse(input)); 252 253 Style* style = test::getValue<Style>(&mTable, u"@style/foo"); 254 ASSERT_NE(style, nullptr); 255 AAPT_ASSERT_TRUE(style->parent); 256 AAPT_ASSERT_TRUE(style->parent.value().name); 257 EXPECT_EQ(test::parseNameOrDie(u"@com.app:style/Theme"), style->parent.value().name.value()); 258} 259 260TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) { 261 std::string input = "<style xmlns:app=\"http://schemas.android.com/apk/res/android\"\n" 262 " name=\"foo\" parent=\"app:Theme\"/>"; 263 ASSERT_TRUE(testParse(input)); 264 265 Style* style = test::getValue<Style>(&mTable, u"@style/foo"); 266 ASSERT_NE(style, nullptr); 267 AAPT_ASSERT_TRUE(style->parent); 268 AAPT_ASSERT_TRUE(style->parent.value().name); 269 EXPECT_EQ(test::parseNameOrDie(u"@android:style/Theme"), style->parent.value().name.value()); 270} 271 272TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) { 273 std::string input = 274 "<style xmlns:app=\"http://schemas.android.com/apk/res/android\" name=\"foo\">\n" 275 " <item name=\"app:bar\">0</item>\n" 276 "</style>"; 277 ASSERT_TRUE(testParse(input)); 278 279 Style* style = test::getValue<Style>(&mTable, u"@style/foo"); 280 ASSERT_NE(style, nullptr); 281 ASSERT_EQ(1u, style->entries.size()); 282 EXPECT_EQ(test::parseNameOrDie(u"@android:attr/bar"), style->entries[0].key.name.value()); 283} 284 285TEST_F(ResourceParserTest, ParseStyleWithInferredParent) { 286 std::string input = "<style name=\"foo.bar\"/>"; 287 ASSERT_TRUE(testParse(input)); 288 289 Style* style = test::getValue<Style>(&mTable, u"@style/foo.bar"); 290 ASSERT_NE(style, nullptr); 291 AAPT_ASSERT_TRUE(style->parent); 292 AAPT_ASSERT_TRUE(style->parent.value().name); 293 EXPECT_EQ(style->parent.value().name.value(), test::parseNameOrDie(u"@style/foo")); 294 EXPECT_TRUE(style->parentInferred); 295} 296 297TEST_F(ResourceParserTest, ParseStyleWithInferredParentOverridenByEmptyParentAttribute) { 298 std::string input = "<style name=\"foo.bar\" parent=\"\"/>"; 299 ASSERT_TRUE(testParse(input)); 300 301 Style* style = test::getValue<Style>(&mTable, u"@style/foo.bar"); 302 ASSERT_NE(style, nullptr); 303 AAPT_EXPECT_FALSE(style->parent); 304 EXPECT_FALSE(style->parentInferred); 305} 306 307TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) { 308 std::string input = "<string name=\"foo\">@+id/bar</string>"; 309 ASSERT_TRUE(testParse(input)); 310 311 Id* id = test::getValue<Id>(&mTable, u"@id/bar"); 312 ASSERT_NE(id, nullptr); 313} 314 315TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) { 316 std::string input = "<declare-styleable name=\"foo\">\n" 317 " <attr name=\"bar\" />\n" 318 " <attr name=\"bat\" format=\"string|reference\"/>\n" 319 " <attr name=\"baz\">\n" 320 " <enum name=\"foo\" value=\"1\"/>\n" 321 " </attr>\n" 322 "</declare-styleable>"; 323 ASSERT_TRUE(testParse(input)); 324 325 Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/bar"); 326 ASSERT_NE(attr, nullptr); 327 EXPECT_TRUE(attr->isWeak()); 328 329 attr = test::getValue<Attribute>(&mTable, u"@attr/bat"); 330 ASSERT_NE(attr, nullptr); 331 EXPECT_TRUE(attr->isWeak()); 332 333 attr = test::getValue<Attribute>(&mTable, u"@attr/baz"); 334 ASSERT_NE(attr, nullptr); 335 EXPECT_TRUE(attr->isWeak()); 336 EXPECT_EQ(1u, attr->symbols.size()); 337 338 EXPECT_NE(nullptr, test::getValue<Id>(&mTable, u"@id/foo")); 339 340 Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo"); 341 ASSERT_NE(styleable, nullptr); 342 ASSERT_EQ(3u, styleable->entries.size()); 343 344 EXPECT_EQ(test::parseNameOrDie(u"@attr/bar"), styleable->entries[0].name.value()); 345 EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), styleable->entries[1].name.value()); 346} 347 348TEST_F(ResourceParserTest, ParseArray) { 349 std::string input = "<array name=\"foo\">\n" 350 " <item>@string/ref</item>\n" 351 " <item>hey</item>\n" 352 " <item>23</item>\n" 353 "</array>"; 354 ASSERT_TRUE(testParse(input)); 355 356 Array* array = test::getValue<Array>(&mTable, u"@array/foo"); 357 ASSERT_NE(array, nullptr); 358 ASSERT_EQ(3u, array->items.size()); 359 360 EXPECT_NE(nullptr, valueCast<Reference>(array->items[0].get())); 361 EXPECT_NE(nullptr, valueCast<String>(array->items[1].get())); 362 EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(array->items[2].get())); 363} 364 365TEST_F(ResourceParserTest, ParseStringArray) { 366 std::string input = "<string-array name=\"foo\">\n" 367 " <item>\"Werk\"</item>\n" 368 "</string-array>\n"; 369 ASSERT_TRUE(testParse(input)); 370 EXPECT_NE(nullptr, test::getValue<Array>(&mTable, u"@array/foo")); 371} 372 373TEST_F(ResourceParserTest, ParsePlural) { 374 std::string input = "<plurals name=\"foo\">\n" 375 " <item quantity=\"other\">apples</item>\n" 376 " <item quantity=\"one\">apple</item>\n" 377 "</plurals>"; 378 ASSERT_TRUE(testParse(input)); 379} 380 381TEST_F(ResourceParserTest, ParseCommentsWithResource) { 382 std::string input = "<!--This is a comment-->\n" 383 "<string name=\"foo\">Hi</string>"; 384 ASSERT_TRUE(testParse(input)); 385 386 String* value = test::getValue<String>(&mTable, u"@string/foo"); 387 ASSERT_NE(nullptr, value); 388 EXPECT_EQ(value->getComment(), u"This is a comment"); 389} 390 391TEST_F(ResourceParserTest, DoNotCombineMultipleComments) { 392 std::string input = "<!--One-->\n" 393 "<!--Two-->\n" 394 "<string name=\"foo\">Hi</string>"; 395 396 ASSERT_TRUE(testParse(input)); 397 398 String* value = test::getValue<String>(&mTable, u"@string/foo"); 399 ASSERT_NE(nullptr, value); 400 EXPECT_EQ(value->getComment(), u"Two"); 401} 402 403TEST_F(ResourceParserTest, IgnoreCommentBeforeEndTag) { 404 std::string input = "<!--One-->\n" 405 "<string name=\"foo\">\n" 406 " Hi\n" 407 "<!--Two-->\n" 408 "</string>"; 409 410 ASSERT_TRUE(testParse(input)); 411 412 String* value = test::getValue<String>(&mTable, u"@string/foo"); 413 ASSERT_NE(nullptr, value); 414 EXPECT_EQ(value->getComment(), u"One"); 415} 416 417TEST_F(ResourceParserTest, ParseNestedComments) { 418 // We only care about declare-styleable and enum/flag attributes because comments 419 // from those end up in R.java 420 std::string input = R"EOF( 421 <declare-styleable name="foo"> 422 <!-- The name of the bar --> 423 <attr name="barName" format="string|reference" /> 424 </declare-styleable> 425 426 <attr name="foo"> 427 <!-- The very first --> 428 <enum name="one" value="1" /> 429 </attr>)EOF"; 430 ASSERT_TRUE(testParse(input)); 431 432 Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo"); 433 ASSERT_NE(nullptr, styleable); 434 ASSERT_EQ(1u, styleable->entries.size()); 435 436 EXPECT_EQ(StringPiece16(u"The name of the bar"), styleable->entries.front().getComment()); 437 438 Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo"); 439 ASSERT_NE(nullptr, attr); 440 ASSERT_EQ(1u, attr->symbols.size()); 441 442 EXPECT_EQ(StringPiece16(u"The very first"), attr->symbols.front().symbol.getComment()); 443} 444 445/* 446 * Declaring an ID as public should not require a separate definition 447 * (as an ID has no value). 448 */ 449TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) { 450 std::string input = "<public type=\"id\" name=\"foo\"/>"; 451 ASSERT_TRUE(testParse(input)); 452 453 Id* id = test::getValue<Id>(&mTable, u"@id/foo"); 454 ASSERT_NE(nullptr, id); 455} 456 457TEST_F(ResourceParserTest, FilterProductsThatDontMatch) { 458 std::string input = "<string name=\"foo\" product=\"phone\">hi</string>\n" 459 "<string name=\"foo\" product=\"no-sdcard\">ho</string>\n" 460 "<string name=\"bar\" product=\"\">wee</string>\n" 461 "<string name=\"baz\">woo</string>\n"; 462 ASSERT_TRUE(testParse(input, std::u16string(u"no-sdcard"))); 463 464 String* fooStr = test::getValue<String>(&mTable, u"@string/foo"); 465 ASSERT_NE(nullptr, fooStr); 466 EXPECT_EQ(StringPiece16(u"ho"), *fooStr->value); 467 468 EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/bar")); 469 EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/baz")); 470} 471 472TEST_F(ResourceParserTest, FailWhenProductFilterStripsOutAllVersionsOfResource) { 473 std::string input = "<string name=\"foo\" product=\"tablet\">hello</string>\n"; 474 ASSERT_FALSE(testParse(input, std::u16string(u"phone"))); 475} 476 477} // namespace aapt 478