794 lines
28 KiB
C++
794 lines
28 KiB
C++
// Copyright 2013 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "net/disk_cache/simple/simple_index.h"
|
|
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <utility>
|
|
|
|
#include "base/files/scoped_temp_dir.h"
|
|
#include "base/functional/bind.h"
|
|
#include "base/hash/hash.h"
|
|
#include "base/memory/raw_ptr.h"
|
|
#include "base/pickle.h"
|
|
#include "base/strings/stringprintf.h"
|
|
#include "base/task/task_runner.h"
|
|
#include "base/test/mock_entropy_provider.h"
|
|
#include "base/threading/platform_thread.h"
|
|
#include "base/time/time.h"
|
|
#include "net/base/cache_type.h"
|
|
#include "net/disk_cache/backend_cleanup_tracker.h"
|
|
#include "net/disk_cache/disk_cache.h"
|
|
#include "net/disk_cache/simple/simple_index_delegate.h"
|
|
#include "net/disk_cache/simple/simple_index_file.h"
|
|
#include "net/disk_cache/simple/simple_test_util.h"
|
|
#include "net/disk_cache/simple/simple_util.h"
|
|
#include "net/test/test_with_task_environment.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
namespace disk_cache {
|
|
namespace {
|
|
|
|
const base::Time kTestLastUsedTime = base::Time::UnixEpoch() + base::Days(20);
|
|
const uint32_t kTestEntrySize = 789;
|
|
const uint8_t kTestEntryMemoryData = 123;
|
|
|
|
uint32_t RoundSize(uint32_t in) {
|
|
return (in + 0xFFu) & 0xFFFFFF00u;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
class EntryMetadataTest : public testing::Test {
|
|
public:
|
|
EntryMetadata NewEntryMetadataWithValues() {
|
|
EntryMetadata entry(kTestLastUsedTime, kTestEntrySize);
|
|
entry.SetInMemoryData(kTestEntryMemoryData);
|
|
return entry;
|
|
}
|
|
|
|
void CheckEntryMetadataValues(const EntryMetadata& entry_metadata) {
|
|
EXPECT_LT(kTestLastUsedTime - base::Seconds(2),
|
|
entry_metadata.GetLastUsedTime());
|
|
EXPECT_GT(kTestLastUsedTime + base::Seconds(2),
|
|
entry_metadata.GetLastUsedTime());
|
|
EXPECT_EQ(RoundSize(kTestEntrySize), entry_metadata.GetEntrySize());
|
|
EXPECT_EQ(kTestEntryMemoryData, entry_metadata.GetInMemoryData());
|
|
}
|
|
};
|
|
|
|
class MockSimpleIndexFile : public SimpleIndexFile,
|
|
public base::SupportsWeakPtr<MockSimpleIndexFile> {
|
|
public:
|
|
explicit MockSimpleIndexFile(net::CacheType cache_type)
|
|
: SimpleIndexFile(nullptr,
|
|
base::MakeRefCounted<TrivialFileOperationsFactory>(),
|
|
cache_type,
|
|
base::FilePath()) {}
|
|
|
|
void LoadIndexEntries(base::Time cache_last_modified,
|
|
base::OnceClosure callback,
|
|
SimpleIndexLoadResult* out_load_result) override {
|
|
load_callback_ = std::move(callback);
|
|
load_result_ = out_load_result;
|
|
++load_index_entries_calls_;
|
|
}
|
|
|
|
void WriteToDisk(net::CacheType cache_type,
|
|
SimpleIndex::IndexWriteToDiskReason reason,
|
|
const SimpleIndex::EntrySet& entry_set,
|
|
uint64_t cache_size,
|
|
base::OnceClosure callback) override {
|
|
disk_writes_++;
|
|
disk_write_entry_set_ = entry_set;
|
|
}
|
|
|
|
void GetAndResetDiskWriteEntrySet(SimpleIndex::EntrySet* entry_set) {
|
|
entry_set->swap(disk_write_entry_set_);
|
|
}
|
|
|
|
void RunLoadCallback() {
|
|
// Clear dangling reference since callback may destroy `load_result_`.
|
|
load_result_ = nullptr;
|
|
std::move(load_callback_).Run();
|
|
}
|
|
SimpleIndexLoadResult* load_result() const { return load_result_; }
|
|
int load_index_entries_calls() const { return load_index_entries_calls_; }
|
|
int disk_writes() const { return disk_writes_; }
|
|
|
|
private:
|
|
base::OnceClosure load_callback_;
|
|
raw_ptr<SimpleIndexLoadResult> load_result_ = nullptr;
|
|
int load_index_entries_calls_ = 0;
|
|
int disk_writes_ = 0;
|
|
SimpleIndex::EntrySet disk_write_entry_set_;
|
|
};
|
|
|
|
class SimpleIndexTest : public net::TestWithTaskEnvironment,
|
|
public SimpleIndexDelegate {
|
|
protected:
|
|
SimpleIndexTest() : hashes_(base::BindRepeating(&HashesInitializer)) {}
|
|
|
|
static uint64_t HashesInitializer(size_t hash_index) {
|
|
return disk_cache::simple_util::GetEntryHashKey(
|
|
base::StringPrintf("key%d", static_cast<int>(hash_index)));
|
|
}
|
|
|
|
void SetUp() override {
|
|
auto index_file = std::make_unique<MockSimpleIndexFile>(CacheType());
|
|
index_file_ = index_file->AsWeakPtr();
|
|
index_ =
|
|
std::make_unique<SimpleIndex>(/* io_thread = */ nullptr,
|
|
/* cleanup_tracker = */ nullptr, this,
|
|
CacheType(), std::move(index_file));
|
|
|
|
index_->Initialize(base::Time());
|
|
}
|
|
|
|
void WaitForTimeChange() {
|
|
const base::Time initial_time = base::Time::Now();
|
|
do {
|
|
base::PlatformThread::YieldCurrentThread();
|
|
} while (base::Time::Now() - initial_time < base::Seconds(1));
|
|
}
|
|
|
|
// From SimpleIndexDelegate:
|
|
void DoomEntries(std::vector<uint64_t>* entry_hashes,
|
|
net::CompletionOnceCallback callback) override {
|
|
for (const uint64_t& entry_hash : *entry_hashes)
|
|
index_->Remove(entry_hash);
|
|
last_doom_entry_hashes_ = *entry_hashes;
|
|
++doom_entries_calls_;
|
|
}
|
|
|
|
// Redirect to allow single "friend" declaration in base class.
|
|
bool GetEntryForTesting(uint64_t key, EntryMetadata* metadata) {
|
|
auto it = index_->entries_set_.find(key);
|
|
if (index_->entries_set_.end() == it)
|
|
return false;
|
|
*metadata = it->second;
|
|
return true;
|
|
}
|
|
|
|
void InsertIntoIndexFileReturn(uint64_t hash_key,
|
|
base::Time last_used_time,
|
|
int entry_size) {
|
|
index_file_->load_result()->entries.insert(std::make_pair(
|
|
hash_key, EntryMetadata(last_used_time,
|
|
base::checked_cast<uint32_t>(entry_size))));
|
|
}
|
|
|
|
void ReturnIndexFile() {
|
|
index_file_->load_result()->did_load = true;
|
|
index_file_->RunLoadCallback();
|
|
}
|
|
|
|
// Non-const for timer manipulation.
|
|
SimpleIndex* index() { return index_.get(); }
|
|
const MockSimpleIndexFile* index_file() const { return index_file_.get(); }
|
|
|
|
const std::vector<uint64_t>& last_doom_entry_hashes() const {
|
|
return last_doom_entry_hashes_;
|
|
}
|
|
int doom_entries_calls() const { return doom_entries_calls_; }
|
|
|
|
virtual net::CacheType CacheType() const { return net::DISK_CACHE; }
|
|
|
|
const simple_util::ImmutableArray<uint64_t, 16> hashes_;
|
|
std::unique_ptr<SimpleIndex> index_;
|
|
base::WeakPtr<MockSimpleIndexFile> index_file_;
|
|
|
|
std::vector<uint64_t> last_doom_entry_hashes_;
|
|
int doom_entries_calls_ = 0;
|
|
};
|
|
|
|
class SimpleIndexAppCacheTest : public SimpleIndexTest {
|
|
protected:
|
|
net::CacheType CacheType() const override { return net::APP_CACHE; }
|
|
};
|
|
|
|
class SimpleIndexCodeCacheTest : public SimpleIndexTest {
|
|
protected:
|
|
net::CacheType CacheType() const override {
|
|
return net::GENERATED_BYTE_CODE_CACHE;
|
|
}
|
|
};
|
|
|
|
TEST_F(EntryMetadataTest, Basics) {
|
|
EntryMetadata entry_metadata;
|
|
EXPECT_EQ(base::Time(), entry_metadata.GetLastUsedTime());
|
|
EXPECT_EQ(0u, entry_metadata.GetEntrySize());
|
|
EXPECT_EQ(0u, entry_metadata.GetInMemoryData());
|
|
|
|
entry_metadata = NewEntryMetadataWithValues();
|
|
CheckEntryMetadataValues(entry_metadata);
|
|
|
|
const base::Time new_time = base::Time::Now();
|
|
entry_metadata.SetLastUsedTime(new_time);
|
|
|
|
EXPECT_LT(new_time - base::Seconds(2), entry_metadata.GetLastUsedTime());
|
|
EXPECT_GT(new_time + base::Seconds(2), entry_metadata.GetLastUsedTime());
|
|
}
|
|
|
|
// Tests that setting an unusually small/large last used time results in
|
|
// truncation (rather than crashing).
|
|
TEST_F(EntryMetadataTest, SaturatedLastUsedTime) {
|
|
EntryMetadata entry_metadata;
|
|
|
|
// Set a time that is too large to be represented internally as 32-bit unix
|
|
// timestamp. Will saturate to a large timestamp (in year 2106).
|
|
entry_metadata.SetLastUsedTime(base::Time::Max());
|
|
EXPECT_EQ(INT64_C(15939440895000000),
|
|
entry_metadata.GetLastUsedTime().ToInternalValue());
|
|
|
|
// Set a time that is too small to be represented by a unix timestamp (before
|
|
// 1970).
|
|
entry_metadata.SetLastUsedTime(
|
|
base::Time::FromInternalValue(7u)); // This is a date in 1601.
|
|
EXPECT_EQ(base::Time::UnixEpoch() + base::Seconds(1),
|
|
entry_metadata.GetLastUsedTime());
|
|
}
|
|
|
|
TEST_F(EntryMetadataTest, Serialize) {
|
|
EntryMetadata entry_metadata = NewEntryMetadataWithValues();
|
|
|
|
base::Pickle pickle;
|
|
entry_metadata.Serialize(net::DISK_CACHE, &pickle);
|
|
|
|
base::PickleIterator it(pickle);
|
|
EntryMetadata new_entry_metadata;
|
|
new_entry_metadata.Deserialize(net::DISK_CACHE, &it, true, true);
|
|
CheckEntryMetadataValues(new_entry_metadata);
|
|
|
|
// Test reading of old format --- the modern serialization of above entry
|
|
// corresponds, in older format, to an entry with size =
|
|
// RoundSize(kTestEntrySize) | kTestEntryMemoryData, which then gets
|
|
// rounded again when stored by EntryMetadata.
|
|
base::PickleIterator it2(pickle);
|
|
EntryMetadata new_entry_metadata2;
|
|
new_entry_metadata2.Deserialize(net::DISK_CACHE, &it2, false, false);
|
|
EXPECT_EQ(RoundSize(RoundSize(kTestEntrySize) | kTestEntryMemoryData),
|
|
new_entry_metadata2.GetEntrySize());
|
|
EXPECT_EQ(0, new_entry_metadata2.GetInMemoryData());
|
|
}
|
|
|
|
TEST_F(SimpleIndexTest, IndexSizeCorrectOnMerge) {
|
|
const unsigned int kSizeResolution = 256u;
|
|
index()->SetMaxSize(100 * kSizeResolution);
|
|
index()->Insert(hashes_.at<2>());
|
|
index()->UpdateEntrySize(hashes_.at<2>(), 2u * kSizeResolution);
|
|
index()->Insert(hashes_.at<3>());
|
|
index()->UpdateEntrySize(hashes_.at<3>(), 3u * kSizeResolution);
|
|
index()->Insert(hashes_.at<4>());
|
|
index()->UpdateEntrySize(hashes_.at<4>(), 4u * kSizeResolution);
|
|
EXPECT_EQ(9u * kSizeResolution, index()->cache_size_);
|
|
{
|
|
auto result = std::make_unique<SimpleIndexLoadResult>();
|
|
result->did_load = true;
|
|
index()->MergeInitializingSet(std::move(result));
|
|
}
|
|
EXPECT_EQ(9u * kSizeResolution, index()->cache_size_);
|
|
{
|
|
auto result = std::make_unique<SimpleIndexLoadResult>();
|
|
result->did_load = true;
|
|
const uint64_t new_hash_key = hashes_.at<11>();
|
|
result->entries.insert(std::make_pair(
|
|
new_hash_key, EntryMetadata(base::Time::Now(), 11u * kSizeResolution)));
|
|
const uint64_t redundant_hash_key = hashes_.at<4>();
|
|
result->entries.insert(
|
|
std::make_pair(redundant_hash_key,
|
|
EntryMetadata(base::Time::Now(), 4u * kSizeResolution)));
|
|
index()->MergeInitializingSet(std::move(result));
|
|
}
|
|
EXPECT_EQ((2u + 3u + 4u + 11u) * kSizeResolution, index()->cache_size_);
|
|
}
|
|
|
|
// State of index changes as expected with an insert and a remove.
|
|
TEST_F(SimpleIndexTest, BasicInsertRemove) {
|
|
// Confirm blank state.
|
|
EntryMetadata metadata;
|
|
EXPECT_EQ(base::Time(), metadata.GetLastUsedTime());
|
|
EXPECT_EQ(0U, metadata.GetEntrySize());
|
|
|
|
// Confirm state after insert.
|
|
index()->Insert(hashes_.at<1>());
|
|
ASSERT_TRUE(GetEntryForTesting(hashes_.at<1>(), &metadata));
|
|
base::Time now(base::Time::Now());
|
|
EXPECT_LT(now - base::Minutes(1), metadata.GetLastUsedTime());
|
|
EXPECT_GT(now + base::Minutes(1), metadata.GetLastUsedTime());
|
|
EXPECT_EQ(0U, metadata.GetEntrySize());
|
|
|
|
// Confirm state after remove.
|
|
metadata = EntryMetadata();
|
|
index()->Remove(hashes_.at<1>());
|
|
EXPECT_FALSE(GetEntryForTesting(hashes_.at<1>(), &metadata));
|
|
EXPECT_EQ(base::Time(), metadata.GetLastUsedTime());
|
|
EXPECT_EQ(0U, metadata.GetEntrySize());
|
|
}
|
|
|
|
TEST_F(SimpleIndexTest, Has) {
|
|
// Confirm the base index has dispatched the request for index entries.
|
|
EXPECT_TRUE(index_file_.get());
|
|
EXPECT_EQ(1, index_file_->load_index_entries_calls());
|
|
|
|
// Confirm "Has()" always returns true before the callback is called.
|
|
const uint64_t kHash1 = hashes_.at<1>();
|
|
EXPECT_TRUE(index()->Has(kHash1));
|
|
index()->Insert(kHash1);
|
|
EXPECT_TRUE(index()->Has(kHash1));
|
|
index()->Remove(kHash1);
|
|
// TODO(morlovich): Maybe return false on explicitly removed entries?
|
|
EXPECT_TRUE(index()->Has(kHash1));
|
|
|
|
ReturnIndexFile();
|
|
|
|
// Confirm "Has() returns conditionally now.
|
|
EXPECT_FALSE(index()->Has(kHash1));
|
|
index()->Insert(kHash1);
|
|
EXPECT_TRUE(index()->Has(kHash1));
|
|
index()->Remove(kHash1);
|
|
}
|
|
|
|
TEST_F(SimpleIndexTest, UseIfExists) {
|
|
// Confirm the base index has dispatched the request for index entries.
|
|
EXPECT_TRUE(index_file_.get());
|
|
EXPECT_EQ(1, index_file_->load_index_entries_calls());
|
|
|
|
// Confirm "UseIfExists()" always returns true before the callback is called
|
|
// and updates mod time if the entry was really there.
|
|
const uint64_t kHash1 = hashes_.at<1>();
|
|
EntryMetadata metadata1, metadata2;
|
|
EXPECT_TRUE(index()->UseIfExists(kHash1));
|
|
EXPECT_FALSE(GetEntryForTesting(kHash1, &metadata1));
|
|
index()->Insert(kHash1);
|
|
EXPECT_TRUE(index()->UseIfExists(kHash1));
|
|
EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata1));
|
|
WaitForTimeChange();
|
|
EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata2));
|
|
EXPECT_EQ(metadata1.GetLastUsedTime(), metadata2.GetLastUsedTime());
|
|
EXPECT_TRUE(index()->UseIfExists(kHash1));
|
|
EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata2));
|
|
EXPECT_LT(metadata1.GetLastUsedTime(), metadata2.GetLastUsedTime());
|
|
index()->Remove(kHash1);
|
|
EXPECT_TRUE(index()->UseIfExists(kHash1));
|
|
|
|
ReturnIndexFile();
|
|
|
|
// Confirm "UseIfExists() returns conditionally now
|
|
EXPECT_FALSE(index()->UseIfExists(kHash1));
|
|
EXPECT_FALSE(GetEntryForTesting(kHash1, &metadata1));
|
|
index()->Insert(kHash1);
|
|
EXPECT_TRUE(index()->UseIfExists(kHash1));
|
|
EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata1));
|
|
WaitForTimeChange();
|
|
EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata2));
|
|
EXPECT_EQ(metadata1.GetLastUsedTime(), metadata2.GetLastUsedTime());
|
|
EXPECT_TRUE(index()->UseIfExists(kHash1));
|
|
EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata2));
|
|
EXPECT_LT(metadata1.GetLastUsedTime(), metadata2.GetLastUsedTime());
|
|
index()->Remove(kHash1);
|
|
EXPECT_FALSE(index()->UseIfExists(kHash1));
|
|
}
|
|
|
|
TEST_F(SimpleIndexTest, UpdateEntrySize) {
|
|
base::Time now(base::Time::Now());
|
|
|
|
index()->SetMaxSize(1000);
|
|
|
|
const uint64_t kHash1 = hashes_.at<1>();
|
|
InsertIntoIndexFileReturn(kHash1, now - base::Days(2), 475);
|
|
ReturnIndexFile();
|
|
|
|
EntryMetadata metadata;
|
|
EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata));
|
|
EXPECT_LT(now - base::Days(2) - base::Seconds(1), metadata.GetLastUsedTime());
|
|
EXPECT_GT(now - base::Days(2) + base::Seconds(1), metadata.GetLastUsedTime());
|
|
EXPECT_EQ(RoundSize(475u), metadata.GetEntrySize());
|
|
|
|
index()->UpdateEntrySize(kHash1, 600u);
|
|
EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata));
|
|
EXPECT_EQ(RoundSize(600u), metadata.GetEntrySize());
|
|
EXPECT_EQ(1, index()->GetEntryCount());
|
|
}
|
|
|
|
TEST_F(SimpleIndexTest, GetEntryCount) {
|
|
EXPECT_EQ(0, index()->GetEntryCount());
|
|
index()->Insert(hashes_.at<1>());
|
|
EXPECT_EQ(1, index()->GetEntryCount());
|
|
index()->Insert(hashes_.at<2>());
|
|
EXPECT_EQ(2, index()->GetEntryCount());
|
|
index()->Insert(hashes_.at<3>());
|
|
EXPECT_EQ(3, index()->GetEntryCount());
|
|
index()->Insert(hashes_.at<3>());
|
|
EXPECT_EQ(3, index()->GetEntryCount());
|
|
index()->Remove(hashes_.at<2>());
|
|
EXPECT_EQ(2, index()->GetEntryCount());
|
|
index()->Insert(hashes_.at<4>());
|
|
EXPECT_EQ(3, index()->GetEntryCount());
|
|
index()->Remove(hashes_.at<3>());
|
|
EXPECT_EQ(2, index()->GetEntryCount());
|
|
index()->Remove(hashes_.at<3>());
|
|
EXPECT_EQ(2, index()->GetEntryCount());
|
|
index()->Remove(hashes_.at<1>());
|
|
EXPECT_EQ(1, index()->GetEntryCount());
|
|
index()->Remove(hashes_.at<4>());
|
|
EXPECT_EQ(0, index()->GetEntryCount());
|
|
}
|
|
|
|
// Confirm that we get the results we expect from a simple init.
|
|
TEST_F(SimpleIndexTest, BasicInit) {
|
|
base::Time now(base::Time::Now());
|
|
|
|
InsertIntoIndexFileReturn(hashes_.at<1>(), now - base::Days(2), 10u);
|
|
InsertIntoIndexFileReturn(hashes_.at<2>(), now - base::Days(3), 1000u);
|
|
|
|
ReturnIndexFile();
|
|
|
|
EntryMetadata metadata;
|
|
EXPECT_TRUE(GetEntryForTesting(hashes_.at<1>(), &metadata));
|
|
EXPECT_EQ(metadata.GetLastUsedTime(),
|
|
index()->GetLastUsedTime(hashes_.at<1>()));
|
|
EXPECT_LT(now - base::Days(2) - base::Seconds(1), metadata.GetLastUsedTime());
|
|
EXPECT_GT(now - base::Days(2) + base::Seconds(1), metadata.GetLastUsedTime());
|
|
EXPECT_EQ(RoundSize(10u), metadata.GetEntrySize());
|
|
EXPECT_TRUE(GetEntryForTesting(hashes_.at<2>(), &metadata));
|
|
EXPECT_EQ(metadata.GetLastUsedTime(),
|
|
index()->GetLastUsedTime(hashes_.at<2>()));
|
|
EXPECT_LT(now - base::Days(3) - base::Seconds(1), metadata.GetLastUsedTime());
|
|
EXPECT_GT(now - base::Days(3) + base::Seconds(1), metadata.GetLastUsedTime());
|
|
EXPECT_EQ(RoundSize(1000u), metadata.GetEntrySize());
|
|
EXPECT_EQ(base::Time(), index()->GetLastUsedTime(hashes_.at<3>()));
|
|
}
|
|
|
|
// Remove something that's going to come in from the loaded index.
|
|
TEST_F(SimpleIndexTest, RemoveBeforeInit) {
|
|
const uint64_t kHash1 = hashes_.at<1>();
|
|
index()->Remove(kHash1);
|
|
|
|
InsertIntoIndexFileReturn(kHash1, base::Time::Now() - base::Days(2), 10u);
|
|
ReturnIndexFile();
|
|
|
|
EXPECT_FALSE(index()->Has(kHash1));
|
|
}
|
|
|
|
// Insert something that's going to come in from the loaded index; correct
|
|
// result?
|
|
TEST_F(SimpleIndexTest, InsertBeforeInit) {
|
|
const uint64_t kHash1 = hashes_.at<1>();
|
|
index()->Insert(kHash1);
|
|
|
|
InsertIntoIndexFileReturn(kHash1, base::Time::Now() - base::Days(2), 10u);
|
|
ReturnIndexFile();
|
|
|
|
EntryMetadata metadata;
|
|
EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata));
|
|
base::Time now(base::Time::Now());
|
|
EXPECT_LT(now - base::Minutes(1), metadata.GetLastUsedTime());
|
|
EXPECT_GT(now + base::Minutes(1), metadata.GetLastUsedTime());
|
|
EXPECT_EQ(0U, metadata.GetEntrySize());
|
|
}
|
|
|
|
// Insert and Remove something that's going to come in from the loaded index.
|
|
TEST_F(SimpleIndexTest, InsertRemoveBeforeInit) {
|
|
const uint64_t kHash1 = hashes_.at<1>();
|
|
index()->Insert(kHash1);
|
|
index()->Remove(kHash1);
|
|
|
|
InsertIntoIndexFileReturn(kHash1, base::Time::Now() - base::Days(2), 10u);
|
|
ReturnIndexFile();
|
|
|
|
EXPECT_FALSE(index()->Has(kHash1));
|
|
}
|
|
|
|
// Insert and Remove something that's going to come in from the loaded index.
|
|
TEST_F(SimpleIndexTest, RemoveInsertBeforeInit) {
|
|
const uint64_t kHash1 = hashes_.at<1>();
|
|
index()->Remove(kHash1);
|
|
index()->Insert(kHash1);
|
|
|
|
InsertIntoIndexFileReturn(kHash1, base::Time::Now() - base::Days(2), 10u);
|
|
ReturnIndexFile();
|
|
|
|
EntryMetadata metadata;
|
|
EXPECT_TRUE(GetEntryForTesting(kHash1, &metadata));
|
|
base::Time now(base::Time::Now());
|
|
EXPECT_LT(now - base::Minutes(1), metadata.GetLastUsedTime());
|
|
EXPECT_GT(now + base::Minutes(1), metadata.GetLastUsedTime());
|
|
EXPECT_EQ(0U, metadata.GetEntrySize());
|
|
}
|
|
|
|
// Do all above tests at once + a non-conflict to test for cross-key
|
|
// interactions.
|
|
TEST_F(SimpleIndexTest, AllInitConflicts) {
|
|
base::Time now(base::Time::Now());
|
|
|
|
index()->Remove(hashes_.at<1>());
|
|
InsertIntoIndexFileReturn(hashes_.at<1>(), now - base::Days(2), 10u);
|
|
index()->Insert(hashes_.at<2>());
|
|
InsertIntoIndexFileReturn(hashes_.at<2>(), now - base::Days(3), 100u);
|
|
index()->Insert(hashes_.at<3>());
|
|
index()->Remove(hashes_.at<3>());
|
|
InsertIntoIndexFileReturn(hashes_.at<3>(), now - base::Days(4), 1000u);
|
|
index()->Remove(hashes_.at<4>());
|
|
index()->Insert(hashes_.at<4>());
|
|
InsertIntoIndexFileReturn(hashes_.at<4>(), now - base::Days(5), 10000u);
|
|
InsertIntoIndexFileReturn(hashes_.at<5>(), now - base::Days(6), 100000u);
|
|
|
|
ReturnIndexFile();
|
|
|
|
EXPECT_FALSE(index()->Has(hashes_.at<1>()));
|
|
|
|
EntryMetadata metadata;
|
|
EXPECT_TRUE(GetEntryForTesting(hashes_.at<2>(), &metadata));
|
|
EXPECT_LT(now - base::Minutes(1), metadata.GetLastUsedTime());
|
|
EXPECT_GT(now + base::Minutes(1), metadata.GetLastUsedTime());
|
|
EXPECT_EQ(0U, metadata.GetEntrySize());
|
|
|
|
EXPECT_FALSE(index()->Has(hashes_.at<3>()));
|
|
|
|
EXPECT_TRUE(GetEntryForTesting(hashes_.at<4>(), &metadata));
|
|
EXPECT_LT(now - base::Minutes(1), metadata.GetLastUsedTime());
|
|
EXPECT_GT(now + base::Minutes(1), metadata.GetLastUsedTime());
|
|
EXPECT_EQ(0U, metadata.GetEntrySize());
|
|
|
|
EXPECT_TRUE(GetEntryForTesting(hashes_.at<5>(), &metadata));
|
|
|
|
EXPECT_GT(now - base::Days(6) + base::Seconds(1), metadata.GetLastUsedTime());
|
|
EXPECT_LT(now - base::Days(6) - base::Seconds(1), metadata.GetLastUsedTime());
|
|
|
|
EXPECT_EQ(RoundSize(100000u), metadata.GetEntrySize());
|
|
}
|
|
|
|
TEST_F(SimpleIndexTest, BasicEviction) {
|
|
base::Time now(base::Time::Now());
|
|
index()->SetMaxSize(1000);
|
|
InsertIntoIndexFileReturn(hashes_.at<1>(), now - base::Days(2), 475u);
|
|
index()->Insert(hashes_.at<2>());
|
|
index()->UpdateEntrySize(hashes_.at<2>(), 475u);
|
|
ReturnIndexFile();
|
|
|
|
WaitForTimeChange();
|
|
|
|
index()->Insert(hashes_.at<3>());
|
|
// Confirm index is as expected: No eviction, everything there.
|
|
EXPECT_EQ(3, index()->GetEntryCount());
|
|
EXPECT_EQ(0, doom_entries_calls());
|
|
EXPECT_TRUE(index()->Has(hashes_.at<1>()));
|
|
EXPECT_TRUE(index()->Has(hashes_.at<2>()));
|
|
EXPECT_TRUE(index()->Has(hashes_.at<3>()));
|
|
|
|
// Trigger an eviction, and make sure the right things are tossed.
|
|
// TODO(morlovich): This is dependent on the innards of the implementation
|
|
// as to at exactly what point we trigger eviction. Not sure how to fix
|
|
// that.
|
|
index()->UpdateEntrySize(hashes_.at<3>(), 475u);
|
|
EXPECT_EQ(1, doom_entries_calls());
|
|
EXPECT_EQ(1, index()->GetEntryCount());
|
|
EXPECT_FALSE(index()->Has(hashes_.at<1>()));
|
|
EXPECT_FALSE(index()->Has(hashes_.at<2>()));
|
|
EXPECT_TRUE(index()->Has(hashes_.at<3>()));
|
|
ASSERT_EQ(2u, last_doom_entry_hashes().size());
|
|
}
|
|
|
|
TEST_F(SimpleIndexTest, EvictBySize) {
|
|
base::Time now(base::Time::Now());
|
|
index()->SetMaxSize(50000);
|
|
InsertIntoIndexFileReturn(hashes_.at<1>(), now - base::Days(2), 475u);
|
|
InsertIntoIndexFileReturn(hashes_.at<2>(), now - base::Days(1), 40000u);
|
|
ReturnIndexFile();
|
|
WaitForTimeChange();
|
|
|
|
index()->Insert(hashes_.at<3>());
|
|
// Confirm index is as expected: No eviction, everything there.
|
|
EXPECT_EQ(3, index()->GetEntryCount());
|
|
EXPECT_EQ(0, doom_entries_calls());
|
|
EXPECT_TRUE(index()->Has(hashes_.at<1>()));
|
|
EXPECT_TRUE(index()->Has(hashes_.at<2>()));
|
|
EXPECT_TRUE(index()->Has(hashes_.at<3>()));
|
|
|
|
// Trigger an eviction, and make sure the right things are tossed.
|
|
// TODO(morlovich): This is dependent on the innards of the implementation
|
|
// as to at exactly what point we trigger eviction. Not sure how to fix
|
|
// that.
|
|
index()->UpdateEntrySize(hashes_.at<3>(), 40000u);
|
|
EXPECT_EQ(1, doom_entries_calls());
|
|
EXPECT_EQ(2, index()->GetEntryCount());
|
|
EXPECT_TRUE(index()->Has(hashes_.at<1>()));
|
|
EXPECT_FALSE(index()->Has(hashes_.at<2>()));
|
|
EXPECT_TRUE(index()->Has(hashes_.at<3>()));
|
|
ASSERT_EQ(1u, last_doom_entry_hashes().size());
|
|
}
|
|
|
|
TEST_F(SimpleIndexCodeCacheTest, DisableEvictBySize) {
|
|
base::Time now(base::Time::Now());
|
|
index()->SetMaxSize(50000);
|
|
InsertIntoIndexFileReturn(hashes_.at<1>(), now - base::Days(2), 475u);
|
|
InsertIntoIndexFileReturn(hashes_.at<2>(), now - base::Days(1), 40000u);
|
|
ReturnIndexFile();
|
|
WaitForTimeChange();
|
|
|
|
index()->Insert(hashes_.at<3>());
|
|
// Confirm index is as expected: No eviction, everything there.
|
|
EXPECT_EQ(3, index()->GetEntryCount());
|
|
EXPECT_EQ(0, doom_entries_calls());
|
|
EXPECT_TRUE(index()->Has(hashes_.at<1>()));
|
|
EXPECT_TRUE(index()->Has(hashes_.at<2>()));
|
|
EXPECT_TRUE(index()->Has(hashes_.at<3>()));
|
|
|
|
// Trigger an eviction, and make sure the right things are tossed.
|
|
// Since evict by size is supposed to be disabled, it evicts in LRU order,
|
|
// so entries 1 and 2 are both kicked out.
|
|
index()->UpdateEntrySize(hashes_.at<3>(), 40000u);
|
|
EXPECT_EQ(1, doom_entries_calls());
|
|
EXPECT_EQ(1, index()->GetEntryCount());
|
|
EXPECT_FALSE(index()->Has(hashes_.at<1>()));
|
|
EXPECT_FALSE(index()->Has(hashes_.at<2>()));
|
|
EXPECT_TRUE(index()->Has(hashes_.at<3>()));
|
|
ASSERT_EQ(2u, last_doom_entry_hashes().size());
|
|
}
|
|
|
|
// Same as test above, but using much older entries to make sure that small
|
|
// things eventually get evictied.
|
|
TEST_F(SimpleIndexTest, EvictBySize2) {
|
|
base::Time now(base::Time::Now());
|
|
index()->SetMaxSize(50000);
|
|
InsertIntoIndexFileReturn(hashes_.at<1>(), now - base::Days(200), 475u);
|
|
InsertIntoIndexFileReturn(hashes_.at<2>(), now - base::Days(1), 40000u);
|
|
ReturnIndexFile();
|
|
WaitForTimeChange();
|
|
|
|
index()->Insert(hashes_.at<3>());
|
|
// Confirm index is as expected: No eviction, everything there.
|
|
EXPECT_EQ(3, index()->GetEntryCount());
|
|
EXPECT_EQ(0, doom_entries_calls());
|
|
EXPECT_TRUE(index()->Has(hashes_.at<1>()));
|
|
EXPECT_TRUE(index()->Has(hashes_.at<2>()));
|
|
EXPECT_TRUE(index()->Has(hashes_.at<3>()));
|
|
|
|
// Trigger an eviction, and make sure the right things are tossed.
|
|
// TODO(morlovich): This is dependent on the innards of the implementation
|
|
// as to at exactly what point we trigger eviction. Not sure how to fix
|
|
// that.
|
|
index()->UpdateEntrySize(hashes_.at<3>(), 40000u);
|
|
EXPECT_EQ(1, doom_entries_calls());
|
|
EXPECT_EQ(1, index()->GetEntryCount());
|
|
EXPECT_FALSE(index()->Has(hashes_.at<1>()));
|
|
EXPECT_FALSE(index()->Has(hashes_.at<2>()));
|
|
EXPECT_TRUE(index()->Has(hashes_.at<3>()));
|
|
ASSERT_EQ(2u, last_doom_entry_hashes().size());
|
|
}
|
|
|
|
// Confirm all the operations queue a disk write at some point in the
|
|
// future.
|
|
TEST_F(SimpleIndexTest, DiskWriteQueued) {
|
|
index()->SetMaxSize(1000);
|
|
ReturnIndexFile();
|
|
|
|
EXPECT_FALSE(index()->HasPendingWrite());
|
|
|
|
const uint64_t kHash1 = hashes_.at<1>();
|
|
index()->Insert(kHash1);
|
|
EXPECT_TRUE(index()->HasPendingWrite());
|
|
index()->write_to_disk_timer_.Stop();
|
|
EXPECT_FALSE(index()->HasPendingWrite());
|
|
|
|
// Attempting to insert a hash that already exists should not queue the
|
|
// write timer.
|
|
index()->Insert(kHash1);
|
|
EXPECT_FALSE(index()->HasPendingWrite());
|
|
|
|
index()->UseIfExists(kHash1);
|
|
EXPECT_TRUE(index()->HasPendingWrite());
|
|
index()->write_to_disk_timer_.Stop();
|
|
|
|
index()->UpdateEntrySize(kHash1, 20u);
|
|
EXPECT_TRUE(index()->HasPendingWrite());
|
|
index()->write_to_disk_timer_.Stop();
|
|
|
|
// Updating to the same size should not queue the write timer.
|
|
index()->UpdateEntrySize(kHash1, 20u);
|
|
EXPECT_FALSE(index()->HasPendingWrite());
|
|
|
|
index()->Remove(kHash1);
|
|
EXPECT_TRUE(index()->HasPendingWrite());
|
|
index()->write_to_disk_timer_.Stop();
|
|
|
|
// Removing a non-existent hash should not queue the write timer.
|
|
index()->Remove(kHash1);
|
|
EXPECT_FALSE(index()->HasPendingWrite());
|
|
}
|
|
|
|
TEST_F(SimpleIndexTest, DiskWriteExecuted) {
|
|
index()->SetMaxSize(1000);
|
|
ReturnIndexFile();
|
|
|
|
EXPECT_FALSE(index()->HasPendingWrite());
|
|
|
|
const uint64_t kHash1 = hashes_.at<1>();
|
|
index()->Insert(kHash1);
|
|
index()->UpdateEntrySize(kHash1, 20u);
|
|
EXPECT_TRUE(index()->HasPendingWrite());
|
|
|
|
EXPECT_EQ(0, index_file_->disk_writes());
|
|
index()->write_to_disk_timer_.FireNow();
|
|
EXPECT_EQ(1, index_file_->disk_writes());
|
|
SimpleIndex::EntrySet entry_set;
|
|
index_file_->GetAndResetDiskWriteEntrySet(&entry_set);
|
|
|
|
uint64_t hash_key = kHash1;
|
|
base::Time now(base::Time::Now());
|
|
ASSERT_EQ(1u, entry_set.size());
|
|
EXPECT_EQ(hash_key, entry_set.begin()->first);
|
|
const EntryMetadata& entry1(entry_set.begin()->second);
|
|
EXPECT_LT(now - base::Minutes(1), entry1.GetLastUsedTime());
|
|
EXPECT_GT(now + base::Minutes(1), entry1.GetLastUsedTime());
|
|
EXPECT_EQ(RoundSize(20u), entry1.GetEntrySize());
|
|
}
|
|
|
|
TEST_F(SimpleIndexTest, DiskWritePostponed) {
|
|
index()->SetMaxSize(1000);
|
|
ReturnIndexFile();
|
|
|
|
EXPECT_FALSE(index()->HasPendingWrite());
|
|
|
|
index()->Insert(hashes_.at<1>());
|
|
index()->UpdateEntrySize(hashes_.at<1>(), 20u);
|
|
EXPECT_TRUE(index()->HasPendingWrite());
|
|
base::TimeTicks expected_trigger(
|
|
index()->write_to_disk_timer_.desired_run_time());
|
|
|
|
WaitForTimeChange();
|
|
EXPECT_EQ(expected_trigger, index()->write_to_disk_timer_.desired_run_time());
|
|
index()->Insert(hashes_.at<2>());
|
|
index()->UpdateEntrySize(hashes_.at<2>(), 40u);
|
|
EXPECT_TRUE(index()->HasPendingWrite());
|
|
EXPECT_LT(expected_trigger, index()->write_to_disk_timer_.desired_run_time());
|
|
index()->write_to_disk_timer_.Stop();
|
|
}
|
|
|
|
// net::APP_CACHE mode should not need to queue disk writes in as many places
|
|
// as the default net::DISK_CACHE mode.
|
|
TEST_F(SimpleIndexAppCacheTest, DiskWriteQueued) {
|
|
index()->SetMaxSize(1000);
|
|
ReturnIndexFile();
|
|
|
|
EXPECT_FALSE(index()->HasPendingWrite());
|
|
|
|
const uint64_t kHash1 = hashes_.at<1>();
|
|
index()->Insert(kHash1);
|
|
EXPECT_TRUE(index()->HasPendingWrite());
|
|
index()->write_to_disk_timer_.Stop();
|
|
EXPECT_FALSE(index()->HasPendingWrite());
|
|
|
|
// Attempting to insert a hash that already exists should not queue the
|
|
// write timer.
|
|
index()->Insert(kHash1);
|
|
EXPECT_FALSE(index()->HasPendingWrite());
|
|
|
|
// Since net::APP_CACHE does not evict or track access times using an
|
|
// entry should not queue the write timer.
|
|
index()->UseIfExists(kHash1);
|
|
EXPECT_FALSE(index()->HasPendingWrite());
|
|
|
|
index()->UpdateEntrySize(kHash1, 20u);
|
|
EXPECT_TRUE(index()->HasPendingWrite());
|
|
index()->write_to_disk_timer_.Stop();
|
|
|
|
// Updating to the same size should not queue the write timer.
|
|
index()->UpdateEntrySize(kHash1, 20u);
|
|
EXPECT_FALSE(index()->HasPendingWrite());
|
|
|
|
index()->Remove(kHash1);
|
|
EXPECT_TRUE(index()->HasPendingWrite());
|
|
index()->write_to_disk_timer_.Stop();
|
|
|
|
// Removing a non-existent hash should not queue the write timer.
|
|
index()->Remove(kHash1);
|
|
EXPECT_FALSE(index()->HasPendingWrite());
|
|
}
|
|
|
|
} // namespace disk_cache
|