• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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