1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
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 <algorithm>
6#include <functional>
7
8#include "base/files/scoped_temp_dir.h"
9#include "base/hash.h"
10#include "base/logging.h"
11#include "base/memory/scoped_ptr.h"
12#include "base/pickle.h"
13#include "base/sha1.h"
14#include "base/strings/stringprintf.h"
15#include "base/task_runner.h"
16#include "base/threading/platform_thread.h"
17#include "base/time/time.h"
18#include "net/base/cache_type.h"
19#include "net/disk_cache/simple/simple_index.h"
20#include "net/disk_cache/simple/simple_index_delegate.h"
21#include "net/disk_cache/simple/simple_index_file.h"
22#include "net/disk_cache/simple/simple_test_util.h"
23#include "net/disk_cache/simple/simple_util.h"
24#include "testing/gtest/include/gtest/gtest.h"
25
26namespace disk_cache {
27namespace {
28
29const base::Time kTestLastUsedTime =
30    base::Time::UnixEpoch() + base::TimeDelta::FromDays(20);
31const int kTestEntrySize = 789;
32
33}  // namespace
34
35
36class EntryMetadataTest  : public testing::Test {
37 public:
38  EntryMetadata NewEntryMetadataWithValues() {
39    return EntryMetadata(kTestLastUsedTime, kTestEntrySize);
40  }
41
42  void CheckEntryMetadataValues(const EntryMetadata& entry_metadata) {
43    EXPECT_LT(kTestLastUsedTime - base::TimeDelta::FromSeconds(2),
44              entry_metadata.GetLastUsedTime());
45    EXPECT_GT(kTestLastUsedTime + base::TimeDelta::FromSeconds(2),
46              entry_metadata.GetLastUsedTime());
47    EXPECT_EQ(kTestEntrySize, entry_metadata.GetEntrySize());
48  }
49};
50
51class MockSimpleIndexFile : public SimpleIndexFile,
52                            public base::SupportsWeakPtr<MockSimpleIndexFile> {
53 public:
54  MockSimpleIndexFile()
55      : SimpleIndexFile(NULL, NULL, net::DISK_CACHE, base::FilePath()),
56        load_result_(NULL),
57        load_index_entries_calls_(0),
58        disk_writes_(0) {}
59
60  virtual void LoadIndexEntries(
61      base::Time cache_last_modified,
62      const base::Closure& callback,
63      SimpleIndexLoadResult* out_load_result) OVERRIDE {
64    load_callback_ = callback;
65    load_result_ = out_load_result;
66    ++load_index_entries_calls_;
67  }
68
69  virtual void WriteToDisk(const SimpleIndex::EntrySet& entry_set,
70                           uint64 cache_size,
71                           const base::TimeTicks& start,
72                           bool app_on_background) OVERRIDE {
73    disk_writes_++;
74    disk_write_entry_set_ = entry_set;
75  }
76
77  void GetAndResetDiskWriteEntrySet(SimpleIndex::EntrySet* entry_set) {
78    entry_set->swap(disk_write_entry_set_);
79  }
80
81  const base::Closure& load_callback() const { return load_callback_; }
82  SimpleIndexLoadResult* load_result() const { return load_result_; }
83  int load_index_entries_calls() const { return load_index_entries_calls_; }
84  int disk_writes() const { return disk_writes_; }
85
86 private:
87  base::Closure load_callback_;
88  SimpleIndexLoadResult* load_result_;
89  int load_index_entries_calls_;
90  int disk_writes_;
91  SimpleIndex::EntrySet disk_write_entry_set_;
92};
93
94class SimpleIndexTest  : public testing::Test, public SimpleIndexDelegate {
95 protected:
96  SimpleIndexTest()
97      : hashes_(base::Bind(&HashesInitializer)),
98        doom_entries_calls_(0) {}
99
100  static uint64 HashesInitializer(size_t hash_index) {
101    return disk_cache::simple_util::GetEntryHashKey(
102        base::StringPrintf("key%d", static_cast<int>(hash_index)));
103  }
104
105  virtual void SetUp() OVERRIDE {
106    scoped_ptr<MockSimpleIndexFile> index_file(new MockSimpleIndexFile());
107    index_file_ = index_file->AsWeakPtr();
108    index_.reset(new SimpleIndex(NULL, this, net::DISK_CACHE,
109                                 index_file.PassAs<SimpleIndexFile>()));
110
111    index_->Initialize(base::Time());
112  }
113
114  void WaitForTimeChange() {
115    const base::Time initial_time = base::Time::Now();
116    do {
117      base::PlatformThread::YieldCurrentThread();
118    } while (base::Time::Now() -
119             initial_time < base::TimeDelta::FromSeconds(1));
120  }
121
122  // From SimpleIndexDelegate:
123  virtual void DoomEntries(std::vector<uint64>* entry_hashes,
124                           const net::CompletionCallback& callback) OVERRIDE {
125    std::for_each(entry_hashes->begin(), entry_hashes->end(),
126                  std::bind1st(std::mem_fun(&SimpleIndex::Remove),
127                               index_.get()));
128    last_doom_entry_hashes_ = *entry_hashes;
129    ++doom_entries_calls_;
130  }
131
132  // Redirect to allow single "friend" declaration in base class.
133  bool GetEntryForTesting(uint64 key, EntryMetadata* metadata) {
134    SimpleIndex::EntrySet::iterator it = index_->entries_set_.find(key);
135    if (index_->entries_set_.end() == it)
136      return false;
137    *metadata = it->second;
138    return true;
139  }
140
141  void InsertIntoIndexFileReturn(uint64 hash_key,
142                                 base::Time last_used_time,
143                                 int entry_size) {
144    index_file_->load_result()->entries.insert(std::make_pair(
145        hash_key, EntryMetadata(last_used_time, entry_size)));
146  }
147
148  void ReturnIndexFile() {
149    index_file_->load_result()->did_load = true;
150    index_file_->load_callback().Run();
151  }
152
153  // Non-const for timer manipulation.
154  SimpleIndex* index() { return index_.get(); }
155  const MockSimpleIndexFile* index_file() const { return index_file_.get(); }
156
157  const std::vector<uint64>& last_doom_entry_hashes() const {
158    return last_doom_entry_hashes_;
159  }
160  int doom_entries_calls() const { return doom_entries_calls_; }
161
162
163  const simple_util::ImmutableArray<uint64, 16> hashes_;
164  scoped_ptr<SimpleIndex> index_;
165  base::WeakPtr<MockSimpleIndexFile> index_file_;
166
167  std::vector<uint64> last_doom_entry_hashes_;
168  int doom_entries_calls_;
169};
170
171TEST_F(EntryMetadataTest, Basics) {
172  EntryMetadata entry_metadata;
173  EXPECT_EQ(base::Time(), entry_metadata.GetLastUsedTime());
174  EXPECT_EQ(0, entry_metadata.GetEntrySize());
175
176  entry_metadata = NewEntryMetadataWithValues();
177  CheckEntryMetadataValues(entry_metadata);
178
179  const base::Time new_time = base::Time::Now();
180  entry_metadata.SetLastUsedTime(new_time);
181
182  EXPECT_LT(new_time - base::TimeDelta::FromSeconds(2),
183            entry_metadata.GetLastUsedTime());
184  EXPECT_GT(new_time + base::TimeDelta::FromSeconds(2),
185            entry_metadata.GetLastUsedTime());
186}
187
188TEST_F(EntryMetadataTest, Serialize) {
189  EntryMetadata entry_metadata = NewEntryMetadataWithValues();
190
191  Pickle pickle;
192  entry_metadata.Serialize(&pickle);
193
194  PickleIterator it(pickle);
195  EntryMetadata new_entry_metadata;
196  new_entry_metadata.Deserialize(&it);
197  CheckEntryMetadataValues(new_entry_metadata);
198}
199
200TEST_F(SimpleIndexTest, IndexSizeCorrectOnMerge) {
201  index()->SetMaxSize(100);
202  index()->Insert(hashes_.at<2>());
203  index()->UpdateEntrySize(hashes_.at<2>(), 2);
204  index()->Insert(hashes_.at<3>());
205  index()->UpdateEntrySize(hashes_.at<3>(), 3);
206  index()->Insert(hashes_.at<4>());
207  index()->UpdateEntrySize(hashes_.at<4>(), 4);
208  EXPECT_EQ(9U, index()->cache_size_);
209  {
210    scoped_ptr<SimpleIndexLoadResult> result(new SimpleIndexLoadResult());
211    result->did_load = true;
212    index()->MergeInitializingSet(result.Pass());
213  }
214  EXPECT_EQ(9U, index()->cache_size_);
215  {
216    scoped_ptr<SimpleIndexLoadResult> result(new SimpleIndexLoadResult());
217    result->did_load = true;
218    const uint64 new_hash_key = hashes_.at<11>();
219    result->entries.insert(
220        std::make_pair(new_hash_key, EntryMetadata(base::Time::Now(), 11)));
221    const uint64 redundant_hash_key = hashes_.at<4>();
222    result->entries.insert(std::make_pair(redundant_hash_key,
223                                          EntryMetadata(base::Time::Now(), 4)));
224    index()->MergeInitializingSet(result.Pass());
225  }
226  EXPECT_EQ(2U + 3U + 4U + 11U, index()->cache_size_);
227}
228
229// State of index changes as expected with an insert and a remove.
230TEST_F(SimpleIndexTest, BasicInsertRemove) {
231  // Confirm blank state.
232  EntryMetadata metadata;
233  EXPECT_EQ(base::Time(), metadata.GetLastUsedTime());
234  EXPECT_EQ(0, metadata.GetEntrySize());
235
236  // Confirm state after insert.
237  index()->Insert(hashes_.at<1>());
238  ASSERT_TRUE(GetEntryForTesting(hashes_.at<1>(), &metadata));
239  base::Time now(base::Time::Now());
240  EXPECT_LT(now - base::TimeDelta::FromMinutes(1), metadata.GetLastUsedTime());
241  EXPECT_GT(now + base::TimeDelta::FromMinutes(1), metadata.GetLastUsedTime());
242  EXPECT_EQ(0, metadata.GetEntrySize());
243
244  // Confirm state after remove.
245  metadata = EntryMetadata();
246  index()->Remove(hashes_.at<1>());
247  EXPECT_FALSE(GetEntryForTesting(hashes_.at<1>(), &metadata));
248  EXPECT_EQ(base::Time(), metadata.GetLastUsedTime());
249  EXPECT_EQ(0, metadata.GetEntrySize());
250}
251
252TEST_F(SimpleIndexTest, Has) {
253  // Confirm the base index has dispatched the request for index entries.
254  EXPECT_TRUE(index_file_.get());
255  EXPECT_EQ(1, index_file_->load_index_entries_calls());
256
257  // Confirm "Has()" always returns true before the callback is called.
258  const uint64 kHash1 = hashes_.at<1>();
259  EXPECT_TRUE(index()->Has(kHash1));
260  index()->Insert(kHash1);
261  EXPECT_TRUE(index()->Has(kHash1));
262  index()->Remove(kHash1);
263  // TODO(rdsmith): Maybe return false on explicitly removed entries?
264  EXPECT_TRUE(index()->Has(kHash1));
265
266  ReturnIndexFile();
267
268  // Confirm "Has() returns conditionally now.
269  EXPECT_FALSE(index()->Has(kHash1));
270  index()->Insert(kHash1);
271  EXPECT_TRUE(index()->Has(kHash1));
272  index()->Remove(kHash1);
273}
274
275TEST_F(SimpleIndexTest, UseIfExists) {
276  // Confirm the base index has dispatched the request for index entries.
277  EXPECT_TRUE(index_file_.get());
278  EXPECT_EQ(1, index_file_->load_index_entries_calls());
279
280  // Confirm "UseIfExists()" always returns true before the callback is called
281  // and updates mod time if the entry was really there.
282  const uint64 kHash1 = hashes_.at<1>();
283  EntryMetadata metadata1, metadata2;
284  EXPECT_TRUE(index()->UseIfExists(kHash1));
285  EXPECT_FALSE(GetEntryForTesting(kHash1, &metadata1));
286  index()->Insert(kHash1);
287  EXPECT_TRUE(index()->UseIfExists(kHash1));
288  EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata1));
289  WaitForTimeChange();
290  EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata2));
291  EXPECT_EQ(metadata1.GetLastUsedTime(), metadata2.GetLastUsedTime());
292  EXPECT_TRUE(index()->UseIfExists(kHash1));
293  EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata2));
294  EXPECT_LT(metadata1.GetLastUsedTime(), metadata2.GetLastUsedTime());
295  index()->Remove(kHash1);
296  EXPECT_TRUE(index()->UseIfExists(kHash1));
297
298  ReturnIndexFile();
299
300  // Confirm "UseIfExists() returns conditionally now
301  EXPECT_FALSE(index()->UseIfExists(kHash1));
302  EXPECT_FALSE(GetEntryForTesting(kHash1, &metadata1));
303  index()->Insert(kHash1);
304  EXPECT_TRUE(index()->UseIfExists(kHash1));
305  EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata1));
306  WaitForTimeChange();
307  EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata2));
308  EXPECT_EQ(metadata1.GetLastUsedTime(), metadata2.GetLastUsedTime());
309  EXPECT_TRUE(index()->UseIfExists(kHash1));
310  EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata2));
311  EXPECT_LT(metadata1.GetLastUsedTime(), metadata2.GetLastUsedTime());
312  index()->Remove(kHash1);
313  EXPECT_FALSE(index()->UseIfExists(kHash1));
314}
315
316TEST_F(SimpleIndexTest, UpdateEntrySize) {
317  base::Time now(base::Time::Now());
318
319  index()->SetMaxSize(1000);
320
321  const uint64 kHash1 = hashes_.at<1>();
322  InsertIntoIndexFileReturn(kHash1, now - base::TimeDelta::FromDays(2), 475);
323  ReturnIndexFile();
324
325  EntryMetadata metadata;
326  EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata));
327  EXPECT_LT(
328      now - base::TimeDelta::FromDays(2) - base::TimeDelta::FromSeconds(1),
329      metadata.GetLastUsedTime());
330  EXPECT_GT(
331      now - base::TimeDelta::FromDays(2) + base::TimeDelta::FromSeconds(1),
332      metadata.GetLastUsedTime());
333  EXPECT_EQ(475, metadata.GetEntrySize());
334
335  index()->UpdateEntrySize(kHash1, 600u);
336  EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata));
337  EXPECT_EQ(600, metadata.GetEntrySize());
338  EXPECT_EQ(1, index()->GetEntryCount());
339}
340
341TEST_F(SimpleIndexTest, GetEntryCount) {
342  EXPECT_EQ(0, index()->GetEntryCount());
343  index()->Insert(hashes_.at<1>());
344  EXPECT_EQ(1, index()->GetEntryCount());
345  index()->Insert(hashes_.at<2>());
346  EXPECT_EQ(2, index()->GetEntryCount());
347  index()->Insert(hashes_.at<3>());
348  EXPECT_EQ(3, index()->GetEntryCount());
349  index()->Insert(hashes_.at<3>());
350  EXPECT_EQ(3, index()->GetEntryCount());
351  index()->Remove(hashes_.at<2>());
352  EXPECT_EQ(2, index()->GetEntryCount());
353  index()->Insert(hashes_.at<4>());
354  EXPECT_EQ(3, index()->GetEntryCount());
355  index()->Remove(hashes_.at<3>());
356  EXPECT_EQ(2, index()->GetEntryCount());
357  index()->Remove(hashes_.at<3>());
358  EXPECT_EQ(2, index()->GetEntryCount());
359  index()->Remove(hashes_.at<1>());
360  EXPECT_EQ(1, index()->GetEntryCount());
361  index()->Remove(hashes_.at<4>());
362  EXPECT_EQ(0, index()->GetEntryCount());
363}
364
365// Confirm that we get the results we expect from a simple init.
366TEST_F(SimpleIndexTest, BasicInit) {
367  base::Time now(base::Time::Now());
368
369  InsertIntoIndexFileReturn(hashes_.at<1>(),
370                            now - base::TimeDelta::FromDays(2),
371                            10u);
372  InsertIntoIndexFileReturn(hashes_.at<2>(),
373                            now - base::TimeDelta::FromDays(3),
374                            100u);
375
376  ReturnIndexFile();
377
378  EntryMetadata metadata;
379  EXPECT_TRUE(GetEntryForTesting(hashes_.at<1>(), &metadata));
380  EXPECT_LT(
381      now - base::TimeDelta::FromDays(2) - base::TimeDelta::FromSeconds(1),
382      metadata.GetLastUsedTime());
383  EXPECT_GT(
384      now - base::TimeDelta::FromDays(2) + base::TimeDelta::FromSeconds(1),
385      metadata.GetLastUsedTime());
386  EXPECT_EQ(10, metadata.GetEntrySize());
387  EXPECT_TRUE(GetEntryForTesting(hashes_.at<2>(), &metadata));
388  EXPECT_LT(
389      now - base::TimeDelta::FromDays(3) - base::TimeDelta::FromSeconds(1),
390      metadata.GetLastUsedTime());
391  EXPECT_GT(
392      now - base::TimeDelta::FromDays(3) + base::TimeDelta::FromSeconds(1),
393      metadata.GetLastUsedTime());
394  EXPECT_EQ(100, metadata.GetEntrySize());
395}
396
397// Remove something that's going to come in from the loaded index.
398TEST_F(SimpleIndexTest, RemoveBeforeInit) {
399  const uint64 kHash1 = hashes_.at<1>();
400  index()->Remove(kHash1);
401
402  InsertIntoIndexFileReturn(kHash1,
403                            base::Time::Now() - base::TimeDelta::FromDays(2),
404                            10u);
405  ReturnIndexFile();
406
407  EXPECT_FALSE(index()->Has(kHash1));
408}
409
410// Insert something that's going to come in from the loaded index; correct
411// result?
412TEST_F(SimpleIndexTest, InsertBeforeInit) {
413  const uint64 kHash1 = hashes_.at<1>();
414  index()->Insert(kHash1);
415
416  InsertIntoIndexFileReturn(kHash1,
417                            base::Time::Now() - base::TimeDelta::FromDays(2),
418                            10u);
419  ReturnIndexFile();
420
421  EntryMetadata metadata;
422  EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata));
423  base::Time now(base::Time::Now());
424  EXPECT_LT(now - base::TimeDelta::FromMinutes(1), metadata.GetLastUsedTime());
425  EXPECT_GT(now + base::TimeDelta::FromMinutes(1), metadata.GetLastUsedTime());
426  EXPECT_EQ(0, metadata.GetEntrySize());
427}
428
429// Insert and Remove something that's going to come in from the loaded index.
430TEST_F(SimpleIndexTest, InsertRemoveBeforeInit) {
431  const uint64 kHash1 = hashes_.at<1>();
432  index()->Insert(kHash1);
433  index()->Remove(kHash1);
434
435  InsertIntoIndexFileReturn(kHash1,
436                            base::Time::Now() - base::TimeDelta::FromDays(2),
437                            10u);
438  ReturnIndexFile();
439
440  EXPECT_FALSE(index()->Has(kHash1));
441}
442
443// Insert and Remove something that's going to come in from the loaded index.
444TEST_F(SimpleIndexTest, RemoveInsertBeforeInit) {
445  const uint64 kHash1 = hashes_.at<1>();
446  index()->Remove(kHash1);
447  index()->Insert(kHash1);
448
449  InsertIntoIndexFileReturn(kHash1,
450                            base::Time::Now() - base::TimeDelta::FromDays(2),
451                            10u);
452  ReturnIndexFile();
453
454  EntryMetadata metadata;
455  EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata));
456  base::Time now(base::Time::Now());
457  EXPECT_LT(now - base::TimeDelta::FromMinutes(1), metadata.GetLastUsedTime());
458  EXPECT_GT(now + base::TimeDelta::FromMinutes(1), metadata.GetLastUsedTime());
459  EXPECT_EQ(0, metadata.GetEntrySize());
460}
461
462// Do all above tests at once + a non-conflict to test for cross-key
463// interactions.
464TEST_F(SimpleIndexTest, AllInitConflicts) {
465  base::Time now(base::Time::Now());
466
467  index()->Remove(hashes_.at<1>());
468  InsertIntoIndexFileReturn(hashes_.at<1>(),
469                            now - base::TimeDelta::FromDays(2),
470                            10u);
471  index()->Insert(hashes_.at<2>());
472  InsertIntoIndexFileReturn(hashes_.at<2>(),
473                            now - base::TimeDelta::FromDays(3),
474                            100u);
475  index()->Insert(hashes_.at<3>());
476  index()->Remove(hashes_.at<3>());
477  InsertIntoIndexFileReturn(hashes_.at<3>(),
478                            now - base::TimeDelta::FromDays(4),
479                            1000u);
480  index()->Remove(hashes_.at<4>());
481  index()->Insert(hashes_.at<4>());
482  InsertIntoIndexFileReturn(hashes_.at<4>(),
483                            now - base::TimeDelta::FromDays(5),
484                            10000u);
485  InsertIntoIndexFileReturn(hashes_.at<5>(),
486                            now - base::TimeDelta::FromDays(6),
487                            100000u);
488
489  ReturnIndexFile();
490
491  EXPECT_FALSE(index()->Has(hashes_.at<1>()));
492
493  EntryMetadata metadata;
494  EXPECT_TRUE(GetEntryForTesting(hashes_.at<2>(), &metadata));
495  EXPECT_LT(now - base::TimeDelta::FromMinutes(1), metadata.GetLastUsedTime());
496  EXPECT_GT(now + base::TimeDelta::FromMinutes(1), metadata.GetLastUsedTime());
497  EXPECT_EQ(0, metadata.GetEntrySize());
498
499  EXPECT_FALSE(index()->Has(hashes_.at<3>()));
500
501  EXPECT_TRUE(GetEntryForTesting(hashes_.at<4>(), &metadata));
502  EXPECT_LT(now - base::TimeDelta::FromMinutes(1), metadata.GetLastUsedTime());
503  EXPECT_GT(now + base::TimeDelta::FromMinutes(1), metadata.GetLastUsedTime());
504  EXPECT_EQ(0, metadata.GetEntrySize());
505
506  EXPECT_TRUE(GetEntryForTesting(hashes_.at<5>(), &metadata));
507
508  EXPECT_GT(
509      now - base::TimeDelta::FromDays(6) + base::TimeDelta::FromSeconds(1),
510      metadata.GetLastUsedTime());
511  EXPECT_LT(
512      now - base::TimeDelta::FromDays(6) - base::TimeDelta::FromSeconds(1),
513      metadata.GetLastUsedTime());
514
515  EXPECT_EQ(100000, metadata.GetEntrySize());
516}
517
518TEST_F(SimpleIndexTest, BasicEviction) {
519  base::Time now(base::Time::Now());
520  index()->SetMaxSize(1000);
521  InsertIntoIndexFileReturn(hashes_.at<1>(),
522                            now - base::TimeDelta::FromDays(2),
523                            475u);
524  index()->Insert(hashes_.at<2>());
525  index()->UpdateEntrySize(hashes_.at<2>(), 475);
526  ReturnIndexFile();
527
528  WaitForTimeChange();
529
530  index()->Insert(hashes_.at<3>());
531  // Confirm index is as expected: No eviction, everything there.
532  EXPECT_EQ(3, index()->GetEntryCount());
533  EXPECT_EQ(0, doom_entries_calls());
534  EXPECT_TRUE(index()->Has(hashes_.at<1>()));
535  EXPECT_TRUE(index()->Has(hashes_.at<2>()));
536  EXPECT_TRUE(index()->Has(hashes_.at<3>()));
537
538  // Trigger an eviction, and make sure the right things are tossed.
539  // TODO(rdsmith): This is dependent on the innards of the implementation
540  // as to at exactly what point we trigger eviction. Not sure how to fix
541  // that.
542  index()->UpdateEntrySize(hashes_.at<3>(), 475);
543  EXPECT_EQ(1, doom_entries_calls());
544  EXPECT_EQ(1, index()->GetEntryCount());
545  EXPECT_FALSE(index()->Has(hashes_.at<1>()));
546  EXPECT_FALSE(index()->Has(hashes_.at<2>()));
547  EXPECT_TRUE(index()->Has(hashes_.at<3>()));
548  ASSERT_EQ(2u, last_doom_entry_hashes().size());
549}
550
551// Confirm all the operations queue a disk write at some point in the
552// future.
553TEST_F(SimpleIndexTest, DiskWriteQueued) {
554  index()->SetMaxSize(1000);
555  ReturnIndexFile();
556
557  EXPECT_FALSE(index()->write_to_disk_timer_.IsRunning());
558
559  const uint64 kHash1 = hashes_.at<1>();
560  index()->Insert(kHash1);
561  EXPECT_TRUE(index()->write_to_disk_timer_.IsRunning());
562  index()->write_to_disk_timer_.Stop();
563  EXPECT_FALSE(index()->write_to_disk_timer_.IsRunning());
564
565  index()->UseIfExists(kHash1);
566  EXPECT_TRUE(index()->write_to_disk_timer_.IsRunning());
567  index()->write_to_disk_timer_.Stop();
568
569  index()->UpdateEntrySize(kHash1, 20);
570  EXPECT_TRUE(index()->write_to_disk_timer_.IsRunning());
571  index()->write_to_disk_timer_.Stop();
572
573  index()->Remove(kHash1);
574  EXPECT_TRUE(index()->write_to_disk_timer_.IsRunning());
575  index()->write_to_disk_timer_.Stop();
576}
577
578TEST_F(SimpleIndexTest, DiskWriteExecuted) {
579  index()->SetMaxSize(1000);
580  ReturnIndexFile();
581
582  EXPECT_FALSE(index()->write_to_disk_timer_.IsRunning());
583
584  const uint64 kHash1 = hashes_.at<1>();
585  index()->Insert(kHash1);
586  index()->UpdateEntrySize(kHash1, 20);
587  EXPECT_TRUE(index()->write_to_disk_timer_.IsRunning());
588  base::Closure user_task(index()->write_to_disk_timer_.user_task());
589  index()->write_to_disk_timer_.Stop();
590
591  EXPECT_EQ(0, index_file_->disk_writes());
592  user_task.Run();
593  EXPECT_EQ(1, index_file_->disk_writes());
594  SimpleIndex::EntrySet entry_set;
595  index_file_->GetAndResetDiskWriteEntrySet(&entry_set);
596
597  uint64 hash_key = kHash1;
598  base::Time now(base::Time::Now());
599  ASSERT_EQ(1u, entry_set.size());
600  EXPECT_EQ(hash_key, entry_set.begin()->first);
601  const EntryMetadata& entry1(entry_set.begin()->second);
602  EXPECT_LT(now - base::TimeDelta::FromMinutes(1), entry1.GetLastUsedTime());
603  EXPECT_GT(now + base::TimeDelta::FromMinutes(1), entry1.GetLastUsedTime());
604  EXPECT_EQ(20, entry1.GetEntrySize());
605}
606
607TEST_F(SimpleIndexTest, DiskWritePostponed) {
608  index()->SetMaxSize(1000);
609  ReturnIndexFile();
610
611  EXPECT_FALSE(index()->write_to_disk_timer_.IsRunning());
612
613  index()->Insert(hashes_.at<1>());
614  index()->UpdateEntrySize(hashes_.at<1>(), 20);
615  EXPECT_TRUE(index()->write_to_disk_timer_.IsRunning());
616  base::TimeTicks expected_trigger(
617      index()->write_to_disk_timer_.desired_run_time());
618
619  WaitForTimeChange();
620  EXPECT_EQ(expected_trigger, index()->write_to_disk_timer_.desired_run_time());
621  index()->Insert(hashes_.at<2>());
622  index()->UpdateEntrySize(hashes_.at<2>(), 40);
623  EXPECT_TRUE(index()->write_to_disk_timer_.IsRunning());
624  EXPECT_LT(expected_trigger, index()->write_to_disk_timer_.desired_run_time());
625  index()->write_to_disk_timer_.Stop();
626}
627
628}  // namespace disk_cache
629