xref: /aosp_15_r20/external/cronet/base/i18n/icu_mergeable_data_file_unittest.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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, &regions));
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, &regions));
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