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