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