1 /*
2 * Copyright (C) 2018 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 "optimize/ResourcePathShortener.h"
18
19 #include "ResourceTable.h"
20 #include "test/Test.h"
21
22 using ::aapt::test::GetValue;
23 using ::testing::Not;
24 using ::testing::NotNull;
25 using ::testing::Eq;
26
GetExtension(android::StringPiece path)27 android::StringPiece GetExtension(android::StringPiece path) {
28 auto iter = std::find(path.begin(), path.end(), '.');
29 return android::StringPiece(iter, path.end() - iter);
30 }
31
FillTable(aapt::test::ResourceTableBuilder & builder,int start,int end)32 void FillTable(aapt::test::ResourceTableBuilder& builder, int start, int end) {
33 for (int i=start; i<end; i++) {
34 builder.AddFileReference(
35 "android:drawable/xmlfile" + std::to_string(i),
36 "res/drawable/xmlfile" + std::to_string(i) + ".xml");
37 }
38 }
39
40 namespace aapt {
41
TEST(ResourcePathShortenerTest,FileRefPathsChangedInResourceTable)42 TEST(ResourcePathShortenerTest, FileRefPathsChangedInResourceTable) {
43 std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
44
45 std::unique_ptr<ResourceTable> table =
46 test::ResourceTableBuilder()
47 .AddFileReference("android:drawable/xmlfile", "res/drawables/xmlfile.xml")
48 .AddFileReference("android:drawable/xmlfile2", "res/drawables/xmlfile2.xml")
49 .AddString("android:string/string", "res/should/still/be/the/same.png")
50 .Build();
51
52 std::map<std::string, std::string> path_map;
53 ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get()));
54
55 // Expect that the path map is populated
56 ASSERT_THAT(path_map.find("res/drawables/xmlfile.xml"), Not(Eq(path_map.end())));
57 ASSERT_THAT(path_map.find("res/drawables/xmlfile2.xml"), Not(Eq(path_map.end())));
58
59 // The file paths were changed
60 EXPECT_THAT(path_map.at("res/drawables/xmlfile.xml"), Not(Eq("res/drawables/xmlfile.xml")));
61 EXPECT_THAT(path_map.at("res/drawables/xmlfile2.xml"), Not(Eq("res/drawables/xmlfile2.xml")));
62
63 // Different file paths should remain different
64 EXPECT_THAT(path_map["res/drawables/xmlfile.xml"],
65 Not(Eq(path_map["res/drawables/xmlfile2.xml"])));
66
67 FileReference* ref =
68 GetValue<FileReference>(table.get(), "android:drawable/xmlfile");
69 ASSERT_THAT(ref, NotNull());
70 // The map correctly points to the new location of the file
71 EXPECT_THAT(path_map["res/drawables/xmlfile.xml"], Eq(*ref->path));
72
73 // Strings should not be affected, only file paths
74 EXPECT_THAT(
75 *GetValue<String>(table.get(), "android:string/string")->value,
76 Eq("res/should/still/be/the/same.png"));
77 EXPECT_THAT(path_map.find("res/should/still/be/the/same.png"), Eq(path_map.end()));
78 }
79
TEST(ResourcePathShortenerTest,SkipColorFileRefPaths)80 TEST(ResourcePathShortenerTest, SkipColorFileRefPaths) {
81 std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
82
83 std::unique_ptr<ResourceTable> table =
84 test::ResourceTableBuilder()
85 .AddFileReference("android:color/colorlist", "res/color/colorlist.xml")
86 .AddFileReference("android:color/colorlist",
87 "res/color-mdp-v21/colorlist.xml",
88 test::ParseConfigOrDie("mdp-v21"))
89 .Build();
90
91 std::map<std::string, std::string> path_map;
92 ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get()));
93
94 // Expect that the path map to not contain the ColorStateList
95 ASSERT_THAT(path_map.find("res/color/colorlist.xml"), Eq(path_map.end()));
96 ASSERT_THAT(path_map.find("res/color-mdp-v21/colorlist.xml"), Eq(path_map.end()));
97 }
98
TEST(ResourcePathShortenerTest,KeepExtensions)99 TEST(ResourcePathShortenerTest, KeepExtensions) {
100 std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
101
102 std::string original_xml_path = "res/drawable/xmlfile.xml";
103 std::string original_png_path = "res/drawable/pngfile.png";
104
105 std::unique_ptr<ResourceTable> table =
106 test::ResourceTableBuilder()
107 .AddFileReference("android:color/xmlfile", original_xml_path)
108 .AddFileReference("android:color/pngfile", original_png_path)
109 .Build();
110
111 std::map<std::string, std::string> path_map;
112 ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get()));
113
114 // Expect that the path map is populated
115 ASSERT_THAT(path_map.find("res/drawable/xmlfile.xml"), Not(Eq(path_map.end())));
116 ASSERT_THAT(path_map.find("res/drawable/pngfile.png"), Not(Eq(path_map.end())));
117
118 auto shortend_xml_path = path_map[original_xml_path];
119 auto shortend_png_path = path_map[original_png_path];
120
121 EXPECT_THAT(GetExtension(path_map[original_xml_path]), Eq(android::StringPiece(".xml")));
122 EXPECT_THAT(GetExtension(path_map[original_png_path]), Eq(android::StringPiece(".png")));
123 }
124
TEST(ResourcePathShortenerTest,DeterministicallyHandleCollisions)125 TEST(ResourcePathShortenerTest, DeterministicallyHandleCollisions) {
126 std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
127
128 // 4000 resources is the limit at which the hash space is expanded to 3
129 // letters to reduce collisions, we want as many collisions as possible thus
130 // N-1.
131 const auto kNumResources = 3999;
132 const auto kNumTries = 5;
133
134 test::ResourceTableBuilder builder1;
135 FillTable(builder1, 0, kNumResources);
136 std::unique_ptr<ResourceTable> table1 = builder1.Build();
137 std::map<std::string, std::string> expected_mapping;
138 ASSERT_TRUE(ResourcePathShortener(expected_mapping).Consume(context.get(), table1.get()));
139
140 // We are trying to ensure lack of non-determinism, it is not simple to prove
141 // a negative, thus we must try the test a few times so that the test itself
142 // is non-flaky. Basically create the pathmap 5 times from the same set of
143 // resources but a different order of addition and then ensure they are always
144 // mapped to the same short path.
145 for (int i=0; i<kNumTries; i++) {
146 test::ResourceTableBuilder builder2;
147 // This loop adds resources to the resource table in the range of
148 // [0:kNumResources). Adding the file references in different order makes
149 // non-determinism more likely to surface. Thus we add resources
150 // [start_index:kNumResources) first then [0:start_index). We also use a
151 // different start_index each run.
152 int start_index = (kNumResources/kNumTries)*i;
153 FillTable(builder2, start_index, kNumResources);
154 FillTable(builder2, 0, start_index);
155 std::unique_ptr<ResourceTable> table2 = builder2.Build();
156
157 std::map<std::string, std::string> actual_mapping;
158 ASSERT_TRUE(ResourcePathShortener(actual_mapping).Consume(context.get(), table2.get()));
159
160 for (auto& item : actual_mapping) {
161 ASSERT_THAT(expected_mapping[item.first], Eq(item.second));
162 }
163 }
164 }
165
166 } // namespace aapt
167