• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, &regions));
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, &regions));
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