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 "ResourceTable.h"
18 #include "Diagnostics.h"
19 #include "ResourceValues.h"
20 #include "test/Test.h"
21 #include "util/Util.h"
22
23 #include <algorithm>
24 #include <ostream>
25 #include <string>
26
27 using ::android::ConfigDescription;
28 using ::android::StringPiece;
29 using ::testing::Eq;
30 using ::testing::NotNull;
31 using ::testing::StrEq;
32
33 using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
34
35 namespace aapt {
36
TEST(ResourceTableTest,FailToAddResourceWithBadName)37 TEST(ResourceTableTest, FailToAddResourceWithBadName) {
38 ResourceTable table;
39
40 EXPECT_FALSE(
41 table.AddResource(NewResourceBuilder(test::ParseNameOrDie("android:id/hey,there")).Build(),
42 test::GetDiagnostics()));
43
44 EXPECT_FALSE(
45 table.AddResource(NewResourceBuilder(test::ParseNameOrDie("android:id/hey:there")).Build(),
46 test::GetDiagnostics()));
47 }
48
TEST(ResourceTableTest,AddResourceWithWeirdNameWhenAddingMangledResources)49 TEST(ResourceTableTest, AddResourceWithWeirdNameWhenAddingMangledResources) {
50 ResourceTable table;
51
52 EXPECT_TRUE(
53 table.AddResource(NewResourceBuilder(test::ParseNameOrDie("android:id/heythere "))
54 .SetAllowMangled(true)
55 .Build(),
56 test::GetDiagnostics()));
57 }
58
TEST(ResourceTableTest,AddOneResource)59 TEST(ResourceTableTest, AddOneResource) {
60 ResourceTable table;
61
62 EXPECT_TRUE(table.AddResource(
63 NewResourceBuilder(test::ParseNameOrDie("android:attr/id"))
64 .SetValue(test::ValueBuilder<Id>().SetSource("test/path/file.xml", 23u).Build())
65 .Build(),
66 test::GetDiagnostics()));
67
68 EXPECT_THAT(test::GetValue<Id>(&table, "android:attr/id"), NotNull());
69 }
70
TEST(ResourceTableTest,AddMultipleResources)71 TEST(ResourceTableTest, AddMultipleResources) {
72 ResourceTable table;
73
74 ConfigDescription language_config;
75 memcpy(language_config.language, "pl", sizeof(language_config.language));
76
77 EXPECT_TRUE(table.AddResource(
78 NewResourceBuilder(test::ParseNameOrDie("android:attr/layout_width"))
79 .SetValue(test::ValueBuilder<Id>().SetSource("test/path/file.xml", 10u).Build())
80 .Build(),
81 test::GetDiagnostics()));
82
83 EXPECT_TRUE(table.AddResource(
84 NewResourceBuilder(test::ParseNameOrDie("android:attr/id"))
85 .SetValue(test::ValueBuilder<Id>().SetSource("test/path/file.xml", 12u).Build())
86 .Build(),
87 test::GetDiagnostics()));
88
89 EXPECT_TRUE(table.AddResource(
90 NewResourceBuilder(test::ParseNameOrDie("android:string/ok"))
91 .SetValue(test::ValueBuilder<Id>().SetSource("test/path/file.xml", 14u).Build())
92 .Build(),
93 test::GetDiagnostics()));
94
95 EXPECT_TRUE(
96 table.AddResource(NewResourceBuilder(test::ParseNameOrDie("android:string/ok"))
97 .SetValue(test::ValueBuilder<BinaryPrimitive>(android::Res_value{})
98 .SetSource("test/path/file.xml", 20u)
99 .Build(),
100 language_config)
101 .Build(),
102 test::GetDiagnostics()));
103
104 EXPECT_THAT(test::GetValue<Id>(&table, "android:attr/layout_width"), NotNull());
105 EXPECT_THAT(test::GetValue<Id>(&table, "android:attr/id"), NotNull());
106 EXPECT_THAT(test::GetValue<Id>(&table, "android:string/ok"), NotNull());
107 EXPECT_THAT(test::GetValueForConfig<BinaryPrimitive>(&table, "android:string/ok", language_config), NotNull());
108 }
109
TEST(ResourceTableTest,OverrideWeakResourceValue)110 TEST(ResourceTableTest, OverrideWeakResourceValue) {
111 ResourceTable table;
112 ASSERT_TRUE(table.AddResource(NewResourceBuilder(test::ParseNameOrDie("android:attr/foo"))
113 .SetValue(test::AttributeBuilder().SetWeak(true).Build())
114 .Build(),
115 test::GetDiagnostics()));
116
117 Attribute* attr = test::GetValue<Attribute>(&table, "android:attr/foo");
118 ASSERT_THAT(attr, NotNull());
119 EXPECT_TRUE(attr->IsWeak());
120
121 ASSERT_TRUE(table.AddResource(NewResourceBuilder(test::ParseNameOrDie("android:attr/foo"))
122 .SetValue(util::make_unique<Attribute>())
123 .Build(),
124 test::GetDiagnostics()));
125
126 attr = test::GetValue<Attribute>(&table, "android:attr/foo");
127 ASSERT_THAT(attr, NotNull());
128 EXPECT_FALSE(attr->IsWeak());
129 }
130
TEST(ResourceTableTest,AllowCompatibleDuplicateAttributes)131 TEST(ResourceTableTest, AllowCompatibleDuplicateAttributes) {
132 ResourceTable table;
133
134 const ResourceName name = test::ParseNameOrDie("android:attr/foo");
135 Attribute attr_one(android::ResTable_map::TYPE_STRING);
136 attr_one.SetWeak(true);
137 Attribute attr_two(android::ResTable_map::TYPE_STRING | android::ResTable_map::TYPE_REFERENCE);
138 attr_two.SetWeak(true);
139
140 ASSERT_TRUE(table.AddResource(
141 NewResourceBuilder(name).SetValue(util::make_unique<Attribute>(attr_one)).Build(),
142 test::GetDiagnostics()));
143 ASSERT_TRUE(table.AddResource(
144 NewResourceBuilder(name).SetValue(util::make_unique<Attribute>(attr_two)).Build(),
145 test::GetDiagnostics()));
146 }
147
TEST(ResourceTableTest,ProductVaryingValues)148 TEST(ResourceTableTest, ProductVaryingValues) {
149 ResourceTable table;
150 ASSERT_TRUE(table.AddResource(
151 NewResourceBuilder(test::ParseNameOrDie("android:string/foo"))
152 .SetValue(util::make_unique<Id>(), test::ParseConfigOrDie("land"), "tablet")
153 .Build(),
154 test::GetDiagnostics()));
155
156 ASSERT_TRUE(table.AddResource(
157 NewResourceBuilder(test::ParseNameOrDie("android:string/foo"))
158 .SetValue(util::make_unique<Id>(), test::ParseConfigOrDie("land"), "phone")
159 .Build(),
160 test::GetDiagnostics()));
161
162 EXPECT_THAT(test::GetValueForConfigAndProduct<Id>(&table, "android:string/foo",test::ParseConfigOrDie("land"), "tablet"), NotNull());
163 EXPECT_THAT(test::GetValueForConfigAndProduct<Id>(&table, "android:string/foo",test::ParseConfigOrDie("land"), "phone"), NotNull());
164
165 Maybe<ResourceTable::SearchResult> sr =
166 table.FindResource(test::ParseNameOrDie("android:string/foo"));
167 ASSERT_TRUE(sr);
168 std::vector<ResourceConfigValue*> values =
169 sr.value().entry->FindAllValues(test::ParseConfigOrDie("land"));
170 ASSERT_EQ(2u, values.size());
171 EXPECT_EQ(std::string("phone"), values[0]->product);
172 EXPECT_EQ(std::string("tablet"), values[1]->product);
173 }
174
LevelToString(Visibility::Level level)175 static StringPiece LevelToString(Visibility::Level level) {
176 switch (level) {
177 case Visibility::Level::kPrivate:
178 return "private";
179 case Visibility::Level::kPublic:
180 return "private";
181 default:
182 return "undefined";
183 }
184 }
185
VisibilityOfResource(const ResourceTable & table,const ResourceNameRef & name,Visibility::Level level,const StringPiece & comment)186 static ::testing::AssertionResult VisibilityOfResource(const ResourceTable& table,
187 const ResourceNameRef& name,
188 Visibility::Level level,
189 const StringPiece& comment) {
190 Maybe<ResourceTable::SearchResult> result = table.FindResource(name);
191 if (!result) {
192 return ::testing::AssertionFailure() << "no resource '" << name << "' found in table";
193 }
194
195 const Visibility& visibility = result.value().entry->visibility;
196 if (visibility.level != level) {
197 return ::testing::AssertionFailure() << "expected visibility " << LevelToString(level)
198 << " but got " << LevelToString(visibility.level);
199 }
200
201 if (visibility.comment != comment) {
202 return ::testing::AssertionFailure() << "expected visibility comment '" << comment
203 << "' but got '" << visibility.comment << "'";
204 }
205 return ::testing::AssertionSuccess();
206 }
207
TEST(ResourceTableTest,SetVisibility)208 TEST(ResourceTableTest, SetVisibility) {
209 using Level = Visibility::Level;
210
211 ResourceTable table;
212 const ResourceName name = test::ParseNameOrDie("android:string/foo");
213
214 Visibility visibility;
215 visibility.level = Visibility::Level::kPrivate;
216 visibility.comment = "private";
217 ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetVisibility(visibility).Build(),
218 test::GetDiagnostics()));
219 ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPrivate, "private"));
220
221 visibility.level = Visibility::Level::kUndefined;
222 visibility.comment = "undefined";
223 ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetVisibility(visibility).Build(),
224 test::GetDiagnostics()));
225 ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPrivate, "private"));
226
227 visibility.level = Visibility::Level::kPublic;
228 visibility.comment = "public";
229 ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetVisibility(visibility).Build(),
230 test::GetDiagnostics()));
231 ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPublic, "public"));
232
233 visibility.level = Visibility::Level::kPrivate;
234 visibility.comment = "private";
235 ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetVisibility(visibility).Build(),
236 test::GetDiagnostics()));
237 ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPublic, "public"));
238 }
239
TEST(ResourceTableTest,SetAllowNew)240 TEST(ResourceTableTest, SetAllowNew) {
241 ResourceTable table;
242 const ResourceName name = test::ParseNameOrDie("android:string/foo");
243
244 AllowNew allow_new;
245 Maybe<ResourceTable::SearchResult> result;
246
247 allow_new.comment = "first";
248 ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetAllowNew(allow_new).Build(),
249 test::GetDiagnostics()));
250 result = table.FindResource(name);
251 ASSERT_TRUE(result);
252 ASSERT_TRUE(result.value().entry->allow_new);
253 ASSERT_THAT(result.value().entry->allow_new.value().comment, StrEq("first"));
254
255 allow_new.comment = "second";
256 ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetAllowNew(allow_new).Build(),
257 test::GetDiagnostics()));
258 result = table.FindResource(name);
259 ASSERT_TRUE(result);
260 ASSERT_TRUE(result.value().entry->allow_new);
261 ASSERT_THAT(result.value().entry->allow_new.value().comment, StrEq("second"));
262 }
263
TEST(ResourceTableTest,SetOverlayable)264 TEST(ResourceTableTest, SetOverlayable) {
265 ResourceTable table;
266 auto overlayable = std::make_shared<Overlayable>("Name", "overlay://theme",
267 Source("res/values/overlayable.xml", 40));
268 OverlayableItem overlayable_item(overlayable);
269 overlayable_item.policies |= PolicyFlags::PRODUCT_PARTITION;
270 overlayable_item.policies |= PolicyFlags::VENDOR_PARTITION;
271 overlayable_item.comment = "comment";
272 overlayable_item.source = Source("res/values/overlayable.xml", 42);
273
274 const ResourceName name = test::ParseNameOrDie("android:string/foo");
275 ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetOverlayable(overlayable_item).Build(),
276 test::GetDiagnostics()));
277 Maybe<ResourceTable::SearchResult> search_result = table.FindResource(name);
278
279 ASSERT_TRUE(search_result);
280 ASSERT_TRUE(search_result.value().entry->overlayable_item);
281
282 OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value();
283 EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name"));
284 EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme"));
285 EXPECT_THAT(result_overlayable_item.overlayable->source.path, Eq("res/values/overlayable.xml"));
286 EXPECT_THAT(result_overlayable_item.overlayable->source.line, 40);
287 EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::PRODUCT_PARTITION
288 | PolicyFlags::VENDOR_PARTITION));
289 ASSERT_THAT(result_overlayable_item.comment, StrEq("comment"));
290 EXPECT_THAT(result_overlayable_item.source.path, Eq("res/values/overlayable.xml"));
291 EXPECT_THAT(result_overlayable_item.source.line, 42);
292 }
293
TEST(ResourceTableTest,SetMultipleOverlayableResources)294 TEST(ResourceTableTest, SetMultipleOverlayableResources) {
295 ResourceTable table;
296
297 const ResourceName foo = test::ParseNameOrDie("android:string/foo");
298 auto group = std::make_shared<Overlayable>("Name", "overlay://theme");
299 OverlayableItem overlayable(group);
300 overlayable.policies = PolicyFlags::PRODUCT_PARTITION;
301 ASSERT_TRUE(table.AddResource(NewResourceBuilder(foo).SetOverlayable(overlayable).Build(),
302 test::GetDiagnostics()));
303
304 const ResourceName bar = test::ParseNameOrDie("android:string/bar");
305 OverlayableItem overlayable2(group);
306 overlayable2.policies = PolicyFlags::PRODUCT_PARTITION;
307 ASSERT_TRUE(table.AddResource(NewResourceBuilder(bar).SetOverlayable(overlayable2).Build(),
308 test::GetDiagnostics()));
309
310 const ResourceName baz = test::ParseNameOrDie("android:string/baz");
311 OverlayableItem overlayable3(group);
312 overlayable3.policies = PolicyFlags::VENDOR_PARTITION;
313 ASSERT_TRUE(table.AddResource(NewResourceBuilder(baz).SetOverlayable(overlayable3).Build(),
314 test::GetDiagnostics()));
315 }
316
TEST(ResourceTableTest,SetOverlayableDifferentResourcesDifferentName)317 TEST(ResourceTableTest, SetOverlayableDifferentResourcesDifferentName) {
318 ResourceTable table;
319
320 const ResourceName foo = test::ParseNameOrDie("android:string/foo");
321 OverlayableItem overlayable_item(std::make_shared<Overlayable>("Name", "overlay://theme"));
322 overlayable_item.policies = PolicyFlags::PRODUCT_PARTITION;
323 ASSERT_TRUE(table.AddResource(NewResourceBuilder(foo).SetOverlayable(overlayable_item).Build(),
324 test::GetDiagnostics()));
325
326 const ResourceName bar = test::ParseNameOrDie("android:string/bar");
327 OverlayableItem overlayable_item2(std::make_shared<Overlayable>("Name2", "overlay://theme"));
328 overlayable_item2.policies = PolicyFlags::PRODUCT_PARTITION;
329 ASSERT_TRUE(table.AddResource(NewResourceBuilder(bar).SetOverlayable(overlayable_item2).Build(),
330 test::GetDiagnostics()));
331 }
332
TEST(ResourceTableTest,SetOverlayableSameResourcesFail)333 TEST(ResourceTableTest, SetOverlayableSameResourcesFail) {
334 ResourceTable table;
335 const ResourceName name = test::ParseNameOrDie("android:string/foo");
336
337 auto overlayable = std::make_shared<Overlayable>("Name", "overlay://theme");
338 OverlayableItem overlayable_item(overlayable);
339 ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetOverlayable(overlayable_item).Build(),
340 test::GetDiagnostics()));
341
342 OverlayableItem overlayable_item2(overlayable);
343 ASSERT_FALSE(table.AddResource(NewResourceBuilder(name).SetOverlayable(overlayable_item2).Build(),
344 test::GetDiagnostics()));
345 }
346
TEST(ResourceTableTest,SetOverlayableSameResourcesDifferentNameFail)347 TEST(ResourceTableTest, SetOverlayableSameResourcesDifferentNameFail) {
348 ResourceTable table;
349 const ResourceName name = test::ParseNameOrDie("android:string/foo");
350
351 auto overlayable = std::make_shared<Overlayable>("Name", "overlay://theme");
352 OverlayableItem overlayable_item(overlayable);
353 ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetOverlayable(overlayable_item).Build(),
354 test::GetDiagnostics()));
355
356 auto overlayable2 = std::make_shared<Overlayable>("Other", "overlay://theme");
357 OverlayableItem overlayable_item2(overlayable2);
358 ASSERT_FALSE(table.AddResource(NewResourceBuilder(name).SetOverlayable(overlayable_item2).Build(),
359 test::GetDiagnostics()));
360 }
361
TEST(ResourceTableTest,ConflictingIds)362 TEST(ResourceTableTest, ConflictingIds) {
363 ResourceTable table;
364 const ResourceName name = test::ParseNameOrDie("android:string/foo");
365 ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetId(0x01010000).Build(),
366 test::GetDiagnostics()));
367 ASSERT_FALSE(table.AddResource(NewResourceBuilder(name).SetId(0x01010001).Build(),
368 test::GetDiagnostics()));
369 }
370
TEST(ResourceTableTest,ConflictingIdsCreateEntry)371 TEST(ResourceTableTest, ConflictingIdsCreateEntry) {
372 ResourceTable table;
373 const ResourceName name = test::ParseNameOrDie("android:string/foo");
374 ASSERT_TRUE(table.AddResource(
375 NewResourceBuilder(name).SetId(0x01010000, OnIdConflict::CREATE_ENTRY).Build(),
376 test::GetDiagnostics()));
377 ASSERT_TRUE(table.AddResource(
378 NewResourceBuilder(name).SetId(0x01010001, OnIdConflict::CREATE_ENTRY).Build(),
379 test::GetDiagnostics()));
380
381 // Non-ambiguous query
382 ASSERT_TRUE(table.AddResource(
383 NewResourceBuilder(name).SetId(0x01010001).SetValue(std::make_unique<Id>()).Build(),
384 test::GetDiagnostics()));
385 }
386
387 } // namespace aapt
388