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 "link/TableMerger.h"
18 
19 #include "filter/ConfigFilter.h"
20 #include "io/FileSystem.h"
21 #include "test/Test.h"
22 
23 using ::aapt::test::ValueEq;
24 using ::testing::Contains;
25 using ::testing::NotNull;
26 using ::testing::UnorderedElementsAreArray;
27 using ::testing::Pointee;
28 using ::testing::Field;
29 using ::testing::Eq;
30 
31 namespace aapt {
32 
33 struct TableMergerTest : public ::testing::Test {
34   std::unique_ptr<IAaptContext> context_;
35 
SetUpaapt::TableMergerTest36   void SetUp() override {
37     context_ =
38         test::ContextBuilder()
39             // We are compiling this package.
40             .SetCompilationPackage("com.app.a")
41 
42             // Merge all packages that have this package ID.
43             .SetPackageId(0x7f)
44 
45             // Mangle all packages that do not have this package name.
46             .SetNameManglerPolicy(NameManglerPolicy{"com.app.a", {"com.app.b"}})
47 
48             .Build();
49   }
50 };
51 
TEST_F(TableMergerTest,SimpleMerge)52 TEST_F(TableMergerTest, SimpleMerge) {
53   std::unique_ptr<ResourceTable> table_a =
54       test::ResourceTableBuilder()
55           .SetPackageId("com.app.a", 0x7f)
56           .AddReference("com.app.a:id/foo", "com.app.a:id/bar")
57           .AddReference("com.app.a:id/bar", "com.app.b:id/foo")
58           .AddValue(
59               "com.app.a:styleable/view",
60               test::StyleableBuilder().AddItem("com.app.b:id/foo").Build())
61           .Build();
62 
63   std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder()
64                                                .SetPackageId("com.app.b", 0x7f)
65                                                .AddSimple("com.app.b:id/foo")
66                                                .Build();
67 
68   ResourceTable final_table;
69   TableMerger merger(context_.get(), &final_table, TableMergerOptions{});
70   io::FileCollection collection;
71 
72   ASSERT_TRUE(merger.Merge({}, table_a.get()));
73   ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection));
74 
75   EXPECT_TRUE(merger.merged_packages().count("com.app.b") != 0);
76 
77   // Entries from com.app.a should not be mangled.
78   EXPECT_TRUE(final_table.FindResource(test::ParseNameOrDie("com.app.a:id/foo")));
79   EXPECT_TRUE(final_table.FindResource(test::ParseNameOrDie("com.app.a:id/bar")));
80   EXPECT_TRUE(final_table.FindResource(test::ParseNameOrDie("com.app.a:styleable/view")));
81 
82   // The unmangled name should not be present.
83   EXPECT_FALSE(final_table.FindResource(test::ParseNameOrDie("com.app.b:id/foo")));
84 
85   // Look for the mangled name.
86   EXPECT_TRUE(final_table.FindResource(test::ParseNameOrDie("com.app.a:id/com.app.b$foo")));
87 }
88 
TEST_F(TableMergerTest,MergeFile)89 TEST_F(TableMergerTest, MergeFile) {
90   ResourceTable final_table;
91   TableMergerOptions options;
92   options.auto_add_overlay = false;
93   TableMerger merger(context_.get(), &final_table, options);
94 
95   ResourceFile file_desc;
96   file_desc.config = test::ParseConfigOrDie("hdpi-v4");
97   file_desc.name = test::ParseNameOrDie("layout/main");
98   file_desc.source = Source("res/layout-hdpi/main.xml");
99   test::TestFile test_file("path/to/res/layout-hdpi/main.xml.flat");
100 
101   ASSERT_TRUE(merger.MergeFile(file_desc, &test_file));
102 
103   FileReference* file = test::GetValueForConfig<FileReference>(
104       &final_table, "com.app.a:layout/main", test::ParseConfigOrDie("hdpi-v4"));
105   ASSERT_THAT(file, NotNull());
106   EXPECT_EQ(std::string("res/layout-hdpi-v4/main.xml"), *file->path);
107 }
108 
TEST_F(TableMergerTest,MergeFileOverlay)109 TEST_F(TableMergerTest, MergeFileOverlay) {
110   ResourceTable final_table;
111   TableMergerOptions options;
112   options.auto_add_overlay = false;
113   TableMerger merger(context_.get(), &final_table, options);
114 
115   ResourceFile file_desc;
116   file_desc.name = test::ParseNameOrDie("xml/foo");
117   test::TestFile file_a("path/to/fileA.xml.flat");
118   test::TestFile file_b("path/to/fileB.xml.flat");
119 
120   ASSERT_TRUE(merger.MergeFile(file_desc, &file_a));
121   ASSERT_TRUE(merger.MergeFileOverlay(file_desc, &file_b));
122 }
123 
TEST_F(TableMergerTest,MergeFileReferences)124 TEST_F(TableMergerTest, MergeFileReferences) {
125   std::unique_ptr<ResourceTable> table_a =
126       test::ResourceTableBuilder()
127           .SetPackageId("com.app.a", 0x7f)
128           .AddFileReference("com.app.a:xml/file", "res/xml/file.xml")
129           .Build();
130   std::unique_ptr<ResourceTable> table_b =
131       test::ResourceTableBuilder()
132           .SetPackageId("com.app.b", 0x7f)
133           .AddFileReference("com.app.b:xml/file", "res/xml/file.xml")
134           .Build();
135 
136   ResourceTable final_table;
137   TableMerger merger(context_.get(), &final_table, TableMergerOptions{});
138   io::FileCollection collection;
139   collection.InsertFile("res/xml/file.xml");
140 
141   ASSERT_TRUE(merger.Merge({}, table_a.get()));
142   ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection));
143 
144   FileReference* f = test::GetValue<FileReference>(&final_table, "com.app.a:xml/file");
145   ASSERT_THAT(f, NotNull());
146   EXPECT_EQ(std::string("res/xml/file.xml"), *f->path);
147 
148   f = test::GetValue<FileReference>(&final_table, "com.app.a:xml/com.app.b$file");
149   ASSERT_THAT(f, NotNull());
150   EXPECT_EQ(std::string("res/xml/com.app.b$file.xml"), *f->path);
151 }
152 
TEST_F(TableMergerTest,OverrideResourceWithOverlay)153 TEST_F(TableMergerTest, OverrideResourceWithOverlay) {
154   std::unique_ptr<ResourceTable> base =
155       test::ResourceTableBuilder()
156           .SetPackageId("", 0x00)
157           .AddValue("bool/foo", ResourceUtils::TryParseBool("true"))
158           .Build();
159   std::unique_ptr<ResourceTable> overlay =
160       test::ResourceTableBuilder()
161           .SetPackageId("", 0x00)
162           .AddValue("bool/foo", ResourceUtils::TryParseBool("false"))
163           .Build();
164 
165   ResourceTable final_table;
166   TableMergerOptions options;
167   options.auto_add_overlay = false;
168   TableMerger merger(context_.get(), &final_table, options);
169 
170   ASSERT_TRUE(merger.Merge({}, base.get()));
171   ASSERT_TRUE(merger.MergeOverlay({}, overlay.get()));
172 
173   BinaryPrimitive* foo = test::GetValue<BinaryPrimitive>(&final_table, "com.app.a:bool/foo");
174   ASSERT_THAT(foo,
175               Pointee(Field(&BinaryPrimitive::value, Field(&android::Res_value::data, Eq(0u)))));
176 }
177 
TEST_F(TableMergerTest,OverrideSameResourceIdsWithOverlay)178 TEST_F(TableMergerTest, OverrideSameResourceIdsWithOverlay) {
179   std::unique_ptr<ResourceTable> base =
180       test::ResourceTableBuilder()
181           .SetPackageId("", 0x7f)
182           .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
183                           SymbolState::kPublic)
184           .Build();
185   std::unique_ptr<ResourceTable> overlay =
186       test::ResourceTableBuilder()
187           .SetPackageId("", 0x7f)
188           .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
189                           SymbolState::kPublic)
190           .Build();
191 
192   ResourceTable final_table;
193   TableMergerOptions options;
194   options.auto_add_overlay = false;
195   TableMerger merger(context_.get(), &final_table, options);
196 
197   ASSERT_TRUE(merger.Merge({}, base.get()));
198   ASSERT_TRUE(merger.MergeOverlay({}, overlay.get()));
199 }
200 
TEST_F(TableMergerTest,FailToOverrideConflictingTypeIdsWithOverlay)201 TEST_F(TableMergerTest, FailToOverrideConflictingTypeIdsWithOverlay) {
202   std::unique_ptr<ResourceTable> base =
203       test::ResourceTableBuilder()
204           .SetPackageId("", 0x7f)
205           .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
206                           SymbolState::kPublic)
207           .Build();
208   std::unique_ptr<ResourceTable> overlay =
209       test::ResourceTableBuilder()
210           .SetPackageId("", 0x7f)
211           .SetSymbolState("bool/foo", ResourceId(0x7f, 0x02, 0x0001),
212                           SymbolState::kPublic)
213           .Build();
214 
215   ResourceTable final_table;
216   TableMergerOptions options;
217   options.auto_add_overlay = false;
218   TableMerger merger(context_.get(), &final_table, options);
219 
220   ASSERT_TRUE(merger.Merge({}, base.get()));
221   ASSERT_FALSE(merger.MergeOverlay({}, overlay.get()));
222 }
223 
TEST_F(TableMergerTest,FailToOverrideConflictingEntryIdsWithOverlay)224 TEST_F(TableMergerTest, FailToOverrideConflictingEntryIdsWithOverlay) {
225   std::unique_ptr<ResourceTable> base =
226       test::ResourceTableBuilder()
227           .SetPackageId("", 0x7f)
228           .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
229                           SymbolState::kPublic)
230           .Build();
231   std::unique_ptr<ResourceTable> overlay =
232       test::ResourceTableBuilder()
233           .SetPackageId("", 0x7f)
234           .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0002),
235                           SymbolState::kPublic)
236           .Build();
237 
238   ResourceTable final_table;
239   TableMergerOptions options;
240   options.auto_add_overlay = false;
241   TableMerger merger(context_.get(), &final_table, options);
242 
243   ASSERT_TRUE(merger.Merge({}, base.get()));
244   ASSERT_FALSE(merger.MergeOverlay({}, overlay.get()));
245 }
246 
TEST_F(TableMergerTest,MergeAddResourceFromOverlay)247 TEST_F(TableMergerTest, MergeAddResourceFromOverlay) {
248   std::unique_ptr<ResourceTable> table_a =
249       test::ResourceTableBuilder().SetPackageId("", 0x7f).Build();
250   std::unique_ptr<ResourceTable> table_b =
251       test::ResourceTableBuilder()
252           .SetPackageId("", 0x7f)
253           .SetSymbolState("bool/foo", {}, SymbolState::kUndefined, true /*allow new overlay*/)
254           .AddValue("bool/foo", ResourceUtils::TryParseBool("true"))
255           .Build();
256 
257   ResourceTable final_table;
258   TableMergerOptions options;
259   options.auto_add_overlay = false;
260   TableMerger merger(context_.get(), &final_table, options);
261 
262   ASSERT_TRUE(merger.Merge({}, table_a.get()));
263   ASSERT_TRUE(merger.MergeOverlay({}, table_b.get()));
264 }
265 
TEST_F(TableMergerTest,MergeAddResourceFromOverlayWithAutoAddOverlay)266 TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) {
267   std::unique_ptr<ResourceTable> table_a =
268       test::ResourceTableBuilder().SetPackageId("", 0x7f).Build();
269   std::unique_ptr<ResourceTable> table_b =
270       test::ResourceTableBuilder()
271           .SetPackageId("", 0x7f)
272           .AddValue("bool/foo", ResourceUtils::TryParseBool("true"))
273           .Build();
274 
275   ResourceTable final_table;
276   TableMergerOptions options;
277   options.auto_add_overlay = true;
278   TableMerger merger(context_.get(), &final_table, options);
279 
280   ASSERT_TRUE(merger.Merge({}, table_a.get()));
281   ASSERT_TRUE(merger.MergeOverlay({}, table_b.get()));
282 }
283 
TEST_F(TableMergerTest,FailToMergeNewResourceWithoutAutoAddOverlay)284 TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) {
285   std::unique_ptr<ResourceTable> table_a =
286       test::ResourceTableBuilder().SetPackageId("", 0x7f).Build();
287   std::unique_ptr<ResourceTable> table_b =
288       test::ResourceTableBuilder()
289           .SetPackageId("", 0x7f)
290           .AddValue("bool/foo", ResourceUtils::TryParseBool("true"))
291           .Build();
292 
293   ResourceTable final_table;
294   TableMergerOptions options;
295   options.auto_add_overlay = false;
296   TableMerger merger(context_.get(), &final_table, options);
297 
298   ASSERT_TRUE(merger.Merge({}, table_a.get()));
299   ASSERT_FALSE(merger.MergeOverlay({}, table_b.get()));
300 }
301 
TEST_F(TableMergerTest,OverlaidStyleablesAndStylesShouldBeMerged)302 TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) {
303   std::unique_ptr<ResourceTable> table_a =
304       test::ResourceTableBuilder()
305           .SetPackageId("com.app.a", 0x7f)
306           .AddValue("com.app.a:styleable/Foo",
307                     test::StyleableBuilder()
308                         .AddItem("com.app.a:attr/bar")
309                         .AddItem("com.app.a:attr/foo", ResourceId(0x01010000))
310                         .Build())
311           .AddValue("com.app.a:style/Theme",
312                     test::StyleBuilder()
313                         .SetParent("com.app.a:style/Parent")
314                         .AddItem("com.app.a:attr/bar", util::make_unique<Id>())
315                         .AddItem("com.app.a:attr/foo", ResourceUtils::MakeBool(false))
316                         .Build())
317           .Build();
318 
319   std::unique_ptr<ResourceTable> table_b =
320       test::ResourceTableBuilder()
321           .SetPackageId("com.app.a", 0x7f)
322           .AddValue("com.app.a:styleable/Foo", test::StyleableBuilder()
323                                                    .AddItem("com.app.a:attr/bat")
324                                                    .AddItem("com.app.a:attr/foo")
325                                                    .Build())
326           .AddValue("com.app.a:style/Theme",
327                     test::StyleBuilder()
328                         .SetParent("com.app.a:style/OverlayParent")
329                         .AddItem("com.app.a:attr/bat", util::make_unique<Id>())
330                         .AddItem("com.app.a:attr/foo", ResourceId(0x01010000),
331                                  ResourceUtils::MakeBool(true))
332                         .Build())
333           .Build();
334 
335   ResourceTable final_table;
336   TableMergerOptions options;
337   options.auto_add_overlay = true;
338   TableMerger merger(context_.get(), &final_table, options);
339 
340   ASSERT_TRUE(merger.Merge({}, table_a.get()));
341   ASSERT_TRUE(merger.MergeOverlay({}, table_b.get()));
342 
343   Styleable* styleable = test::GetValue<Styleable>(&final_table, "com.app.a:styleable/Foo");
344   ASSERT_THAT(styleable, NotNull());
345 
346   std::vector<Reference> expected_refs = {
347       Reference(test::ParseNameOrDie("com.app.a:attr/bar")),
348       Reference(test::ParseNameOrDie("com.app.a:attr/bat")),
349       Reference(test::ParseNameOrDie("com.app.a:attr/foo"), ResourceId(0x01010000)),
350   };
351   EXPECT_THAT(styleable->entries, UnorderedElementsAreArray(expected_refs));
352 
353   Style* style = test::GetValue<Style>(&final_table, "com.app.a:style/Theme");
354   ASSERT_THAT(style, NotNull());
355 
356   std::vector<Reference> extracted_refs;
357   for (const auto& entry : style->entries) {
358     extracted_refs.push_back(entry.key);
359   }
360   EXPECT_THAT(extracted_refs, UnorderedElementsAreArray(expected_refs));
361 
362   const auto expected = ResourceUtils::MakeBool(true);
363   EXPECT_THAT(style->entries, Contains(Field(&Style::Entry::value, Pointee(ValueEq(*expected)))));
364   EXPECT_THAT(style->parent,
365               Eq(make_value(Reference(test::ParseNameOrDie("com.app.a:style/OverlayParent")))));
366 }
367 
368 }  // namespace aapt
369