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(msg_0.size(), borrow_0->Write(0, base::as_byte_span(msg_0)));
104 EXPECT_EQ(msg_1.size(), borrow_1->Write(0, base::as_byte_span(msg_1)));
105
106 // For stream 0 do release/close, for stream 1 do close/release --- where
107 // release happens when borrow_{0,1} go out of scope
108 file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_1);
109 }
110 file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_0);
111
112 // Verify contents.
113 std::string verify_0, verify_1;
114 EXPECT_TRUE(ReadFileToString(path_0, &verify_0));
115 EXPECT_TRUE(ReadFileToString(path_1, &verify_1));
116 EXPECT_EQ(msg_0, verify_0);
117 EXPECT_EQ(msg_1, verify_1);
118 EXPECT_TRUE(file_tracker_.IsEmptyForTesting());
119 }
120
TEST_F(SimpleFileTrackerTest,Collision)121 TEST_F(SimpleFileTrackerTest, Collision) {
122 // Two entries with same key.
123 SyncEntryPointer entry = MakeSyncEntry(1);
124 SyncEntryPointer entry2 = MakeSyncEntry(1);
125 TrivialFileOperations ops;
126
127 base::FilePath path = cache_path_.AppendASCII("file");
128 base::FilePath path2 = cache_path_.AppendASCII("file2");
129
130 std::unique_ptr<base::File> file = std::make_unique<base::File>(
131 path, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
132 std::unique_ptr<base::File> file2 = std::make_unique<base::File>(
133 path2, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
134 ASSERT_TRUE(file->IsValid());
135 ASSERT_TRUE(file2->IsValid());
136
137 file_tracker_.Register(entry.get(), SimpleFileTracker::SubFile::FILE_0,
138 std::move(file));
139 file_tracker_.Register(entry2.get(), SimpleFileTracker::SubFile::FILE_0,
140 std::move(file2));
141
142 std::string_view msg = "Alpha";
143 std::string_view msg2 = "Beta";
144
145 {
146 SimpleFileTracker::FileHandle borrow = file_tracker_.Acquire(
147 &ops, entry.get(), SimpleFileTracker::SubFile::FILE_0);
148 SimpleFileTracker::FileHandle borrow2 = file_tracker_.Acquire(
149 &ops, entry2.get(), SimpleFileTracker::SubFile::FILE_0);
150
151 EXPECT_EQ(msg.size(), borrow->Write(0, base::as_byte_span(msg)));
152 EXPECT_EQ(msg2.size(), borrow2->Write(0, base::as_byte_span(msg2)));
153 }
154 file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_0);
155 file_tracker_.Close(entry2.get(), SimpleFileTracker::SubFile::FILE_0);
156
157 // Verify contents.
158 std::string verify, verify2;
159 EXPECT_TRUE(ReadFileToString(path, &verify));
160 EXPECT_TRUE(ReadFileToString(path2, &verify2));
161 EXPECT_EQ(msg, verify);
162 EXPECT_EQ(msg2, verify2);
163 EXPECT_TRUE(file_tracker_.IsEmptyForTesting());
164 }
165
TEST_F(SimpleFileTrackerTest,Reopen)166 TEST_F(SimpleFileTrackerTest, Reopen) {
167 // We may sometimes go Register -> Close -> Register, with info still
168 // alive.
169 SyncEntryPointer entry = MakeSyncEntry(1);
170
171 base::FilePath path_0 = cache_path_.AppendASCII("file_0");
172 base::FilePath path_1 = cache_path_.AppendASCII("file_1");
173
174 std::unique_ptr<base::File> file_0 = std::make_unique<base::File>(
175 path_0, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
176 std::unique_ptr<base::File> file_1 = std::make_unique<base::File>(
177 path_1, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
178 ASSERT_TRUE(file_0->IsValid());
179 ASSERT_TRUE(file_1->IsValid());
180
181 file_tracker_.Register(entry.get(), SimpleFileTracker::SubFile::FILE_0,
182 std::move(file_0));
183 file_tracker_.Register(entry.get(), SimpleFileTracker::SubFile::FILE_1,
184 std::move(file_1));
185
186 file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_1);
187 std::unique_ptr<base::File> file_1b = std::make_unique<base::File>(
188 path_1, base::File::FLAG_OPEN | base::File::FLAG_WRITE);
189 ASSERT_TRUE(file_1b->IsValid());
190 file_tracker_.Register(entry.get(), SimpleFileTracker::SubFile::FILE_1,
191 std::move(file_1b));
192 file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_0);
193 file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_1);
194 EXPECT_TRUE(file_tracker_.IsEmptyForTesting());
195 }
196
TEST_F(SimpleFileTrackerTest,PointerStability)197 TEST_F(SimpleFileTrackerTest, PointerStability) {
198 // Make sure the FileHandle lent out doesn't get screwed up as we update
199 // the state (and potentially move the underlying base::File object around).
200 const int kEntries = 8;
201 std::array<SyncEntryPointer, kEntries> entries = {
202 MakeSyncEntry(1), MakeSyncEntry(1), MakeSyncEntry(1), MakeSyncEntry(1),
203 MakeSyncEntry(1), MakeSyncEntry(1), MakeSyncEntry(1), MakeSyncEntry(1),
204 };
205 TrivialFileOperations ops;
206 std::unique_ptr<base::File> file_0 = std::make_unique<base::File>(
207 cache_path_.AppendASCII("0"),
208 base::File::FLAG_CREATE | base::File::FLAG_WRITE);
209 ASSERT_TRUE(file_0->IsValid());
210 file_tracker_.Register(entries[0].get(), SimpleFileTracker::SubFile::FILE_0,
211 std::move(file_0));
212
213 std::string_view msg = "Message to write";
214 {
215 SimpleFileTracker::FileHandle borrow = file_tracker_.Acquire(
216 &ops, entries[0].get(), SimpleFileTracker::SubFile::FILE_0);
217 for (int i = 1; i < kEntries; ++i) {
218 std::unique_ptr<base::File> file_n = std::make_unique<base::File>(
219 cache_path_.AppendASCII(base::NumberToString(i)),
220 base::File::FLAG_CREATE | base::File::FLAG_WRITE);
221 ASSERT_TRUE(file_n->IsValid());
222 file_tracker_.Register(entries[i].get(),
223 SimpleFileTracker::SubFile::FILE_0,
224 std::move(file_n));
225 }
226
227 EXPECT_EQ(msg.size(), borrow->Write(0, base::as_byte_span(msg)));
228 }
229
230 for (const auto& entry : entries)
231 file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_0);
232
233 // Verify the file.
234 std::string verify;
235 EXPECT_TRUE(ReadFileToString(cache_path_.AppendASCII("0"), &verify));
236 EXPECT_EQ(msg, verify);
237 EXPECT_TRUE(file_tracker_.IsEmptyForTesting());
238 }
239
TEST_F(SimpleFileTrackerTest,Doom)240 TEST_F(SimpleFileTrackerTest, Doom) {
241 SyncEntryPointer entry1 = MakeSyncEntry(1);
242 base::FilePath path1 = cache_path_.AppendASCII("file1");
243 std::unique_ptr<base::File> file1 = std::make_unique<base::File>(
244 path1, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
245 ASSERT_TRUE(file1->IsValid());
246
247 file_tracker_.Register(entry1.get(), SimpleFileTracker::SubFile::FILE_0,
248 std::move(file1));
249 SimpleFileTracker::EntryFileKey key1 = entry1->entry_file_key();
250 file_tracker_.Doom(entry1.get(), &key1);
251 EXPECT_NE(0u, key1.doom_generation);
252
253 // Other entry with same key.
254 SyncEntryPointer entry2 = MakeSyncEntry(1);
255 base::FilePath path2 = cache_path_.AppendASCII("file2");
256 std::unique_ptr<base::File> file2 = std::make_unique<base::File>(
257 path2, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
258 ASSERT_TRUE(file2->IsValid());
259
260 file_tracker_.Register(entry2.get(), SimpleFileTracker::SubFile::FILE_0,
261 std::move(file2));
262 SimpleFileTracker::EntryFileKey key2 = entry2->entry_file_key();
263 file_tracker_.Doom(entry2.get(), &key2);
264 EXPECT_NE(0u, key2.doom_generation);
265 EXPECT_NE(key1.doom_generation, key2.doom_generation);
266
267 file_tracker_.Close(entry1.get(), SimpleFileTracker::SubFile::FILE_0);
268 file_tracker_.Close(entry2.get(), SimpleFileTracker::SubFile::FILE_0);
269 }
270
TEST_F(SimpleFileTrackerTest,OverLimit)271 TEST_F(SimpleFileTrackerTest, OverLimit) {
272 base::HistogramTester histogram_tester;
273
274 const int kEntries = 10; // want more than FD limit in fixture.
275 std::vector<SyncEntryPointer> entries;
276 std::vector<base::FilePath> names;
277 TrivialFileOperations ops;
278 for (int i = 0; i < kEntries; ++i) {
279 SyncEntryPointer entry = MakeSyncEntry(i);
280 base::FilePath name =
281 entry->GetFilenameForSubfile(SimpleFileTracker::SubFile::FILE_0);
282 std::unique_ptr<base::File> file = std::make_unique<base::File>(
283 name, base::File::FLAG_CREATE | base::File::FLAG_WRITE |
284 base::File::FLAG_READ);
285 ASSERT_TRUE(file->IsValid());
286 file_tracker_.Register(entry.get(), SimpleFileTracker::SubFile::FILE_0,
287 std::move(file));
288 entries.push_back(std::move(entry));
289 names.push_back(name);
290 }
291
292 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
293 disk_cache::FD_LIMIT_CLOSE_FILE,
294 kEntries - kFileLimit);
295 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
296 disk_cache::FD_LIMIT_REOPEN_FILE, 0);
297 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
298 disk_cache::FD_LIMIT_FAIL_REOPEN_FILE, 0);
299
300 // Grab the last one; we will hold it open till the end of the test. It's
301 // still open, so no change in stats after.
302 SimpleFileTracker::FileHandle borrow_last = file_tracker_.Acquire(
303 &ops, entries[kEntries - 1].get(), SimpleFileTracker::SubFile::FILE_0);
304 EXPECT_EQ(1u, borrow_last->Write(0, base::byte_span_from_cstring("L")));
305
306 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
307 disk_cache::FD_LIMIT_CLOSE_FILE,
308 kEntries - kFileLimit);
309 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
310 disk_cache::FD_LIMIT_REOPEN_FILE, 0);
311 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
312 disk_cache::FD_LIMIT_FAIL_REOPEN_FILE, 0);
313
314 // Delete file for [2], to cause error on its re-open.
315 EXPECT_TRUE(base::DeleteFile(names[2])) << names[2];
316
317 // Reacquire all the other files.
318 for (int i = 0; i < kEntries - 1; ++i) {
319 SimpleFileTracker::FileHandle borrow = file_tracker_.Acquire(
320 &ops, entries[i].get(), SimpleFileTracker::SubFile::FILE_0);
321 if (i != 2) {
322 EXPECT_TRUE(borrow.IsOK());
323 char c = static_cast<char>(i);
324 EXPECT_EQ(1u, borrow->Write(0, base::byte_span_from_ref(c)));
325 } else {
326 EXPECT_FALSE(borrow.IsOK());
327 }
328 }
329
330 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
331 disk_cache::FD_LIMIT_CLOSE_FILE,
332 kEntries - kFileLimit + kEntries - 2);
333 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
334 disk_cache::FD_LIMIT_REOPEN_FILE,
335 kEntries - 2);
336 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
337 disk_cache::FD_LIMIT_FAIL_REOPEN_FILE, 1);
338
339 // Doom file for [1].
340 SimpleFileTracker::EntryFileKey key = entries[1]->entry_file_key();
341 file_tracker_.Doom(entries[1].get(), &key);
342 base::FilePath old_path = names[1];
343 UpdateEntryFileKey(entries[1].get(), key);
344 base::FilePath new_path =
345 entries[1]->GetFilenameForSubfile(SimpleFileTracker::SubFile::FILE_0);
346 EXPECT_TRUE(new_path.BaseName().MaybeAsASCII().starts_with("todelete_"));
347 EXPECT_TRUE(base::Move(old_path, new_path));
348
349 // Now re-acquire everything again; this time reading.
350 for (int i = 0; i < kEntries - 1; ++i) {
351 SimpleFileTracker::FileHandle borrow = file_tracker_.Acquire(
352 &ops, entries[i].get(), SimpleFileTracker::SubFile::FILE_0);
353 char read;
354 char expected = static_cast<char>(i);
355 if (i != 2) {
356 EXPECT_TRUE(borrow.IsOK());
357 EXPECT_EQ(1, borrow->Read(0, base::byte_span_from_ref(read)));
358 EXPECT_EQ(expected, read);
359 } else {
360 EXPECT_FALSE(borrow.IsOK());
361 }
362 }
363
364 histogram_tester.ExpectBucketCount(
365 "SimpleCache.FileDescriptorLimiterAction",
366 disk_cache::FD_LIMIT_CLOSE_FILE,
367 kEntries - kFileLimit + 2 * (kEntries - 2));
368 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
369 disk_cache::FD_LIMIT_REOPEN_FILE,
370 2 * (kEntries - 2));
371 histogram_tester.ExpectBucketCount("SimpleCache.FileDescriptorLimiterAction",
372 disk_cache::FD_LIMIT_FAIL_REOPEN_FILE, 2);
373
374 // Read from the last one, too. Should still be fine.
375 char read;
376 EXPECT_EQ(1, borrow_last->Read(0, base::byte_span_from_ref(read)));
377 EXPECT_EQ('L', read);
378
379 for (const auto& entry : entries)
380 file_tracker_.Close(entry.get(), SimpleFileTracker::SubFile::FILE_0);
381 }
382
383 } // namespace disk_cache
384