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 "compile/IdAssigner.h"
18 
19 #include "test/Test.h"
20 
21 namespace aapt {
22 
23 struct IdAssignerTests : public ::testing::Test {
SetUpaapt::IdAssignerTests24   void SetUp() override {
25     context = test::ContextBuilder().SetCompilationPackage("android").SetPackageId(0x01).Build();
26   }
27   std::unique_ptr<IAaptContext> context;
28 };
29 
30 ::testing::AssertionResult VerifyIds(ResourceTable* table);
31 
TEST_F(IdAssignerTests,AssignIds)32 TEST_F(IdAssignerTests, AssignIds) {
33   auto table = test::ResourceTableBuilder()
34                    .AddSimple("android:attr/foo")
35                    .AddSimple("android:attr/bar")
36                    .AddSimple("android:id/foo")
37                    .Build();
38   IdAssigner assigner;
39 
40   ASSERT_TRUE(assigner.Consume(context.get(), table.get()));
41   ASSERT_TRUE(VerifyIds(table.get()));
42 }
43 
TEST_F(IdAssignerTests,AssignIdsWithReservedIds)44 TEST_F(IdAssignerTests, AssignIdsWithReservedIds) {
45   auto table = test::ResourceTableBuilder()
46                    .AddSimple("android:id/foo", ResourceId(0x01010000))
47                    .AddSimple("android:dimen/two")
48                    .AddSimple("android:integer/three")
49                    .AddSimple("android:string/five")
50                    .AddSimple("android:attr/fun", ResourceId(0x01040000))
51                    .AddSimple("android:attr/foo", ResourceId(0x01040006))
52                    .AddSimple("android:attr/bar")
53                    .AddSimple("android:attr/baz")
54                    .Build();
55 
56   IdAssigner assigner;
57   ASSERT_TRUE(assigner.Consume(context.get(), table.get()));
58   ASSERT_TRUE(VerifyIds(table.get()));
59 
60   std::optional<ResourceTable::SearchResult> maybe_result;
61 
62   // Expect to fill in the gaps between 0x0101XXXX and 0x0104XXXX.
63 
64   maybe_result = table->FindResource(test::ParseNameOrDie("android:dimen/two"));
65   ASSERT_TRUE(maybe_result);
66   EXPECT_EQ(0x01020000, maybe_result.value().entry->id);
67 
68   maybe_result =
69       table->FindResource(test::ParseNameOrDie("android:integer/three"));
70   ASSERT_TRUE(maybe_result);
71   EXPECT_EQ(0x01030000, maybe_result.value().entry->id);
72 
73   // Expect to bypass the reserved 0x0104XXXX IDs and use the next 0x0105XXXX
74   // IDs.
75 
76   maybe_result =
77       table->FindResource(test::ParseNameOrDie("android:string/five"));
78   ASSERT_TRUE(maybe_result);
79   EXPECT_EQ(0x01050000, maybe_result.value().entry->id);
80 
81   // Expect to fill in the gaps between 0x01040000 and 0x01040006.
82 
83   maybe_result = table->FindResource(test::ParseNameOrDie("android:attr/bar"));
84   ASSERT_TRUE(maybe_result);
85   EXPECT_EQ(0x01040001, maybe_result.value().entry->id);
86 
87   maybe_result = table->FindResource(test::ParseNameOrDie("android:attr/baz"));
88   ASSERT_TRUE(maybe_result);
89   EXPECT_EQ(0x01040002, maybe_result.value().entry->id);
90 }
91 
TEST_F(IdAssignerTests,FailWhenNonUniqueIdsAssigned)92 TEST_F(IdAssignerTests, FailWhenNonUniqueIdsAssigned) {
93   auto table = test::ResourceTableBuilder()
94                    .AddSimple("android:attr/foo", ResourceId(0x01040006))
95                    .AddSimple("android:attr/bar", ResourceId(0x01040006))
96                    .Build();
97   IdAssigner assigner;
98   ASSERT_FALSE(assigner.Consume(context.get(), table.get()));
99 }
100 
TEST_F(IdAssignerTests,FailWhenNonUniqueTypeIdsAssigned)101 TEST_F(IdAssignerTests, FailWhenNonUniqueTypeIdsAssigned) {
102   auto table = test::ResourceTableBuilder()
103                    .AddSimple("android:string/foo", ResourceId(0x01040000))
104                    .AddSimple("android:attr/bar", ResourceId(0x01040006))
105                    .Build();
106   IdAssigner assigner;
107   ASSERT_FALSE(assigner.Consume(context.get(), table.get()));
108 }
109 
TEST_F(IdAssignerTests,FailWhenTypeHasTwoNonStagedIds)110 TEST_F(IdAssignerTests, FailWhenTypeHasTwoNonStagedIds) {
111   auto table = test::ResourceTableBuilder()
112                    .AddSimple("android:attr/foo", ResourceId(0x01050000))
113                    .AddSimple("android:attr/bar", ResourceId(0x01040006))
114                    .Build();
115   IdAssigner assigner;
116   ASSERT_FALSE(assigner.Consume(context.get(), table.get()));
117 }
118 
TEST_F(IdAssignerTests,FailWhenTypeHasTwoNonStagedIdsRegardlessOfStagedId)119 TEST_F(IdAssignerTests, FailWhenTypeHasTwoNonStagedIdsRegardlessOfStagedId) {
120   auto table =
121       test::ResourceTableBuilder()
122           .AddSimple("android:attr/foo", ResourceId(0x01050000))
123           .AddSimple("android:attr/bar", ResourceId(0x01ff0006))
124           .Add(NewResourceBuilder("android:attr/staged_baz")
125                    .SetId(0x01ff0000)
126                    .SetVisibility({.staged_api = true, .level = Visibility::Level::kPublic})
127                    .Build())
128           .Build();
129   IdAssigner assigner;
130   ASSERT_FALSE(assigner.Consume(context.get(), table.get()));
131 }
132 
TEST_F(IdAssignerTests,FailWhenTypeHaveBothStagedAndNonStagedIds)133 TEST_F(IdAssignerTests, FailWhenTypeHaveBothStagedAndNonStagedIds) {
134   auto table =
135       test::ResourceTableBuilder()
136           .AddSimple("android:attr/foo", ResourceId(0x01010000))
137           .Add(NewResourceBuilder("android:bool/staged_baz")
138                    .SetId(0x01010001)
139                    .SetVisibility({.staged_api = true, .level = Visibility::Level::kPublic})
140                    .Build())
141           .Build();
142   IdAssigner assigner;
143   ASSERT_FALSE(assigner.Consume(context.get(), table.get()));
144 }
145 
TEST_F(IdAssignerTests,AssignIdsWithIdMap)146 TEST_F(IdAssignerTests, AssignIdsWithIdMap) {
147   auto table = test::ResourceTableBuilder()
148                    .AddSimple("android:attr/foo")
149                    .AddSimple("android:attr/bar")
150                    .Build();
151   std::unordered_map<ResourceName, ResourceId> id_map = {
152       {test::ParseNameOrDie("android:attr/foo"), ResourceId(0x01010002)}};
153   IdAssigner assigner(&id_map);
154   ASSERT_TRUE(assigner.Consume(context.get(), table.get()));
155   ASSERT_TRUE(VerifyIds(table.get()));
156   auto result = table->FindResource(test::ParseNameOrDie("android:attr/foo"));
157   ASSERT_TRUE(result);
158 
159   const ResourceTable::SearchResult& search_result = result.value();
160   EXPECT_EQ(0x01010002, search_result.entry->id);
161 }
162 
TEST_F(IdAssignerTests,UseAllEntryIds)163 TEST_F(IdAssignerTests, UseAllEntryIds) {
164   ResourceTable table;
165   const size_t max_entry_id = std::numeric_limits<uint16_t>::max();
166   for (size_t i = 0; i <= max_entry_id; i++) {
167     ASSERT_TRUE(
168         table.AddResource(NewResourceBuilder("android:attr/res" + std::to_string(i)).Build(),
169                           context->GetDiagnostics()));
170   }
171   IdAssigner assigner;
172   ASSERT_TRUE(assigner.Consume(context.get(), &table));
173 }
174 
TEST_F(IdAssignerTests,ExaustEntryIds)175 TEST_F(IdAssignerTests, ExaustEntryIds) {
176   ResourceTable table;
177   const size_t max_entry_id = std::numeric_limits<uint16_t>::max() + 1u;
178   for (size_t i = 0; i <= max_entry_id; i++) {
179     ASSERT_TRUE(
180         table.AddResource(NewResourceBuilder("android:attr/res" + std::to_string(i)).Build(),
181                           context->GetDiagnostics()));
182   }
183   IdAssigner assigner;
184   ASSERT_FALSE(assigner.Consume(context.get(), &table));
185 }
186 
TEST_F(IdAssignerTests,ExaustEntryIdsLastIdIsPublic)187 TEST_F(IdAssignerTests, ExaustEntryIdsLastIdIsPublic) {
188   ResourceTable table;
189   ASSERT_TRUE(table.AddResource(NewResourceBuilder("android:attr/res").SetId(0x0101ffff).Build(),
190                                 context->GetDiagnostics()));
191   const size_t max_entry_id = std::numeric_limits<uint16_t>::max();
192   for (size_t i = 0; i <= max_entry_id; i++) {
193     ASSERT_TRUE(
194         table.AddResource(NewResourceBuilder("android:attr/res" + std::to_string(i)).Build(),
195                           context->GetDiagnostics()));
196   }
197   IdAssigner assigner;
198   ASSERT_FALSE(assigner.Consume(context.get(), &table));
199 }
200 
VerifyIds(ResourceTable * table)201 ::testing::AssertionResult VerifyIds(ResourceTable* table) {
202   std::set<ResourceId> seen_ids;
203   for (auto& package : table->packages) {
204     for (auto& type : package->types) {
205       for (auto& entry : type->entries) {
206         if (!entry->id) {
207           return ::testing::AssertionFailure()
208                  << "resource " << ResourceNameRef(package->name, type->named_type, entry->name)
209                  << " has no ID";
210         }
211         if (!seen_ids.insert(entry->id.value()).second) {
212           return ::testing::AssertionFailure()
213                  << "resource " << ResourceNameRef(package->name, type->named_type, entry->name)
214                  << " has a non-unique ID" << std::hex << entry->id.value() << std::dec;
215         }
216       }
217     }
218   }
219 
220   return ::testing::AssertionSuccess() << "all IDs are unique and assigned";
221 }
222 
223 }  // namespace aapt
224