blob: 2af289e0b49d89e86d1b8362056d97c6f0eb7dc3 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/optimization_guide/hint_cache_store.h"
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/macros.h"
#include "base/optional.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "components/leveldb_proto/testing/fake_db.h"
#include "components/optimization_guide/hint_update_data.h"
#include "components/optimization_guide/optimization_guide_features.h"
#include "components/optimization_guide/proto/hint_cache.pb.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
using leveldb_proto::test::FakeDB;
using testing::Mock;
namespace optimization_guide {
namespace {
constexpr char kDefaultComponentVersion[] = "1.0.0";
constexpr char kUpdateComponentVersion[] = "2.0.0";
std::string GetHostSuffix(size_t id) {
// Host suffix alternates between two different domain types depending on
// whether the id is odd or even.
if (id % 2 == 0) {
return "domain" + base::NumberToString(id) + ".org";
} else {
return "different.domain" + base::NumberToString(id) + ".co.in";
}
}
enum class MetadataSchemaState {
kMissing,
kInvalid,
kValid,
};
} // namespace
class HintCacheStoreTest : public testing::Test {
public:
using StoreEntry = proto::StoreEntry;
using StoreEntryMap = std::map<HintCacheStore::EntryKey, StoreEntry>;
HintCacheStoreTest() : db_(nullptr) {}
void TearDown() override { last_loaded_hint_.reset(); }
// Initializes the entries contained within the database on startup.
void SeedInitialData(
MetadataSchemaState state,
base::Optional<size_t> component_hint_count = base::Optional<size_t>(),
base::Optional<base::Time> fetched_hints_update =
base::Optional<base::Time>()) {
db_store_.clear();
// Add a metadata schema entry if its state isn't kMissing. The version
// entry version is set to the store's current version if the state is
// kValid; otherwise, it's set to the invalid version of "0".
if (state == MetadataSchemaState::kValid) {
db_store_[HintCacheStore::GetMetadataTypeEntryKey(
HintCacheStore::MetadataType::kSchema)]
.set_version(HintCacheStore::kStoreSchemaVersion);
} else if (state == MetadataSchemaState::kInvalid) {
db_store_[HintCacheStore::GetMetadataTypeEntryKey(
HintCacheStore::MetadataType::kSchema)]
.set_version("0");
}
// If the database is being seeded with component hints, it is indicated
// with a provided count. Add the component metadata with the default
// component version and then add the indicated number of component hints.
// if (component_hint_count && component_hint_count >
// static_cast<size_t>(0)) {
if (component_hint_count && component_hint_count > 0u) {
db_store_[HintCacheStore::GetMetadataTypeEntryKey(
HintCacheStore::MetadataType::kComponent)]
.set_version(kDefaultComponentVersion);
HintCacheStore::EntryKeyPrefix component_hint_key_prefix =
HintCacheStore::GetComponentHintEntryKeyPrefix(
base::Version(kDefaultComponentVersion));
for (size_t i = 0; i < component_hint_count.value(); ++i) {
std::string host_suffix = GetHostSuffix(i);
StoreEntry& entry = db_store_[component_hint_key_prefix + host_suffix];
entry.set_entry_type(static_cast<proto::StoreEntryType>(
HintCacheStore::StoreEntryType::kComponentHint));
proto::Hint* hint = entry.mutable_hint();
hint->set_key(host_suffix);
hint->set_key_representation(proto::HOST_SUFFIX);
proto::PageHint* page_hint = hint->add_page_hints();
page_hint->set_page_pattern("page pattern " + base::NumberToString(i));
}
}
if (fetched_hints_update) {
db_store_[HintCacheStore::GetMetadataTypeEntryKey(
HintCacheStore::MetadataType::kFetched)]
.set_update_time_secs(
fetched_hints_update->ToDeltaSinceWindowsEpoch().InSeconds());
}
}
// Moves the specified number of component hints into the update data.
void SeedComponentUpdateData(HintUpdateData* update_data,
size_t component_hint_count) {
for (size_t i = 0; i < component_hint_count; ++i) {
std::string host_suffix = GetHostSuffix(i);
proto::Hint hint;
hint.set_key(host_suffix);
hint.set_key_representation(proto::HOST_SUFFIX);
proto::PageHint* page_hint = hint.add_page_hints();
page_hint->set_page_pattern("page pattern " + base::NumberToString(i));
update_data->MoveHintIntoUpdateData(std::move(hint));
}
}
// Moves the specified number of component hints into the update data.
void SeedFetchedUpdateData(HintUpdateData* update_data,
size_t fetched_hint_count) {
for (size_t i = 0; i < fetched_hint_count; ++i) {
std::string host_suffix = GetHostSuffix(i);
proto::Hint hint;
hint.set_key(host_suffix);
hint.set_key_representation(proto::HOST_SUFFIX);
proto::PageHint* page_hint = hint.add_page_hints();
page_hint->set_page_pattern("page pattern " + base::NumberToString(i));
update_data->MoveHintIntoUpdateData(std::move(hint));
}
}
void CreateDatabase() {
// Reset everything.
db_ = nullptr;
hint_store_.reset();
// Setup the fake db and the class under test.
auto db = std::make_unique<FakeDB<StoreEntry>>(&db_store_);
db_ = db.get();
hint_store_ = std::make_unique<HintCacheStore>(std::move(db));
}
void InitializeDatabase(bool success, bool purge_existing_data = false) {
EXPECT_CALL(*this, OnInitialized());
hint_store()->Initialize(purge_existing_data,
base::BindOnce(&HintCacheStoreTest::OnInitialized,
base::Unretained(this)));
// OnDatabaseInitialized callback
db()->InitStatusCallback(success ? leveldb_proto::Enums::kOK
: leveldb_proto::Enums::kError);
}
void InitializeStore(MetadataSchemaState state,
bool purge_existing_data = false) {
InitializeDatabase(true /*=success*/, purge_existing_data);
if (purge_existing_data) {
// OnPurgeDatabase callback
db()->UpdateCallback(true);
return;
}
// OnLoadMetadata callback
db()->LoadCallback(true);
if (state == MetadataSchemaState::kValid) {
// OnLoadHintEntryKeys callback
db()->LoadCallback(true);
} else {
// OnPurgeDatabase callback
db()->UpdateCallback(true);
}
}
void UpdateComponentHints(std::unique_ptr<HintUpdateData> component_data,
bool update_success = true,
bool load_hint_entry_keys_success = true) {
EXPECT_CALL(*this, OnUpdateHints());
hint_store()->UpdateComponentHints(
std::move(component_data),
base::BindOnce(&HintCacheStoreTest::OnUpdateHints,
base::Unretained(this)));
// OnUpdateHints callback
db()->UpdateCallback(update_success);
if (update_success) {
// OnLoadHintEntryKeys callback
db()->LoadCallback(load_hint_entry_keys_success);
}
}
void UpdateFetchedHints(std::unique_ptr<HintUpdateData> fetched_data,
bool update_success = true,
bool load_hint_entry_keys_success = true) {
EXPECT_CALL(*this, OnUpdateHints());
hint_store()->UpdateFetchedHints(
std::move(fetched_data),
base::BindOnce(&HintCacheStoreTest::OnUpdateHints,
base::Unretained(this)));
// OnUpdateHints callback
db()->UpdateCallback(update_success);
if (update_success) {
// OnLoadHintEntryKeys callback
db()->LoadCallback(load_hint_entry_keys_success);
}
}
void ClearFetchedHintsFromDatabase() {
hint_store()->ClearFetchedHintsFromDatabase();
db()->UpdateCallback(true);
db()->LoadCallback(true);
}
void PurgeExpiredFetchedHints() {
hint_store()->PurgeExpiredFetchedHints();
// OnFetchedHintsLoadedToMaybePurge
db()->LoadCallback(true);
// OnUpdateHints
db()->UpdateCallback(true);
// OnLoadHintEntryKeys callback
db()->LoadCallback(true);
}
bool IsMetadataSchemaEntryKeyPresent() const {
return IsKeyPresent(HintCacheStore::GetMetadataTypeEntryKey(
HintCacheStore::MetadataType::kSchema));
}
// Verifies that the fetched metadata has the expected next update time.
void ExpectFetchedMetadata(base::Time update_time) const {
const auto& metadata_entry =
db_store_.find(HintCacheStore::GetMetadataTypeEntryKey(
HintCacheStore::MetadataType::kFetched));
if (metadata_entry != db_store_.end()) {
// The next update time should have same time up to the second as the
// metadata entry is stored in seconds.
EXPECT_TRUE(
base::Time::FromDeltaSinceWindowsEpoch(base::TimeDelta::FromSeconds(
metadata_entry->second.update_time_secs())) -
update_time <
base::TimeDelta::FromSeconds(1));
} else {
FAIL() << "No fetched metadata found";
}
}
// Verifies that the component metadata has the expected version and all
// expected component hints are present.
void ExpectComponentHintsPresent(const std::string& version,
int count) const {
const auto& metadata_entry =
db_store_.find(HintCacheStore::GetMetadataTypeEntryKey(
HintCacheStore::MetadataType::kComponent));
if (metadata_entry != db_store_.end()) {
EXPECT_EQ(metadata_entry->second.version(), version);
} else {
FAIL() << "No component metadata found";
}
HintCacheStore::EntryKeyPrefix component_hint_entry_key_prefix =
HintCacheStore::GetComponentHintEntryKeyPrefix(base::Version(version));
for (int i = 0; i < count; ++i) {
std::string host_suffix = GetHostSuffix(i);
HintCacheStore::EntryKey hint_entry_key =
component_hint_entry_key_prefix + host_suffix;
const auto& hint_entry = db_store_.find(hint_entry_key);
if (hint_entry == db_store_.end()) {
FAIL() << "No entry found for component hint: " << hint_entry_key;
continue;
}
if (!hint_entry->second.has_hint()) {
FAIL() << "Component hint entry does not have hint: " << hint_entry_key;
continue;
}
EXPECT_EQ(hint_entry->second.hint().key(), host_suffix);
}
}
// Returns true if the data is present for the given key.
bool IsKeyPresent(const HintCacheStore::EntryKey& entry_key) const {
return db_store_.find(entry_key) != db_store_.end();
}
size_t GetDBStoreEntryCount() const { return db_store_.size(); }
size_t GetStoreHintEntryKeyCount() const {
return hint_store_->GetHintEntryKeyCount();
}
HintCacheStore* hint_store() { return hint_store_.get(); }
FakeDB<proto::StoreEntry>* db() { return db_; }
const HintCacheStore::EntryKey& last_loaded_hint_entry_key() const {
return last_loaded_hint_entry_key_;
}
proto::Hint* last_loaded_hint() { return last_loaded_hint_.get(); }
void OnHintLoaded(const HintCacheStore::EntryKey& hint_entry_key,
std::unique_ptr<proto::Hint> loaded_hint) {
last_loaded_hint_entry_key_ = hint_entry_key;
last_loaded_hint_ = std::move(loaded_hint);
}
MOCK_METHOD0(OnInitialized, void());
MOCK_METHOD0(OnUpdateHints, void());
private:
FakeDB<proto::StoreEntry>* db_;
StoreEntryMap db_store_;
std::unique_ptr<HintCacheStore> hint_store_;
HintCacheStore::EntryKey last_loaded_hint_entry_key_;
std::unique_ptr<proto::Hint> last_loaded_hint_;
DISALLOW_COPY_AND_ASSIGN(HintCacheStoreTest);
};
TEST_F(HintCacheStoreTest, NoInitialization) {
base::HistogramTester histogram_tester;
SeedInitialData(MetadataSchemaState::kMissing);
CreateDatabase();
histogram_tester.ExpectTotalCount(
"OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult", 0);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 0 /* kUninitialized */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 1 /* kInitializing */,
0);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 2 /* kAvailable */, 0);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 3 /* kFailed */, 0);
}
TEST_F(HintCacheStoreTest, InitializeFailedOnInitializeWithNoInitialData) {
base::HistogramTester histogram_tester;
SeedInitialData(MetadataSchemaState::kMissing);
CreateDatabase();
InitializeDatabase(false /*=success*/);
// In the case where initialization fails, the store should be fully purged.
EXPECT_EQ(GetDBStoreEntryCount(), static_cast<size_t>(0));
EXPECT_EQ(GetStoreHintEntryKeyCount(), static_cast<size_t>(0));
histogram_tester.ExpectTotalCount(
"OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult", 0);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 0 /* kUninitialized */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 1 /* kInitializing */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 2 /* kAvailable */, 0);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 3 /* kFailed */, 1);
}
TEST_F(HintCacheStoreTest, InitializeFailedOnLoadMetadataWithNoInitialData) {
base::HistogramTester histogram_tester;
SeedInitialData(MetadataSchemaState::kMissing);
CreateDatabase();
InitializeDatabase(true /*=success*/);
// OnLoadMetadata callback
db()->LoadCallback(false);
// In the case where initialization fails, the store should be fully purged.
EXPECT_EQ(GetDBStoreEntryCount(), static_cast<size_t>(0));
EXPECT_EQ(GetStoreHintEntryKeyCount(), static_cast<size_t>(0));
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult",
1 /* kLoadMetadataFailed */, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 0 /* kUninitialized */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 1 /* kInitializing */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 2 /* kAvailable */, 0);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 3 /* kFailed */, 1);
}
TEST_F(HintCacheStoreTest, InitializeFailedOnUpdateMetadataNoInitialData) {
base::HistogramTester histogram_tester;
SeedInitialData(MetadataSchemaState::kMissing);
CreateDatabase();
InitializeDatabase(true /*=success*/);
// OnLoadMetadata callback
db()->LoadCallback(true);
// OnPurgeDatabase callback
db()->UpdateCallback(false);
// In the case where initialization fails, the store should be fully purged.
EXPECT_EQ(GetDBStoreEntryCount(), static_cast<size_t>(0));
EXPECT_EQ(GetStoreHintEntryKeyCount(), static_cast<size_t>(0));
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult",
2 /* kSchemaMetadataMissing */, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 0 /* kUninitialized */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 1 /* kInitializing */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 2 /* kAvailable */, 0);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 3 /* kFailed */, 1);
}
TEST_F(HintCacheStoreTest, InitializeFailedOnInitializeWithInitialData) {
base::HistogramTester histogram_tester;
SeedInitialData(MetadataSchemaState::kValid, 10);
CreateDatabase();
InitializeDatabase(false /*=success*/);
// In the case where initialization fails, the store should be fully purged.
EXPECT_EQ(GetDBStoreEntryCount(), static_cast<size_t>(0));
EXPECT_EQ(GetStoreHintEntryKeyCount(), static_cast<size_t>(0));
histogram_tester.ExpectTotalCount(
"OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult", 0);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 0 /* kUninitialized */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 1 /* kInitializing */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 2 /* kAvailable */, 0);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 3 /* kFailed */, 1);
}
TEST_F(HintCacheStoreTest, InitializeFailedOnLoadMetadataWithInitialData) {
base::HistogramTester histogram_tester;
SeedInitialData(MetadataSchemaState::kValid, 10);
CreateDatabase();
InitializeDatabase(true /*=success*/);
// OnLoadMetadata callback
db()->LoadCallback(false);
// In the case where initialization fails, the store should be fully purged.
EXPECT_EQ(GetDBStoreEntryCount(), static_cast<size_t>(0));
EXPECT_EQ(GetStoreHintEntryKeyCount(), static_cast<size_t>(0));
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult",
1 /* kLoadMetadataFailed */, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 0 /* kUninitialized */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 1 /* kInitializing */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 2 /* kAvailable */, 0);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 3 /* kFailed */, 1);
}
TEST_F(HintCacheStoreTest,
InitializeFailedOnUpdateMetadataWithInvalidSchemaEntry) {
base::HistogramTester histogram_tester;
SeedInitialData(MetadataSchemaState::kInvalid, 10);
CreateDatabase();
InitializeDatabase(true /*=success*/);
// OnLoadMetadata callback
db()->LoadCallback(true);
// OnPurgeDatabase callback
db()->UpdateCallback(false);
// In the case where initialization fails, the store should be fully purged.
EXPECT_EQ(GetDBStoreEntryCount(), static_cast<size_t>(0));
EXPECT_EQ(GetStoreHintEntryKeyCount(), static_cast<size_t>(0));
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult",
3 /* kSchemaMetadataWrongVersion */, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 0 /* kUninitialized */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 1 /* kInitializing */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 2 /* kAvailable */, 0);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 3 /* kFailed */, 1);
}
TEST_F(HintCacheStoreTest, InitializeFailedOnLoadHintEntryKeysWithInitialData) {
base::HistogramTester histogram_tester;
SeedInitialData(MetadataSchemaState::kValid, 10, base::Time().Now());
CreateDatabase();
InitializeDatabase(true /*=success*/);
// OnLoadMetadata callback
db()->LoadCallback(true);
// OnLoadHintEntryKeys callback
db()->LoadCallback(false);
// In the case where initialization fails, the store should be fully purged.
EXPECT_EQ(GetDBStoreEntryCount(), static_cast<size_t>(0));
EXPECT_EQ(GetStoreHintEntryKeyCount(), static_cast<size_t>(0));
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult",
0 /* kSuccess */, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 0 /* kUninitialized */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 1 /* kInitializing */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 2 /* kAvailable */, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 3 /* kFailed */, 1);
}
TEST_F(HintCacheStoreTest, InitializeSucceededWithoutSchemaEntry) {
base::HistogramTester histogram_tester;
MetadataSchemaState schema_state = MetadataSchemaState::kMissing;
SeedInitialData(schema_state);
CreateDatabase();
InitializeStore(schema_state);
// The store should contain the schema metadata entry and nothing else.
EXPECT_EQ(GetDBStoreEntryCount(), static_cast<size_t>(1));
EXPECT_EQ(GetStoreHintEntryKeyCount(), static_cast<size_t>(0));
EXPECT_TRUE(IsMetadataSchemaEntryKeyPresent());
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult",
2 /* kSchemaMetadataMissing */, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 0 /* kUninitialized */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 1 /* kInitializing */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 2 /* kAvailable */, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 3 /* kFailed */, 0);
}
TEST_F(HintCacheStoreTest, InitializeSucceededWithInvalidSchemaEntry) {
base::HistogramTester histogram_tester;
MetadataSchemaState schema_state = MetadataSchemaState::kInvalid;
SeedInitialData(schema_state);
CreateDatabase();
InitializeStore(schema_state);
// The store should contain the schema metadata entry and nothing else.
EXPECT_EQ(GetDBStoreEntryCount(), static_cast<size_t>(1));
EXPECT_EQ(GetStoreHintEntryKeyCount(), static_cast<size_t>(0));
EXPECT_TRUE(IsMetadataSchemaEntryKeyPresent());
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult",
3 /* kSchemaMetadataWrongVersion */, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 0 /* kUninitialized */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 1 /* kInitializing */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 2 /* kAvailable */, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 3 /* kFailed */, 0);
}
TEST_F(HintCacheStoreTest, InitializeSucceededWithValidSchemaEntry) {
base::HistogramTester histogram_tester;
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
SeedInitialData(schema_state);
CreateDatabase();
InitializeStore(schema_state);
// The store should contain the schema metadata entry and nothing else.
EXPECT_EQ(GetDBStoreEntryCount(), static_cast<size_t>(1));
EXPECT_EQ(GetStoreHintEntryKeyCount(), static_cast<size_t>(0));
EXPECT_TRUE(IsMetadataSchemaEntryKeyPresent());
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult",
4 /* kComponentMetadataMissing*/, 0);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult",
5 /* kFetchedMetadataMissing*/, 0);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult",
6 /* kComponentAndFetchedMetadataMissing*/, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 0 /* kUninitialized */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 1 /* kInitializing */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 2 /* kAvailable */, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 3 /* kFailed */, 0);
}
TEST_F(HintCacheStoreTest,
InitializeSucceededWithInvalidSchemaEntryAndInitialData) {
base::HistogramTester histogram_tester;
MetadataSchemaState schema_state = MetadataSchemaState::kInvalid;
SeedInitialData(schema_state, 10);
CreateDatabase();
InitializeStore(schema_state);
// The store should contain the schema metadata entry and nothing else, as
// the initial component hints are all purged.
EXPECT_EQ(GetDBStoreEntryCount(), static_cast<size_t>(1));
EXPECT_EQ(GetStoreHintEntryKeyCount(), static_cast<size_t>(0));
EXPECT_TRUE(IsMetadataSchemaEntryKeyPresent());
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult",
3 /* kSchemaMetadataWrongVersion */, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 0 /* kUninitialized */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 1 /* kInitializing */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 2 /* kAvailable */, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 3 /* kFailed */, 0);
}
TEST_F(HintCacheStoreTest, InitializeSucceededWithPurgeExistingData) {
base::HistogramTester histogram_tester;
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
SeedInitialData(schema_state, 10);
CreateDatabase();
InitializeStore(schema_state, true /*=purge_existing_data*/);
// The store should contain the schema metadata entry and nothing else.
EXPECT_EQ(GetDBStoreEntryCount(), static_cast<size_t>(1));
EXPECT_EQ(GetStoreHintEntryKeyCount(), static_cast<size_t>(0));
EXPECT_TRUE(IsMetadataSchemaEntryKeyPresent());
histogram_tester.ExpectTotalCount(
"OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult", 0);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 0 /* kUninitialized */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 1 /* kInitializing */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 2 /* kAvailable */, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 3 /* kFailed */, 0);
}
TEST_F(HintCacheStoreTest,
InitializeSucceededWithValidSchemaEntryAndInitialData) {
base::HistogramTester histogram_tester;
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
size_t component_hint_count = 10;
SeedInitialData(schema_state, component_hint_count, base::Time().Now());
CreateDatabase();
InitializeStore(schema_state);
// The store should contain the schema metadata entry, the component metadata
// entry, and all of the initial component hints.
EXPECT_EQ(GetDBStoreEntryCount(),
static_cast<size_t>(component_hint_count + 3));
EXPECT_EQ(GetStoreHintEntryKeyCount(), component_hint_count);
EXPECT_TRUE(IsMetadataSchemaEntryKeyPresent());
ExpectComponentHintsPresent(kDefaultComponentVersion, component_hint_count);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult",
0 /* kSuccess */, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 0 /* kUninitialized */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 1 /* kInitializing */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 2 /* kAvailable */, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 3 /* kFailed */, 0);
}
TEST_F(HintCacheStoreTest,
InitializeSucceededWithValidSchemaEntryAndComponentDataOnly) {
base::HistogramTester histogram_tester;
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
size_t component_hint_count = 10;
SeedInitialData(schema_state, component_hint_count);
CreateDatabase();
InitializeStore(schema_state);
// The store should contain the schema metadata entry, the component metadata
// entry, and all of the initial component hints.
EXPECT_EQ(GetDBStoreEntryCount(),
static_cast<size_t>(component_hint_count + 2));
EXPECT_EQ(GetStoreHintEntryKeyCount(), component_hint_count);
EXPECT_TRUE(IsMetadataSchemaEntryKeyPresent());
ExpectComponentHintsPresent(kDefaultComponentVersion, component_hint_count);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult",
4 /* kComponentMetadataMissing*/, 0);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult",
5 /* kFetchedMetadataMissing*/, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult",
6 /* kComponentAndFetchedMetadataMissing*/, 0);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 0 /* kUninitialized */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 1 /* kInitializing */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 2 /* kAvailable */, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 3 /* kFailed */, 0);
}
TEST_F(HintCacheStoreTest,
InitializeSucceededWithValidSchemaEntryAndFetchedMetaData) {
base::HistogramTester histogram_tester;
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
size_t component_hint_count = 0;
SeedInitialData(schema_state, component_hint_count, base::Time().Now());
CreateDatabase();
InitializeStore(schema_state);
// The store should contain the schema metadata entry, the component metadata
// entry, and all of the initial component hints.
EXPECT_EQ(GetDBStoreEntryCount(),
static_cast<size_t>(component_hint_count + 2));
EXPECT_EQ(GetStoreHintEntryKeyCount(), component_hint_count);
EXPECT_TRUE(IsMetadataSchemaEntryKeyPresent());
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult",
4 /* kComponentMetadataMissing*/, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 0 /* kUninitialized */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 1 /* kInitializing */,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 2 /* kAvailable */, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheLevelDBStore.Status", 3 /* kFailed */, 0);
}
TEST_F(HintCacheStoreTest,
CreateComponentUpdateDataFailsForUninitializedStore) {
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
SeedInitialData(schema_state, 10);
CreateDatabase();
// HintUpdateData for a component update should only be created if the store
// is initialized.
EXPECT_FALSE(hint_store()->MaybeCreateUpdateDataForComponentHints(
base::Version(kUpdateComponentVersion)));
}
TEST_F(HintCacheStoreTest, CreateComponentUpdateDataFailsForEarlierVersion) {
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
SeedInitialData(schema_state, 10);
CreateDatabase();
InitializeStore(schema_state);
// No HintUpdateData for a component update should be created when the
// component version of the update is older than the store's component
// version.
EXPECT_FALSE(hint_store()->MaybeCreateUpdateDataForComponentHints(
base::Version("0.0.0")));
}
TEST_F(HintCacheStoreTest, CreateComponentUpdateDataFailsForCurrentVersion) {
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
SeedInitialData(schema_state, 10);
CreateDatabase();
InitializeStore(schema_state);
// No HintUpdateData should be created when the component version of the
// update is the same as the store's component version.
EXPECT_FALSE(hint_store()->MaybeCreateUpdateDataForComponentHints(
base::Version(kDefaultComponentVersion)));
}
TEST_F(HintCacheStoreTest,
CreateComponentUpdateDataSucceedsWithNoPreexistingVersion) {
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
SeedInitialData(schema_state);
CreateDatabase();
InitializeStore(schema_state);
// HintUpdateData for a component update should be created when there is no
// pre-existing component in the store.
EXPECT_TRUE(hint_store()->MaybeCreateUpdateDataForComponentHints(
base::Version(kDefaultComponentVersion)));
}
TEST_F(HintCacheStoreTest, CreateComponentUpdateDataSucceedsForNewerVersion) {
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
SeedInitialData(schema_state, 10);
CreateDatabase();
InitializeStore(schema_state);
// HintUpdateData for a component update should be created when the component
// version of the update is newer than the store's component version.
EXPECT_TRUE(hint_store()->MaybeCreateUpdateDataForComponentHints(
base::Version(kUpdateComponentVersion)));
}
TEST_F(HintCacheStoreTest, UpdateComponentHintsUpdateEntriesFails) {
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
SeedInitialData(schema_state, 10);
CreateDatabase();
InitializeStore(schema_state);
std::unique_ptr<HintUpdateData> update_data =
hint_store()->MaybeCreateUpdateDataForComponentHints(
base::Version(kUpdateComponentVersion));
ASSERT_TRUE(update_data);
SeedComponentUpdateData(update_data.get(), 5);
UpdateComponentHints(std::move(update_data), false /*update_success*/);
// The store should be purged if the component data update fails.
EXPECT_EQ(GetDBStoreEntryCount(), static_cast<size_t>(0));
EXPECT_EQ(GetStoreHintEntryKeyCount(), static_cast<size_t>(0));
}
TEST_F(HintCacheStoreTest, UpdateComponentHintsGetKeysFails) {
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
SeedInitialData(schema_state, 10);
CreateDatabase();
InitializeStore(schema_state);
std::unique_ptr<HintUpdateData> update_data =
hint_store()->MaybeCreateUpdateDataForComponentHints(
base::Version(kUpdateComponentVersion));
ASSERT_TRUE(update_data);
SeedComponentUpdateData(update_data.get(), 5);
UpdateComponentHints(std::move(update_data), true /*update_success*/,
false /*load_hints_keys_success*/);
// The store should be purged if loading the keys after the component update
// fails.
EXPECT_EQ(GetDBStoreEntryCount(), static_cast<size_t>(0));
EXPECT_EQ(GetStoreHintEntryKeyCount(), static_cast<size_t>(0));
}
TEST_F(HintCacheStoreTest, UpdateComponentHints) {
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
size_t initial_hint_count = 10;
size_t update_hint_count = 5;
SeedInitialData(schema_state, initial_hint_count);
CreateDatabase();
InitializeStore(schema_state);
std::unique_ptr<HintUpdateData> update_data =
hint_store()->MaybeCreateUpdateDataForComponentHints(
base::Version(kUpdateComponentVersion));
ASSERT_TRUE(update_data);
SeedComponentUpdateData(update_data.get(), update_hint_count);
UpdateComponentHints(std::move(update_data));
// When the component update succeeds, the store should contain the schema
// metadata entry, the component metadata entry, and all of the update's
// component hints.
EXPECT_EQ(GetDBStoreEntryCount(), update_hint_count + 2);
EXPECT_EQ(GetStoreHintEntryKeyCount(), update_hint_count);
ExpectComponentHintsPresent(kUpdateComponentVersion, update_hint_count);
}
TEST_F(HintCacheStoreTest, UpdateComponentHintsAfterInitializationDataPurge) {
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
size_t initial_hint_count = 10;
size_t update_hint_count = 5;
SeedInitialData(schema_state, initial_hint_count);
CreateDatabase();
InitializeStore(schema_state, true /*=purge_existing_data*/);
std::unique_ptr<HintUpdateData> update_data =
hint_store()->MaybeCreateUpdateDataForComponentHints(
base::Version(kUpdateComponentVersion));
ASSERT_TRUE(update_data);
SeedComponentUpdateData(update_data.get(), update_hint_count);
UpdateComponentHints(std::move(update_data));
// When the component update succeeds, the store should contain the schema
// metadata entry, the component metadata entry, and all of the update's
// component hints.
EXPECT_EQ(GetDBStoreEntryCount(), update_hint_count + 2);
EXPECT_EQ(GetStoreHintEntryKeyCount(), update_hint_count);
ExpectComponentHintsPresent(kUpdateComponentVersion, update_hint_count);
}
TEST_F(HintCacheStoreTest, CreateComponentDataWithAlreadyUpdatedVersionFails) {
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
size_t initial_hint_count = 10;
size_t update_hint_count = 5;
SeedInitialData(schema_state, initial_hint_count);
CreateDatabase();
InitializeStore(schema_state);
std::unique_ptr<HintUpdateData> update_data =
hint_store()->MaybeCreateUpdateDataForComponentHints(
base::Version(kUpdateComponentVersion));
ASSERT_TRUE(update_data);
SeedComponentUpdateData(update_data.get(), update_hint_count);
UpdateComponentHints(std::move(update_data));
// HintUpdateData for the component update should not be created for a second
// component update with the same version as the first component update.
EXPECT_FALSE(hint_store()->MaybeCreateUpdateDataForComponentHints(
base::Version(kUpdateComponentVersion)));
}
TEST_F(HintCacheStoreTest, UpdateComponentHintsWithUpdatedVersionFails) {
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
size_t initial_hint_count = 10;
size_t update_hint_count_1 = 5;
size_t update_hint_count_2 = 15;
SeedInitialData(schema_state, initial_hint_count);
CreateDatabase();
InitializeStore(schema_state);
// Create two updates for the same component version with different counts.
std::unique_ptr<HintUpdateData> update_data_1 =
hint_store()->MaybeCreateUpdateDataForComponentHints(
base::Version(kUpdateComponentVersion));
std::unique_ptr<HintUpdateData> update_data_2 =
hint_store()->MaybeCreateUpdateDataForComponentHints(
base::Version(kUpdateComponentVersion));
ASSERT_TRUE(update_data_1);
SeedComponentUpdateData(update_data_1.get(), update_hint_count_1);
ASSERT_TRUE(update_data_2);
SeedComponentUpdateData(update_data_2.get(), update_hint_count_2);
// Update the component data with the same component version twice:
// first with |update_data_1| and then with |update_data_2|.
UpdateComponentHints(std::move(update_data_1));
EXPECT_CALL(*this, OnUpdateHints());
hint_store()->UpdateComponentHints(
std::move(update_data_2),
base::BindOnce(&HintCacheStoreTest::OnUpdateHints,
base::Unretained(this)));
// Verify that the store is populated with the component data from
// |update_data_1| and not |update_data_2|.
EXPECT_EQ(GetDBStoreEntryCount(), update_hint_count_1 + 2);
EXPECT_EQ(GetStoreHintEntryKeyCount(), update_hint_count_1);
ExpectComponentHintsPresent(kUpdateComponentVersion, update_hint_count_1);
}
TEST_F(HintCacheStoreTest, LoadHintOnUnavailableStore) {
size_t initial_hint_count = 10;
SeedInitialData(MetadataSchemaState::kValid, initial_hint_count);
CreateDatabase();
const HintCacheStore::EntryKey kInvalidEntryKey = "invalid";
hint_store()->LoadHint(kInvalidEntryKey,
base::BindOnce(&HintCacheStoreTest::OnHintLoaded,
base::Unretained(this)));
// Verify that the OnHintLoaded callback runs when the store is unavailable
// and that both the key and the hint were correctly set in it.
EXPECT_EQ(last_loaded_hint_entry_key(), kInvalidEntryKey);
EXPECT_FALSE(last_loaded_hint());
}
TEST_F(HintCacheStoreTest, LoadHintFailure) {
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
size_t hint_count = 10;
SeedInitialData(schema_state, hint_count);
CreateDatabase();
InitializeStore(schema_state);
const HintCacheStore::EntryKey kInvalidEntryKey = "invalid";
hint_store()->LoadHint(kInvalidEntryKey,
base::BindOnce(&HintCacheStoreTest::OnHintLoaded,
base::Unretained(this)));
// OnLoadHint callback
db()->GetCallback(false);
// Verify that the OnHintLoaded callback runs when the store is unavailable
// and that both the key and the hint were correctly set in it.
EXPECT_EQ(last_loaded_hint_entry_key(), kInvalidEntryKey);
EXPECT_FALSE(last_loaded_hint());
}
TEST_F(HintCacheStoreTest, LoadHintSuccessInitialData) {
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
size_t hint_count = 10;
SeedInitialData(schema_state, hint_count);
CreateDatabase();
InitializeStore(schema_state);
// Verify that all component hints in the initial data can successfully be
// loaded from the store.
for (size_t i = 0; i < hint_count; ++i) {
std::string host_suffix = GetHostSuffix(i);
HintCacheStore::EntryKey hint_entry_key;
if (!hint_store()->FindHintEntryKey(host_suffix, &hint_entry_key)) {
FAIL() << "Hint entry not found for host suffix: " << host_suffix;
continue;
}
hint_store()->LoadHint(hint_entry_key,
base::BindOnce(&HintCacheStoreTest::OnHintLoaded,
base::Unretained(this)));
// OnLoadHint callback
db()->GetCallback(true);
EXPECT_EQ(last_loaded_hint_entry_key(), hint_entry_key);
if (!last_loaded_hint()) {
FAIL() << "Loaded hint was null for entry key: " << hint_entry_key;
continue;
}
EXPECT_EQ(last_loaded_hint()->key(), host_suffix);
}
}
TEST_F(HintCacheStoreTest, LoadHintSuccessUpdateData) {
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
size_t initial_hint_count = 10;
size_t update_hint_count = 5;
SeedInitialData(schema_state, initial_hint_count);
CreateDatabase();
InitializeStore(schema_state);
std::unique_ptr<HintUpdateData> update_data =
hint_store()->MaybeCreateUpdateDataForComponentHints(
base::Version(kUpdateComponentVersion));
ASSERT_TRUE(update_data);
SeedComponentUpdateData(update_data.get(), update_hint_count);
UpdateComponentHints(std::move(update_data));
// Verify that all component hints within a successful component update can
// be loaded from the store.
for (size_t i = 0; i < update_hint_count; ++i) {
std::string host_suffix = GetHostSuffix(i);
HintCacheStore::EntryKey hint_entry_key;
if (!hint_store()->FindHintEntryKey(host_suffix, &hint_entry_key)) {
FAIL() << "Hint entry not found for host suffix: " << host_suffix;
continue;
}
hint_store()->LoadHint(hint_entry_key,
base::BindOnce(&HintCacheStoreTest::OnHintLoaded,
base::Unretained(this)));
// OnLoadHint callback
db()->GetCallback(true);
EXPECT_EQ(last_loaded_hint_entry_key(), hint_entry_key);
if (!last_loaded_hint()) {
FAIL() << "Loaded hint was null for entry key: " << hint_entry_key;
continue;
}
EXPECT_EQ(last_loaded_hint()->key(), host_suffix);
}
}
TEST_F(HintCacheStoreTest, FindHintEntryKeyOnUnavailableStore) {
size_t initial_hint_count = 10;
SeedInitialData(MetadataSchemaState::kValid, initial_hint_count);
CreateDatabase();
std::string host_suffix = GetHostSuffix(0);
HintCacheStore::EntryKey hint_entry_key;
// Verify that hint entry keys can't be found when the store is unavailable.
EXPECT_FALSE(hint_store()->FindHintEntryKey(host_suffix, &hint_entry_key));
}
TEST_F(HintCacheStoreTest, FindHintEntryKeyInitialData) {
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
size_t hint_count = 10;
SeedInitialData(schema_state, hint_count);
CreateDatabase();
InitializeStore(schema_state);
// Verify that all hints contained within the initial store data are reported
// as being found and hints that are not containd within the initial data are
// properly reported as not being found.
for (size_t i = 0; i < hint_count * 2; ++i) {
std::string host_suffix = GetHostSuffix(i);
HintCacheStore::EntryKey hint_entry_key;
bool success = hint_store()->FindHintEntryKey(host_suffix, &hint_entry_key);
EXPECT_EQ(success, i < hint_count);
}
}
TEST_F(HintCacheStoreTest, FindHintEntryKeyUpdateData) {
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
size_t initial_hint_count = 10;
size_t update_hint_count = 5;
SeedInitialData(schema_state, initial_hint_count);
CreateDatabase();
InitializeStore(schema_state);
std::unique_ptr<HintUpdateData> update_data =
hint_store()->MaybeCreateUpdateDataForComponentHints(
base::Version(kUpdateComponentVersion));
ASSERT_TRUE(update_data);
SeedComponentUpdateData(update_data.get(), update_hint_count);
UpdateComponentHints(std::move(update_data));
// Verify that all hints contained within the component update are reported
// by the store as being found and hints that are not containd within the
// component update are properly reported as not being found.
for (size_t i = 0; i < update_hint_count * 2; ++i) {
std::string host_suffix = GetHostSuffix(i);
HintCacheStore::EntryKey hint_entry_key;
bool success = hint_store()->FindHintEntryKey(host_suffix, &hint_entry_key);
EXPECT_EQ(success, i < update_hint_count);
}
}
TEST_F(HintCacheStoreTest, FetchedHintsMetadataStored) {
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
base::Time update_time = base::Time().Now();
SeedInitialData(schema_state, 10, update_time);
CreateDatabase();
InitializeStore(schema_state);
ExpectFetchedMetadata(update_time);
}
TEST_F(HintCacheStoreTest, FindHintEntryKeyForFetchedHints) {
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
size_t update_hint_count = 5;
base::Time update_time = base::Time().Now();
SeedInitialData(schema_state, 0);
CreateDatabase();
InitializeStore(schema_state);
std::unique_ptr<HintUpdateData> update_data =
hint_store()->CreateUpdateDataForFetchedHints(
update_time, update_time + optimization_guide::features::
StoredFetchedHintsFreshnessDuration());
ASSERT_TRUE(update_data);
SeedFetchedUpdateData(update_data.get(), update_hint_count);
UpdateFetchedHints(std::move(update_data));
for (size_t i = 0; i < update_hint_count; ++i) {
std::string host_suffix = GetHostSuffix(i);
HintCacheStore::EntryKey hint_entry_key;
bool success = hint_store()->FindHintEntryKey(host_suffix, &hint_entry_key);
EXPECT_EQ(success, i < update_hint_count);
}
}
TEST_F(HintCacheStoreTest, FindHintEntryKeyCheckFetchedBeforeComponentHints) {
base::HistogramTester histogram_tester;
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
size_t initial_hint_count = 10;
base::Time update_time = base::Time().Now();
SeedInitialData(schema_state, initial_hint_count);
CreateDatabase();
InitializeStore(schema_state);
base::Version version("2.0.0");
std::unique_ptr<HintUpdateData> update_data =
hint_store()->MaybeCreateUpdateDataForComponentHints(
base::Version(kUpdateComponentVersion));
ASSERT_TRUE(update_data);
proto::Hint hint1;
hint1.set_key("domain1.org");
hint1.set_key_representation(proto::HOST_SUFFIX);
update_data->MoveHintIntoUpdateData(std::move(hint1));
proto::Hint hint2;
hint2.set_key("host.domain2.org");
hint2.set_key_representation(proto::HOST_SUFFIX);
update_data->MoveHintIntoUpdateData(std::move(hint2));
UpdateComponentHints(std::move(update_data));
// Add fetched hints to the store that overlap with the same hosts as the
// initial set.
update_data = hint_store()->CreateUpdateDataForFetchedHints(
update_time,
update_time +
optimization_guide::features::StoredFetchedHintsFreshnessDuration());
proto::Hint hint;
hint.set_key("domain2.org");
hint.set_key_representation(proto::HOST_SUFFIX);
update_data->MoveHintIntoUpdateData(std::move(hint));
UpdateFetchedHints(std::move(update_data));
// Hint for host.domain2.org should be a fetched hint ("3_" prefix)
// as fetched hints take priority.
std::string host_suffix = "host.domain2.org";
HintCacheStore::EntryKey hint_entry_key;
if (!hint_store()->FindHintEntryKey(host_suffix, &hint_entry_key)) {
FAIL() << "Hint entry not found for host suffix: " << host_suffix;
}
EXPECT_EQ(hint_entry_key, "3_domain2.org");
host_suffix = "subdomain.domain1.org";
if (!hint_store()->FindHintEntryKey(host_suffix, &hint_entry_key)) {
FAIL() << "Hint entry not found for host suffix: " << host_suffix;
}
EXPECT_EQ(hint_entry_key, "2_2.0.0_domain1.org");
}
TEST_F(HintCacheStoreTest, ClearFetchedHints) {
base::HistogramTester histogram_tester;
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
size_t initial_hint_count = 10;
base::Time update_time = base::Time().Now();
SeedInitialData(schema_state, initial_hint_count);
CreateDatabase();
InitializeStore(schema_state);
base::Version version("2.0.0");
std::unique_ptr<HintUpdateData> update_data =
hint_store()->MaybeCreateUpdateDataForComponentHints(
base::Version(kUpdateComponentVersion));
ASSERT_TRUE(update_data);
proto::Hint hint1;
hint1.set_key("domain1.org");
hint1.set_key_representation(proto::HOST_SUFFIX);
update_data->MoveHintIntoUpdateData(std::move(hint1));
proto::Hint hint2;
hint2.set_key("host.domain2.org");
hint2.set_key_representation(proto::HOST_SUFFIX);
update_data->MoveHintIntoUpdateData(std::move(hint2));
UpdateComponentHints(std::move(update_data));
// Add fetched hints to the store that overlap with the same hosts as the
// initial set.
update_data = hint_store()->CreateUpdateDataForFetchedHints(
update_time, update_time + base::TimeDelta().FromDays(7));
proto::Hint fetched_hint1;
fetched_hint1.set_key("domain2.org");
fetched_hint1.set_key_representation(proto::HOST_SUFFIX);
update_data->MoveHintIntoUpdateData(std::move(fetched_hint1));
proto::Hint fetched_hint2;
fetched_hint2.set_key("domain3.org");
fetched_hint2.set_key_representation(proto::HOST_SUFFIX);
update_data->MoveHintIntoUpdateData(std::move(fetched_hint2));
UpdateFetchedHints(std::move(update_data));
// Hint for host.domain2.org should be a fetched hint ("3_" prefix)
// as fetched hints take priority.
std::string host_suffix = "host.domain2.org";
HintCacheStore::EntryKey hint_entry_key;
if (!hint_store()->FindHintEntryKey(host_suffix, &hint_entry_key)) {
FAIL() << "Hint entry not found for host suffix: " << host_suffix;
}
EXPECT_EQ(hint_entry_key, "3_domain2.org");
host_suffix = "subdomain.domain1.org";
if (!hint_store()->FindHintEntryKey(host_suffix, &hint_entry_key)) {
FAIL() << "Hint entry not found for host suffix: " << host_suffix;
}
EXPECT_EQ(hint_entry_key, "2_2.0.0_domain1.org");
// Remove the fetched hints from the HintCacheStore.
ClearFetchedHintsFromDatabase();
host_suffix = "domain1.org";
// Component hint should still exist.
EXPECT_TRUE(hint_store()->FindHintEntryKey(host_suffix, &hint_entry_key));
host_suffix = "domain3.org";
// Fetched hint should not still exist.
EXPECT_FALSE(hint_store()->FindHintEntryKey(host_suffix, &hint_entry_key));
// Add Components back - newer version.
base::Version version3("3.0.0");
std::unique_ptr<HintUpdateData> update_data2 =
hint_store()->MaybeCreateUpdateDataForComponentHints(version3);
ASSERT_TRUE(update_data2);
proto::Hint new_hint2;
new_hint2.set_key("domain2.org");
new_hint2.set_key_representation(proto::HOST_SUFFIX);
update_data2->MoveHintIntoUpdateData(std::move(new_hint2));
UpdateComponentHints(std::move(update_data2));
host_suffix = "host.domain2.org";
EXPECT_TRUE(hint_store()->FindHintEntryKey(host_suffix, &hint_entry_key));
update_data = hint_store()->CreateUpdateDataForFetchedHints(
update_time,
update_time +
optimization_guide::features::StoredFetchedHintsFreshnessDuration());
proto::Hint new_hint;
new_hint.set_key("domain1.org");
new_hint.set_key_representation(proto::HOST_SUFFIX);
update_data->MoveHintIntoUpdateData(std::move(new_hint));
UpdateFetchedHints(std::move(update_data));
// Add fetched hints to the store that overlap with the same hosts as the
// initial set.
host_suffix = "subdomain.domain1.org";
if (!hint_store()->FindHintEntryKey(host_suffix, &hint_entry_key)) {
FAIL() << "Hint entry not found for host suffix: " << host_suffix;
}
EXPECT_EQ(hint_entry_key, "3_domain1.org");
}
TEST_F(HintCacheStoreTest, FetchHintsPurgeExpiredFetchedHints) {
base::HistogramTester histogram_tester;
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
size_t initial_hint_count = 10;
base::Time update_time = base::Time().Now();
SeedInitialData(schema_state, initial_hint_count);
CreateDatabase();
InitializeStore(schema_state);
base::Version version("2.0.0");
std::unique_ptr<HintUpdateData> update_data =
hint_store()->MaybeCreateUpdateDataForComponentHints(
base::Version(kUpdateComponentVersion));
ASSERT_TRUE(update_data);
proto::Hint hint1;
hint1.set_key("domain1.org");
hint1.set_key_representation(proto::HOST_SUFFIX);
update_data->MoveHintIntoUpdateData(std::move(hint1));
proto::Hint hint2;
hint2.set_key("host.domain2.org");
hint2.set_key_representation(proto::HOST_SUFFIX);
update_data->MoveHintIntoUpdateData(std::move(hint2));
UpdateComponentHints(std::move(update_data));
// Add fetched hints to the store that overlap with the same hosts as the
// initial set.
update_data = hint_store()->CreateUpdateDataForFetchedHints(
update_time, update_time + base::TimeDelta().FromDays(7));
proto::Hint fetched_hint1;
fetched_hint1.set_key("domain2.org");
fetched_hint1.set_key_representation(proto::HOST_SUFFIX);
update_data->MoveHintIntoUpdateData(std::move(fetched_hint1));
proto::Hint fetched_hint2;
fetched_hint2.set_key("domain3.org");
fetched_hint2.set_key_representation(proto::HOST_SUFFIX);
update_data->MoveHintIntoUpdateData(std::move(fetched_hint2));
UpdateFetchedHints(std::move(update_data));
// Add expired fetched hints to the store.
update_data = hint_store()->CreateUpdateDataForFetchedHints(
update_time, update_time - base::TimeDelta().FromDays(7));
proto::Hint fetched_hint3;
fetched_hint1.set_key("domain4.org");
fetched_hint1.set_key_representation(proto::HOST_SUFFIX);
update_data->MoveHintIntoUpdateData(std::move(fetched_hint1));
proto::Hint fetched_hint4;
fetched_hint2.set_key("domain5.org");
fetched_hint2.set_key_representation(proto::HOST_SUFFIX);
update_data->MoveHintIntoUpdateData(std::move(fetched_hint2));
UpdateFetchedHints(std::move(update_data));
PurgeExpiredFetchedHints();
HintCacheStore::EntryKey hint_entry_key;
EXPECT_FALSE(hint_store()->FindHintEntryKey("domain4.org", &hint_entry_key));
EXPECT_FALSE(hint_store()->FindHintEntryKey("domain5.org", &hint_entry_key));
EXPECT_TRUE(hint_store()->FindHintEntryKey("domain2.org", &hint_entry_key));
EXPECT_TRUE(hint_store()->FindHintEntryKey("domain3.org", &hint_entry_key));
}
TEST_F(HintCacheStoreTest, FetchedHintsLoadExpiredHint) {
base::HistogramTester histogram_tester;
MetadataSchemaState schema_state = MetadataSchemaState::kValid;
size_t initial_hint_count = 10;
base::Time update_time = base::Time().Now();
SeedInitialData(schema_state, initial_hint_count);
CreateDatabase();
InitializeStore(schema_state);
base::Version version("2.0.0");
std::unique_ptr<HintUpdateData> update_data =
hint_store()->MaybeCreateUpdateDataForComponentHints(
base::Version(kUpdateComponentVersion));
ASSERT_TRUE(update_data);
proto::Hint hint1;
hint1.set_key("domain1.org");
hint1.set_key_representation(proto::HOST_SUFFIX);
update_data->MoveHintIntoUpdateData(std::move(hint1));
proto::Hint hint2;
hint2.set_key("host.domain2.org");
hint2.set_key_representation(proto::HOST_SUFFIX);
update_data->MoveHintIntoUpdateData(std::move(hint2));
UpdateComponentHints(std::move(update_data));
// Add fetched hints to the store that expired.
update_data = hint_store()->CreateUpdateDataForFetchedHints(
update_time, update_time - base::TimeDelta().FromDays(10));
proto::Hint fetched_hint1;
fetched_hint1.set_key("domain2.org");
fetched_hint1.set_key_representation(proto::HOST_SUFFIX);
update_data->MoveHintIntoUpdateData(std::move(fetched_hint1));
proto::Hint fetched_hint2;
fetched_hint2.set_key("domain3.org");
fetched_hint2.set_key_representation(proto::HOST_SUFFIX);
update_data->MoveHintIntoUpdateData(std::move(fetched_hint2));
UpdateFetchedHints(std::move(update_data));
// Hint for host.domain2.org should be a fetched hint ("3_" prefix)
// as fetched hints take priority.
std::string host_suffix = "host.domain2.org";
HintCacheStore::EntryKey hint_entry_key;
if (!hint_store()->FindHintEntryKey(host_suffix, &hint_entry_key)) {
FAIL() << "Hint entry not found for host suffix: " << host_suffix;
}
EXPECT_EQ(hint_entry_key, "3_domain2.org");
hint_store()->LoadHint(hint_entry_key,
base::BindOnce(&HintCacheStoreTest::OnHintLoaded,
base::Unretained(this)));
// OnLoadHint callback
db()->GetCallback(true);
// |hint_entry_key| will be a fetched hint but the entry will be empty.
EXPECT_EQ(last_loaded_hint_entry_key(), hint_entry_key);
EXPECT_FALSE(last_loaded_hint());
histogram_tester.ExpectBucketCount(
"OptimizationGuide.HintCacheStore.OnLoadHint.FetchedHintExpired", true,
1);
}
} // namespace optimization_guide