1 // Copyright 2013 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 "net/disk_cache/simple/simple_index.h"
6
7 #include <algorithm>
8 #include <functional>
9 #include <memory>
10 #include <utility>
11
12 #include "base/feature_list.h"
13 #include "base/files/scoped_temp_dir.h"
14 #include "base/functional/bind.h"
15 #include "base/hash/hash.h"
16 #include "base/memory/raw_ptr.h"
17 #include "base/pickle.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/task/task_runner.h"
20 #include "base/test/mock_entropy_provider.h"
21 #include "base/test/scoped_feature_list.h"
22 #include "base/threading/platform_thread.h"
23 #include "base/time/time.h"
24 #include "net/base/cache_type.h"
25 #include "net/base/features.h"
26 #include "net/disk_cache/backend_cleanup_tracker.h"
27 #include "net/disk_cache/disk_cache.h"
28 #include "net/disk_cache/memory_entry_data_hints.h"
29 #include "net/disk_cache/simple/simple_index_delegate.h"
30 #include "net/disk_cache/simple/simple_index_file.h"
31 #include "net/disk_cache/simple/simple_test_util.h"
32 #include "net/disk_cache/simple/simple_util.h"
33 #include "net/test/test_with_task_environment.h"
34 #include "testing/gtest/include/gtest/gtest.h"
35
36 namespace disk_cache {
37 namespace {
38
39 const base::Time kTestLastUsedTime = base::Time::UnixEpoch() + base::Days(20);
40 const uint32_t kTestEntrySize = 789;
41 const uint8_t kTestEntryMemoryData = 123;
42
RoundSize(uint32_t in)43 uint32_t RoundSize(uint32_t in) {
44 return (in + 0xFFu) & 0xFFFFFF00u;
45 }
46
47 } // namespace
48
49 class EntryMetadataTest : public testing::Test {
50 public:
NewEntryMetadataWithValues()51 EntryMetadata NewEntryMetadataWithValues() {
52 EntryMetadata entry(kTestLastUsedTime, kTestEntrySize);
53 entry.SetInMemoryData(kTestEntryMemoryData);
54 return entry;
55 }
56
CheckEntryMetadataValues(const EntryMetadata & entry_metadata)57 void CheckEntryMetadataValues(const EntryMetadata& entry_metadata) {
58 EXPECT_LT(kTestLastUsedTime - base::Seconds(2),
59 entry_metadata.GetLastUsedTime());
60 EXPECT_GT(kTestLastUsedTime + base::Seconds(2),
61 entry_metadata.GetLastUsedTime());
62 EXPECT_EQ(RoundSize(kTestEntrySize), entry_metadata.GetEntrySize());
63 EXPECT_EQ(kTestEntryMemoryData, entry_metadata.GetInMemoryData());
64 }
65 };
66
67 class MockSimpleIndexFile final : public SimpleIndexFile {
68 public:
MockSimpleIndexFile(net::CacheType cache_type)69 explicit MockSimpleIndexFile(net::CacheType cache_type)
70 : SimpleIndexFile(nullptr,
71 base::MakeRefCounted<TrivialFileOperationsFactory>(),
72 cache_type,
73 base::FilePath()) {}
74
LoadIndexEntries(base::Time cache_last_modified,base::OnceClosure callback,SimpleIndexLoadResult * out_load_result)75 void LoadIndexEntries(base::Time cache_last_modified,
76 base::OnceClosure callback,
77 SimpleIndexLoadResult* out_load_result) override {
78 load_callback_ = std::move(callback);
79 load_result_ = out_load_result;
80 ++load_index_entries_calls_;
81 }
82
WriteToDisk(net::CacheType cache_type,SimpleIndex::IndexWriteToDiskReason reason,const SimpleIndex::EntrySet & entry_set,uint64_t cache_size,base::OnceClosure callback)83 void WriteToDisk(net::CacheType cache_type,
84 SimpleIndex::IndexWriteToDiskReason reason,
85 const SimpleIndex::EntrySet& entry_set,
86 uint64_t cache_size,
87 base::OnceClosure callback) override {
88 disk_writes_++;
89 disk_write_entry_set_ = entry_set;
90 }
91
GetAndResetDiskWriteEntrySet(SimpleIndex::EntrySet * entry_set)92 void GetAndResetDiskWriteEntrySet(SimpleIndex::EntrySet* entry_set) {
93 entry_set->swap(disk_write_entry_set_);
94 }
95
RunLoadCallback()96 void RunLoadCallback() {
97 // Clear dangling reference since callback may destroy `load_result_`.
98 load_result_ = nullptr;
99 std::move(load_callback_).Run();
100 }
load_result() const101 SimpleIndexLoadResult* load_result() const { return load_result_; }
load_index_entries_calls() const102 int load_index_entries_calls() const { return load_index_entries_calls_; }
disk_writes() const103 int disk_writes() const { return disk_writes_; }
104
AsWeakPtr()105 base::WeakPtr<MockSimpleIndexFile> AsWeakPtr() {
106 return weak_ptr_factory_.GetWeakPtr();
107 }
108
109 private:
110 base::OnceClosure load_callback_;
111 raw_ptr<SimpleIndexLoadResult> load_result_ = nullptr;
112 int load_index_entries_calls_ = 0;
113 int disk_writes_ = 0;
114 SimpleIndex::EntrySet disk_write_entry_set_;
115 base::WeakPtrFactory<MockSimpleIndexFile> weak_ptr_factory_{this};
116 };
117
118 class SimpleIndexTest : public net::TestWithTaskEnvironment,
119 public SimpleIndexDelegate {
120 protected:
SimpleIndexTest()121 SimpleIndexTest() : hashes_(base::BindRepeating(&HashesInitializer)) {}
122
HashesInitializer(size_t hash_index)123 static uint64_t HashesInitializer(size_t hash_index) {
124 return disk_cache::simple_util::GetEntryHashKey(
125 base::StringPrintf("key%d", static_cast<int>(hash_index)));
126 }
127
SetUp()128 void SetUp() override {
129 auto index_file = std::make_unique<MockSimpleIndexFile>(CacheType());
130 index_file_ = index_file->AsWeakPtr();
131 index_ =
132 std::make_unique<SimpleIndex>(/* io_thread = */ nullptr,
133 /* cleanup_tracker = */ nullptr, this,
134 CacheType(), std::move(index_file));
135
136 index_->Initialize(base::Time());
137 }
138
WaitForTimeChange()139 void WaitForTimeChange() {
140 const base::Time initial_time = base::Time::Now();
141 do {
142 base::PlatformThread::YieldCurrentThread();
143 } while (base::Time::Now() - initial_time < base::Seconds(1));
144 }
145
146 // From SimpleIndexDelegate:
DoomEntries(std::vector<uint64_t> * entry_hashes,net::CompletionOnceCallback callback)147 void DoomEntries(std::vector<uint64_t>* entry_hashes,
148 net::CompletionOnceCallback callback) override {
149 for (const uint64_t& entry_hash : *entry_hashes)
150 index_->Remove(entry_hash);
151 last_doom_entry_hashes_ = *entry_hashes;
152 ++doom_entries_calls_;
153 }
154
155 // Redirect to allow single "friend" declaration in base class.
GetEntryForTesting(uint64_t key,EntryMetadata * metadata)156 bool GetEntryForTesting(uint64_t key, EntryMetadata* metadata) {
157 auto it = index_->entries_set_.find(key);
158 if (index_->entries_set_.end() == it)
159 return false;
160 *metadata = it->second;
161 return true;
162 }
163
InsertIntoIndexFileReturn(uint64_t hash_key,base::Time last_used_time,int entry_size)164 void InsertIntoIndexFileReturn(uint64_t hash_key,
165 base::Time last_used_time,
166 int entry_size) {
167 index_file_->load_result()->entries.emplace(
168 hash_key, EntryMetadata(last_used_time,
169 base::checked_cast<uint32_t>(entry_size)));
170 }
171
InsertIntoIndexFileWithPrioritizeCachingFlagReturn(uint64_t hash_key,base::Time last_used_time,int entry_size)172 void InsertIntoIndexFileWithPrioritizeCachingFlagReturn(
173 uint64_t hash_key,
174 base::Time last_used_time,
175 int entry_size) {
176 EntryMetadata entry_meta_data =
177 EntryMetadata(last_used_time, base::checked_cast<uint32_t>(entry_size));
178 entry_meta_data.SetInMemoryData(HINT_HIGH_PRIORITY);
179 index_file_->load_result()->entries.emplace(hash_key, entry_meta_data);
180 }
181
ReturnIndexFile()182 void ReturnIndexFile() {
183 index_file_->load_result()->did_load = true;
184 index_file_->RunLoadCallback();
185 }
186
187 // Non-const for timer manipulation.
index()188 SimpleIndex* index() { return index_.get(); }
index_file() const189 const MockSimpleIndexFile* index_file() const { return index_file_.get(); }
190
last_doom_entry_hashes() const191 const std::vector<uint64_t>& last_doom_entry_hashes() const {
192 return last_doom_entry_hashes_;
193 }
doom_entries_calls() const194 int doom_entries_calls() const { return doom_entries_calls_; }
195
CacheType() const196 virtual net::CacheType CacheType() const { return net::DISK_CACHE; }
197
198 const simple_util::ImmutableArray<uint64_t, 16> hashes_;
199 std::unique_ptr<SimpleIndex> index_;
200 base::WeakPtr<MockSimpleIndexFile> index_file_;
201
202 std::vector<uint64_t> last_doom_entry_hashes_;
203 int doom_entries_calls_ = 0;
204 };
205
206 class SimpleIndexAppCacheTest : public SimpleIndexTest {
207 protected:
CacheType() const208 net::CacheType CacheType() const override { return net::APP_CACHE; }
209 };
210
211 class SimpleIndexCodeCacheTest : public SimpleIndexTest {
212 protected:
CacheType() const213 net::CacheType CacheType() const override {
214 return net::GENERATED_BYTE_CODE_CACHE;
215 }
216 };
217
TEST_F(EntryMetadataTest,Basics)218 TEST_F(EntryMetadataTest, Basics) {
219 EntryMetadata entry_metadata;
220 EXPECT_EQ(base::Time(), entry_metadata.GetLastUsedTime());
221 EXPECT_EQ(0u, entry_metadata.GetEntrySize());
222 EXPECT_EQ(0u, entry_metadata.GetInMemoryData());
223
224 entry_metadata = NewEntryMetadataWithValues();
225 CheckEntryMetadataValues(entry_metadata);
226
227 const base::Time new_time = base::Time::Now();
228 entry_metadata.SetLastUsedTime(new_time);
229
230 EXPECT_LT(new_time - base::Seconds(2), entry_metadata.GetLastUsedTime());
231 EXPECT_GT(new_time + base::Seconds(2), entry_metadata.GetLastUsedTime());
232 }
233
234 // Tests that setting an unusually small/large last used time results in
235 // truncation (rather than crashing).
TEST_F(EntryMetadataTest,SaturatedLastUsedTime)236 TEST_F(EntryMetadataTest, SaturatedLastUsedTime) {
237 EntryMetadata entry_metadata;
238
239 // Set a time that is too large to be represented internally as 32-bit unix
240 // timestamp. Will saturate to a large timestamp (in year 2106).
241 entry_metadata.SetLastUsedTime(base::Time::Max());
242 EXPECT_EQ(INT64_C(15939440895000000),
243 entry_metadata.GetLastUsedTime().ToInternalValue());
244
245 // Set a time that is too small to be represented by a unix timestamp (before
246 // 1970).
247 entry_metadata.SetLastUsedTime(
248 base::Time::FromInternalValue(7u)); // This is a date in 1601.
249 EXPECT_EQ(base::Time::UnixEpoch() + base::Seconds(1),
250 entry_metadata.GetLastUsedTime());
251 }
252
TEST_F(EntryMetadataTest,Serialize)253 TEST_F(EntryMetadataTest, Serialize) {
254 EntryMetadata entry_metadata = NewEntryMetadataWithValues();
255
256 base::Pickle pickle;
257 entry_metadata.Serialize(net::DISK_CACHE, &pickle);
258
259 base::PickleIterator it(pickle);
260 EntryMetadata new_entry_metadata;
261 new_entry_metadata.Deserialize(net::DISK_CACHE, &it, true, true);
262 CheckEntryMetadataValues(new_entry_metadata);
263
264 // Test reading of old format --- the modern serialization of above entry
265 // corresponds, in older format, to an entry with size =
266 // RoundSize(kTestEntrySize) | kTestEntryMemoryData, which then gets
267 // rounded again when stored by EntryMetadata.
268 base::PickleIterator it2(pickle);
269 EntryMetadata new_entry_metadata2;
270 new_entry_metadata2.Deserialize(net::DISK_CACHE, &it2, false, false);
271 EXPECT_EQ(RoundSize(RoundSize(kTestEntrySize) | kTestEntryMemoryData),
272 new_entry_metadata2.GetEntrySize());
273 EXPECT_EQ(0, new_entry_metadata2.GetInMemoryData());
274 }
275
TEST_F(SimpleIndexTest,IndexSizeCorrectOnMerge)276 TEST_F(SimpleIndexTest, IndexSizeCorrectOnMerge) {
277 const unsigned int kSizeResolution = 256u;
278 index()->SetMaxSize(100 * kSizeResolution);
279 index()->Insert(hashes_.at<2>());
280 index()->UpdateEntrySize(hashes_.at<2>(), 2u * kSizeResolution);
281 index()->Insert(hashes_.at<3>());
282 index()->UpdateEntrySize(hashes_.at<3>(), 3u * kSizeResolution);
283 index()->Insert(hashes_.at<4>());
284 index()->UpdateEntrySize(hashes_.at<4>(), 4u * kSizeResolution);
285 EXPECT_EQ(9u * kSizeResolution, index()->cache_size_);
286 {
287 auto result = std::make_unique<SimpleIndexLoadResult>();
288 result->did_load = true;
289 index()->MergeInitializingSet(std::move(result));
290 }
291 EXPECT_EQ(9u * kSizeResolution, index()->cache_size_);
292 {
293 auto result = std::make_unique<SimpleIndexLoadResult>();
294 result->did_load = true;
295 const uint64_t new_hash_key = hashes_.at<11>();
296 result->entries.emplace(
297 new_hash_key, EntryMetadata(base::Time::Now(), 11u * kSizeResolution));
298 const uint64_t redundant_hash_key = hashes_.at<4>();
299 result->entries.emplace(
300 redundant_hash_key,
301 EntryMetadata(base::Time::Now(), 4u * kSizeResolution));
302 index()->MergeInitializingSet(std::move(result));
303 }
304 EXPECT_EQ((2u + 3u + 4u + 11u) * kSizeResolution, index()->cache_size_);
305 }
306
307 // State of index changes as expected with an insert and a remove.
TEST_F(SimpleIndexTest,BasicInsertRemove)308 TEST_F(SimpleIndexTest, BasicInsertRemove) {
309 // Confirm blank state.
310 EntryMetadata metadata;
311 EXPECT_EQ(base::Time(), metadata.GetLastUsedTime());
312 EXPECT_EQ(0U, metadata.GetEntrySize());
313
314 // Confirm state after insert.
315 index()->Insert(hashes_.at<1>());
316 ASSERT_TRUE(GetEntryForTesting(hashes_.at<1>(), &metadata));
317 base::Time now(base::Time::Now());
318 EXPECT_LT(now - base::Minutes(1), metadata.GetLastUsedTime());
319 EXPECT_GT(now + base::Minutes(1), metadata.GetLastUsedTime());
320 EXPECT_EQ(0U, metadata.GetEntrySize());
321
322 // Confirm state after remove.
323 metadata = EntryMetadata();
324 index()->Remove(hashes_.at<1>());
325 EXPECT_FALSE(GetEntryForTesting(hashes_.at<1>(), &metadata));
326 EXPECT_EQ(base::Time(), metadata.GetLastUsedTime());
327 EXPECT_EQ(0U, metadata.GetEntrySize());
328 }
329
TEST_F(SimpleIndexTest,Has)330 TEST_F(SimpleIndexTest, Has) {
331 // Confirm the base index has dispatched the request for index entries.
332 EXPECT_TRUE(index_file_.get());
333 EXPECT_EQ(1, index_file_->load_index_entries_calls());
334
335 // Confirm "Has()" always returns true before the callback is called.
336 const uint64_t kHash1 = hashes_.at<1>();
337 EXPECT_TRUE(index()->Has(kHash1));
338 index()->Insert(kHash1);
339 EXPECT_TRUE(index()->Has(kHash1));
340 index()->Remove(kHash1);
341 // TODO(morlovich): Maybe return false on explicitly removed entries?
342 EXPECT_TRUE(index()->Has(kHash1));
343
344 ReturnIndexFile();
345
346 // Confirm "Has() returns conditionally now.
347 EXPECT_FALSE(index()->Has(kHash1));
348 index()->Insert(kHash1);
349 EXPECT_TRUE(index()->Has(kHash1));
350 index()->Remove(kHash1);
351 }
352
TEST_F(SimpleIndexTest,UseIfExists)353 TEST_F(SimpleIndexTest, UseIfExists) {
354 // Confirm the base index has dispatched the request for index entries.
355 EXPECT_TRUE(index_file_.get());
356 EXPECT_EQ(1, index_file_->load_index_entries_calls());
357
358 // Confirm "UseIfExists()" always returns true before the callback is called
359 // and updates mod time if the entry was really there.
360 const uint64_t kHash1 = hashes_.at<1>();
361 EntryMetadata metadata1, metadata2;
362 EXPECT_TRUE(index()->UseIfExists(kHash1));
363 EXPECT_FALSE(GetEntryForTesting(kHash1, &metadata1));
364 index()->Insert(kHash1);
365 EXPECT_TRUE(index()->UseIfExists(kHash1));
366 EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata1));
367 WaitForTimeChange();
368 EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata2));
369 EXPECT_EQ(metadata1.GetLastUsedTime(), metadata2.GetLastUsedTime());
370 EXPECT_TRUE(index()->UseIfExists(kHash1));
371 EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata2));
372 EXPECT_LT(metadata1.GetLastUsedTime(), metadata2.GetLastUsedTime());
373 index()->Remove(kHash1);
374 EXPECT_TRUE(index()->UseIfExists(kHash1));
375
376 ReturnIndexFile();
377
378 // Confirm "UseIfExists() returns conditionally now
379 EXPECT_FALSE(index()->UseIfExists(kHash1));
380 EXPECT_FALSE(GetEntryForTesting(kHash1, &metadata1));
381 index()->Insert(kHash1);
382 EXPECT_TRUE(index()->UseIfExists(kHash1));
383 EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata1));
384 WaitForTimeChange();
385 EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata2));
386 EXPECT_EQ(metadata1.GetLastUsedTime(), metadata2.GetLastUsedTime());
387 EXPECT_TRUE(index()->UseIfExists(kHash1));
388 EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata2));
389 EXPECT_LT(metadata1.GetLastUsedTime(), metadata2.GetLastUsedTime());
390 index()->Remove(kHash1);
391 EXPECT_FALSE(index()->UseIfExists(kHash1));
392 }
393
TEST_F(SimpleIndexTest,UpdateEntrySize)394 TEST_F(SimpleIndexTest, UpdateEntrySize) {
395 base::Time now(base::Time::Now());
396
397 index()->SetMaxSize(1000);
398
399 const uint64_t kHash1 = hashes_.at<1>();
400 InsertIntoIndexFileReturn(kHash1, now - base::Days(2), 475);
401 ReturnIndexFile();
402
403 EntryMetadata metadata;
404 EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata));
405 EXPECT_LT(now - base::Days(2) - base::Seconds(1), metadata.GetLastUsedTime());
406 EXPECT_GT(now - base::Days(2) + base::Seconds(1), metadata.GetLastUsedTime());
407 EXPECT_EQ(RoundSize(475u), metadata.GetEntrySize());
408
409 index()->UpdateEntrySize(kHash1, 600u);
410 EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata));
411 EXPECT_EQ(RoundSize(600u), metadata.GetEntrySize());
412 EXPECT_EQ(1, index()->GetEntryCount());
413 }
414
TEST_F(SimpleIndexTest,GetEntryCount)415 TEST_F(SimpleIndexTest, GetEntryCount) {
416 EXPECT_EQ(0, index()->GetEntryCount());
417 index()->Insert(hashes_.at<1>());
418 EXPECT_EQ(1, index()->GetEntryCount());
419 index()->Insert(hashes_.at<2>());
420 EXPECT_EQ(2, index()->GetEntryCount());
421 index()->Insert(hashes_.at<3>());
422 EXPECT_EQ(3, index()->GetEntryCount());
423 index()->Insert(hashes_.at<3>());
424 EXPECT_EQ(3, index()->GetEntryCount());
425 index()->Remove(hashes_.at<2>());
426 EXPECT_EQ(2, index()->GetEntryCount());
427 index()->Insert(hashes_.at<4>());
428 EXPECT_EQ(3, index()->GetEntryCount());
429 index()->Remove(hashes_.at<3>());
430 EXPECT_EQ(2, index()->GetEntryCount());
431 index()->Remove(hashes_.at<3>());
432 EXPECT_EQ(2, index()->GetEntryCount());
433 index()->Remove(hashes_.at<1>());
434 EXPECT_EQ(1, index()->GetEntryCount());
435 index()->Remove(hashes_.at<4>());
436 EXPECT_EQ(0, index()->GetEntryCount());
437 }
438
439 // Confirm that we get the results we expect from a simple init.
TEST_F(SimpleIndexTest,BasicInit)440 TEST_F(SimpleIndexTest, BasicInit) {
441 base::Time now(base::Time::Now());
442
443 InsertIntoIndexFileReturn(hashes_.at<1>(), now - base::Days(2), 10u);
444 InsertIntoIndexFileReturn(hashes_.at<2>(), now - base::Days(3), 1000u);
445
446 ReturnIndexFile();
447
448 EntryMetadata metadata;
449 EXPECT_TRUE(GetEntryForTesting(hashes_.at<1>(), &metadata));
450 EXPECT_EQ(metadata.GetLastUsedTime(),
451 index()->GetLastUsedTime(hashes_.at<1>()));
452 EXPECT_LT(now - base::Days(2) - base::Seconds(1), metadata.GetLastUsedTime());
453 EXPECT_GT(now - base::Days(2) + base::Seconds(1), metadata.GetLastUsedTime());
454 EXPECT_EQ(RoundSize(10u), metadata.GetEntrySize());
455 EXPECT_TRUE(GetEntryForTesting(hashes_.at<2>(), &metadata));
456 EXPECT_EQ(metadata.GetLastUsedTime(),
457 index()->GetLastUsedTime(hashes_.at<2>()));
458 EXPECT_LT(now - base::Days(3) - base::Seconds(1), metadata.GetLastUsedTime());
459 EXPECT_GT(now - base::Days(3) + base::Seconds(1), metadata.GetLastUsedTime());
460 EXPECT_EQ(RoundSize(1000u), metadata.GetEntrySize());
461 EXPECT_EQ(base::Time(), index()->GetLastUsedTime(hashes_.at<3>()));
462 }
463
464 // Remove something that's going to come in from the loaded index.
TEST_F(SimpleIndexTest,RemoveBeforeInit)465 TEST_F(SimpleIndexTest, RemoveBeforeInit) {
466 const uint64_t kHash1 = hashes_.at<1>();
467 index()->Remove(kHash1);
468
469 InsertIntoIndexFileReturn(kHash1, base::Time::Now() - base::Days(2), 10u);
470 ReturnIndexFile();
471
472 EXPECT_FALSE(index()->Has(kHash1));
473 }
474
475 // Insert something that's going to come in from the loaded index; correct
476 // result?
TEST_F(SimpleIndexTest,InsertBeforeInit)477 TEST_F(SimpleIndexTest, InsertBeforeInit) {
478 const uint64_t kHash1 = hashes_.at<1>();
479 index()->Insert(kHash1);
480
481 InsertIntoIndexFileReturn(kHash1, base::Time::Now() - base::Days(2), 10u);
482 ReturnIndexFile();
483
484 EntryMetadata metadata;
485 EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata));
486 base::Time now(base::Time::Now());
487 EXPECT_LT(now - base::Minutes(1), metadata.GetLastUsedTime());
488 EXPECT_GT(now + base::Minutes(1), metadata.GetLastUsedTime());
489 EXPECT_EQ(0U, metadata.GetEntrySize());
490 }
491
492 // Insert and Remove something that's going to come in from the loaded index.
TEST_F(SimpleIndexTest,InsertRemoveBeforeInit)493 TEST_F(SimpleIndexTest, InsertRemoveBeforeInit) {
494 const uint64_t kHash1 = hashes_.at<1>();
495 index()->Insert(kHash1);
496 index()->Remove(kHash1);
497
498 InsertIntoIndexFileReturn(kHash1, base::Time::Now() - base::Days(2), 10u);
499 ReturnIndexFile();
500
501 EXPECT_FALSE(index()->Has(kHash1));
502 }
503
504 // Insert and Remove something that's going to come in from the loaded index.
TEST_F(SimpleIndexTest,RemoveInsertBeforeInit)505 TEST_F(SimpleIndexTest, RemoveInsertBeforeInit) {
506 const uint64_t kHash1 = hashes_.at<1>();
507 index()->Remove(kHash1);
508 index()->Insert(kHash1);
509
510 InsertIntoIndexFileReturn(kHash1, base::Time::Now() - base::Days(2), 10u);
511 ReturnIndexFile();
512
513 EntryMetadata metadata;
514 EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata));
515 base::Time now(base::Time::Now());
516 EXPECT_LT(now - base::Minutes(1), metadata.GetLastUsedTime());
517 EXPECT_GT(now + base::Minutes(1), metadata.GetLastUsedTime());
518 EXPECT_EQ(0U, metadata.GetEntrySize());
519 }
520
521 // Do all above tests at once + a non-conflict to test for cross-key
522 // interactions.
TEST_F(SimpleIndexTest,AllInitConflicts)523 TEST_F(SimpleIndexTest, AllInitConflicts) {
524 base::Time now(base::Time::Now());
525
526 index()->Remove(hashes_.at<1>());
527 InsertIntoIndexFileReturn(hashes_.at<1>(), now - base::Days(2), 10u);
528 index()->Insert(hashes_.at<2>());
529 InsertIntoIndexFileReturn(hashes_.at<2>(), now - base::Days(3), 100u);
530 index()->Insert(hashes_.at<3>());
531 index()->Remove(hashes_.at<3>());
532 InsertIntoIndexFileReturn(hashes_.at<3>(), now - base::Days(4), 1000u);
533 index()->Remove(hashes_.at<4>());
534 index()->Insert(hashes_.at<4>());
535 InsertIntoIndexFileReturn(hashes_.at<4>(), now - base::Days(5), 10000u);
536 InsertIntoIndexFileReturn(hashes_.at<5>(), now - base::Days(6), 100000u);
537
538 ReturnIndexFile();
539
540 EXPECT_FALSE(index()->Has(hashes_.at<1>()));
541
542 EntryMetadata metadata;
543 EXPECT_TRUE(GetEntryForTesting(hashes_.at<2>(), &metadata));
544 EXPECT_LT(now - base::Minutes(1), metadata.GetLastUsedTime());
545 EXPECT_GT(now + base::Minutes(1), metadata.GetLastUsedTime());
546 EXPECT_EQ(0U, metadata.GetEntrySize());
547
548 EXPECT_FALSE(index()->Has(hashes_.at<3>()));
549
550 EXPECT_TRUE(GetEntryForTesting(hashes_.at<4>(), &metadata));
551 EXPECT_LT(now - base::Minutes(1), metadata.GetLastUsedTime());
552 EXPECT_GT(now + base::Minutes(1), metadata.GetLastUsedTime());
553 EXPECT_EQ(0U, metadata.GetEntrySize());
554
555 EXPECT_TRUE(GetEntryForTesting(hashes_.at<5>(), &metadata));
556
557 EXPECT_GT(now - base::Days(6) + base::Seconds(1), metadata.GetLastUsedTime());
558 EXPECT_LT(now - base::Days(6) - base::Seconds(1), metadata.GetLastUsedTime());
559
560 EXPECT_EQ(RoundSize(100000u), metadata.GetEntrySize());
561 }
562
TEST_F(SimpleIndexTest,BasicEviction)563 TEST_F(SimpleIndexTest, BasicEviction) {
564 base::Time now(base::Time::Now());
565 index()->SetMaxSize(1000);
566 InsertIntoIndexFileReturn(hashes_.at<1>(), now - base::Days(2), 475u);
567 index()->Insert(hashes_.at<2>());
568 index()->UpdateEntrySize(hashes_.at<2>(), 475u);
569 ReturnIndexFile();
570
571 WaitForTimeChange();
572
573 index()->Insert(hashes_.at<3>());
574 // Confirm index is as expected: No eviction, everything there.
575 EXPECT_EQ(3, index()->GetEntryCount());
576 EXPECT_EQ(0, doom_entries_calls());
577 EXPECT_TRUE(index()->Has(hashes_.at<1>()));
578 EXPECT_TRUE(index()->Has(hashes_.at<2>()));
579 EXPECT_TRUE(index()->Has(hashes_.at<3>()));
580
581 // Trigger an eviction, and make sure the right things are tossed.
582 // TODO(morlovich): This is dependent on the innards of the implementation
583 // as to at exactly what point we trigger eviction. Not sure how to fix
584 // that.
585 index()->UpdateEntrySize(hashes_.at<3>(), 475u);
586 EXPECT_EQ(1, doom_entries_calls());
587 EXPECT_EQ(1, index()->GetEntryCount());
588 EXPECT_FALSE(index()->Has(hashes_.at<1>()));
589 EXPECT_FALSE(index()->Has(hashes_.at<2>()));
590 EXPECT_TRUE(index()->Has(hashes_.at<3>()));
591 ASSERT_EQ(2u, last_doom_entry_hashes().size());
592 }
593
TEST_F(SimpleIndexTest,EvictBySize)594 TEST_F(SimpleIndexTest, EvictBySize) {
595 base::Time now(base::Time::Now());
596 index()->SetMaxSize(50000);
597 InsertIntoIndexFileReturn(hashes_.at<1>(), now - base::Days(2), 475u);
598 InsertIntoIndexFileReturn(hashes_.at<2>(), now - base::Days(1), 40000u);
599 ReturnIndexFile();
600 WaitForTimeChange();
601
602 index()->Insert(hashes_.at<3>());
603 // Confirm index is as expected: No eviction, everything there.
604 EXPECT_EQ(3, index()->GetEntryCount());
605 EXPECT_EQ(0, doom_entries_calls());
606 EXPECT_TRUE(index()->Has(hashes_.at<1>()));
607 EXPECT_TRUE(index()->Has(hashes_.at<2>()));
608 EXPECT_TRUE(index()->Has(hashes_.at<3>()));
609
610 // Trigger an eviction, and make sure the right things are tossed.
611 // TODO(morlovich): This is dependent on the innards of the implementation
612 // as to at exactly what point we trigger eviction. Not sure how to fix
613 // that.
614 index()->UpdateEntrySize(hashes_.at<3>(), 40000u);
615 EXPECT_EQ(1, doom_entries_calls());
616 EXPECT_EQ(2, index()->GetEntryCount());
617 EXPECT_TRUE(index()->Has(hashes_.at<1>()));
618 EXPECT_FALSE(index()->Has(hashes_.at<2>()));
619 EXPECT_TRUE(index()->Has(hashes_.at<3>()));
620 ASSERT_EQ(1u, last_doom_entry_hashes().size());
621 }
622
TEST_F(SimpleIndexCodeCacheTest,DisableEvictBySize)623 TEST_F(SimpleIndexCodeCacheTest, DisableEvictBySize) {
624 base::Time now(base::Time::Now());
625 index()->SetMaxSize(50000);
626 InsertIntoIndexFileReturn(hashes_.at<1>(), now - base::Days(2), 475u);
627 InsertIntoIndexFileReturn(hashes_.at<2>(), now - base::Days(1), 40000u);
628 ReturnIndexFile();
629 WaitForTimeChange();
630
631 index()->Insert(hashes_.at<3>());
632 // Confirm index is as expected: No eviction, everything there.
633 EXPECT_EQ(3, index()->GetEntryCount());
634 EXPECT_EQ(0, doom_entries_calls());
635 EXPECT_TRUE(index()->Has(hashes_.at<1>()));
636 EXPECT_TRUE(index()->Has(hashes_.at<2>()));
637 EXPECT_TRUE(index()->Has(hashes_.at<3>()));
638
639 // Trigger an eviction, and make sure the right things are tossed.
640 // Since evict by size is supposed to be disabled, it evicts in LRU order,
641 // so entries 1 and 2 are both kicked out.
642 index()->UpdateEntrySize(hashes_.at<3>(), 40000u);
643 EXPECT_EQ(1, doom_entries_calls());
644 EXPECT_EQ(1, index()->GetEntryCount());
645 EXPECT_FALSE(index()->Has(hashes_.at<1>()));
646 EXPECT_FALSE(index()->Has(hashes_.at<2>()));
647 EXPECT_TRUE(index()->Has(hashes_.at<3>()));
648 ASSERT_EQ(2u, last_doom_entry_hashes().size());
649 }
650
651 // Same as test above, but using much older entries to make sure that small
652 // things eventually get evictied.
TEST_F(SimpleIndexTest,EvictBySize2)653 TEST_F(SimpleIndexTest, EvictBySize2) {
654 base::Time now(base::Time::Now());
655 index()->SetMaxSize(50000);
656 InsertIntoIndexFileReturn(hashes_.at<1>(), now - base::Days(200), 475u);
657 InsertIntoIndexFileReturn(hashes_.at<2>(), now - base::Days(1), 40000u);
658 ReturnIndexFile();
659 WaitForTimeChange();
660
661 index()->Insert(hashes_.at<3>());
662 // Confirm index is as expected: No eviction, everything there.
663 EXPECT_EQ(3, index()->GetEntryCount());
664 EXPECT_EQ(0, doom_entries_calls());
665 EXPECT_TRUE(index()->Has(hashes_.at<1>()));
666 EXPECT_TRUE(index()->Has(hashes_.at<2>()));
667 EXPECT_TRUE(index()->Has(hashes_.at<3>()));
668
669 // Trigger an eviction, and make sure the right things are tossed.
670 // TODO(morlovich): This is dependent on the innards of the implementation
671 // as to at exactly what point we trigger eviction. Not sure how to fix
672 // that.
673 index()->UpdateEntrySize(hashes_.at<3>(), 40000u);
674 EXPECT_EQ(1, doom_entries_calls());
675 EXPECT_EQ(1, index()->GetEntryCount());
676 EXPECT_FALSE(index()->Has(hashes_.at<1>()));
677 EXPECT_FALSE(index()->Has(hashes_.at<2>()));
678 EXPECT_TRUE(index()->Has(hashes_.at<3>()));
679 ASSERT_EQ(2u, last_doom_entry_hashes().size());
680 }
681
682 class SimpleIndexPrioritizedCachingTest : public SimpleIndexTest {
683 public:
SimpleIndexPrioritizedCachingTest()684 SimpleIndexPrioritizedCachingTest() {
685 feature_list_.InitAndEnableFeature(
686 net::features::kSimpleCachePrioritizedCaching);
687 }
688 ~SimpleIndexPrioritizedCachingTest() override = default;
689
690 private:
691 base::test::ScopedFeatureList feature_list_;
692 };
693
TEST_F(SimpleIndexPrioritizedCachingTest,EvictPrioritization)694 TEST_F(SimpleIndexPrioritizedCachingTest, EvictPrioritization) {
695 const auto caching_prioritization_period =
696 net::features::kSimpleCachePrioritizedCachingPrioritizationPeriod.Get();
697 auto now = base::Time::Now();
698 index()->SetMaxSize(50000);
699 InsertIntoIndexFileWithPrioritizeCachingFlagReturn(
700 hashes_.at<1>(), now - caching_prioritization_period * 0.8, 20000u);
701 InsertIntoIndexFileReturn(hashes_.at<2>(),
702 now - caching_prioritization_period * 0.4, 20000u);
703 ReturnIndexFile();
704 WaitForTimeChange();
705
706 index()->Insert(hashes_.at<3>());
707 // Confirm index is as expected: No eviction, everything there.
708 EXPECT_EQ(3, index()->GetEntryCount());
709 EXPECT_EQ(0, doom_entries_calls());
710 EXPECT_TRUE(index()->Has(hashes_.at<1>()));
711 EXPECT_TRUE(index()->Has(hashes_.at<2>()));
712 EXPECT_TRUE(index()->Has(hashes_.at<3>()));
713
714 // Trigger an eviction, and make sure the right things are tossed.
715 index()->UpdateEntrySize(hashes_.at<3>(), 20000u);
716 EXPECT_EQ(1, doom_entries_calls());
717 EXPECT_EQ(2, index()->GetEntryCount());
718 // The entry with the priority flag is kept, even if it's older.
719 EXPECT_TRUE(index()->Has(hashes_.at<1>()));
720 // The entry without the priority flag is evicted, even if it's newer.
721 EXPECT_FALSE(index()->Has(hashes_.at<2>()));
722 EXPECT_TRUE(index()->Has(hashes_.at<3>()));
723 ASSERT_EQ(1u, last_doom_entry_hashes().size());
724 }
725
TEST_F(SimpleIndexPrioritizedCachingTest,EvictPrioritizationOutOfPeriod)726 TEST_F(SimpleIndexPrioritizedCachingTest, EvictPrioritizationOutOfPeriod) {
727 const auto caching_prioritization_period =
728 net::features::kSimpleCachePrioritizedCachingPrioritizationPeriod.Get();
729 auto now = base::Time::Now();
730 index()->SetMaxSize(50000);
731 InsertIntoIndexFileWithPrioritizeCachingFlagReturn(
732 hashes_.at<1>(), now - caching_prioritization_period * 2, 20000u);
733 InsertIntoIndexFileReturn(hashes_.at<2>(),
734 now - caching_prioritization_period, 20000u);
735 ReturnIndexFile();
736 WaitForTimeChange();
737
738 index()->Insert(hashes_.at<3>());
739 // Confirm index is as expected: No eviction, everything there.
740 EXPECT_EQ(3, index()->GetEntryCount());
741 EXPECT_EQ(0, doom_entries_calls());
742 EXPECT_TRUE(index()->Has(hashes_.at<1>()));
743 EXPECT_TRUE(index()->Has(hashes_.at<2>()));
744 EXPECT_TRUE(index()->Has(hashes_.at<3>()));
745
746 // Trigger an eviction, and make sure the right things are tossed.
747 index()->UpdateEntrySize(hashes_.at<3>(), 20000u);
748 EXPECT_EQ(1, doom_entries_calls());
749 EXPECT_EQ(2, index()->GetEntryCount());
750 // The older entry is evicted, even if it has the priority flag, when the
751 // entry is out of the prioritization period.
752 EXPECT_FALSE(index()->Has(hashes_.at<1>()));
753 EXPECT_TRUE(index()->Has(hashes_.at<2>()));
754 EXPECT_TRUE(index()->Has(hashes_.at<3>()));
755 ASSERT_EQ(1u, last_doom_entry_hashes().size());
756 }
757
TEST_F(SimpleIndexTest,EvictPrioritizationFeatureDefaultDisabled)758 TEST_F(SimpleIndexTest, EvictPrioritizationFeatureDefaultDisabled) {
759 const auto caching_prioritization_period =
760 net::features::kSimpleCachePrioritizedCachingPrioritizationPeriod.Get();
761 auto now = base::Time::Now();
762 index()->SetMaxSize(50000);
763 InsertIntoIndexFileWithPrioritizeCachingFlagReturn(
764 hashes_.at<1>(), now - caching_prioritization_period * 0.8, 20000u);
765 InsertIntoIndexFileReturn(hashes_.at<2>(),
766 now - caching_prioritization_period * 0.4, 20000u);
767 ReturnIndexFile();
768 WaitForTimeChange();
769
770 index()->Insert(hashes_.at<3>());
771 // Confirm index is as expected: No eviction, everything there.
772 EXPECT_EQ(3, index()->GetEntryCount());
773 EXPECT_EQ(0, doom_entries_calls());
774 EXPECT_TRUE(index()->Has(hashes_.at<1>()));
775 EXPECT_TRUE(index()->Has(hashes_.at<2>()));
776 EXPECT_TRUE(index()->Has(hashes_.at<3>()));
777
778 // Trigger an eviction, and make sure the right things are tossed.
779 index()->UpdateEntrySize(hashes_.at<3>(), 20000u);
780 EXPECT_EQ(1, doom_entries_calls());
781 EXPECT_EQ(2, index()->GetEntryCount());
782 // The older entry is evicted, even if it has the priority flag, when the
783 // feature is disabled.
784 EXPECT_FALSE(index()->Has(hashes_.at<1>()));
785 // The newer entry is kept, even if it doesn't have the priority flag, when
786 // the feature is disabled.
787 EXPECT_TRUE(index()->Has(hashes_.at<2>()));
788 EXPECT_TRUE(index()->Has(hashes_.at<3>()));
789 ASSERT_EQ(1u, last_doom_entry_hashes().size());
790 }
791
792 // Confirm all the operations queue a disk write at some point in the
793 // future.
TEST_F(SimpleIndexTest,DiskWriteQueued)794 TEST_F(SimpleIndexTest, DiskWriteQueued) {
795 index()->SetMaxSize(1000);
796 ReturnIndexFile();
797
798 EXPECT_FALSE(index()->HasPendingWrite());
799
800 const uint64_t kHash1 = hashes_.at<1>();
801 index()->Insert(kHash1);
802 EXPECT_TRUE(index()->HasPendingWrite());
803 index()->write_to_disk_timer_.Stop();
804 EXPECT_FALSE(index()->HasPendingWrite());
805
806 // Attempting to insert a hash that already exists should not queue the
807 // write timer.
808 index()->Insert(kHash1);
809 EXPECT_FALSE(index()->HasPendingWrite());
810
811 index()->UseIfExists(kHash1);
812 EXPECT_TRUE(index()->HasPendingWrite());
813 index()->write_to_disk_timer_.Stop();
814
815 index()->UpdateEntrySize(kHash1, 20u);
816 EXPECT_TRUE(index()->HasPendingWrite());
817 index()->write_to_disk_timer_.Stop();
818
819 // Updating to the same size should not queue the write timer.
820 index()->UpdateEntrySize(kHash1, 20u);
821 EXPECT_FALSE(index()->HasPendingWrite());
822
823 index()->Remove(kHash1);
824 EXPECT_TRUE(index()->HasPendingWrite());
825 index()->write_to_disk_timer_.Stop();
826
827 // Removing a non-existent hash should not queue the write timer.
828 index()->Remove(kHash1);
829 EXPECT_FALSE(index()->HasPendingWrite());
830 }
831
TEST_F(SimpleIndexTest,DiskWriteExecuted)832 TEST_F(SimpleIndexTest, DiskWriteExecuted) {
833 index()->SetMaxSize(1000);
834 ReturnIndexFile();
835
836 EXPECT_FALSE(index()->HasPendingWrite());
837
838 const uint64_t kHash1 = hashes_.at<1>();
839 index()->Insert(kHash1);
840 index()->UpdateEntrySize(kHash1, 20u);
841 EXPECT_TRUE(index()->HasPendingWrite());
842
843 EXPECT_EQ(0, index_file_->disk_writes());
844 index()->write_to_disk_timer_.FireNow();
845 EXPECT_EQ(1, index_file_->disk_writes());
846 SimpleIndex::EntrySet entry_set;
847 index_file_->GetAndResetDiskWriteEntrySet(&entry_set);
848
849 uint64_t hash_key = kHash1;
850 base::Time now(base::Time::Now());
851 ASSERT_EQ(1u, entry_set.size());
852 EXPECT_EQ(hash_key, entry_set.begin()->first);
853 const EntryMetadata& entry1(entry_set.begin()->second);
854 EXPECT_LT(now - base::Minutes(1), entry1.GetLastUsedTime());
855 EXPECT_GT(now + base::Minutes(1), entry1.GetLastUsedTime());
856 EXPECT_EQ(RoundSize(20u), entry1.GetEntrySize());
857 }
858
TEST_F(SimpleIndexTest,DiskWritePostponed)859 TEST_F(SimpleIndexTest, DiskWritePostponed) {
860 index()->SetMaxSize(1000);
861 ReturnIndexFile();
862
863 EXPECT_FALSE(index()->HasPendingWrite());
864
865 index()->Insert(hashes_.at<1>());
866 index()->UpdateEntrySize(hashes_.at<1>(), 20u);
867 EXPECT_TRUE(index()->HasPendingWrite());
868 base::TimeTicks expected_trigger(
869 index()->write_to_disk_timer_.desired_run_time());
870
871 WaitForTimeChange();
872 EXPECT_EQ(expected_trigger, index()->write_to_disk_timer_.desired_run_time());
873 index()->Insert(hashes_.at<2>());
874 index()->UpdateEntrySize(hashes_.at<2>(), 40u);
875 EXPECT_TRUE(index()->HasPendingWrite());
876 EXPECT_LT(expected_trigger, index()->write_to_disk_timer_.desired_run_time());
877 index()->write_to_disk_timer_.Stop();
878 }
879
880 // net::APP_CACHE mode should not need to queue disk writes in as many places
881 // as the default net::DISK_CACHE mode.
TEST_F(SimpleIndexAppCacheTest,DiskWriteQueued)882 TEST_F(SimpleIndexAppCacheTest, DiskWriteQueued) {
883 index()->SetMaxSize(1000);
884 ReturnIndexFile();
885
886 EXPECT_FALSE(index()->HasPendingWrite());
887
888 const uint64_t kHash1 = hashes_.at<1>();
889 index()->Insert(kHash1);
890 EXPECT_TRUE(index()->HasPendingWrite());
891 index()->write_to_disk_timer_.Stop();
892 EXPECT_FALSE(index()->HasPendingWrite());
893
894 // Attempting to insert a hash that already exists should not queue the
895 // write timer.
896 index()->Insert(kHash1);
897 EXPECT_FALSE(index()->HasPendingWrite());
898
899 // Since net::APP_CACHE does not evict or track access times using an
900 // entry should not queue the write timer.
901 index()->UseIfExists(kHash1);
902 EXPECT_FALSE(index()->HasPendingWrite());
903
904 index()->UpdateEntrySize(kHash1, 20u);
905 EXPECT_TRUE(index()->HasPendingWrite());
906 index()->write_to_disk_timer_.Stop();
907
908 // Updating to the same size should not queue the write timer.
909 index()->UpdateEntrySize(kHash1, 20u);
910 EXPECT_FALSE(index()->HasPendingWrite());
911
912 index()->Remove(kHash1);
913 EXPECT_TRUE(index()->HasPendingWrite());
914 index()->write_to_disk_timer_.Stop();
915
916 // Removing a non-existent hash should not queue the write timer.
917 index()->Remove(kHash1);
918 EXPECT_FALSE(index()->HasPendingWrite());
919 }
920
921 } // namespace disk_cache
922