1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/i18n/icu_mergeable_data_file.h"
6
7 #include "base/debug/proc_maps_linux.h"
8 #include "base/files/scoped_temp_dir.h"
9 #include "base/i18n/icu_util.h"
10 #include "build/build_config.h"
11 #include "testing/gtest/include/gtest/gtest.h"
12
13 #if !BUILDFLAG(IS_NACL)
14 #if ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE
15
16 namespace base::i18n {
17
18 class IcuMergeableDataFileTest : public testing::Test {
19 protected:
SetUp()20 void SetUp() override { ResetGlobalsForTesting(); }
21 };
22
TEST_F(IcuMergeableDataFileTest,IcuDataFileMergesCommonPages)23 TEST_F(IcuMergeableDataFileTest, IcuDataFileMergesCommonPages) {
24 // Create two temporary files mocking Ash and Lacros's versions of ICU.
25 base::ScopedTempDir temp_dir;
26 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
27
28 FilePath ash_path = temp_dir.GetPath().AppendASCII("ash_icudtl.dat");
29 FilePath lacros_path = temp_dir.GetPath().AppendASCII("lacros_icudtl.dat");
30
31 uint32_t flags =
32 base::File::FLAG_CREATE | base::File::FLAG_READ | base::File::FLAG_WRITE;
33
34 File ash_file(ash_path, flags);
35 File lacros_file(lacros_path, flags);
36 ASSERT_TRUE(ash_file.IsValid());
37 ASSERT_TRUE(lacros_file.IsValid());
38
39 // Prepare some data to use for filling in the mock files.
40 std::vector<char> pg0(0x1000, 0x00);
41 std::vector<char> pg1(0x1000, 0x11);
42 std::vector<char> pg2(0x1000, 0x22);
43 std::vector<char> pg3(0x1000, 0x33);
44 std::vector<char> pg4(0x0333, 0x44);
45 int pg0_sz = pg0.size(), pg1_sz = pg1.size(), pg2_sz = pg2.size(),
46 pg3_sz = pg3.size(), pg4_sz = pg4.size();
47
48 // Build Ash's file structure:
49 // 0x0000 .. 0x1000 => { 0x00, ... } | Shared
50 // 0x1000 .. 0x2000 => { 0x11, ... } | Shared
51 // 0x2000 .. 0x3000 => { 0x22, ... }
52 // 0x3000 .. 0x3333 => { 0x44, ... } | Shared
53 ASSERT_EQ(pg0_sz, ash_file.WriteAtCurrentPos(pg0.data(), pg0_sz));
54 ASSERT_EQ(pg1_sz, ash_file.WriteAtCurrentPos(pg1.data(), pg1_sz));
55 ASSERT_EQ(pg2_sz, ash_file.WriteAtCurrentPos(pg2.data(), pg2_sz));
56 ASSERT_EQ(pg4_sz, ash_file.WriteAtCurrentPos(pg4.data(), pg4_sz));
57 ASSERT_TRUE(ash_file.Flush());
58 ash_file.Close();
59
60 // Build Lacros's file structure:
61 // 0x0000 .. 0x1000 => { 0x00, ... } | Shared
62 // 0x1000 .. 0x2000 => { 0x11, ... } | Shared
63 // 0x2000 .. 0x3000 => { 0x33, ... }
64 // 0x3000 .. 0x3333 => { 0x44, ... } | Shared
65 ASSERT_EQ(pg0_sz, lacros_file.WriteAtCurrentPos(pg0.data(), pg0_sz));
66 ASSERT_EQ(pg1_sz, lacros_file.WriteAtCurrentPos(pg1.data(), pg1_sz));
67 ASSERT_EQ(pg3_sz, lacros_file.WriteAtCurrentPos(pg3.data(), pg3_sz));
68 ASSERT_EQ(pg4_sz, lacros_file.WriteAtCurrentPos(pg4.data(), pg4_sz));
69 ASSERT_TRUE(lacros_file.Flush());
70
71 // Load Lacros's file and try to merge against Ash's.
72 IcuDataFile icu_data_file;
73 ASSERT_TRUE(icu_data_file.Initialize(std::move(lacros_file),
74 MemoryMappedFile::Region::kWholeFile));
75 // NOTE: we need to manually call MergeWithAshVersion with a custom path,
76 // because this test will be run in a linux-lacros-rel environment where
77 // there's no Ash installed in the default ChromeOS directory.
78 ASSERT_TRUE(icu_data_file.MergeWithAshVersion(ash_path));
79
80 // Check that Lacros's file content is correct.
81 EXPECT_EQ(0, memcmp(icu_data_file.data() + 0x0000, pg0.data(), pg0_sz));
82 EXPECT_EQ(0, memcmp(icu_data_file.data() + 0x1000, pg1.data(), pg1_sz));
83 EXPECT_EQ(0, memcmp(icu_data_file.data() + 0x2000, pg3.data(), pg3_sz));
84 EXPECT_EQ(0, memcmp(icu_data_file.data() + 0x3000, pg4.data(), pg4_sz));
85
86 // Parse the kernel's memory map structures to check if the merge happened.
87 std::string proc_maps;
88 std::vector<debug::MappedMemoryRegion> regions;
89 ASSERT_TRUE(debug::ReadProcMaps(&proc_maps));
90 ASSERT_TRUE(ParseProcMaps(proc_maps, ®ions));
91
92 uintptr_t lacros_start = reinterpret_cast<uintptr_t>(icu_data_file.data());
93 bool region1_ok = false, region2_ok = false, region3_ok = false;
94
95 for (const auto& region : regions) {
96 if (region.start == lacros_start) {
97 // 0x0000 .. 0x2000 => Ash (merged)
98 EXPECT_EQ(lacros_start + 0x2000, region.end);
99 EXPECT_EQ(ash_path.value(), region.path);
100 region1_ok = true;
101 } else if (region.start == lacros_start + 0x2000) {
102 // 0x2000 .. 0x3000 => Lacros (not merged)
103 EXPECT_EQ(lacros_start + 0x3000, region.end);
104 EXPECT_EQ(lacros_path.value(), region.path);
105 region2_ok = true;
106 } else if (region.start == lacros_start + 0x3000) {
107 // 0x3000 .. 0x3333 => Ash (merged)
108 EXPECT_EQ(lacros_start + 0x4000, region.end); // Page-aligned address.
109 EXPECT_EQ(ash_path.value(), region.path);
110 region3_ok = true;
111 }
112 }
113 EXPECT_TRUE(region1_ok && region2_ok && region3_ok);
114 EXPECT_FALSE(icu_data_file.used_cached_hashes());
115 }
116
TEST_F(IcuMergeableDataFileTest,IcuDataFileMergesCommonPagesWithCachedHashes)117 TEST_F(IcuMergeableDataFileTest, IcuDataFileMergesCommonPagesWithCachedHashes) {
118 // Create two temporary files mocking Ash and Lacros's versions of ICU.
119 base::ScopedTempDir temp_dir;
120 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
121
122 FilePath ash_path = temp_dir.GetPath().AppendASCII("ash_icudtl.dat");
123 FilePath lacros_path = temp_dir.GetPath().AppendASCII("lacros_icudtl.dat");
124
125 // Create the hash files as well.
126 FilePath ash_hash_path = ash_path.AddExtensionASCII(
127 IcuMergeableDataFile::kIcuDataFileHashExtension);
128 FilePath lacros_hash_path = lacros_path.AddExtensionASCII(
129 IcuMergeableDataFile::kIcuDataFileHashExtension);
130
131 uint32_t flags =
132 base::File::FLAG_CREATE | base::File::FLAG_READ | base::File::FLAG_WRITE;
133
134 File ash_file(ash_path, flags);
135 File ash_hash_file(ash_hash_path, flags);
136 File lacros_file(lacros_path, flags);
137 File lacros_hash_file(lacros_hash_path, flags);
138 ASSERT_TRUE(ash_file.IsValid());
139 ASSERT_TRUE(ash_hash_file.IsValid());
140 ASSERT_TRUE(lacros_file.IsValid());
141 ASSERT_TRUE(lacros_hash_file.IsValid());
142
143 // Prepare some data to use for filling in the mock files.
144 std::vector<char> pg0(0x1000, 0x00);
145 std::vector<char> pg1(0x1000, 0x11);
146 std::vector<char> pg2(0x1000, 0x22);
147 std::vector<char> pg3(0x1000, 0x33);
148 std::vector<char> pg4(0x0333, 0x44);
149 int pg0_sz = pg0.size(), pg1_sz = pg1.size(), pg2_sz = pg2.size(),
150 pg3_sz = pg3.size(), pg4_sz = pg4.size();
151
152 // Build Ash's file structure:
153 // 0x0000 .. 0x1000 => { 0x00, ... } | Shared
154 // 0x1000 .. 0x2000 => { 0x11, ... } | Shared
155 // 0x2000 .. 0x3000 => { 0x22, ... }
156 // 0x3000 .. 0x3333 => { 0x44, ... } | Shared
157 ASSERT_EQ(pg0_sz, ash_file.WriteAtCurrentPos(pg0.data(), pg0_sz));
158 ASSERT_EQ(pg1_sz, ash_file.WriteAtCurrentPos(pg1.data(), pg1_sz));
159 ASSERT_EQ(pg2_sz, ash_file.WriteAtCurrentPos(pg2.data(), pg2_sz));
160 ASSERT_EQ(pg4_sz, ash_file.WriteAtCurrentPos(pg4.data(), pg4_sz));
161 ASSERT_TRUE(ash_file.Flush());
162 ash_file.Close();
163 // Build Ash's hash file structure. Actual hashes don't matter.
164 ASSERT_EQ(8, ash_hash_file.WriteAtCurrentPos(
165 "\x00\x00\x00\x00\x00\x00\x00\x00", 8));
166 ASSERT_EQ(8, ash_hash_file.WriteAtCurrentPos(
167 "\x11\x11\x11\x11\x11\x11\x11\x11", 8));
168 ASSERT_EQ(8, ash_hash_file.WriteAtCurrentPos(
169 "\x22\x22\x22\x22\x22\x22\x22\x22", 8));
170 ASSERT_EQ(8, ash_hash_file.WriteAtCurrentPos(
171 "\x44\x44\x44\x44\x44\x44\x44\x44", 8));
172 ASSERT_TRUE(ash_hash_file.Flush());
173 ash_hash_file.Close();
174
175 // Build Lacros's file structure:
176 // 0x0000 .. 0x1000 => { 0x00, ... } | Shared
177 // 0x1000 .. 0x2000 => { 0x11, ... } | Shared
178 // 0x2000 .. 0x3000 => { 0x33, ... }
179 // 0x3000 .. 0x3333 => { 0x44, ... } | Shared
180 ASSERT_EQ(pg0_sz, lacros_file.WriteAtCurrentPos(pg0.data(), pg0_sz));
181 ASSERT_EQ(pg1_sz, lacros_file.WriteAtCurrentPos(pg1.data(), pg1_sz));
182 ASSERT_EQ(pg3_sz, lacros_file.WriteAtCurrentPos(pg3.data(), pg3_sz));
183 ASSERT_EQ(pg4_sz, lacros_file.WriteAtCurrentPos(pg4.data(), pg4_sz));
184 ASSERT_TRUE(lacros_file.Flush());
185 // Build Lacros's hash file structure. Actual hashes don't matter.
186 ASSERT_EQ(8, lacros_hash_file.WriteAtCurrentPos(
187 "\x00\x00\x00\x00\x00\x00\x00\x00", 8));
188 ASSERT_EQ(8, lacros_hash_file.WriteAtCurrentPos(
189 "\x11\x11\x11\x11\x11\x11\x11\x11", 8));
190 // NOTE: Simulate hash collision.
191 ASSERT_EQ(8, lacros_hash_file.WriteAtCurrentPos(
192 "\x22\x22\x22\x22\x22\x22\x22\x22", 8));
193 ASSERT_EQ(8, lacros_hash_file.WriteAtCurrentPos(
194 "\x44\x44\x44\x44\x44\x44\x44\x44", 8));
195 ASSERT_TRUE(lacros_hash_file.Flush());
196 lacros_hash_file.Close();
197
198 // Load Lacros's file and try to merge against Ash's.
199 IcuDataFile icu_data_file;
200 ASSERT_TRUE(icu_data_file.Initialize(std::move(lacros_file),
201 MemoryMappedFile::Region::kWholeFile));
202 ASSERT_TRUE(icu_data_file.MergeWithAshVersion(ash_path));
203
204 // Check that Lacros's file content is correct.
205 EXPECT_EQ(0, memcmp(icu_data_file.data() + 0x0000, pg0.data(), pg0_sz));
206 EXPECT_EQ(0, memcmp(icu_data_file.data() + 0x1000, pg1.data(), pg1_sz));
207 EXPECT_EQ(0, memcmp(icu_data_file.data() + 0x2000, pg3.data(), pg3_sz));
208 EXPECT_EQ(0, memcmp(icu_data_file.data() + 0x3000, pg4.data(), pg4_sz));
209
210 // Parse the kernel's memory map structures to check if the merge happened.
211 std::string proc_maps;
212 std::vector<debug::MappedMemoryRegion> regions;
213 ASSERT_TRUE(debug::ReadProcMaps(&proc_maps));
214 ASSERT_TRUE(ParseProcMaps(proc_maps, ®ions));
215
216 uintptr_t lacros_start = reinterpret_cast<uintptr_t>(icu_data_file.data());
217 bool region1_ok = false, region2_ok = false, region3_ok = false;
218
219 for (const auto& region : regions) {
220 if (region.start == lacros_start) {
221 // 0x0000 .. 0x2000 => Ash (merged)
222 EXPECT_EQ(lacros_start + 0x2000, region.end);
223 EXPECT_EQ(ash_path.value(), region.path);
224 region1_ok = true;
225 } else if (region.start == lacros_start + 0x2000) {
226 // 0x2000 .. 0x3000 => Lacros (not merged)
227 EXPECT_EQ(lacros_start + 0x3000, region.end);
228 EXPECT_EQ(lacros_path.value(), region.path);
229 region2_ok = true;
230 } else if (region.start == lacros_start + 0x3000) {
231 // 0x3000 .. 0x3333 => Ash (merged)
232 EXPECT_EQ(lacros_start + 0x4000, region.end); // Page-aligned address.
233 EXPECT_EQ(ash_path.value(), region.path);
234 region3_ok = true;
235 }
236 }
237 EXPECT_TRUE(region1_ok && region2_ok && region3_ok);
238 EXPECT_TRUE(icu_data_file.used_cached_hashes());
239 }
240
241 } // namespace base::i18n
242
243 #endif // ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE
244 #endif // !BUILDFLAG(IS_NACL)
245