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