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