ResourceParser_test.cpp revision 1ab598f46c3ff520a67f9d80194847741f3467ab
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 std::stringstream input(kXmlPreamble); 53 input << "<resources>\n" << str << "\n</resources>" << std::endl; 54 ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, {}); 55 XmlPullParser xmlParser(input); 56 if (parser.parse(&xmlParser)) { 57 return ::testing::AssertionSuccess(); 58 } 59 return ::testing::AssertionFailure(); 60 } 61}; 62 63TEST_F(ResourceParserTest, ParseQuotedString) { 64 std::string input = "<string name=\"foo\"> \" hey there \" </string>"; 65 ASSERT_TRUE(testParse(input)); 66 67 String* str = test::getValue<String>(&mTable, u"@string/foo"); 68 ASSERT_NE(nullptr, str); 69 EXPECT_EQ(std::u16string(u" hey there "), *str->value); 70} 71 72TEST_F(ResourceParserTest, ParseEscapedString) { 73 std::string input = "<string name=\"foo\">\\?123</string>"; 74 ASSERT_TRUE(testParse(input)); 75 76 String* str = test::getValue<String>(&mTable, u"@string/foo"); 77 ASSERT_NE(nullptr, str); 78 EXPECT_EQ(std::u16string(u"?123"), *str->value); 79} 80 81TEST_F(ResourceParserTest, IgnoreXliffTags) { 82 std::string input = "<string name=\"foo\" \n" 83 " xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n" 84 " There are <xliff:g id=\"count\">%1$d</xliff:g> apples</string>"; 85 ASSERT_TRUE(testParse(input)); 86 87 String* str = test::getValue<String>(&mTable, u"@string/foo"); 88 ASSERT_NE(nullptr, str); 89 EXPECT_EQ(StringPiece16(u"There are %1$d apples"), StringPiece16(*str->value)); 90} 91 92TEST_F(ResourceParserTest, ParseNull) { 93 std::string input = "<integer name=\"foo\">@null</integer>"; 94 ASSERT_TRUE(testParse(input)); 95 96 // The Android runtime treats a value of android::Res_value::TYPE_NULL as 97 // a non-existing value, and this causes problems in styles when trying to resolve 98 // an attribute. Null values must be encoded as android::Res_value::TYPE_REFERENCE 99 // with a data value of 0. 100 BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, u"@integer/foo"); 101 ASSERT_NE(nullptr, integer); 102 EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE), integer->value.dataType); 103 EXPECT_EQ(0u, integer->value.data); 104} 105 106TEST_F(ResourceParserTest, ParseEmpty) { 107 std::string input = "<integer name=\"foo\">@empty</integer>"; 108 ASSERT_TRUE(testParse(input)); 109 110 BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, u"@integer/foo"); 111 ASSERT_NE(nullptr, integer); 112 EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType); 113 EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data); 114} 115 116TEST_F(ResourceParserTest, ParseAttr) { 117 std::string input = "<attr name=\"foo\" format=\"string\"/>\n" 118 "<attr name=\"bar\"/>"; 119 ASSERT_TRUE(testParse(input)); 120 121 Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo"); 122 ASSERT_NE(nullptr, attr); 123 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); 124 125 attr = test::getValue<Attribute>(&mTable, u"@attr/bar"); 126 ASSERT_NE(nullptr, attr); 127 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask); 128} 129 130TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) { 131 std::string input = "<declare-styleable name=\"Styleable\">\n" 132 " <attr name=\"foo\" />\n" 133 "</declare-styleable>\n" 134 "<attr name=\"foo\" format=\"string\"/>"; 135 ASSERT_TRUE(testParse(input)); 136 137 Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo"); 138 ASSERT_NE(nullptr, attr); 139 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); 140} 141 142TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) { 143 std::string input = "<declare-styleable name=\"Theme\">" 144 " <attr name=\"foo\" />\n" 145 "</declare-styleable>\n" 146 "<declare-styleable name=\"Window\">\n" 147 " <attr name=\"foo\" format=\"boolean\"/>\n" 148 "</declare-styleable>"; 149 ASSERT_TRUE(testParse(input)); 150 151 Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo"); 152 ASSERT_NE(nullptr, attr); 153 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask); 154} 155 156TEST_F(ResourceParserTest, ParseEnumAttr) { 157 std::string input = "<attr name=\"foo\">\n" 158 " <enum name=\"bar\" value=\"0\"/>\n" 159 " <enum name=\"bat\" value=\"1\"/>\n" 160 " <enum name=\"baz\" value=\"2\"/>\n" 161 "</attr>"; 162 ASSERT_TRUE(testParse(input)); 163 164 Attribute* enumAttr = test::getValue<Attribute>(&mTable, u"@attr/foo"); 165 ASSERT_NE(enumAttr, nullptr); 166 EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM); 167 ASSERT_EQ(enumAttr->symbols.size(), 3u); 168 169 AAPT_ASSERT_TRUE(enumAttr->symbols[0].symbol.name); 170 EXPECT_EQ(enumAttr->symbols[0].symbol.name.value().entry, u"bar"); 171 EXPECT_EQ(enumAttr->symbols[0].value, 0u); 172 173 AAPT_ASSERT_TRUE(enumAttr->symbols[1].symbol.name); 174 EXPECT_EQ(enumAttr->symbols[1].symbol.name.value().entry, u"bat"); 175 EXPECT_EQ(enumAttr->symbols[1].value, 1u); 176 177 AAPT_ASSERT_TRUE(enumAttr->symbols[2].symbol.name); 178 EXPECT_EQ(enumAttr->symbols[2].symbol.name.value().entry, u"baz"); 179 EXPECT_EQ(enumAttr->symbols[2].value, 2u); 180} 181 182TEST_F(ResourceParserTest, ParseFlagAttr) { 183 std::string input = "<attr name=\"foo\">\n" 184 " <flag name=\"bar\" value=\"0\"/>\n" 185 " <flag name=\"bat\" value=\"1\"/>\n" 186 " <flag name=\"baz\" value=\"2\"/>\n" 187 "</attr>"; 188 ASSERT_TRUE(testParse(input)); 189 190 Attribute* flagAttr = test::getValue<Attribute>(&mTable, u"@attr/foo"); 191 ASSERT_NE(flagAttr, nullptr); 192 EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS); 193 ASSERT_EQ(flagAttr->symbols.size(), 3u); 194 195 AAPT_ASSERT_TRUE(flagAttr->symbols[0].symbol.name); 196 EXPECT_EQ(flagAttr->symbols[0].symbol.name.value().entry, u"bar"); 197 EXPECT_EQ(flagAttr->symbols[0].value, 0u); 198 199 AAPT_ASSERT_TRUE(flagAttr->symbols[1].symbol.name); 200 EXPECT_EQ(flagAttr->symbols[1].symbol.name.value().entry, u"bat"); 201 EXPECT_EQ(flagAttr->symbols[1].value, 1u); 202 203 AAPT_ASSERT_TRUE(flagAttr->symbols[2].symbol.name); 204 EXPECT_EQ(flagAttr->symbols[2].symbol.name.value().entry, u"baz"); 205 EXPECT_EQ(flagAttr->symbols[2].value, 2u); 206 207 std::unique_ptr<BinaryPrimitive> flagValue = ResourceUtils::tryParseFlagSymbol(flagAttr, 208 u"baz|bat"); 209 ASSERT_NE(flagValue, nullptr); 210 EXPECT_EQ(flagValue->value.data, 1u | 2u); 211} 212 213TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) { 214 std::string input = "<attr name=\"foo\">\n" 215 " <enum name=\"bar\" value=\"0\"/>\n" 216 " <enum name=\"bat\" value=\"1\"/>\n" 217 " <enum name=\"bat\" value=\"2\"/>\n" 218 "</attr>"; 219 ASSERT_FALSE(testParse(input)); 220} 221 222TEST_F(ResourceParserTest, ParseStyle) { 223 std::string input = "<style name=\"foo\" parent=\"@style/fu\">\n" 224 " <item name=\"bar\">#ffffffff</item>\n" 225 " <item name=\"bat\">@string/hey</item>\n" 226 " <item name=\"baz\"><b>hey</b></item>\n" 227 "</style>"; 228 ASSERT_TRUE(testParse(input)); 229 230 Style* style = test::getValue<Style>(&mTable, u"@style/foo"); 231 ASSERT_NE(style, nullptr); 232 AAPT_ASSERT_TRUE(style->parent); 233 AAPT_ASSERT_TRUE(style->parent.value().name); 234 EXPECT_EQ(test::parseNameOrDie(u"@style/fu"), style->parent.value().name.value()); 235 ASSERT_EQ(3u, style->entries.size()); 236 237 AAPT_ASSERT_TRUE(style->entries[0].key.name); 238 EXPECT_EQ(test::parseNameOrDie(u"@attr/bar"), style->entries[0].key.name.value()); 239 240 AAPT_ASSERT_TRUE(style->entries[1].key.name); 241 EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), style->entries[1].key.name.value()); 242 243 AAPT_ASSERT_TRUE(style->entries[2].key.name); 244 EXPECT_EQ(test::parseNameOrDie(u"@attr/baz"), style->entries[2].key.name.value()); 245} 246 247TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) { 248 std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>"; 249 ASSERT_TRUE(testParse(input)); 250 251 Style* style = test::getValue<Style>(&mTable, u"@style/foo"); 252 ASSERT_NE(style, nullptr); 253 AAPT_ASSERT_TRUE(style->parent); 254 AAPT_ASSERT_TRUE(style->parent.value().name); 255 EXPECT_EQ(test::parseNameOrDie(u"@com.app:style/Theme"), style->parent.value().name.value()); 256} 257 258TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) { 259 std::string input = "<style xmlns:app=\"http://schemas.android.com/apk/res/android\"\n" 260 " name=\"foo\" parent=\"app:Theme\"/>"; 261 ASSERT_TRUE(testParse(input)); 262 263 Style* style = test::getValue<Style>(&mTable, u"@style/foo"); 264 ASSERT_NE(style, nullptr); 265 AAPT_ASSERT_TRUE(style->parent); 266 AAPT_ASSERT_TRUE(style->parent.value().name); 267 EXPECT_EQ(test::parseNameOrDie(u"@android:style/Theme"), style->parent.value().name.value()); 268} 269 270TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) { 271 std::string input = 272 "<style xmlns:app=\"http://schemas.android.com/apk/res/android\" name=\"foo\">\n" 273 " <item name=\"app:bar\">0</item>\n" 274 "</style>"; 275 ASSERT_TRUE(testParse(input)); 276 277 Style* style = test::getValue<Style>(&mTable, u"@style/foo"); 278 ASSERT_NE(style, nullptr); 279 ASSERT_EQ(1u, style->entries.size()); 280 EXPECT_EQ(test::parseNameOrDie(u"@android:attr/bar"), style->entries[0].key.name.value()); 281} 282 283TEST_F(ResourceParserTest, ParseStyleWithInferredParent) { 284 std::string input = "<style name=\"foo.bar\"/>"; 285 ASSERT_TRUE(testParse(input)); 286 287 Style* style = test::getValue<Style>(&mTable, u"@style/foo.bar"); 288 ASSERT_NE(style, nullptr); 289 AAPT_ASSERT_TRUE(style->parent); 290 AAPT_ASSERT_TRUE(style->parent.value().name); 291 EXPECT_EQ(style->parent.value().name.value(), test::parseNameOrDie(u"@style/foo")); 292 EXPECT_TRUE(style->parentInferred); 293} 294 295TEST_F(ResourceParserTest, ParseStyleWithInferredParentOverridenByEmptyParentAttribute) { 296 std::string input = "<style name=\"foo.bar\" parent=\"\"/>"; 297 ASSERT_TRUE(testParse(input)); 298 299 Style* style = test::getValue<Style>(&mTable, u"@style/foo.bar"); 300 ASSERT_NE(style, nullptr); 301 AAPT_EXPECT_FALSE(style->parent); 302 EXPECT_FALSE(style->parentInferred); 303} 304 305TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) { 306 std::string input = "<string name=\"foo\">@+id/bar</string>"; 307 ASSERT_TRUE(testParse(input)); 308 309 Id* id = test::getValue<Id>(&mTable, u"@id/bar"); 310 ASSERT_NE(id, nullptr); 311} 312 313TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) { 314 std::string input = "<declare-styleable name=\"foo\">\n" 315 " <attr name=\"bar\" />\n" 316 " <attr name=\"bat\" format=\"string|reference\"/>\n" 317 "</declare-styleable>"; 318 ASSERT_TRUE(testParse(input)); 319 320 Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/bar"); 321 ASSERT_NE(attr, nullptr); 322 EXPECT_TRUE(attr->isWeak()); 323 324 attr = test::getValue<Attribute>(&mTable, u"@attr/bat"); 325 ASSERT_NE(attr, nullptr); 326 EXPECT_TRUE(attr->isWeak()); 327 328 Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo"); 329 ASSERT_NE(styleable, nullptr); 330 ASSERT_EQ(2u, styleable->entries.size()); 331 332 EXPECT_EQ(test::parseNameOrDie(u"@attr/bar"), styleable->entries[0].name.value()); 333 EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), styleable->entries[1].name.value()); 334} 335 336TEST_F(ResourceParserTest, ParseArray) { 337 std::string input = "<array name=\"foo\">\n" 338 " <item>@string/ref</item>\n" 339 " <item>hey</item>\n" 340 " <item>23</item>\n" 341 "</array>"; 342 ASSERT_TRUE(testParse(input)); 343 344 Array* array = test::getValue<Array>(&mTable, u"@array/foo"); 345 ASSERT_NE(array, nullptr); 346 ASSERT_EQ(3u, array->items.size()); 347 348 EXPECT_NE(nullptr, valueCast<Reference>(array->items[0].get())); 349 EXPECT_NE(nullptr, valueCast<String>(array->items[1].get())); 350 EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(array->items[2].get())); 351} 352 353TEST_F(ResourceParserTest, ParsePlural) { 354 std::string input = "<plurals name=\"foo\">\n" 355 " <item quantity=\"other\">apples</item>\n" 356 " <item quantity=\"one\">apple</item>\n" 357 "</plurals>"; 358 ASSERT_TRUE(testParse(input)); 359} 360 361TEST_F(ResourceParserTest, ParseCommentsWithResource) { 362 std::string input = "<!-- This is a comment -->\n" 363 "<string name=\"foo\">Hi</string>"; 364 ASSERT_TRUE(testParse(input)); 365 366 Maybe<ResourceTable::SearchResult> result = mTable.findResource( 367 test::parseNameOrDie(u"@string/foo")); 368 AAPT_ASSERT_TRUE(result); 369 370 ResourceEntry* entry = result.value().entry; 371 ASSERT_NE(entry, nullptr); 372 ASSERT_FALSE(entry->values.empty()); 373 EXPECT_EQ(entry->values.front().comment, u"This is a comment"); 374} 375 376/* 377 * Declaring an ID as public should not require a separate definition 378 * (as an ID has no value). 379 */ 380TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) { 381 std::string input = "<public type=\"id\" name=\"foo\"/>"; 382 ASSERT_TRUE(testParse(input)); 383 384 Id* id = test::getValue<Id>(&mTable, u"@id/foo"); 385 ASSERT_NE(nullptr, id); 386} 387 388} // namespace aapt 389