1 // Copyright 2017 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 <memory>
6 #include <string>
7 #include <string_view>
8
9 #include "base/files/file.h"
10 #include "base/files/file_path.h"
11 #include "base/files/file_util.h"
12 #include "base/memory/raw_ptr.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_util.h"
15 #include "base/test/metrics/histogram_tester.h"
16 #include "net/base/cache_type.h"
17 #include "net/disk_cache/disk_cache.h"
18 #include "net/disk_cache/disk_cache_test_base.h"
19 #include "net/disk_cache/simple/simple_file_tracker.h"
20 #include "net/disk_cache/simple/simple_histogram_enums.h"
21 #include "net/disk_cache/simple/simple_synchronous_entry.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23
24 namespace disk_cache {
25
26 class SimpleFileTrackerTest : public DiskCacheTest {
27 public:
DeleteSyncEntry(SimpleSynchronousEntry * entry)28 void DeleteSyncEntry(SimpleSynchronousEntry* entry) { delete entry; }
29
30 // We limit open files to 4 for the fixture, as this is large enough
31 // that simple tests don't have to worry about naming files normally,
32 // but small enough to test with easily.
33 static const int kFileLimit = 4;
34
35 protected:
SimpleFileTrackerTest()36 SimpleFileTrackerTest() : file_tracker_(kFileLimit) {}
37
38 // A bit of messiness since we rely on friendship of the fixture to be able to
39 // create/delete SimpleSynchronousEntry objects.
40 class SyncEntryDeleter {
41 public:
SyncEntryDeleter(SimpleFileTrackerTest * fixture)42 explicit SyncEntryDeleter(SimpleFileTrackerTest* fixture)
43 : fixture_(fixture) {}
operator ()(SimpleSynchronousEntry * entry)44 void operator()(SimpleSynchronousEntry* entry) {
45 fixture_->DeleteSyncEntry(entry);
46 }
47
48 private:
49 raw_ptr<SimpleFileTrackerTest> fixture_;
50 };
51
52 using SyncEntryPointer =
53 std::unique_ptr<SimpleSynchronousEntry, SyncEntryDeleter>;
54
MakeSyncEntry(uint64_t hash)55 SyncEntryPointer MakeSyncEntry(uint64_t hash) {
56 return SyncEntryPointer(
57 new SimpleSynchronousEntry(
58 net::DISK_CACHE, cache_path_, "dummy", hash, &file_tracker_,
59 base::MakeRefCounted<disk_cache::TrivialFileOperationsFactory>()
60 ->CreateUnbound(),
61 /*stream_0_size=*/-1),
62 SyncEntryDeleter(this));
63 }
64
UpdateEntryFileKey(SimpleSynchronousEntry * sync_entry,SimpleFileTracker::EntryFileKey file_key)65 void UpdateEntryFileKey(SimpleSynchronousEntry* sync_entry,
66 SimpleFileTracker::EntryFileKey file_key) {
67 sync_entry->entry_file_key_ = file_key;
68 }
69
70 SimpleFileTracker file_tracker_;
71 };
72
TEST_F(SimpleFileTrackerTest,Basic)73 TEST_F(SimpleFileTrackerTest, Basic) {
74 SyncEntryPointer entry = MakeSyncEntry(1);
75 TrivialFileOperations ops;
76
77 // Just transfer some files to the tracker, and then do some I/O on getting
78 // them back.
79 base::FilePath path_0 = cache_path_.AppendASCII("file_0");
80 base::FilePath path_1 = cache_path_.AppendASCII("file_1");
81
82 std::unique_ptr<base::File> file_0 = std::make_unique<base::File>(
83 path_0, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
84 std::unique_ptr<base::File> file_1 = std::make_unique<base::File>(
85 path_1, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
86 ASSERT_TRUE(file_0->IsValid());
87 ASSERT_TRUE(file_1->IsValid());
88
89 file_tracker_.Register(entry.get(), SimpleFileTracker::SubFile::FILE_0,
90 std::move(file_0));
91 file_tracker_.Register(entry.get(), SimpleFileTracker::SubFile::FILE_1,
92 std::move(file_1));
93
94 std::string_view msg_0 = "Hello";
95 std::string_view msg_1 = "Worldish Place";
96
97 {
98 SimpleFileTracker::FileHandle borrow_0 = file_tracker_.Acquire(
99 &ops, entry.get(), SimpleFileTracker::SubFile::FILE_0);
100 SimpleFileTracker::FileHandle borrow_1 = file_tracker_.Acquire(
101 &ops, entry.get(), SimpleFileTracker::SubFile::FILE_1);
102
103 EXPECT_EQ(static_cast<int>(msg_0.size()),
104 borrow_0->Write(0, msg_0.data(), msg_0.size()));
105 EXPECT_EQ(static_cast<int>(msg_1.size()),
106 borrow_1->Write(0, msg_1.data(), msg_1.size()));
107
108 // For stream 0 do release/close, for stream 1 do close/release --- where
109 // release happens when borrow_{0,1} go out of scope
110 file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_1);
111 }
112 file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_0);
113
114 // Verify contents.
115 std::string verify_0, verify_1;
116 EXPECT_TRUE(ReadFileToString(path_0, &verify_0));
117 EXPECT_TRUE(ReadFileToString(path_1, &verify_1));
118 EXPECT_EQ(msg_0, verify_0);
119 EXPECT_EQ(msg_1, verify_1);
120 EXPECT_TRUE(file_tracker_.IsEmptyForTesting());
121 }
122
TEST_F(SimpleFileTrackerTest,Collision)123 TEST_F(SimpleFileTrackerTest, Collision) {
124 // Two entries with same key.
125 SyncEntryPointer entry = MakeSyncEntry(1);
126 SyncEntryPointer entry2 = MakeSyncEntry(1);
127 TrivialFileOperations ops;
128
129 base::FilePath path = cache_path_.AppendASCII("file");
130 base::FilePath path2 = cache_path_.AppendASCII("file2");
131
132 std::unique_ptr<base::File> file = std::make_unique<base::File>(
133 path, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
134 std::unique_ptr<base::File> file2 = std::make_unique<base::File>(
135 path2, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
136 ASSERT_TRUE(file->IsValid());
137 ASSERT_TRUE(file2->IsValid());
138
139 file_tracker_.Register(entry.get(), SimpleFileTracker::SubFile::FILE_0,
140 std::move(file));
141 file_tracker_.Register(entry2.get(), SimpleFileTracker::SubFile::FILE_0,
142 std::move(file2));
143
144 std::string_view msg = "Alpha";
145 std::string_view msg2 = "Beta";
146
147 {
148 SimpleFileTracker::FileHandle borrow = file_tracker_.Acquire(
149 &ops, entry.get(), SimpleFileTracker::SubFile::FILE_0);
150 SimpleFileTracker::FileHandle borrow2 = file_tracker_.Acquire(
151 &ops, entry2.get(), SimpleFileTracker::SubFile::FILE_0);
152
153 EXPECT_EQ(static_cast<int>(msg.size()),
154 borrow->Write(0, msg.data(), msg.size()));
155 EXPECT_EQ(static_cast<int>(msg2.size()),
156 borrow2->Write(0, msg2.data(), msg2.size()));
157 }
158 file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_0);
159 file_tracker_.Close(entry2.get(), SimpleFileTracker::SubFile::FILE_0);
160
161 // Verify contents.
162 std::string verify, verify2;
163 EXPECT_TRUE(ReadFileToString(path, &verify));
164 EXPECT_TRUE(ReadFileToString(path2, &verify2));
165 EXPECT_EQ(msg, verify);
166 EXPECT_EQ(msg2, verify2);
167 EXPECT_TRUE(file_tracker_.IsEmptyForTesting());
168 }
169
TEST_F(SimpleFileTrackerTest,Reopen)170 TEST_F(SimpleFileTrackerTest, Reopen) {
171 // We may sometimes go Register -> Close -> Register, with info still
172 // alive.
173 SyncEntryPointer entry = MakeSyncEntry(1);
174
175 base::FilePath path_0 = cache_path_.AppendASCII("file_0");
176 base::FilePath path_1 = cache_path_.AppendASCII("file_1");
177
178 std::unique_ptr<base::File> file_0 = std::make_unique<base::File>(
179 path_0, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
180 std::unique_ptr<base::File> file_1 = std::make_unique<base::File>(
181 path_1, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
182 ASSERT_TRUE(file_0->IsValid());
183 ASSERT_TRUE(file_1->IsValid());
184
185 file_tracker_.Register(entry.get(), SimpleFileTracker::SubFile::FILE_0,
186 std::move(file_0));
187 file_tracker_.Register(entry.get(), SimpleFileTracker::SubFile::FILE_1,
188 std::move(file_1));
189
190 file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_1);
191 std::unique_ptr<base::File> file_1b = std::make_unique<base::File>(
192 path_1, base::File::FLAG_OPEN | base::File::FLAG_WRITE);
193 ASSERT_TRUE(file_1b->IsValid());
194 file_tracker_.Register(entry.get(), SimpleFileTracker::SubFile::FILE_1,
195 std::move(file_1b));
196 file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_0);
197 file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_1);
198 EXPECT_TRUE(file_tracker_.IsEmptyForTesting());
199 }
200
TEST_F(SimpleFileTrackerTest,PointerStability)201 TEST_F(SimpleFileTrackerTest, PointerStability) {
202 // Make sure the FileHandle lent out doesn't get screwed up as we update
203 // the state (and potentially move the underlying base::File object around).
204 const int kEntries = 8;
205 SyncEntryPointer entries[kEntries] = {
206 MakeSyncEntry(1), MakeSyncEntry(1), MakeSyncEntry(1), MakeSyncEntry(1),
207 MakeSyncEntry(1), MakeSyncEntry(1), MakeSyncEntry(1), MakeSyncEntry(1),
208 };
209 TrivialFileOperations ops;
210 std::unique_ptr<base::File> file_0 = std::make_unique<base::File>(
211 cache_path_.AppendASCII("0"),
212 base::File::FLAG_CREATE | base::File::FLAG_WRITE);
213 ASSERT_TRUE(file_0->IsValid());
214 file_tracker_.Register(entries[0].get(), SimpleFileTracker::SubFile::FILE_0,
215 std::move(file_0));
216
217 std::string_view msg = "Message to write";
218 {
219 SimpleFileTracker::FileHandle borrow = file_tracker_.Acquire(
220 &ops, entries[0].get(), SimpleFileTracker::SubFile::FILE_0);
221 for (int i = 1; i < kEntries; ++i) {
222 std::unique_ptr<base::File> file_n = std::make_unique<base::File>(
223 cache_path_.AppendASCII(base::NumberToString(i)),
224 base::File::FLAG_CREATE | base::File::FLAG_WRITE);
225 ASSERT_TRUE(file_n->IsValid());
226 file_tracker_.Register(entries[i].get(),
227 SimpleFileTracker::SubFile::FILE_0,
228 std::move(file_n));
229 }
230
231 EXPECT_EQ(static_cast<int>(msg.size()),
232 borrow->Write(0, msg.data(), msg.size()));
233 }
234
235 for (const auto& entry : entries)
236 file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_0);
237
238 // Verify the file.
239 std::string verify;
240 EXPECT_TRUE(ReadFileToString(cache_path_.AppendASCII("0"), &verify));
241 EXPECT_EQ(msg, verify);
242 EXPECT_TRUE(file_tracker_.IsEmptyForTesting());
243 }
244
TEST_F(SimpleFileTrackerTest,Doom)245 TEST_F(SimpleFileTrackerTest, Doom) {
246 SyncEntryPointer entry1 = MakeSyncEntry(1);
247 base::FilePath path1 = cache_path_.AppendASCII("file1");
248 std::unique_ptr<base::File> file1 = std::make_unique<base::File>(
249 path1, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
250 ASSERT_TRUE(file1->IsValid());
251
252 file_tracker_.Register(entry1.get(), SimpleFileTracker::SubFile::FILE_0,
253 std::move(file1));
254 SimpleFileTracker::EntryFileKey key1 = entry1->entry_file_key();
255 file_tracker_.Doom(entry1.get(), &key1);
256 EXPECT_NE(0u, key1.doom_generation);
257
258 // Other entry with same key.
259 SyncEntryPointer entry2 = MakeSyncEntry(1);
260 base::FilePath path2 = cache_path_.AppendASCII("file2");
261 std::unique_ptr<base::File> file2 = std::make_unique<base::File>(
262 path2, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
263 ASSERT_TRUE(file2->IsValid());
264
265 file_tracker_.Register(entry2.get(), SimpleFileTracker::SubFile::FILE_0,
266 std::move(file2));
267 SimpleFileTracker::EntryFileKey key2 = entry2->entry_file_key();
268 file_tracker_.Doom(entry2.get(), &key2);
269 EXPECT_NE(0u, key2.doom_generation);
270 EXPECT_NE(key1.doom_generation, key2.doom_generation);
271
272 file_tracker_.Close(entry1.get(), SimpleFileTracker::SubFile::FILE_0);
273 file_tracker_.Close(entry2.get(), SimpleFileTracker::SubFile::FILE_0);
274 }
275
TEST_F(SimpleFileTrackerTest,OverLimit)276 TEST_F(SimpleFileTrackerTest, OverLimit) {
277 base::HistogramTester histogram_tester;
278
279 const int kEntries = 10; // want more than FD limit in fixture.
280 std::vector<SyncEntryPointer> entries;
281 std::vector<base::FilePath> names;
282 TrivialFileOperations ops;
283 for (int i = 0; i < kEntries; ++i) {
284 SyncEntryPointer entry = MakeSyncEntry(i);
285 base::FilePath name =
286 entry->GetFilenameForSubfile(SimpleFileTracker::SubFile::FILE_0);
287 std::unique_ptr<base::File> file = std::make_unique<base::File>(
288 name, base::File::FLAG_CREATE | base::File::FLAG_WRITE |
289 base::File::FLAG_READ);
290 ASSERT_TRUE(file->IsValid());
291 file_tracker_.Register(entry.get(), SimpleFileTracker::SubFile::FILE_0,
292 std::move(file));
293 entries.push_back(std::move(entry));
294 names.push_back(name);
295 }
296
297 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
298 disk_cache::FD_LIMIT_CLOSE_FILE,
299 kEntries - kFileLimit);
300 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
301 disk_cache::FD_LIMIT_REOPEN_FILE, 0);
302 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
303 disk_cache::FD_LIMIT_FAIL_REOPEN_FILE, 0);
304
305 // Grab the last one; we will hold it open till the end of the test. It's
306 // still open, so no change in stats after.
307 SimpleFileTracker::FileHandle borrow_last = file_tracker_.Acquire(
308 &ops, entries[kEntries - 1].get(), SimpleFileTracker::SubFile::FILE_0);
309 EXPECT_EQ(1, borrow_last->Write(0, "L", 1));
310
311 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
312 disk_cache::FD_LIMIT_CLOSE_FILE,
313 kEntries - kFileLimit);
314 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
315 disk_cache::FD_LIMIT_REOPEN_FILE, 0);
316 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
317 disk_cache::FD_LIMIT_FAIL_REOPEN_FILE, 0);
318
319 // Delete file for [2], to cause error on its re-open.
320 EXPECT_TRUE(base::DeleteFile(names[2])) << names[2];
321
322 // Reacquire all the other files.
323 for (int i = 0; i < kEntries - 1; ++i) {
324 SimpleFileTracker::FileHandle borrow = file_tracker_.Acquire(
325 &ops, entries[i].get(), SimpleFileTracker::SubFile::FILE_0);
326 if (i != 2) {
327 EXPECT_TRUE(borrow.IsOK());
328 char c = static_cast<char>(i);
329 EXPECT_EQ(1, borrow->Write(0, &c, 1));
330 } else {
331 EXPECT_FALSE(borrow.IsOK());
332 }
333 }
334
335 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
336 disk_cache::FD_LIMIT_CLOSE_FILE,
337 kEntries - kFileLimit + kEntries - 2);
338 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
339 disk_cache::FD_LIMIT_REOPEN_FILE,
340 kEntries - 2);
341 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
342 disk_cache::FD_LIMIT_FAIL_REOPEN_FILE, 1);
343
344 // Doom file for [1].
345 SimpleFileTracker::EntryFileKey key = entries[1]->entry_file_key();
346 file_tracker_.Doom(entries[1].get(), &key);
347 base::FilePath old_path = names[1];
348 UpdateEntryFileKey(entries[1].get(), key);
349 base::FilePath new_path =
350 entries[1]->GetFilenameForSubfile(SimpleFileTracker::SubFile::FILE_0);
351 EXPECT_TRUE(new_path.BaseName().MaybeAsASCII().starts_with("todelete_"));
352 EXPECT_TRUE(base::Move(old_path, new_path));
353
354 // Now re-acquire everything again; this time reading.
355 for (int i = 0; i < kEntries - 1; ++i) {
356 SimpleFileTracker::FileHandle borrow = file_tracker_.Acquire(
357 &ops, entries[i].get(), SimpleFileTracker::SubFile::FILE_0);
358 char read;
359 char expected = static_cast<char>(i);
360 if (i != 2) {
361 EXPECT_TRUE(borrow.IsOK());
362 EXPECT_EQ(1, borrow->Read(0, &read, 1));
363 EXPECT_EQ(expected, read);
364 } else {
365 EXPECT_FALSE(borrow.IsOK());
366 }
367 }
368
369 histogram_tester.ExpectBucketCount(
370 "SimpleCache.FileDescriptorLimiterAction",
371 disk_cache::FD_LIMIT_CLOSE_FILE,
372 kEntries - kFileLimit + 2 * (kEntries - 2));
373 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
374 disk_cache::FD_LIMIT_REOPEN_FILE,
375 2 * (kEntries - 2));
376 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
377 disk_cache::FD_LIMIT_FAIL_REOPEN_FILE, 2);
378
379 // Read from the last one, too. Should still be fine.
380 char read;
381 EXPECT_EQ(1, borrow_last->Read(0, &read, 1));
382 EXPECT_EQ('L', read);
383
384 for (const auto& entry : entries)
385 file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_0);
386 }
387
388 } // namespace disk_cache
389