ResourceParser_test.cpp revision 58a20a6482a56a262fd83a617482641e3a981db1
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 "test/Test.h"
22#include "xml/XmlPullParser.h"
23
24#include <sstream>
25#include <string>
26
27namespace aapt {
28
29constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
30
31TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) {
32    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
33    std::stringstream input(kXmlPreamble);
34    input << "<attr name=\"foo\"/>" << std::endl;
35    ResourceTable table;
36    ResourceParser parser(context->getDiagnostics(), &table, Source{ "test" }, {});
37    xml::XmlPullParser xmlParser(input);
38    ASSERT_FALSE(parser.parse(&xmlParser));
39}
40
41struct ResourceParserTest : public ::testing::Test {
42    ResourceTable mTable;
43    std::unique_ptr<IAaptContext> mContext;
44
45    void SetUp() override {
46        mContext = test::ContextBuilder().build();
47    }
48
49    ::testing::AssertionResult testParse(const StringPiece& str) {
50        return testParse(str, ConfigDescription{});
51    }
52
53    ::testing::AssertionResult testParse(const StringPiece& str, const ConfigDescription& config) {
54        std::stringstream input(kXmlPreamble);
55        input << "<resources>\n" << str << "\n</resources>" << std::endl;
56        ResourceParserOptions parserOptions;
57        ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, config,
58                              parserOptions);
59        xml::XmlPullParser xmlParser(input);
60        if (parser.parse(&xmlParser)) {
61            return ::testing::AssertionSuccess();
62        }
63        return ::testing::AssertionFailure();
64    }
65};
66
67TEST_F(ResourceParserTest, ParseQuotedString) {
68    std::string input = "<string name=\"foo\">   \"  hey there \" </string>";
69    ASSERT_TRUE(testParse(input));
70
71    String* str = test::getValue<String>(&mTable, "string/foo");
72    ASSERT_NE(nullptr, str);
73    EXPECT_EQ(std::string("  hey there "), *str->value);
74}
75
76TEST_F(ResourceParserTest, ParseEscapedString) {
77    std::string input = "<string name=\"foo\">\\?123</string>";
78    ASSERT_TRUE(testParse(input));
79
80    String* str = test::getValue<String>(&mTable, "string/foo");
81    ASSERT_NE(nullptr, str);
82    EXPECT_EQ(std::string("?123"), *str->value);
83}
84
85TEST_F(ResourceParserTest, ParseFormattedString) {
86    std::string input = "<string name=\"foo\">%d %s</string>";
87    ASSERT_FALSE(testParse(input));
88
89    input = "<string name=\"foo\">%1$d %2$s</string>";
90    ASSERT_TRUE(testParse(input));
91}
92
93TEST_F(ResourceParserTest, IgnoreXliffTags) {
94    std::string input = "<string name=\"foo\" \n"
95                        "        xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n"
96                        "  There are <xliff:g id=\"count\">%1$d</xliff:g> apples</string>";
97    ASSERT_TRUE(testParse(input));
98
99    String* str = test::getValue<String>(&mTable, "string/foo");
100    ASSERT_NE(nullptr, str);
101    EXPECT_EQ(StringPiece("There are %1$d apples"), StringPiece(*str->value));
102}
103
104TEST_F(ResourceParserTest, ParseNull) {
105    std::string input = "<integer name=\"foo\">@null</integer>";
106    ASSERT_TRUE(testParse(input));
107
108    // The Android runtime treats a value of android::Res_value::TYPE_NULL as
109    // a non-existing value, and this causes problems in styles when trying to resolve
110    // an attribute. Null values must be encoded as android::Res_value::TYPE_REFERENCE
111    // with a data value of 0.
112    BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, "integer/foo");
113    ASSERT_NE(nullptr, integer);
114    EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE), integer->value.dataType);
115    EXPECT_EQ(0u, integer->value.data);
116}
117
118TEST_F(ResourceParserTest, ParseEmpty) {
119    std::string input = "<integer name=\"foo\">@empty</integer>";
120    ASSERT_TRUE(testParse(input));
121
122    BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, "integer/foo");
123    ASSERT_NE(nullptr, integer);
124    EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType);
125    EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data);
126}
127
128TEST_F(ResourceParserTest, ParseAttr) {
129    std::string input = "<attr name=\"foo\" format=\"string\"/>\n"
130                        "<attr name=\"bar\"/>";
131    ASSERT_TRUE(testParse(input));
132
133    Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
134    ASSERT_NE(nullptr, attr);
135    EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
136
137    attr = test::getValue<Attribute>(&mTable, "attr/bar");
138    ASSERT_NE(nullptr, attr);
139    EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask);
140}
141
142// Old AAPT allowed attributes to be defined under different configurations, but ultimately
143// stored them with the default configuration. Check that we have the same behavior.
144TEST_F(ResourceParserTest, ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig) {
145    const ConfigDescription watchConfig = test::parseConfigOrDie("watch");
146    std::string input = R"EOF(
147        <attr name="foo" />
148        <declare-styleable name="bar">
149          <attr name="baz" />
150        </declare-styleable>)EOF";
151    ASSERT_TRUE(testParse(input, watchConfig));
152
153    EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, "attr/foo", watchConfig));
154    EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, "attr/baz", watchConfig));
155    EXPECT_EQ(nullptr, test::getValueForConfig<Styleable>(&mTable, "styleable/bar", watchConfig));
156
157    EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, "attr/foo"));
158    EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, "attr/baz"));
159    EXPECT_NE(nullptr, test::getValue<Styleable>(&mTable, "styleable/bar"));
160}
161
162TEST_F(ResourceParserTest, ParseAttrWithMinMax) {
163    std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"integer\"/>";
164    ASSERT_TRUE(testParse(input));
165
166    Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
167    ASSERT_NE(nullptr, attr);
168    EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->typeMask);
169    EXPECT_EQ(10, attr->minInt);
170    EXPECT_EQ(23, attr->maxInt);
171}
172
173TEST_F(ResourceParserTest, FailParseAttrWithMinMaxButNotInteger) {
174    std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"string\"/>";
175    ASSERT_FALSE(testParse(input));
176}
177
178TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) {
179    std::string input = "<declare-styleable name=\"Styleable\">\n"
180                        "  <attr name=\"foo\" />\n"
181                        "</declare-styleable>\n"
182                        "<attr name=\"foo\" format=\"string\"/>";
183    ASSERT_TRUE(testParse(input));
184
185    Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
186    ASSERT_NE(nullptr, attr);
187    EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
188}
189
190TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) {
191    std::string input = "<declare-styleable name=\"Theme\">"
192                        "  <attr name=\"foo\" />\n"
193                        "</declare-styleable>\n"
194                        "<declare-styleable name=\"Window\">\n"
195                        "  <attr name=\"foo\" format=\"boolean\"/>\n"
196                        "</declare-styleable>";
197    ASSERT_TRUE(testParse(input));
198
199    Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
200    ASSERT_NE(nullptr, attr);
201    EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask);
202}
203
204TEST_F(ResourceParserTest, ParseEnumAttr) {
205    std::string input = "<attr name=\"foo\">\n"
206                        "  <enum name=\"bar\" value=\"0\"/>\n"
207                        "  <enum name=\"bat\" value=\"1\"/>\n"
208                        "  <enum name=\"baz\" value=\"2\"/>\n"
209                        "</attr>";
210    ASSERT_TRUE(testParse(input));
211
212    Attribute* enumAttr = test::getValue<Attribute>(&mTable, "attr/foo");
213    ASSERT_NE(enumAttr, nullptr);
214    EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM);
215    ASSERT_EQ(enumAttr->symbols.size(), 3u);
216
217    AAPT_ASSERT_TRUE(enumAttr->symbols[0].symbol.name);
218    EXPECT_EQ(enumAttr->symbols[0].symbol.name.value().entry, "bar");
219    EXPECT_EQ(enumAttr->symbols[0].value, 0u);
220
221    AAPT_ASSERT_TRUE(enumAttr->symbols[1].symbol.name);
222    EXPECT_EQ(enumAttr->symbols[1].symbol.name.value().entry, "bat");
223    EXPECT_EQ(enumAttr->symbols[1].value, 1u);
224
225    AAPT_ASSERT_TRUE(enumAttr->symbols[2].symbol.name);
226    EXPECT_EQ(enumAttr->symbols[2].symbol.name.value().entry, "baz");
227    EXPECT_EQ(enumAttr->symbols[2].value, 2u);
228}
229
230TEST_F(ResourceParserTest, ParseFlagAttr) {
231    std::string input = "<attr name=\"foo\">\n"
232                        "  <flag name=\"bar\" value=\"0\"/>\n"
233                        "  <flag name=\"bat\" value=\"1\"/>\n"
234                        "  <flag name=\"baz\" value=\"2\"/>\n"
235                        "</attr>";
236    ASSERT_TRUE(testParse(input));
237
238    Attribute* flagAttr = test::getValue<Attribute>(&mTable, "attr/foo");
239    ASSERT_NE(nullptr, flagAttr);
240    EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS);
241    ASSERT_EQ(flagAttr->symbols.size(), 3u);
242
243    AAPT_ASSERT_TRUE(flagAttr->symbols[0].symbol.name);
244    EXPECT_EQ(flagAttr->symbols[0].symbol.name.value().entry, "bar");
245    EXPECT_EQ(flagAttr->symbols[0].value, 0u);
246
247    AAPT_ASSERT_TRUE(flagAttr->symbols[1].symbol.name);
248    EXPECT_EQ(flagAttr->symbols[1].symbol.name.value().entry, "bat");
249    EXPECT_EQ(flagAttr->symbols[1].value, 1u);
250
251    AAPT_ASSERT_TRUE(flagAttr->symbols[2].symbol.name);
252    EXPECT_EQ(flagAttr->symbols[2].symbol.name.value().entry, "baz");
253    EXPECT_EQ(flagAttr->symbols[2].value, 2u);
254
255    std::unique_ptr<BinaryPrimitive> flagValue = ResourceUtils::tryParseFlagSymbol(flagAttr,
256                                                                                   "baz|bat");
257    ASSERT_NE(nullptr, flagValue);
258    EXPECT_EQ(flagValue->value.data, 1u | 2u);
259}
260
261TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) {
262    std::string input = "<attr name=\"foo\">\n"
263                        "  <enum name=\"bar\" value=\"0\"/>\n"
264                        "  <enum name=\"bat\" value=\"1\"/>\n"
265                        "  <enum name=\"bat\" value=\"2\"/>\n"
266                        "</attr>";
267    ASSERT_FALSE(testParse(input));
268}
269
270TEST_F(ResourceParserTest, ParseStyle) {
271    std::string input = "<style name=\"foo\" parent=\"@style/fu\">\n"
272                        "  <item name=\"bar\">#ffffffff</item>\n"
273                        "  <item name=\"bat\">@string/hey</item>\n"
274                        "  <item name=\"baz\"><b>hey</b></item>\n"
275                        "</style>";
276    ASSERT_TRUE(testParse(input));
277
278    Style* style = test::getValue<Style>(&mTable, "style/foo");
279    ASSERT_NE(nullptr, style);
280    AAPT_ASSERT_TRUE(style->parent);
281    AAPT_ASSERT_TRUE(style->parent.value().name);
282    EXPECT_EQ(test::parseNameOrDie("style/fu"), style->parent.value().name.value());
283    ASSERT_EQ(3u, style->entries.size());
284
285    AAPT_ASSERT_TRUE(style->entries[0].key.name);
286    EXPECT_EQ(test::parseNameOrDie("attr/bar"), style->entries[0].key.name.value());
287
288    AAPT_ASSERT_TRUE(style->entries[1].key.name);
289    EXPECT_EQ(test::parseNameOrDie("attr/bat"), style->entries[1].key.name.value());
290
291    AAPT_ASSERT_TRUE(style->entries[2].key.name);
292    EXPECT_EQ(test::parseNameOrDie("attr/baz"), style->entries[2].key.name.value());
293}
294
295TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) {
296    std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>";
297    ASSERT_TRUE(testParse(input));
298
299    Style* style = test::getValue<Style>(&mTable, "style/foo");
300    ASSERT_NE(nullptr, style);
301    AAPT_ASSERT_TRUE(style->parent);
302    AAPT_ASSERT_TRUE(style->parent.value().name);
303    EXPECT_EQ(test::parseNameOrDie("com.app:style/Theme"), style->parent.value().name.value());
304}
305
306TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) {
307    std::string input = "<style xmlns:app=\"http://schemas.android.com/apk/res/android\"\n"
308                        "       name=\"foo\" parent=\"app:Theme\"/>";
309    ASSERT_TRUE(testParse(input));
310
311    Style* style = test::getValue<Style>(&mTable, "style/foo");
312    ASSERT_NE(nullptr, style);
313    AAPT_ASSERT_TRUE(style->parent);
314    AAPT_ASSERT_TRUE(style->parent.value().name);
315    EXPECT_EQ(test::parseNameOrDie("android:style/Theme"), style->parent.value().name.value());
316}
317
318TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) {
319    std::string input =
320            "<style xmlns:app=\"http://schemas.android.com/apk/res/android\" name=\"foo\">\n"
321            "  <item name=\"app:bar\">0</item>\n"
322            "</style>";
323    ASSERT_TRUE(testParse(input));
324
325    Style* style = test::getValue<Style>(&mTable, "style/foo");
326    ASSERT_NE(nullptr, style);
327    ASSERT_EQ(1u, style->entries.size());
328    EXPECT_EQ(test::parseNameOrDie("android:attr/bar"), style->entries[0].key.name.value());
329}
330
331TEST_F(ResourceParserTest, ParseStyleWithInferredParent) {
332    std::string input = "<style name=\"foo.bar\"/>";
333    ASSERT_TRUE(testParse(input));
334
335    Style* style = test::getValue<Style>(&mTable, "style/foo.bar");
336    ASSERT_NE(nullptr, style);
337    AAPT_ASSERT_TRUE(style->parent);
338    AAPT_ASSERT_TRUE(style->parent.value().name);
339    EXPECT_EQ(style->parent.value().name.value(), test::parseNameOrDie("style/foo"));
340    EXPECT_TRUE(style->parentInferred);
341}
342
343TEST_F(ResourceParserTest, ParseStyleWithInferredParentOverridenByEmptyParentAttribute) {
344    std::string input = "<style name=\"foo.bar\" parent=\"\"/>";
345    ASSERT_TRUE(testParse(input));
346
347    Style* style = test::getValue<Style>(&mTable, "style/foo.bar");
348    ASSERT_NE(nullptr, style);
349    AAPT_EXPECT_FALSE(style->parent);
350    EXPECT_FALSE(style->parentInferred);
351}
352
353TEST_F(ResourceParserTest, ParseStyleWithPrivateParentReference) {
354    std::string input = R"EOF(<style name="foo" parent="*android:style/bar" />)EOF";
355    ASSERT_TRUE(testParse(input));
356
357    Style* style = test::getValue<Style>(&mTable, "style/foo");
358    ASSERT_NE(nullptr, style);
359    AAPT_ASSERT_TRUE(style->parent);
360    EXPECT_TRUE(style->parent.value().privateReference);
361}
362
363TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) {
364    std::string input = "<string name=\"foo\">@+id/bar</string>";
365    ASSERT_TRUE(testParse(input));
366
367    Id* id = test::getValue<Id>(&mTable, "id/bar");
368    ASSERT_NE(id, nullptr);
369}
370
371TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) {
372    std::string input = "<declare-styleable name=\"foo\">\n"
373                        "  <attr name=\"bar\" />\n"
374                        "  <attr name=\"bat\" format=\"string|reference\"/>\n"
375                        "  <attr name=\"baz\">\n"
376                        "    <enum name=\"foo\" value=\"1\"/>\n"
377                        "  </attr>\n"
378                        "</declare-styleable>";
379    ASSERT_TRUE(testParse(input));
380
381    Maybe<ResourceTable::SearchResult> result =
382            mTable.findResource(test::parseNameOrDie("styleable/foo"));
383    AAPT_ASSERT_TRUE(result);
384    EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbolStatus.state);
385
386    Attribute* attr = test::getValue<Attribute>(&mTable, "attr/bar");
387    ASSERT_NE(attr, nullptr);
388    EXPECT_TRUE(attr->isWeak());
389
390    attr = test::getValue<Attribute>(&mTable, "attr/bat");
391    ASSERT_NE(attr, nullptr);
392    EXPECT_TRUE(attr->isWeak());
393
394    attr = test::getValue<Attribute>(&mTable, "attr/baz");
395    ASSERT_NE(attr, nullptr);
396    EXPECT_TRUE(attr->isWeak());
397    EXPECT_EQ(1u, attr->symbols.size());
398
399    EXPECT_NE(nullptr, test::getValue<Id>(&mTable, "id/foo"));
400
401    Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo");
402    ASSERT_NE(styleable, nullptr);
403    ASSERT_EQ(3u, styleable->entries.size());
404
405    EXPECT_EQ(test::parseNameOrDie("attr/bar"), styleable->entries[0].name.value());
406    EXPECT_EQ(test::parseNameOrDie("attr/bat"), styleable->entries[1].name.value());
407}
408
409TEST_F(ResourceParserTest, ParsePrivateAttributesDeclareStyleable) {
410    std::string input = "<declare-styleable name=\"foo\" xmlns:privAndroid=\"http://schemas.android.com/apk/prv/res/android\">\n"
411                        "  <attr name=\"*android:bar\" />\n"
412                        "  <attr name=\"privAndroid:bat\" />\n"
413                        "</declare-styleable>";
414    ASSERT_TRUE(testParse(input));
415    Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo");
416    ASSERT_NE(nullptr, styleable);
417    ASSERT_EQ(2u, styleable->entries.size());
418
419    EXPECT_TRUE(styleable->entries[0].privateReference);
420    AAPT_ASSERT_TRUE(styleable->entries[0].name);
421    EXPECT_EQ(std::string("android"), styleable->entries[0].name.value().package);
422
423    EXPECT_TRUE(styleable->entries[1].privateReference);
424    AAPT_ASSERT_TRUE(styleable->entries[1].name);
425    EXPECT_EQ(std::string("android"), styleable->entries[1].name.value().package);
426}
427
428TEST_F(ResourceParserTest, ParseArray) {
429    std::string input = "<array name=\"foo\">\n"
430                        "  <item>@string/ref</item>\n"
431                        "  <item>hey</item>\n"
432                        "  <item>23</item>\n"
433                        "</array>";
434    ASSERT_TRUE(testParse(input));
435
436    Array* array = test::getValue<Array>(&mTable, "array/foo");
437    ASSERT_NE(array, nullptr);
438    ASSERT_EQ(3u, array->items.size());
439
440    EXPECT_NE(nullptr, valueCast<Reference>(array->items[0].get()));
441    EXPECT_NE(nullptr, valueCast<String>(array->items[1].get()));
442    EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(array->items[2].get()));
443}
444
445TEST_F(ResourceParserTest, ParseStringArray) {
446    std::string input = "<string-array name=\"foo\">\n"
447                        "  <item>\"Werk\"</item>\n"
448                        "</string-array>\n";
449    ASSERT_TRUE(testParse(input));
450    EXPECT_NE(nullptr, test::getValue<Array>(&mTable, "array/foo"));
451}
452
453TEST_F(ResourceParserTest, ParsePlural) {
454    std::string input = "<plurals name=\"foo\">\n"
455                        "  <item quantity=\"other\">apples</item>\n"
456                        "  <item quantity=\"one\">apple</item>\n"
457                        "</plurals>";
458    ASSERT_TRUE(testParse(input));
459}
460
461TEST_F(ResourceParserTest, ParseCommentsWithResource) {
462    std::string input = "<!--This is a comment-->\n"
463                        "<string name=\"foo\">Hi</string>";
464    ASSERT_TRUE(testParse(input));
465
466    String* value = test::getValue<String>(&mTable, "string/foo");
467    ASSERT_NE(nullptr, value);
468    EXPECT_EQ(value->getComment(), "This is a comment");
469}
470
471TEST_F(ResourceParserTest, DoNotCombineMultipleComments) {
472    std::string input = "<!--One-->\n"
473                        "<!--Two-->\n"
474                        "<string name=\"foo\">Hi</string>";
475
476    ASSERT_TRUE(testParse(input));
477
478    String* value = test::getValue<String>(&mTable, "string/foo");
479    ASSERT_NE(nullptr, value);
480    EXPECT_EQ(value->getComment(), "Two");
481}
482
483TEST_F(ResourceParserTest, IgnoreCommentBeforeEndTag) {
484    std::string input = "<!--One-->\n"
485                        "<string name=\"foo\">\n"
486                        "  Hi\n"
487                        "<!--Two-->\n"
488                        "</string>";
489
490    ASSERT_TRUE(testParse(input));
491
492    String* value = test::getValue<String>(&mTable, "string/foo");
493    ASSERT_NE(nullptr, value);
494    EXPECT_EQ(value->getComment(), "One");
495}
496
497TEST_F(ResourceParserTest, ParseNestedComments) {
498    // We only care about declare-styleable and enum/flag attributes because comments
499    // from those end up in R.java
500    std::string input = R"EOF(
501        <declare-styleable name="foo">
502          <!-- The name of the bar -->
503          <attr name="barName" format="string|reference" />
504        </declare-styleable>
505
506        <attr name="foo">
507          <!-- The very first -->
508          <enum name="one" value="1" />
509        </attr>)EOF";
510    ASSERT_TRUE(testParse(input));
511
512    Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo");
513    ASSERT_NE(nullptr, styleable);
514    ASSERT_EQ(1u, styleable->entries.size());
515
516    EXPECT_EQ(StringPiece("The name of the bar"), styleable->entries.front().getComment());
517
518    Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
519    ASSERT_NE(nullptr, attr);
520    ASSERT_EQ(1u, attr->symbols.size());
521
522    EXPECT_EQ(StringPiece("The very first"), attr->symbols.front().symbol.getComment());
523}
524
525/*
526 * Declaring an ID as public should not require a separate definition
527 * (as an ID has no value).
528 */
529TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) {
530    std::string input = "<public type=\"id\" name=\"foo\"/>";
531    ASSERT_TRUE(testParse(input));
532
533    Id* id = test::getValue<Id>(&mTable, "id/foo");
534    ASSERT_NE(nullptr, id);
535}
536
537TEST_F(ResourceParserTest, KeepAllProducts) {
538    std::string input = R"EOF(
539        <string name="foo" product="phone">hi</string>
540        <string name="foo" product="no-sdcard">ho</string>
541        <string name="bar" product="">wee</string>
542        <string name="baz">woo</string>
543        <string name="bit" product="phablet">hoot</string>
544        <string name="bot" product="default">yes</string>
545    )EOF";
546    ASSERT_TRUE(testParse(input));
547
548    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/foo",
549                                                                 ConfigDescription::defaultConfig(),
550                                                                 "phone"));
551    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/foo",
552                                                                 ConfigDescription::defaultConfig(),
553                                                                 "no-sdcard"));
554    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/bar",
555                                                                 ConfigDescription::defaultConfig(),
556                                                                 ""));
557    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/baz",
558                                                                 ConfigDescription::defaultConfig(),
559                                                                 ""));
560    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/bit",
561                                                                 ConfigDescription::defaultConfig(),
562                                                                 "phablet"));
563    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/bot",
564                                                                 ConfigDescription::defaultConfig(),
565                                                                 "default"));
566}
567
568TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) {
569    std::string input = R"EOF(
570    <public-group type="attr" first-id="0x01010040">
571      <public name="foo" />
572      <public name="bar" />
573    </public-group>)EOF";
574    ASSERT_TRUE(testParse(input));
575
576    Maybe<ResourceTable::SearchResult> result = mTable.findResource(
577            test::parseNameOrDie("attr/foo"));
578    AAPT_ASSERT_TRUE(result);
579
580    AAPT_ASSERT_TRUE(result.value().package->id);
581    AAPT_ASSERT_TRUE(result.value().type->id);
582    AAPT_ASSERT_TRUE(result.value().entry->id);
583    ResourceId actualId(result.value().package->id.value(),
584                        result.value().type->id.value(),
585                        result.value().entry->id.value());
586    EXPECT_EQ(ResourceId(0x01010040), actualId);
587
588    result = mTable.findResource(test::parseNameOrDie("attr/bar"));
589    AAPT_ASSERT_TRUE(result);
590
591    AAPT_ASSERT_TRUE(result.value().package->id);
592    AAPT_ASSERT_TRUE(result.value().type->id);
593    AAPT_ASSERT_TRUE(result.value().entry->id);
594    actualId = ResourceId(result.value().package->id.value(),
595                          result.value().type->id.value(),
596                          result.value().entry->id.value());
597    EXPECT_EQ(ResourceId(0x01010041), actualId);
598}
599
600TEST_F(ResourceParserTest, ExternalTypesShouldOnlyBeReferences) {
601    std::string input = R"EOF(<item type="layout" name="foo">@layout/bar</item>)EOF";
602    ASSERT_TRUE(testParse(input));
603
604    input = R"EOF(<item type="layout" name="bar">"this is a string"</item>)EOF";
605    ASSERT_FALSE(testParse(input));
606}
607
608TEST_F(ResourceParserTest, AddResourcesElementShouldAddEntryWithUndefinedSymbol) {
609    std::string input = R"EOF(<add-resource name="bar" type="string" />)EOF";
610    ASSERT_TRUE(testParse(input));
611
612    Maybe<ResourceTable::SearchResult> result = mTable.findResource(
613            test::parseNameOrDie("string/bar"));
614    AAPT_ASSERT_TRUE(result);
615    const ResourceEntry* entry = result.value().entry;
616    ASSERT_NE(nullptr, entry);
617    EXPECT_EQ(SymbolState::kUndefined, entry->symbolStatus.state);
618}
619
620TEST_F(ResourceParserTest, ParseItemElementWithFormat) {
621    std::string input = R"EOF(<item name="foo" type="integer" format="float">0.3</item>)EOF";
622    ASSERT_TRUE(testParse(input));
623
624    BinaryPrimitive* val = test::getValue<BinaryPrimitive>(&mTable, "integer/foo");
625    ASSERT_NE(nullptr, val);
626
627    EXPECT_EQ(uint32_t(android::Res_value::TYPE_FLOAT), val->value.dataType);
628}
629
630} // namespace aapt
631