1 /*
2  * Copyright (C) 2016 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 "compile/InlineXmlFormatParser.h"
18 
19 #include "test/Test.h"
20 
21 using ::testing::Eq;
22 using ::testing::IsNull;
23 using ::testing::Not;
24 using ::testing::NotNull;
25 using ::testing::SizeIs;
26 using ::testing::StrEq;
27 
28 namespace aapt {
29 
TEST(InlineXmlFormatParserTest,PassThrough)30 TEST(InlineXmlFormatParserTest, PassThrough) {
31   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
32   std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
33       <View xmlns:android="http://schemas.android.com/apk/res/android">
34         <View android:text="hey">
35           <View android:id="hi" />
36         </View>
37       </View>)");
38 
39   InlineXmlFormatParser parser;
40   ASSERT_TRUE(parser.Consume(context.get(), doc.get()));
41   EXPECT_THAT(parser.GetExtractedInlineXmlDocuments(), SizeIs(0u));
42 }
43 
44 TEST(InlineXmlFormatParserTest, ExtractOneXmlResource) {
45   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
46   std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
47       <View1 xmlns:android="http://schemas.android.com/apk/res/android"
48             xmlns:aapt="http://schemas.android.com/aapt">
49         <aapt:attr name="android:text">
50           <View2 android:text="hey">
51             <View3 android:id="hi" />
52           </View2>
53         </aapt:attr>
54       </View1>)");
55 
56   doc->file.name = test::ParseNameOrDie("layout/main");
57   doc->file.type = ResourceFile::Type::kProtoXml;
58 
59   InlineXmlFormatParser parser;
60   ASSERT_TRUE(parser.Consume(context.get(), doc.get()));
61 
62   // One XML resource should have been extracted.
63   EXPECT_THAT(parser.GetExtractedInlineXmlDocuments(), SizeIs(1u));
64 
65   xml::Element* el = doc->root.get();
66   ASSERT_THAT(el, NotNull());
67   EXPECT_THAT(el->name, StrEq("View1"));
68 
69   // The <aapt:attr> tag should be extracted.
70   EXPECT_THAT(el->FindChild(xml::kSchemaAapt, "attr"), IsNull());
71 
72   // The 'android:text' attribute should be set with a reference.
73   xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "text");
74   ASSERT_THAT(attr, NotNull());
75 
76   ResourceNameRef name_ref;
77   ASSERT_TRUE(ResourceUtils::ParseReference(attr->value, &name_ref));
78 
79   xml::XmlResource* extracted_doc = parser.GetExtractedInlineXmlDocuments()[0].get();
80   ASSERT_THAT(extracted_doc, NotNull());
81 
82   // Make sure the generated reference is correct.
83   EXPECT_THAT(extracted_doc->file.name, Eq(name_ref));
84 
85   // Make sure the ResourceFile::Type is the same.
86   EXPECT_THAT(extracted_doc->file.type, Eq(ResourceFile::Type::kProtoXml));
87 
88   // Verify the structure of the extracted XML.
89   el = extracted_doc->root.get();
90   ASSERT_THAT(el, NotNull());
91   EXPECT_THAT(el->name, StrEq("View2"));
92   EXPECT_THAT(el->FindChild({}, "View3"), NotNull());
93 }
94 
95 TEST(InlineXmlFormatParserTest, ExtractTwoXmlResources) {
96   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
97   std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
98       <View1 xmlns:android="http://schemas.android.com/apk/res/android"
99             xmlns:aapt="http://schemas.android.com/aapt">
100         <aapt:attr name="android:text">
101           <View2 android:text="hey">
102             <View3 android:id="hi" />
103           </View2>
104         </aapt:attr>
105 
106         <aapt:attr name="android:drawable">
107           <vector />
108         </aapt:attr>
109       </View1>)");
110 
111   doc->file.name = test::ParseNameOrDie("layout/main");
112 
113   InlineXmlFormatParser parser;
114   ASSERT_TRUE(parser.Consume(context.get(), doc.get()));
115   ASSERT_THAT(parser.GetExtractedInlineXmlDocuments(), SizeIs(2u));
116 
117   xml::Element* el = doc->root.get();
118   ASSERT_THAT(el, NotNull());
119   EXPECT_THAT(el->name, StrEq("View1"));
120 
121   xml::Attribute* attr_text = el->FindAttribute(xml::kSchemaAndroid, "text");
122   ASSERT_THAT(attr_text, NotNull());
123 
124   xml::Attribute* attr_drawable = el->FindAttribute(xml::kSchemaAndroid, "drawable");
125   ASSERT_THAT(attr_drawable, NotNull());
126 
127   // The two extracted resources should have different names.
128   EXPECT_THAT(attr_text->value, Not(Eq(attr_drawable->value)));
129 
130   // The child <aapt:attr> elements should be gone.
131   EXPECT_THAT(el->FindChild(xml::kSchemaAapt, "attr"), IsNull());
132 
133   xml::XmlResource* extracted_doc_text = parser.GetExtractedInlineXmlDocuments()[0].get();
134   ASSERT_THAT(extracted_doc_text, NotNull());
135   ASSERT_THAT(extracted_doc_text->root, NotNull());
136   EXPECT_THAT(extracted_doc_text->root->name, StrEq("View2"));
137 
138   xml::XmlResource* extracted_doc_drawable = parser.GetExtractedInlineXmlDocuments()[1].get();
139   ASSERT_THAT(extracted_doc_drawable, NotNull());
140   ASSERT_THAT(extracted_doc_drawable->root, NotNull());
141   EXPECT_THAT(extracted_doc_drawable->root->name, StrEq("vector"));
142 }
143 
144 TEST(InlineXmlFormatParserTest, ExtractNestedXmlResources) {
145   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
146   std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
147       <base_root xmlns:android="http://schemas.android.com/apk/res/android"
148             xmlns:aapt="http://schemas.android.com/aapt">
149           <aapt:attr name="inline_xml">
150               <inline_root>
151                   <aapt:attr name="nested_inline_xml">
152                       <nested_inline_root/>
153                   </aapt:attr>
154                   <aapt:attr name="another_nested_inline_xml">
155                       <root/>
156                   </aapt:attr>
157               </inline_root>
158           </aapt:attr>
159           <aapt:attr name="turtles">
160               <root1>
161                   <aapt:attr name="all">
162                       <root2>
163                           <aapt:attr name="the">
164                               <root3>
165                                   <aapt:attr name="way">
166                                       <root4>
167                                           <aapt:attr name="down">
168                                               <root5/>
169                                           </aapt:attr>
170                                       </root4>
171                                   </aapt:attr>
172                               </root3>
173                           </aapt:attr>
174                       </root2>
175                   </aapt:attr>
176               </root1>
177           </aapt:attr>
178       </base_root>)");
179 
180   doc->file.name = test::ParseNameOrDie("layout/main");
181 
182   InlineXmlFormatParser parser;
183   ASSERT_TRUE(parser.Consume(context.get(), doc.get()));
184   // Confirm that all of the nested inline xmls are parsed out.
185   ASSERT_THAT(parser.GetExtractedInlineXmlDocuments(), SizeIs(8u));
186 }
187 
188 TEST(InlineXmlFormatParserTest, ExtractIntoAppAttribute) {
189   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
190   std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
191       <parent xmlns:app="http://schemas.android.com/apk/res-auto"
192               xmlns:aapt="http://schemas.android.com/aapt">
193             <aapt:attr name="app:foo">
194                 <child />
195             </aapt:attr>
196       </parent>)");
197 
198   doc->file.name = test::ParseNameOrDie("layout/main");
199 
200   InlineXmlFormatParser parser;
201   ASSERT_TRUE(parser.Consume(context.get(), doc.get()));
202 
203   ASSERT_THAT(doc->root, NotNull());
204   EXPECT_THAT(doc->root->FindAttribute(xml::kSchemaAuto, "foo"), NotNull());
205 }
206 
207 TEST(InlineXmlFormatParserTest, ExtractIntoNoNamespaceAttribute) {
208   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
209   std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
210       <parent xmlns:aapt="http://schemas.android.com/aapt">
211             <aapt:attr name="foo">
212                 <child />
213             </aapt:attr>
214       </parent>)");
215 
216   doc->file.name = test::ParseNameOrDie("layout/main");
217 
218   InlineXmlFormatParser parser;
219   ASSERT_TRUE(parser.Consume(context.get(), doc.get()));
220 
221   ASSERT_THAT(doc->root, NotNull());
222   EXPECT_THAT(doc->root->FindAttribute({}, "foo"), NotNull());
223 }
224 
225 }  // namespace aapt
226