ResourceParser_test.cpp revision 6f6ceb7e1456698b1f33e04536bfb3227f9fcfcb
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 97struct ResourceParserTest : public ::testing::Test { 98 virtual void SetUp() override { 99 mTable = std::make_shared<ResourceTable>(); 100 mTable->setPackage(u"android"); 101 } 102 103 ::testing::AssertionResult testParse(std::istream& in) { 104 std::stringstream input(kXmlPreamble); 105 input << "<resources>" << std::endl 106 << in.rdbuf() << std::endl 107 << "</resources>" << std::endl; 108 ResourceParser parser(mTable, Source{ "test" }, {}, 109 std::make_shared<SourceXmlPullParser>(input)); 110 if (parser.parse()) { 111 return ::testing::AssertionSuccess(); 112 } 113 return ::testing::AssertionFailure(); 114 } 115 116 template <typename T> 117 const T* findResource(const ResourceNameRef& name, const ConfigDescription& config) { 118 using std::begin; 119 using std::end; 120 121 const ResourceTableType* type; 122 const ResourceEntry* entry; 123 std::tie(type, entry) = mTable->findResource(name); 124 if (!type || !entry) { 125 return nullptr; 126 } 127 128 for (const auto& configValue : entry->values) { 129 if (configValue.config == config) { 130 return dynamic_cast<const T*>(configValue.value.get()); 131 } 132 } 133 return nullptr; 134 } 135 136 template <typename T> 137 const T* findResource(const ResourceNameRef& name) { 138 return findResource<T>(name, {}); 139 } 140 141 std::shared_ptr<ResourceTable> mTable; 142}; 143 144TEST_F(ResourceParserTest, FailToParseWithNoRootResourcesElement) { 145 std::stringstream input(kXmlPreamble); 146 input << "<attr name=\"foo\"/>" << std::endl; 147 ResourceParser parser(mTable, {}, {}, std::make_shared<SourceXmlPullParser>(input)); 148 ASSERT_FALSE(parser.parse()); 149} 150 151TEST_F(ResourceParserTest, ParseQuotedString) { 152 std::stringstream input("<string name=\"foo\"> \" hey there \" </string>"); 153 ASSERT_TRUE(testParse(input)); 154 155 const String* str = findResource<String>(ResourceName{ 156 u"android", ResourceType::kString, u"foo"}); 157 ASSERT_NE(nullptr, str); 158 EXPECT_EQ(std::u16string(u" hey there "), *str->value); 159} 160 161TEST_F(ResourceParserTest, ParseEscapedString) { 162 std::stringstream input("<string name=\"foo\">\\?123</string>"); 163 ASSERT_TRUE(testParse(input)); 164 165 const String* str = findResource<String>(ResourceName{ 166 u"android", ResourceType::kString, u"foo" }); 167 ASSERT_NE(nullptr, str); 168 EXPECT_EQ(std::u16string(u"?123"), *str->value); 169} 170 171TEST_F(ResourceParserTest, ParseAttr) { 172 std::stringstream input; 173 input << "<attr name=\"foo\" format=\"string\"/>" << std::endl 174 << "<attr name=\"bar\"/>" << std::endl; 175 ASSERT_TRUE(testParse(input)); 176 177 const Attribute* attr = findResource<Attribute>(ResourceName{ 178 u"android", ResourceType::kAttr, u"foo"}); 179 EXPECT_NE(nullptr, attr); 180 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); 181 182 attr = findResource<Attribute>(ResourceName{ 183 u"android", ResourceType::kAttr, u"bar"}); 184 EXPECT_NE(nullptr, attr); 185 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask); 186} 187 188TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) { 189 std::stringstream input; 190 input << "<declare-styleable name=\"Styleable\">" << std::endl 191 << " <attr name=\"foo\" />" << std::endl 192 << "</declare-styleable>" << std::endl 193 << "<attr name=\"foo\" format=\"string\"/>" << std::endl; 194 ASSERT_TRUE(testParse(input)); 195 196 const Attribute* attr = findResource<Attribute>(ResourceName{ 197 u"android", ResourceType::kAttr, u"foo"}); 198 ASSERT_NE(nullptr, attr); 199 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); 200} 201 202TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) { 203 std::stringstream input; 204 input << "<declare-styleable name=\"Theme\">" << std::endl 205 << " <attr name=\"foo\" />" << std::endl 206 << "</declare-styleable>" << std::endl 207 << "<declare-styleable name=\"Window\">" << std::endl 208 << " <attr name=\"foo\" format=\"boolean\"/>" << std::endl 209 << "</declare-styleable>" << std::endl; 210 211 ASSERT_TRUE(testParse(input)); 212 213 const Attribute* attr = findResource<Attribute>(ResourceName{ 214 u"android", ResourceType::kAttr, u"foo"}); 215 ASSERT_NE(nullptr, attr); 216 EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask); 217} 218 219TEST_F(ResourceParserTest, ParseEnumAttr) { 220 std::stringstream input; 221 input << "<attr name=\"foo\">" << std::endl 222 << " <enum name=\"bar\" value=\"0\"/>" << std::endl 223 << " <enum name=\"bat\" value=\"1\"/>" << std::endl 224 << " <enum name=\"baz\" value=\"2\"/>" << std::endl 225 << "</attr>" << std::endl; 226 ASSERT_TRUE(testParse(input)); 227 228 const Attribute* enumAttr = findResource<Attribute>(ResourceName{ 229 u"android", ResourceType::kAttr, u"foo"}); 230 ASSERT_NE(enumAttr, nullptr); 231 EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM); 232 ASSERT_EQ(enumAttr->symbols.size(), 3u); 233 234 EXPECT_EQ(enumAttr->symbols[0].symbol.name.entry, u"bar"); 235 EXPECT_EQ(enumAttr->symbols[0].value, 0u); 236 237 EXPECT_EQ(enumAttr->symbols[1].symbol.name.entry, u"bat"); 238 EXPECT_EQ(enumAttr->symbols[1].value, 1u); 239 240 EXPECT_EQ(enumAttr->symbols[2].symbol.name.entry, u"baz"); 241 EXPECT_EQ(enumAttr->symbols[2].value, 2u); 242} 243 244TEST_F(ResourceParserTest, ParseFlagAttr) { 245 std::stringstream input; 246 input << "<attr name=\"foo\">" << std::endl 247 << " <flag name=\"bar\" value=\"0\"/>" << std::endl 248 << " <flag name=\"bat\" value=\"1\"/>" << std::endl 249 << " <flag name=\"baz\" value=\"2\"/>" << std::endl 250 << "</attr>" << std::endl; 251 ASSERT_TRUE(testParse(input)); 252 253 const Attribute* flagAttr = findResource<Attribute>(ResourceName{ 254 u"android", ResourceType::kAttr, u"foo"}); 255 ASSERT_NE(flagAttr, nullptr); 256 EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS); 257 ASSERT_EQ(flagAttr->symbols.size(), 3u); 258 259 EXPECT_EQ(flagAttr->symbols[0].symbol.name.entry, u"bar"); 260 EXPECT_EQ(flagAttr->symbols[0].value, 0u); 261 262 EXPECT_EQ(flagAttr->symbols[1].symbol.name.entry, u"bat"); 263 EXPECT_EQ(flagAttr->symbols[1].value, 1u); 264 265 EXPECT_EQ(flagAttr->symbols[2].symbol.name.entry, u"baz"); 266 EXPECT_EQ(flagAttr->symbols[2].value, 2u); 267 268 std::unique_ptr<BinaryPrimitive> flagValue = 269 ResourceParser::tryParseFlagSymbol(*flagAttr, u"baz|bat"); 270 ASSERT_NE(flagValue, nullptr); 271 EXPECT_EQ(flagValue->value.data, 1u | 2u); 272} 273 274TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) { 275 std::stringstream input; 276 input << "<attr name=\"foo\">" << std::endl 277 << " <enum name=\"bar\" value=\"0\"/>" << std::endl 278 << " <enum name=\"bat\" value=\"1\"/>" << std::endl 279 << " <enum name=\"bat\" value=\"2\"/>" << std::endl 280 << "</attr>" << std::endl; 281 ASSERT_FALSE(testParse(input)); 282} 283 284TEST_F(ResourceParserTest, ParseStyle) { 285 std::stringstream input; 286 input << "<style name=\"foo\" parent=\"fu\">" << std::endl 287 << " <item name=\"bar\">#ffffffff</item>" << std::endl 288 << " <item name=\"bat\">@string/hey</item>" << std::endl 289 << " <item name=\"baz\"><b>hey</b></item>" << std::endl 290 << "</style>" << std::endl; 291 ASSERT_TRUE(testParse(input)); 292 293 const Style* style = findResource<Style>(ResourceName{ 294 u"android", ResourceType::kStyle, u"foo"}); 295 ASSERT_NE(style, nullptr); 296 EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"fu"), style->parent.name); 297 ASSERT_EQ(style->entries.size(), 3u); 298 299 EXPECT_EQ(style->entries[0].key.name, 300 (ResourceName{ u"android", ResourceType::kAttr, u"bar" })); 301 EXPECT_EQ(style->entries[1].key.name, 302 (ResourceName{ u"android", ResourceType::kAttr, u"bat" })); 303 EXPECT_EQ(style->entries[2].key.name, 304 (ResourceName{ u"android", ResourceType::kAttr, u"baz" })); 305} 306 307TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) { 308 std::stringstream input; 309 input << "<string name=\"foo\">@+id/bar</string>" << std::endl; 310 ASSERT_TRUE(testParse(input)); 311 312 const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"bar"}); 313 ASSERT_NE(id, nullptr); 314} 315 316TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) { 317 std::stringstream input; 318 input << "<declare-styleable name=\"foo\">" << std::endl 319 << " <attr name=\"bar\" />" << std::endl 320 << " <attr name=\"bat\" format=\"string|reference\"/>" << std::endl 321 << "</declare-styleable>" << std::endl; 322 ASSERT_TRUE(testParse(input)); 323 324 const Attribute* attr = findResource<Attribute>(ResourceName{ 325 u"android", ResourceType::kAttr, u"bar"}); 326 ASSERT_NE(attr, nullptr); 327 EXPECT_TRUE(attr->isWeak()); 328 329 attr = findResource<Attribute>(ResourceName{ u"android", ResourceType::kAttr, u"bat"}); 330 ASSERT_NE(attr, nullptr); 331 EXPECT_TRUE(attr->isWeak()); 332 333 const Styleable* styleable = findResource<Styleable>(ResourceName{ 334 u"android", ResourceType::kStyleable, u"foo" }); 335 ASSERT_NE(styleable, nullptr); 336 ASSERT_EQ(2u, styleable->entries.size()); 337 338 EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bar"}), styleable->entries[0].name); 339 EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bat"}), styleable->entries[1].name); 340} 341 342TEST_F(ResourceParserTest, ParseArray) { 343 std::stringstream input; 344 input << "<array name=\"foo\">" << std::endl 345 << " <item>@string/ref</item>" << std::endl 346 << " <item>hey</item>" << std::endl 347 << " <item>23</item>" << std::endl 348 << "</array>" << std::endl; 349 ASSERT_TRUE(testParse(input)); 350 351 const Array* array = findResource<Array>(ResourceName{ 352 u"android", ResourceType::kArray, u"foo" }); 353 ASSERT_NE(array, nullptr); 354 ASSERT_EQ(3u, array->items.size()); 355 356 EXPECT_NE(nullptr, dynamic_cast<const Reference*>(array->items[0].get())); 357 EXPECT_NE(nullptr, dynamic_cast<const String*>(array->items[1].get())); 358 EXPECT_NE(nullptr, dynamic_cast<const BinaryPrimitive*>(array->items[2].get())); 359} 360 361TEST_F(ResourceParserTest, ParsePlural) { 362 std::stringstream input; 363 input << "<plurals name=\"foo\">" << std::endl 364 << " <item quantity=\"other\">apples</item>" << std::endl 365 << " <item quantity=\"one\">apple</item>" << std::endl 366 << "</plurals>" << std::endl 367 << std::endl; 368 ASSERT_TRUE(testParse(input)); 369} 370 371TEST_F(ResourceParserTest, ParseCommentsWithResource) { 372 std::stringstream input; 373 input << "<!-- This is a comment -->" << std::endl 374 << "<string name=\"foo\">Hi</string>" << std::endl; 375 ASSERT_TRUE(testParse(input)); 376 377 const ResourceTableType* type; 378 const ResourceEntry* entry; 379 std::tie(type, entry) = mTable->findResource(ResourceName{ 380 u"android", ResourceType::kString, u"foo"}); 381 ASSERT_NE(type, nullptr); 382 ASSERT_NE(entry, nullptr); 383 ASSERT_FALSE(entry->values.empty()); 384 EXPECT_EQ(entry->values.front().comment, u"This is a comment"); 385} 386 387/* 388 * Declaring an ID as public should not require a separate definition 389 * (as an ID has no value). 390 */ 391TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) { 392 std::stringstream input("<public type=\"id\" name=\"foo\"/>"); 393 ASSERT_TRUE(testParse(input)); 394 395 const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"foo" }); 396 ASSERT_NE(nullptr, id); 397} 398 399} // namespace aapt 400