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
23using ::aapt::test::ValueEq;
24using ::testing::Contains;
25using ::testing::NotNull;
26using ::testing::UnorderedElementsAreArray;
27using ::testing::Pointee;
28using ::testing::Field;
29using ::testing::Eq;
30
31namespace aapt {
32
33struct TableMergerTest : public ::testing::Test {
34  std::unique_ptr<IAaptContext> context_;
35
36  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
52TEST_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
89TEST_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
109TEST_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
124TEST_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
153TEST_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
178TEST_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
201TEST_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
224TEST_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
247TEST_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
266TEST_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
284TEST_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
302TEST_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