blob: 763eeca85d96eb777f10ebf824ca9cca784f3662 [file] [log] [blame]
// Copyright 2023 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/extras/sqlite/sqlite_persistent_shared_dictionary_store.h"
#include <optional>
#include <tuple>
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/test_file_util.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "net/base/schemeful_site.h"
#include "net/extras/shared_dictionary/shared_dictionary_info.h"
#include "net/shared_dictionary/shared_dictionary_isolation_key.h"
#include "net/test/test_with_task_environment.h"
#include "sql/database.h"
#include "sql/meta_table.h"
#include "sql/statement.h"
#include "sql/test/test_helpers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::Pair;
using ::testing::UnorderedElementsAreArray;
namespace net {
namespace {
const base::FilePath::CharType kSharedDictionaryStoreFilename[] =
FILE_PATH_LITERAL("SharedDictionary");
const int kCurrentVersionNumber = 3;
int GetDBCurrentVersionNumber(sql::Database* db) {
static constexpr char kGetDBCurrentVersionQuery[] =
"SELECT value FROM meta WHERE key='version'";
sql::Statement statement(db->GetUniqueStatement(kGetDBCurrentVersionQuery));
statement.Step();
return statement.ColumnInt(0);
}
bool CreateV1Schema(sql::Database* db) {
sql::MetaTable meta_table;
CHECK(meta_table.Init(db, 1, 1));
constexpr char kTotalDictSizeKey[] = "total_dict_size";
static constexpr char kCreateTableQuery[] =
// clang-format off
"CREATE TABLE dictionaries("
"id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
"frame_origin TEXT NOT NULL,"
"top_frame_site TEXT NOT NULL,"
"host TEXT NOT NULL,"
"match TEXT NOT NULL,"
"url TEXT NOT NULL,"
"res_time INTEGER NOT NULL,"
"exp_time INTEGER NOT NULL,"
"last_used_time INTEGER NOT NULL,"
"size INTEGER NOT NULL,"
"sha256 BLOB NOT NULL,"
"token_high INTEGER NOT NULL,"
"token_low INTEGER NOT NULL)";
// clang-format on
static constexpr char kCreateUniqueIndexQuery[] =
// clang-format off
"CREATE UNIQUE INDEX unique_index ON dictionaries("
"frame_origin,"
"top_frame_site,"
"host,"
"match)";
// clang-format on
// This index is used for the size and count limitation per top_frame_site.
static constexpr char kCreateTopFrameSiteIndexQuery[] =
// clang-format off
"CREATE INDEX top_frame_site_index ON dictionaries("
"top_frame_site)";
// clang-format on
// This index is used for GetDictionaries().
static constexpr char kCreateIsolationIndexQuery[] =
// clang-format off
"CREATE INDEX isolation_index ON dictionaries("
"frame_origin,"
"top_frame_site)";
// clang-format on
// This index will be used when implementing garbage collection logic of
// SharedDictionaryDiskCache.
static constexpr char kCreateTokenIndexQuery[] =
// clang-format off
"CREATE INDEX token_index ON dictionaries("
"token_high, token_low)";
// clang-format on
// This index will be used when implementing clearing expired dictionary
// logic.
static constexpr char kCreateExpirationTimeIndexQuery[] =
// clang-format off
"CREATE INDEX exp_time_index ON dictionaries("
"exp_time)";
// clang-format on
// This index will be used when implementing clearing dictionary logic which
// will be called from BrowsingDataRemover.
static constexpr char kCreateLastUsedTimeIndexQuery[] =
// clang-format off
"CREATE INDEX last_used_time_index ON dictionaries("
"last_used_time)";
// clang-format on
if (!db->Execute(kCreateTableQuery) ||
!db->Execute(kCreateUniqueIndexQuery) ||
!db->Execute(kCreateTopFrameSiteIndexQuery) ||
!db->Execute(kCreateIsolationIndexQuery) ||
!db->Execute(kCreateTokenIndexQuery) ||
!db->Execute(kCreateExpirationTimeIndexQuery) ||
!db->Execute(kCreateLastUsedTimeIndexQuery) ||
!meta_table.SetValue(kTotalDictSizeKey, 0)) {
return false;
}
return true;
}
bool CreateV2Schema(sql::Database* db) {
sql::MetaTable meta_table;
CHECK(meta_table.Init(db, 2, 2));
constexpr char kTotalDictSizeKey[] = "total_dict_size";
static constexpr char kCreateTableQuery[] =
// clang-format off
"CREATE TABLE dictionaries("
"primary_key INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
"frame_origin TEXT NOT NULL,"
"top_frame_site TEXT NOT NULL,"
"host TEXT NOT NULL,"
"match TEXT NOT NULL,"
"match_dest TEXT NOT NULL,"
"id TEXT NOT NULL,"
"url TEXT NOT NULL,"
"res_time INTEGER NOT NULL,"
"exp_time INTEGER NOT NULL,"
"last_used_time INTEGER NOT NULL,"
"size INTEGER NOT NULL,"
"sha256 BLOB NOT NULL,"
"token_high INTEGER NOT NULL,"
"token_low INTEGER NOT NULL)";
// clang-format on
static constexpr char kCreateUniqueIndexQuery[] =
// clang-format off
"CREATE UNIQUE INDEX unique_index ON dictionaries("
"frame_origin,"
"top_frame_site,"
"host,"
"match,"
"match_dest)";
// clang-format on
// This index is used for the size and count limitation per top_frame_site.
static constexpr char kCreateTopFrameSiteIndexQuery[] =
// clang-format off
"CREATE INDEX top_frame_site_index ON dictionaries("
"top_frame_site)";
// clang-format on
// This index is used for GetDictionaries().
static constexpr char kCreateIsolationIndexQuery[] =
// clang-format off
"CREATE INDEX isolation_index ON dictionaries("
"frame_origin,"
"top_frame_site)";
// clang-format on
// This index will be used when implementing garbage collection logic of
// SharedDictionaryDiskCache.
static constexpr char kCreateTokenIndexQuery[] =
// clang-format off
"CREATE INDEX token_index ON dictionaries("
"token_high, token_low)";
// clang-format on
// This index will be used when implementing clearing expired dictionary
// logic.
static constexpr char kCreateExpirationTimeIndexQuery[] =
// clang-format off
"CREATE INDEX exp_time_index ON dictionaries("
"exp_time)";
// clang-format on
// This index will be used when implementing clearing dictionary logic which
// will be called from BrowsingDataRemover.
static constexpr char kCreateLastUsedTimeIndexQuery[] =
// clang-format off
"CREATE INDEX last_used_time_index ON dictionaries("
"last_used_time)";
// clang-format on
if (!db->Execute(kCreateTableQuery) ||
!db->Execute(kCreateUniqueIndexQuery) ||
!db->Execute(kCreateTopFrameSiteIndexQuery) ||
!db->Execute(kCreateIsolationIndexQuery) ||
!db->Execute(kCreateTokenIndexQuery) ||
!db->Execute(kCreateExpirationTimeIndexQuery) ||
!db->Execute(kCreateLastUsedTimeIndexQuery) ||
!meta_table.SetValue(kTotalDictSizeKey, 0)) {
return false;
}
return true;
}
SQLitePersistentSharedDictionaryStore::RegisterDictionaryResult
RegisterDictionaryImpl(SQLitePersistentSharedDictionaryStore* store,
const SharedDictionaryIsolationKey& isolation_key,
SharedDictionaryInfo dictionary_info,
uint64_t max_size_per_site = 1000000,
uint64_t max_count_per_site = 1000) {
std::optional<SQLitePersistentSharedDictionaryStore::RegisterDictionaryResult>
result_out;
base::RunLoop run_loop;
store->RegisterDictionary(
isolation_key, std::move(dictionary_info), max_size_per_site,
max_count_per_site,
base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::
RegisterDictionaryResultOrError result) {
ASSERT_TRUE(result.has_value());
result_out = result.value();
run_loop.Quit();
}));
run_loop.Run();
CHECK(result_out);
return *result_out;
}
// Register following 4 dictionaries for ProcessEviction tests.
// dict1: size=1000 last_used_time=now
// dict2: size=3000 last_used_time=now+4
// dict3: size=5000 last_used_time=now+2
// dict4: size=7000 last_used_time=now+3
std::tuple<SharedDictionaryInfo,
SharedDictionaryInfo,
SharedDictionaryInfo,
SharedDictionaryInfo>
RegisterSharedDictionariesForProcessEvictionTest(
SQLitePersistentSharedDictionaryStore* store,
const SharedDictionaryIsolationKey& isolation_key) {
const base::Time now = base::Time::Now();
auto token1 = base::UnguessableToken::Create();
SharedDictionaryInfo dict1 =
SharedDictionaryInfo(GURL("https://a.example/dict"),
/*last_fetch_time=*/now, /*response_time=*/now,
/*expiration*/ base::Seconds(100), "/pattern*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ now,
/*size=*/1000, SHA256HashValue({{0x00, 0x01}}),
/*disk_cache_key_token=*/token1,
/*primary_key_in_database=*/std::nullopt);
auto result1 = RegisterDictionaryImpl(store, isolation_key, dict1);
dict1.set_primary_key_in_database(result1.primary_key_in_database());
auto token2 = base::UnguessableToken::Create();
SharedDictionaryInfo dict2 =
SharedDictionaryInfo(GURL("https://b.example/dict"),
/*last_fetch_time=*/now, /*response_time=*/now,
/*expiration*/ base::Seconds(100), "/pattern*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ now + base::Seconds(1),
/*size=*/3000, SHA256HashValue({{0x00, 0x02}}),
/*disk_cache_key_token=*/token2,
/*primary_key_in_database=*/std::nullopt);
auto result2 = RegisterDictionaryImpl(store, isolation_key, dict2);
dict2.set_primary_key_in_database(result2.primary_key_in_database());
auto token3 = base::UnguessableToken::Create();
SharedDictionaryInfo dict3 =
SharedDictionaryInfo(GURL("https://c.example/dict"),
/*last_fetch_time=*/now, /*response_time=*/now,
/*expiration*/ base::Seconds(100), "/pattern*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ now + base::Seconds(2),
/*size=*/5000, SHA256HashValue({{0x00, 0x03}}),
/*disk_cache_key_token=*/token3,
/*primary_key_in_database=*/std::nullopt);
auto result3 = RegisterDictionaryImpl(store, isolation_key, dict3);
dict3.set_primary_key_in_database(result3.primary_key_in_database());
auto token4 = base::UnguessableToken::Create();
SharedDictionaryInfo dict4 =
SharedDictionaryInfo(GURL("https://d.example/dict"),
/*last_fetch_time=*/now, /*response_time=*/now,
/*expiration*/ base::Seconds(100), "/pattern*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ now + base::Seconds(3),
/*size=*/7000, SHA256HashValue({{0x00, 0x04}}),
/*disk_cache_key_token=*/token4,
/*primary_key_in_database=*/std::nullopt);
auto result4 = RegisterDictionaryImpl(store, isolation_key, dict4);
dict4.set_primary_key_in_database(result4.primary_key_in_database());
// Call UpdateDictionaryLastUsedTime to update the last used time of dict2.
store->UpdateDictionaryLastUsedTime(*dict2.primary_key_in_database(),
now + base::Seconds(4));
SharedDictionaryInfo updated_dict2 = SharedDictionaryInfo(
dict2.url(), dict2.last_fetch_time(), dict2.response_time(),
dict2.expiration(), dict2.match(), dict2.match_dest_string(), dict2.id(),
now + base::Seconds(4), dict2.size(), dict2.hash(),
dict2.disk_cache_key_token(), dict2.primary_key_in_database());
return {dict1, updated_dict2, dict3, dict4};
}
} // namespace
SharedDictionaryIsolationKey CreateIsolationKey(
const std::string& frame_origin_str,
const std::optional<std::string>& top_frame_site_str = std::nullopt) {
return SharedDictionaryIsolationKey(
url::Origin::Create(GURL(frame_origin_str)),
top_frame_site_str ? SchemefulSite(GURL(*top_frame_site_str))
: SchemefulSite(GURL(frame_origin_str)));
}
class SQLitePersistentSharedDictionaryStoreTest : public ::testing::Test,
public WithTaskEnvironment {
public:
SQLitePersistentSharedDictionaryStoreTest()
: WithTaskEnvironment(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
isolation_key_(CreateIsolationKey("https://origin.test/")),
dictionary_info_(
GURL("https://origin.test/dict"),
/*last_fetch_time=*/base::Time::Now() - base::Seconds(9),
/*response_time=*/base::Time::Now() - base::Seconds(10),
/*expiration*/ base::Seconds(100),
"/pattern*",
/*match_dest_string=*/"",
/*id=*/"dictionary_id",
/*last_used_time*/ base::Time::Now(),
/*size=*/1000,
SHA256HashValue({{0x00, 0x01}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt) {}
SQLitePersistentSharedDictionaryStoreTest(
const SQLitePersistentSharedDictionaryStoreTest&) = delete;
SQLitePersistentSharedDictionaryStoreTest& operator=(
const SQLitePersistentSharedDictionaryStoreTest&) = delete;
void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
void TearDown() override { DestroyStore(); }
protected:
base::FilePath GetStroeFilePath() const {
return temp_dir_.GetPath().Append(kSharedDictionaryStoreFilename);
}
void CreateStore() {
CHECK(!store_);
store_ = std::make_unique<SQLitePersistentSharedDictionaryStore>(
GetStroeFilePath(), client_task_runner_, background_task_runner_);
}
void DestroyStore() {
store_.reset();
// Make sure we wait until the destructor has run by running all
// TaskEnvironment tasks.
RunUntilIdle();
}
uint64_t GetTotalDictionarySize() {
base::RunLoop run_loop;
uint64_t total_dictionary_size_out = 0;
store_->GetTotalDictionarySize(base::BindLambdaForTesting(
[&](base::expected<
uint64_t, SQLitePersistentSharedDictionaryStore::Error> result) {
ASSERT_TRUE(result.has_value());
total_dictionary_size_out = result.value();
run_loop.Quit();
}));
run_loop.Run();
return total_dictionary_size_out;
}
SQLitePersistentSharedDictionaryStore::RegisterDictionaryResult
RegisterDictionary(const SharedDictionaryIsolationKey& isolation_key,
SharedDictionaryInfo dictionary_info) {
return RegisterDictionaryImpl(store_.get(), isolation_key,
std::move(dictionary_info));
}
std::vector<SharedDictionaryInfo> GetDictionaries(
const SharedDictionaryIsolationKey& isolation_key) {
std::vector<SharedDictionaryInfo> result_dictionaries;
base::RunLoop run_loop;
store_->GetDictionaries(
isolation_key,
base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::DictionaryListOrError
result) {
ASSERT_TRUE(result.has_value());
result_dictionaries = std::move(result.value());
run_loop.Quit();
}));
run_loop.Run();
return result_dictionaries;
}
std::map<SharedDictionaryIsolationKey, std::vector<SharedDictionaryInfo>>
GetAllDictionaries() {
std::map<SharedDictionaryIsolationKey, std::vector<SharedDictionaryInfo>>
result_all_dictionaries;
base::RunLoop run_loop;
store_->GetAllDictionaries(base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::DictionaryMapOrError
result) {
ASSERT_TRUE(result.has_value());
result_all_dictionaries = std::move(result.value());
run_loop.Quit();
}));
run_loop.Run();
return result_all_dictionaries;
}
std::vector<SharedDictionaryUsageInfo> GetUsageInfo() {
std::vector<SharedDictionaryUsageInfo> result_usage_info;
base::RunLoop run_loop;
store_->GetUsageInfo(base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::UsageInfoOrError result) {
ASSERT_TRUE(result.has_value());
result_usage_info = std::move(result.value());
run_loop.Quit();
}));
run_loop.Run();
return result_usage_info;
}
std::vector<url::Origin> GetOriginsBetween(const base::Time start_time,
const base::Time end_time) {
std::vector<url::Origin> origins;
base::RunLoop run_loop;
store_->GetOriginsBetween(
start_time, end_time,
base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::OriginListOrError
result) {
ASSERT_TRUE(result.has_value());
origins = std::move(result.value());
run_loop.Quit();
}));
run_loop.Run();
return origins;
}
std::set<base::UnguessableToken> ClearAllDictionaries() {
base::RunLoop run_loop;
std::set<base::UnguessableToken> tokens;
store_->ClearAllDictionaries(base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::UnguessableTokenSetOrError
result) {
ASSERT_TRUE(result.has_value());
tokens = std::move(result.value());
run_loop.Quit();
}));
run_loop.Run();
return tokens;
}
std::set<base::UnguessableToken> ClearDictionaries(
const base::Time start_time,
const base::Time end_time,
base::RepeatingCallback<bool(const GURL&)> url_matcher) {
base::RunLoop run_loop;
std::set<base::UnguessableToken> tokens;
store_->ClearDictionaries(
start_time, end_time, std::move(url_matcher),
base::BindLambdaForTesting([&](SQLitePersistentSharedDictionaryStore::
UnguessableTokenSetOrError result) {
ASSERT_TRUE(result.has_value());
tokens = std::move(result.value());
run_loop.Quit();
}));
run_loop.Run();
return tokens;
}
std::set<base::UnguessableToken> ClearDictionariesForIsolationKey(
const SharedDictionaryIsolationKey& isolation_key) {
base::RunLoop run_loop;
std::set<base::UnguessableToken> tokens;
store_->ClearDictionariesForIsolationKey(
isolation_key,
base::BindLambdaForTesting([&](SQLitePersistentSharedDictionaryStore::
UnguessableTokenSetOrError result) {
ASSERT_TRUE(result.has_value());
tokens = std::move(result.value());
run_loop.Quit();
}));
run_loop.Run();
return tokens;
}
std::set<base::UnguessableToken> DeleteExpiredDictionaries(
const base::Time now) {
base::RunLoop run_loop;
std::set<base::UnguessableToken> tokens;
store_->DeleteExpiredDictionaries(
now,
base::BindLambdaForTesting([&](SQLitePersistentSharedDictionaryStore::
UnguessableTokenSetOrError result) {
ASSERT_TRUE(result.has_value());
tokens = std::move(result.value());
run_loop.Quit();
}));
run_loop.Run();
return tokens;
}
std::set<base::UnguessableToken> ProcessEviction(
uint64_t cache_max_size,
uint64_t size_low_watermark,
uint64_t cache_max_count,
uint64_t count_low_watermark) {
base::RunLoop run_loop;
std::set<base::UnguessableToken> tokens;
store_->ProcessEviction(
cache_max_size, size_low_watermark, cache_max_count,
count_low_watermark,
base::BindLambdaForTesting([&](SQLitePersistentSharedDictionaryStore::
UnguessableTokenSetOrError result) {
ASSERT_TRUE(result.has_value());
tokens = std::move(result.value());
run_loop.Quit();
}));
run_loop.Run();
return tokens;
}
std::set<base::UnguessableToken> GetAllDiskCacheKeyTokens() {
base::RunLoop run_loop;
std::set<base::UnguessableToken> tokens;
store_->GetAllDiskCacheKeyTokens(base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::UnguessableTokenSetOrError
result) {
ASSERT_TRUE(result.has_value());
tokens = std::move(result.value());
run_loop.Quit();
}));
run_loop.Run();
return tokens;
}
SQLitePersistentSharedDictionaryStore::Error
DeleteDictionariesByDiskCacheKeyTokens(
std::set<base::UnguessableToken> disk_cache_key_tokens) {
base::RunLoop run_loop;
SQLitePersistentSharedDictionaryStore::Error error_out;
store_->DeleteDictionariesByDiskCacheKeyTokens(
std::move(disk_cache_key_tokens),
base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::Error result_error) {
error_out = result_error;
run_loop.Quit();
}));
run_loop.Run();
return error_out;
}
SQLitePersistentSharedDictionaryStore::Error UpdateDictionaryLastFetchTime(
const int64_t primary_key_in_database,
const base::Time last_fetch_time) {
base::RunLoop run_loop;
SQLitePersistentSharedDictionaryStore::Error error_out;
store_->UpdateDictionaryLastFetchTime(
primary_key_in_database, last_fetch_time,
base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::Error result_error) {
error_out = result_error;
run_loop.Quit();
}));
run_loop.Run();
return error_out;
}
void CorruptDatabaseFile() {
// Execute CreateStore(), ClearAllDictionaries() and DestroyStore() to
// create a database file.
CreateStore();
ClearAllDictionaries();
DestroyStore();
// Corrupt the database.
CHECK(sql::test::CorruptSizeInHeader(GetStroeFilePath()));
}
void ManipulateDatabase(const std::vector<std::string>& queries) {
// We don't allow manipulating the database while `store_` exists.
ASSERT_FALSE(store_);
std::unique_ptr<sql::Database> db =
std::make_unique<sql::Database>(sql::test::kTestTag);
ASSERT_TRUE(db->Open(GetStroeFilePath()));
sql::MetaTable meta_table;
ASSERT_TRUE(meta_table.Init(db.get(), kCurrentVersionNumber,
kCurrentVersionNumber));
for (const std::string& query : queries) {
ASSERT_TRUE(db->Execute(query));
}
db->Close();
}
void MakeFileUnwritable() {
file_permissions_restorer_ =
std::make_unique<base::FilePermissionRestorer>(GetStroeFilePath());
ASSERT_TRUE(base::MakeFileUnwritable(GetStroeFilePath()));
}
void CheckStoreRecovered() {
CreateStore();
EXPECT_TRUE(GetDictionaries(isolation_key_).empty());
EXPECT_TRUE(GetAllDictionaries().empty());
DestroyStore();
}
void RunMultipleDictionariesTest(
const SharedDictionaryIsolationKey isolation_key1,
const SharedDictionaryInfo dictionary_info1,
const SharedDictionaryIsolationKey isolation_key2,
const SharedDictionaryInfo dictionary_info2,
bool expect_merged);
void RunGetTotalDictionarySizeFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error);
void RunRegisterDictionaryFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error);
void RunGetDictionariesFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error);
void RunGetAllDictionariesFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error);
void RunGetUsageInfoFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error);
void RunGetOriginsBetweenFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error);
void RunClearAllDictionariesFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error);
void RunClearDictionariesFailureTest(
base::RepeatingCallback<bool(const GURL&)> url_matcher,
SQLitePersistentSharedDictionaryStore::Error expected_error);
void RunClearDictionariesForIsolationKeyFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error);
void RunDeleteExpiredDictionariesFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error);
void RunProcessEvictionFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error);
void RunGetAllDiskCacheKeyTokensFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error);
base::ScopedTempDir temp_dir_;
std::unique_ptr<SQLitePersistentSharedDictionaryStore> store_;
const scoped_refptr<base::SequencedTaskRunner> client_task_runner_ =
base::SingleThreadTaskRunner::GetCurrentDefault();
const scoped_refptr<base::SequencedTaskRunner> background_task_runner_ =
base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()});
// `file_permissions_restorer_` must be below `temp_dir_` to restore the
// file permission correctly.
std::unique_ptr<base::FilePermissionRestorer> file_permissions_restorer_;
const SharedDictionaryIsolationKey isolation_key_;
const SharedDictionaryInfo dictionary_info_;
};
TEST_F(SQLitePersistentSharedDictionaryStoreTest, SingleDictionary) {
CreateStore();
EXPECT_EQ(0u, GetTotalDictionarySize());
auto register_dictionary_result =
RegisterDictionary(isolation_key_, dictionary_info_);
EXPECT_EQ(dictionary_info_.size(),
register_dictionary_result.total_dictionary_size());
EXPECT_EQ(1u, register_dictionary_result.total_dictionary_count());
SharedDictionaryInfo expected_info = dictionary_info_;
expected_info.set_primary_key_in_database(
register_dictionary_result.primary_key_in_database());
EXPECT_EQ(dictionary_info_.size(), GetTotalDictionarySize());
EXPECT_THAT(GetDictionaries(isolation_key_),
ElementsAreArray({expected_info}));
EXPECT_THAT(
GetAllDictionaries(),
ElementsAre(Pair(isolation_key_, ElementsAreArray({expected_info}))));
EXPECT_THAT(GetUsageInfo(),
ElementsAre(SharedDictionaryUsageInfo{
.isolation_key = isolation_key_,
.total_size_bytes = dictionary_info_.size()}));
EXPECT_TRUE(
GetOriginsBetween(dictionary_info_.response_time() - base::Seconds(1),
dictionary_info_.response_time())
.empty());
EXPECT_THAT(
GetOriginsBetween(dictionary_info_.response_time(),
dictionary_info_.response_time() + base::Seconds(1)),
ElementsAreArray({isolation_key_.frame_origin()}));
EXPECT_THAT(
ClearAllDictionaries(),
UnorderedElementsAreArray({dictionary_info_.disk_cache_key_token()}));
EXPECT_EQ(0u, GetTotalDictionarySize());
EXPECT_TRUE(GetDictionaries(isolation_key_).empty());
EXPECT_TRUE(GetAllDictionaries().empty());
EXPECT_TRUE(GetUsageInfo().empty());
}
void SQLitePersistentSharedDictionaryStoreTest::RunMultipleDictionariesTest(
const SharedDictionaryIsolationKey isolation_key1,
const SharedDictionaryInfo dictionary_info1,
const SharedDictionaryIsolationKey isolation_key2,
const SharedDictionaryInfo dictionary_info2,
bool expect_merged) {
CreateStore();
auto register_dictionary_result1 =
RegisterDictionary(isolation_key1, dictionary_info1);
EXPECT_EQ(dictionary_info1.size(),
register_dictionary_result1.total_dictionary_size());
EXPECT_EQ(1u, register_dictionary_result1.total_dictionary_count());
auto register_dictionary_result2 =
RegisterDictionary(isolation_key2, dictionary_info2);
EXPECT_EQ(expect_merged ? 1u : 2u,
register_dictionary_result2.total_dictionary_count());
EXPECT_NE(register_dictionary_result1.primary_key_in_database(),
register_dictionary_result2.primary_key_in_database());
SharedDictionaryInfo expected_info1 = dictionary_info1;
SharedDictionaryInfo expected_info2 = dictionary_info2;
expected_info1.set_primary_key_in_database(
register_dictionary_result1.primary_key_in_database());
expected_info2.set_primary_key_in_database(
register_dictionary_result2.primary_key_in_database());
base::Time oldest_response_time = std::min(dictionary_info1.response_time(),
dictionary_info2.response_time());
base::Time latest_response_time = std::max(dictionary_info1.response_time(),
dictionary_info2.response_time());
std::set<base::UnguessableToken> registered_tokens;
if (isolation_key1 == isolation_key2) {
if (expect_merged) {
registered_tokens.insert(expected_info2.disk_cache_key_token());
EXPECT_EQ(dictionary_info2.size(),
register_dictionary_result2.total_dictionary_size());
EXPECT_THAT(GetDictionaries(isolation_key1),
ElementsAreArray({expected_info2}));
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key1,
ElementsAreArray({expected_info2}))));
ASSERT_TRUE(register_dictionary_result2.replaced_disk_cache_key_token());
EXPECT_EQ(dictionary_info1.disk_cache_key_token(),
*register_dictionary_result2.replaced_disk_cache_key_token());
EXPECT_THAT(GetUsageInfo(),
ElementsAre(SharedDictionaryUsageInfo{
.isolation_key = isolation_key1,
.total_size_bytes = dictionary_info2.size()}));
EXPECT_THAT(GetOriginsBetween(oldest_response_time,
latest_response_time + base::Seconds(1)),
ElementsAreArray({isolation_key2.frame_origin()}));
} else {
registered_tokens.insert(expected_info1.disk_cache_key_token());
registered_tokens.insert(expected_info2.disk_cache_key_token());
EXPECT_EQ(dictionary_info1.size() + dictionary_info2.size(),
register_dictionary_result2.total_dictionary_size());
EXPECT_THAT(GetDictionaries(isolation_key1),
UnorderedElementsAreArray({expected_info1, expected_info2}));
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key1,
UnorderedElementsAreArray(
{expected_info1, expected_info2}))));
EXPECT_THAT(GetUsageInfo(),
ElementsAre(SharedDictionaryUsageInfo{
.isolation_key = isolation_key1,
.total_size_bytes =
dictionary_info1.size() + dictionary_info2.size()}));
EXPECT_THAT(GetOriginsBetween(oldest_response_time,
latest_response_time + base::Seconds(1)),
UnorderedElementsAreArray({isolation_key1.frame_origin()}));
}
} else {
registered_tokens.insert(expected_info1.disk_cache_key_token());
registered_tokens.insert(expected_info2.disk_cache_key_token());
EXPECT_EQ(dictionary_info1.size() + dictionary_info2.size(),
register_dictionary_result2.total_dictionary_size());
EXPECT_THAT(GetDictionaries(isolation_key1),
ElementsAreArray({expected_info1}));
EXPECT_THAT(GetDictionaries(isolation_key2),
ElementsAreArray({expected_info2}));
EXPECT_THAT(
GetAllDictionaries(),
ElementsAre(Pair(isolation_key1, ElementsAreArray({expected_info1})),
Pair(isolation_key2, ElementsAreArray({expected_info2}))));
EXPECT_THAT(GetUsageInfo(),
UnorderedElementsAreArray(
{SharedDictionaryUsageInfo{
.isolation_key = isolation_key1,
.total_size_bytes = dictionary_info1.size()},
SharedDictionaryUsageInfo{
.isolation_key = isolation_key2,
.total_size_bytes = dictionary_info2.size()}}));
EXPECT_THAT(GetOriginsBetween(oldest_response_time,
latest_response_time + base::Seconds(1)),
UnorderedElementsAreArray({isolation_key1.frame_origin(),
isolation_key2.frame_origin()}));
}
EXPECT_THAT(ClearAllDictionaries(),
UnorderedElementsAreArray(registered_tokens));
EXPECT_TRUE(GetDictionaries(isolation_key_).empty());
EXPECT_TRUE(GetAllDictionaries().empty());
EXPECT_TRUE(GetUsageInfo().empty());
EXPECT_TRUE(GetOriginsBetween(oldest_response_time,
latest_response_time + base::Seconds(1))
.empty());
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
MultipleDictionariesDifferentOriginSameSite) {
SharedDictionaryIsolationKey isolation_key1 =
CreateIsolationKey("https://www1.origin.test/");
SharedDictionaryIsolationKey isolation_key2 =
CreateIsolationKey("https://www2.origin.test/");
EXPECT_NE(isolation_key1, isolation_key2);
EXPECT_NE(isolation_key1.frame_origin(), isolation_key2.frame_origin());
EXPECT_EQ(isolation_key1.top_frame_site(), isolation_key2.top_frame_site());
RunMultipleDictionariesTest(isolation_key1, dictionary_info_, isolation_key2,
dictionary_info_, /*expect_merged=*/false);
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
MultipleDictionariesDifferentSite) {
SharedDictionaryIsolationKey isolation_key1 =
CreateIsolationKey("https://origin1.test/");
SharedDictionaryIsolationKey isolation_key2 =
CreateIsolationKey("https://origin2.test/");
EXPECT_NE(isolation_key1, isolation_key2);
EXPECT_NE(isolation_key1.frame_origin(), isolation_key2.frame_origin());
EXPECT_NE(isolation_key1.top_frame_site(), isolation_key2.top_frame_site());
RunMultipleDictionariesTest(isolation_key1, dictionary_info_, isolation_key2,
dictionary_info_, /*expect_merged=*/false);
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
MultipleDictionariesDifferentHostDifferentMatch) {
RunMultipleDictionariesTest(
isolation_key_,
SharedDictionaryInfo(
GURL("https://origin1.test/dict"),
/*last_fetch_time=*/base::Time::Now() - base::Seconds(9),
/*response_time=*/base::Time::Now() - base::Seconds(10),
/*expiration*/ base::Seconds(100), /*match=*/"/pattern1*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/1000, SHA256HashValue({{0x00, 0x01}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt),
isolation_key_,
SharedDictionaryInfo(
GURL("https://origin2.test/dict"),
/*last_fetch_time=*/base::Time::Now() - base::Seconds(19),
/*response_time=*/base::Time::Now() - base::Seconds(20),
/*expiration*/ base::Seconds(200), /*match=*/"/pattern2*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/2000, SHA256HashValue({{0x00, 0x02}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt),
/*expect_merged=*/false);
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
SameIsolationKeySameHostDifferentMatchSameMatchDest) {
RunMultipleDictionariesTest(
isolation_key_,
SharedDictionaryInfo(
GURL("https://origin.test/dict"),
/*last_fetch_time=*/base::Time::Now() - base::Seconds(9),
/*response_time=*/base::Time::Now() - base::Seconds(10),
/*expiration*/ base::Seconds(100), /*match=*/"/pattern1*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/1000, SHA256HashValue({{0x00, 0x01}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt),
isolation_key_,
SharedDictionaryInfo(
GURL("https://origin.test/dict"),
/*last_fetch_time=*/base::Time::Now() - base::Seconds(19),
/*response_time=*/base::Time::Now() - base::Seconds(20),
/*expiration*/ base::Seconds(200), /*match=*/"/pattern2*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/2000, SHA256HashValue({{0x00, 0x02}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt),
/*expect_merged=*/false);
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
SameIsolationKeySameHostSameMatchSameMatchDest) {
RunMultipleDictionariesTest(
isolation_key_,
SharedDictionaryInfo(
GURL("https://origin.test/dict"),
/*last_fetch_time=*/base::Time::Now() - base::Seconds(9),
/*response_time=*/base::Time::Now() - base::Seconds(10),
/*expiration*/ base::Seconds(100), /*match=*/"/pattern*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/1000, SHA256HashValue({{0x00, 0x01}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt),
isolation_key_,
SharedDictionaryInfo(
GURL("https://origin.test/dict"),
/*last_fetch_time=*/base::Time::Now() - base::Seconds(19),
/*response_time=*/base::Time::Now() - base::Seconds(20),
/*expiration*/ base::Seconds(200), /*match=*/"/pattern*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/2000, SHA256HashValue({{0x00, 0x02}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt),
/*expect_merged=*/true);
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
SameIsolationKeySameHostSameMatchDifferentMatchDest) {
RunMultipleDictionariesTest(
isolation_key_,
SharedDictionaryInfo(
GURL("https://origin.test/dict"),
/*last_fetch_time=*/base::Time::Now() - base::Seconds(9),
/*response_time=*/base::Time::Now() - base::Seconds(10),
/*expiration*/ base::Seconds(100), /*match=*/"/pattern*",
/*match_dest_string=*/"document", /*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/1000, SHA256HashValue({{0x00, 0x01}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt),
isolation_key_,
SharedDictionaryInfo(
GURL("https://origin.test/dict"),
/*last_fetch_time=*/base::Time::Now() - base::Seconds(19),
/*response_time=*/base::Time::Now() - base::Seconds(20),
/*expiration*/ base::Seconds(200), /*match=*/"/pattern*",
/*match_dest_string=*/"script", /*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/2000, SHA256HashValue({{0x00, 0x02}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt),
/*expect_merged=*/false);
}
void SQLitePersistentSharedDictionaryStoreTest::
RunGetTotalDictionarySizeFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error) {
CreateStore();
base::RunLoop run_loop;
store_->GetTotalDictionarySize(base::BindLambdaForTesting(
[&](base::expected<uint64_t, SQLitePersistentSharedDictionaryStore::Error>
result) {
ASSERT_FALSE(result.has_value());
EXPECT_EQ(expected_error, result.error());
run_loop.Quit();
}));
run_loop.Run();
DestroyStore();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
GetTotalDictionarySizeErrorInitializationFailure) {
CorruptDatabaseFile();
RunGetTotalDictionarySizeFailureTest(SQLitePersistentSharedDictionaryStore::
Error::kFailedToInitializeDatabase);
CheckStoreRecovered();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
GetTotalDictionarySizeErrorFailedToGetTotalDictSize) {
CreateStore();
EXPECT_TRUE(ClearAllDictionaries().empty());
DestroyStore();
ManipulateDatabase({"DELETE FROM meta WHERE key='total_dict_size'"});
RunGetTotalDictionarySizeFailureTest(
SQLitePersistentSharedDictionaryStore::Error::kFailedToGetTotalDictSize);
CreateStore();
// ClearAllDictionaries() resets total_dict_size in metadata.
EXPECT_TRUE(ClearAllDictionaries().empty());
// So GetTotalDictionarySize() should succeed.
EXPECT_EQ(0u, GetTotalDictionarySize());
}
void SQLitePersistentSharedDictionaryStoreTest::
RunRegisterDictionaryFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error) {
CreateStore();
base::RunLoop run_loop;
store_->RegisterDictionary(
isolation_key_, dictionary_info_, /*max_size_per_site=*/1000000,
/*max_count_per_site=*/1000,
base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::
RegisterDictionaryResultOrError result) {
ASSERT_FALSE(result.has_value());
EXPECT_EQ(expected_error, result.error());
run_loop.Quit();
}));
run_loop.Run();
DestroyStore();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
RegisterDictionaryErrorDatabaseInitializationFailure) {
CorruptDatabaseFile();
RunRegisterDictionaryFailureTest(SQLitePersistentSharedDictionaryStore::
Error::kFailedToInitializeDatabase);
CheckStoreRecovered();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
RegisterDictionaryErrorInvalidSql) {
ManipulateDatabase({"CREATE TABLE dictionaries (dummy TEST NOT NULL)"});
RunRegisterDictionaryFailureTest(
SQLitePersistentSharedDictionaryStore::Error::kInvalidSql);
}
#if !BUILDFLAG(IS_FUCHSIA)
// MakeFileUnwritable() doesn't cause the failure on Fuchsia. So disabling the
// test on Fuchsia.
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
RegisterDictionaryErrorSqlExecutionFailure) {
CreateStore();
ClearAllDictionaries();
DestroyStore();
MakeFileUnwritable();
RunRegisterDictionaryFailureTest(SQLitePersistentSharedDictionaryStore::
Error::kFailedToInitializeDatabase);
}
#endif // !BUILDFLAG(IS_FUCHSIA)
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
RegisterDictionaryErrorFailedToGetTotalDictSize) {
CreateStore();
ClearAllDictionaries();
DestroyStore();
ManipulateDatabase({"DELETE FROM meta WHERE key='total_dict_size'"});
RunRegisterDictionaryFailureTest(
SQLitePersistentSharedDictionaryStore::Error::kFailedToGetTotalDictSize);
CreateStore();
// ClearAllDictionaries() resets total_dict_size in metadata.
EXPECT_TRUE(ClearAllDictionaries().empty());
// So RegisterDictionary() should succeed.
RegisterDictionary(isolation_key_, dictionary_info_);
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
RegisterDictionaryErrorInvalidTotalDictSize) {
CreateStore();
SharedDictionaryInfo dictionary_info(
dictionary_info_.url(), /*last_fetch_time*/ base::Time::Now(),
/*response_time*/ base::Time::Now(), dictionary_info_.expiration(),
dictionary_info_.match(), dictionary_info_.match_dest_string(),
dictionary_info_.id(),
/*last_used_time*/ base::Time::Now(), dictionary_info_.size() + 1,
SHA256HashValue({{0x00, 0x02}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt);
// Register the dictionary which size is dictionary_info_.size() + 1.
base::RunLoop run_loop;
store_->RegisterDictionary(
isolation_key_, dictionary_info, /*max_size_per_site=*/1000000,
/*max_count_per_site=*/1000,
base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::
RegisterDictionaryResultOrError result) {
EXPECT_TRUE(result.has_value());
run_loop.Quit();
}));
run_loop.Run();
DestroyStore();
// Set total_dict_size in metadata to 0.
ManipulateDatabase({"UPDATE meta SET value=0 WHERE key='total_dict_size'"});
// Registering `dictionary_info_` which size is smaller than the previous
// dictionary cause InvalidTotalDictSize error because the calculated total
// size will be negative.
RunRegisterDictionaryFailureTest(
SQLitePersistentSharedDictionaryStore::Error::kInvalidTotalDictSize);
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
RegisterDictionaryErrorTooBigDictionary) {
CreateStore();
uint64_t max_size_per_site = 10000;
base::RunLoop run_loop;
store_->RegisterDictionary(
isolation_key_,
SharedDictionaryInfo(
GURL("https://a.example/dict"), /*last_fetch_time*/ base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/max_size_per_site + 1, SHA256HashValue({{0x00, 0x01}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt),
max_size_per_site,
/*max_count_per_site=*/1000,
base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::
RegisterDictionaryResultOrError result) {
ASSERT_FALSE(result.has_value());
EXPECT_EQ(
SQLitePersistentSharedDictionaryStore::Error::kTooBigDictionary,
result.error());
run_loop.Quit();
}));
run_loop.Run();
EXPECT_EQ(0u, GetTotalDictionarySize());
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
RegisterDictionaryPerSiteEvictionWhenExceededSizeLimit) {
CreateStore();
uint64_t max_size_per_site = 10000;
uint64_t max_count_per_site = 100;
auto isolation_key1 = CreateIsolationKey("https://origin1.test",
"https://top-frame-site1.test");
auto dict1 = SharedDictionaryInfo(
GURL("https://a.example/dict"), /*last_fetch_time*/ base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/max_size_per_site, SHA256HashValue({{0x00, 0x01}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt);
auto result1 = RegisterDictionaryImpl(store_.get(), isolation_key1, dict1,
max_size_per_site, max_count_per_site);
dict1.set_primary_key_in_database(result1.primary_key_in_database());
EXPECT_TRUE(result1.evicted_disk_cache_key_tokens().empty());
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key1, ElementsAreArray({dict1}))));
FastForwardBy(base::Seconds(1));
auto isolation_key2 = CreateIsolationKey("https://origin1.test",
"https://top-frame-site2.test");
auto dict2 = SharedDictionaryInfo(
GURL("https://b.example/dict"), /*last_fetch_time*/ base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/max_size_per_site / 2, SHA256HashValue({{0x00, 0x02}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt);
auto result2 = RegisterDictionaryImpl(store_.get(), isolation_key2, dict2,
max_size_per_site, max_count_per_site);
dict2.set_primary_key_in_database(result2.primary_key_in_database());
EXPECT_TRUE(result2.evicted_disk_cache_key_tokens().empty());
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key1, ElementsAreArray({dict1})),
Pair(isolation_key2, ElementsAreArray({dict2}))));
FastForwardBy(base::Seconds(1));
auto dict3 = SharedDictionaryInfo(
GURL("https://c.example/dict"), /*last_fetch_time*/ base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/max_size_per_site / 2, SHA256HashValue({{0x00, 0x03}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt);
auto result3 = RegisterDictionaryImpl(store_.get(), isolation_key2, dict3,
max_size_per_site, max_count_per_site);
dict3.set_primary_key_in_database(result3.primary_key_in_database());
EXPECT_TRUE(result3.evicted_disk_cache_key_tokens().empty());
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key1, ElementsAreArray({dict1})),
Pair(isolation_key2,
UnorderedElementsAreArray({dict2, dict3}))));
FastForwardBy(base::Seconds(1));
// The top frame site of `isolation_key3` is same as the top frame site of
// `isolation_key2`.
auto isolation_key3 = CreateIsolationKey("https://origin2.test",
"https://top-frame-site2.test");
auto dict4 = SharedDictionaryInfo(
GURL("https://d.example/dict"), /*last_fetch_time*/ base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/1, SHA256HashValue({{0x00, 0x04}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt);
auto result4 = RegisterDictionaryImpl(store_.get(), isolation_key3, dict4,
max_size_per_site, max_count_per_site);
dict4.set_primary_key_in_database(result4.primary_key_in_database());
// dict2.size() + dict3.size() + dict4.size() exceeds `max_size_per_site`. So
// the oldest dictionary `dict2` must be evicted.
EXPECT_THAT(result4.evicted_disk_cache_key_tokens(),
ElementsAreArray({dict2.disk_cache_key_token()}));
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key1, ElementsAreArray({dict1})),
Pair(isolation_key2, ElementsAreArray({dict3})),
Pair(isolation_key3, ElementsAreArray({dict4}))));
EXPECT_EQ(dict1.size() + dict3.size() + dict4.size(),
GetTotalDictionarySize());
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
RegisterDictionaryPerSiteEvictionWhenExceededCountLimit) {
CreateStore();
uint64_t max_size_per_site = 10000;
uint64_t max_count_per_site = 2;
auto isolation_key1 = CreateIsolationKey("https://origin1.test",
"https://top-frame-site1.test");
auto dict1 = SharedDictionaryInfo(
GURL("https://a.example/dict"), /*last_fetch_time*/ base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/100, SHA256HashValue({{0x00, 0x01}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt);
auto result1 = RegisterDictionaryImpl(store_.get(), isolation_key1, dict1,
max_size_per_site, max_count_per_site);
dict1.set_primary_key_in_database(result1.primary_key_in_database());
EXPECT_TRUE(result1.evicted_disk_cache_key_tokens().empty());
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key1, ElementsAreArray({dict1}))));
FastForwardBy(base::Seconds(1));
auto isolation_key2 = CreateIsolationKey("https://origin1.test",
"https://top-frame-site2.test");
auto dict2 = SharedDictionaryInfo(
GURL("https://b.example/dict"), /*last_fetch_time*/ base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/200, SHA256HashValue({{0x00, 0x02}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt);
auto result2 = RegisterDictionaryImpl(store_.get(), isolation_key2, dict2,
max_size_per_site, max_count_per_site);
dict2.set_primary_key_in_database(result2.primary_key_in_database());
EXPECT_TRUE(result2.evicted_disk_cache_key_tokens().empty());
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key1, ElementsAreArray({dict1})),
Pair(isolation_key2, ElementsAreArray({dict2}))));
FastForwardBy(base::Seconds(1));
auto dict3 = SharedDictionaryInfo(
GURL("https://c.example/dict"), /*last_fetch_time*/ base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/400, SHA256HashValue({{0x00, 0x03}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt);
auto result3 = RegisterDictionaryImpl(store_.get(), isolation_key2, dict3,
max_size_per_site, max_count_per_site);
dict3.set_primary_key_in_database(result3.primary_key_in_database());
EXPECT_TRUE(result3.evicted_disk_cache_key_tokens().empty());
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key1, ElementsAreArray({dict1})),
Pair(isolation_key2,
UnorderedElementsAreArray({dict2, dict3}))));
FastForwardBy(base::Seconds(1));
// The top frame site of `isolation_key3` is same as the top frame site of
// `isolation_key2`.
auto isolation_key3 = CreateIsolationKey("https://origin2.test",
"https://top-frame-site2.test");
auto dict4 = SharedDictionaryInfo(
GURL("https://d.example/dict"), /*last_fetch_time*/ base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/800, SHA256HashValue({{0x00, 0x04}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt);
auto result4 = RegisterDictionaryImpl(store_.get(), isolation_key3, dict4,
max_size_per_site, max_count_per_site);
dict4.set_primary_key_in_database(result4.primary_key_in_database());
// The dictionary count on "https://top-frame-site2.test" exceeds
// `max_count_per_site`. So the oldest dictionary `dict2` must be evicted.
EXPECT_THAT(result4.evicted_disk_cache_key_tokens(),
ElementsAreArray({dict2.disk_cache_key_token()}));
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key1, ElementsAreArray({dict1})),
Pair(isolation_key2, ElementsAreArray({dict3})),
Pair(isolation_key3, ElementsAreArray({dict4}))));
EXPECT_EQ(dict1.size() + dict3.size() + dict4.size(),
GetTotalDictionarySize());
}
TEST_F(
SQLitePersistentSharedDictionaryStoreTest,
RegisterDictionaryPerSiteEvictionWhenExceededCountLimitWithoutSizeLimit) {
CreateStore();
uint64_t max_size_per_site = 0;
uint64_t max_count_per_site = 2;
auto isolation_key1 = CreateIsolationKey("https://origin1.test",
"https://top-frame-site1.test");
auto dict1 = SharedDictionaryInfo(
GURL("https://a.example/dict"), /*last_fetch_time*/ base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/100, SHA256HashValue({{0x00, 0x01}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt);
auto result1 = RegisterDictionaryImpl(store_.get(), isolation_key1, dict1,
max_size_per_site, max_count_per_site);
dict1.set_primary_key_in_database(result1.primary_key_in_database());
EXPECT_TRUE(result1.evicted_disk_cache_key_tokens().empty());
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key1, ElementsAreArray({dict1}))));
FastForwardBy(base::Seconds(1));
auto isolation_key2 = CreateIsolationKey("https://origin1.test",
"https://top-frame-site2.test");
auto dict2 = SharedDictionaryInfo(
GURL("https://b.example/dict"), /*last_fetch_time*/ base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/200, SHA256HashValue({{0x00, 0x02}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt);
auto result2 = RegisterDictionaryImpl(store_.get(), isolation_key2, dict2,
max_size_per_site, max_count_per_site);
dict2.set_primary_key_in_database(result2.primary_key_in_database());
EXPECT_TRUE(result2.evicted_disk_cache_key_tokens().empty());
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key1, ElementsAreArray({dict1})),
Pair(isolation_key2, ElementsAreArray({dict2}))));
FastForwardBy(base::Seconds(1));
auto dict3 = SharedDictionaryInfo(
GURL("https://c.example/dict"), /*last_fetch_time*/ base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/400, SHA256HashValue({{0x00, 0x03}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt);
auto result3 = RegisterDictionaryImpl(store_.get(), isolation_key2, dict3,
max_size_per_site, max_count_per_site);
dict3.set_primary_key_in_database(result3.primary_key_in_database());
EXPECT_TRUE(result3.evicted_disk_cache_key_tokens().empty());
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key1, ElementsAreArray({dict1})),
Pair(isolation_key2,
UnorderedElementsAreArray({dict2, dict3}))));
FastForwardBy(base::Seconds(1));
// The top frame site of `isolation_key3` is same as the top frame site of
// `isolation_key2`.
auto isolation_key3 = CreateIsolationKey("https://origin2.test",
"https://top-frame-site2.test");
auto dict4 = SharedDictionaryInfo(
GURL("https://d.example/dict"), /*last_fetch_time*/ base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/800, SHA256HashValue({{0x00, 0x04}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt);
auto result4 = RegisterDictionaryImpl(store_.get(), isolation_key3, dict4,
max_size_per_site, max_count_per_site);
dict4.set_primary_key_in_database(result4.primary_key_in_database());
// The dictionary count on "https://top-frame-site2.test" exceeds
// `max_count_per_site`. So the oldest dictionary `dict2` must be evicted.
EXPECT_THAT(result4.evicted_disk_cache_key_tokens(),
ElementsAreArray({dict2.disk_cache_key_token()}));
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key1, ElementsAreArray({dict1})),
Pair(isolation_key2, ElementsAreArray({dict3})),
Pair(isolation_key3, ElementsAreArray({dict4}))));
EXPECT_EQ(dict1.size() + dict3.size() + dict4.size(),
GetTotalDictionarySize());
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
RegisterDictionaryPerSiteEvictionWhenExceededBothSizeAndCountLimit) {
CreateStore();
uint64_t max_size_per_site = 800;
uint64_t max_count_per_site = 2;
auto isolation_key1 = CreateIsolationKey("https://origin1.test",
"https://top-frame-site1.test");
auto dict1 = SharedDictionaryInfo(
GURL("https://a.example/dict"), /*last_fetch_time*/ base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/100, SHA256HashValue({{0x00, 0x01}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt);
auto result1 = RegisterDictionaryImpl(store_.get(), isolation_key1, dict1,
max_size_per_site, max_count_per_site);
dict1.set_primary_key_in_database(result1.primary_key_in_database());
EXPECT_TRUE(result1.evicted_disk_cache_key_tokens().empty());
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key1, ElementsAreArray({dict1}))));
FastForwardBy(base::Seconds(1));
auto isolation_key2 = CreateIsolationKey("https://origin1.test",
"https://top-frame-site2.test");
auto dict2 = SharedDictionaryInfo(
GURL("https://b.example/dict"), /*last_fetch_time*/ base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/200, SHA256HashValue({{0x00, 0x02}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt);
auto result2 = RegisterDictionaryImpl(store_.get(), isolation_key2, dict2,
max_size_per_site, max_count_per_site);
dict2.set_primary_key_in_database(result2.primary_key_in_database());
EXPECT_TRUE(result2.evicted_disk_cache_key_tokens().empty());
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key1, ElementsAreArray({dict1})),
Pair(isolation_key2, ElementsAreArray({dict2}))));
FastForwardBy(base::Seconds(1));
auto dict3 = SharedDictionaryInfo(
GURL("https://c.example/dict"), /*last_fetch_time*/ base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/400, SHA256HashValue({{0x00, 0x03}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt);
auto result3 = RegisterDictionaryImpl(store_.get(), isolation_key2, dict3,
max_size_per_site, max_count_per_site);
dict3.set_primary_key_in_database(result3.primary_key_in_database());
EXPECT_TRUE(result3.evicted_disk_cache_key_tokens().empty());
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key1, ElementsAreArray({dict1})),
Pair(isolation_key2,
UnorderedElementsAreArray({dict2, dict3}))));
FastForwardBy(base::Seconds(1));
// The top frame site of `isolation_key3` is same as the top frame site of
// `isolation_key2`.
auto isolation_key3 = CreateIsolationKey("https://origin2.test",
"https://top-frame-site2.test");
auto dict4 = SharedDictionaryInfo(
GURL("https://d.example/dict"), /*last_fetch_time*/ base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/800, SHA256HashValue({{0x00, 0x04}}),
/*disk_cache_key_token=*/base::UnguessableToken::Create(),
/*primary_key_in_database=*/std::nullopt);
auto result4 = RegisterDictionaryImpl(store_.get(), isolation_key3, dict4,
max_size_per_site, max_count_per_site);
dict4.set_primary_key_in_database(result4.primary_key_in_database());
// The dictionary count on "https://top-frame-site2.test" exceeds
// `max_count_per_site`. Also dictionary size on
// "https://top-frame-site2.test" exceeds `max_size_per_site`.
// So both `dict2` and `dict3` must be evicted.
EXPECT_THAT(result4.evicted_disk_cache_key_tokens(),
UnorderedElementsAreArray({dict2.disk_cache_key_token(),
dict3.disk_cache_key_token()}));
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key1, ElementsAreArray({dict1})),
Pair(isolation_key3, ElementsAreArray({dict4}))));
EXPECT_EQ(dict1.size() + dict4.size(), GetTotalDictionarySize());
}
void SQLitePersistentSharedDictionaryStoreTest::RunGetDictionariesFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error) {
CreateStore();
base::RunLoop run_loop;
store_->GetDictionaries(
isolation_key_,
base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::DictionaryListOrError
result) {
ASSERT_FALSE(result.has_value());
EXPECT_EQ(expected_error, result.error());
run_loop.Quit();
}));
run_loop.Run();
DestroyStore();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
GetDictionariesErrorDatabaseInitializationFailure) {
CorruptDatabaseFile();
RunGetDictionariesFailureTest(SQLitePersistentSharedDictionaryStore::Error::
kFailedToInitializeDatabase);
CheckStoreRecovered();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
GetDictionariesErrorInvalidSql) {
ManipulateDatabase({"CREATE TABLE dictionaries (dummy TEST NOT NULL)"});
RunGetDictionariesFailureTest(
SQLitePersistentSharedDictionaryStore::Error::kInvalidSql);
}
void SQLitePersistentSharedDictionaryStoreTest::
RunGetAllDictionariesFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error) {
CreateStore();
base::RunLoop run_loop;
store_->GetAllDictionaries(base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::DictionaryMapOrError result) {
ASSERT_FALSE(result.has_value());
EXPECT_EQ(expected_error, result.error());
run_loop.Quit();
}));
run_loop.Run();
DestroyStore();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
GetAllDictionariesErrorDatabaseInitializationFailure) {
CorruptDatabaseFile();
RunGetAllDictionariesFailureTest(SQLitePersistentSharedDictionaryStore::
Error::kFailedToInitializeDatabase);
CheckStoreRecovered();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
GetAllDictionariesErrorInvalidSql) {
ManipulateDatabase({"CREATE TABLE dictionaries (dummy TEST NOT NULL)"});
RunGetAllDictionariesFailureTest(
SQLitePersistentSharedDictionaryStore::Error::kInvalidSql);
}
void SQLitePersistentSharedDictionaryStoreTest::RunGetUsageInfoFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error) {
CreateStore();
base::RunLoop run_loop;
store_->GetUsageInfo(base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::UsageInfoOrError result) {
ASSERT_FALSE(result.has_value());
EXPECT_EQ(expected_error, result.error());
run_loop.Quit();
}));
run_loop.Run();
DestroyStore();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
RunGetUsageInfoErrorDatabaseInitializationFailure) {
CorruptDatabaseFile();
RunGetUsageInfoFailureTest(SQLitePersistentSharedDictionaryStore::Error::
kFailedToInitializeDatabase);
CheckStoreRecovered();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
RunGetUsageInfoErrorInvalidSql) {
ManipulateDatabase({"CREATE TABLE dictionaries (dummy TEST NOT NULL)"});
RunGetUsageInfoFailureTest(
SQLitePersistentSharedDictionaryStore::Error::kInvalidSql);
}
void SQLitePersistentSharedDictionaryStoreTest::RunGetOriginsBetweenFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error) {
CreateStore();
base::RunLoop run_loop;
store_->GetOriginsBetween(
base::Time::Now(), base::Time::Now() + base::Seconds(1),
base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::OriginListOrError result) {
ASSERT_FALSE(result.has_value());
EXPECT_EQ(expected_error, result.error());
run_loop.Quit();
}));
run_loop.Run();
DestroyStore();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
RunGetOriginsBetweenErrorDatabaseInitializationFailure) {
CorruptDatabaseFile();
RunGetOriginsBetweenFailureTest(SQLitePersistentSharedDictionaryStore::Error::
kFailedToInitializeDatabase);
CheckStoreRecovered();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
RunGetOriginsBetweenErrorInvalidSql) {
ManipulateDatabase({"CREATE TABLE dictionaries (dummy TEST NOT NULL)"});
RunGetOriginsBetweenFailureTest(
SQLitePersistentSharedDictionaryStore::Error::kInvalidSql);
}
void SQLitePersistentSharedDictionaryStoreTest::
RunClearAllDictionariesFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error) {
CreateStore();
base::RunLoop run_loop;
store_->ClearAllDictionaries(base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::UnguessableTokenSetOrError
result) {
ASSERT_FALSE(result.has_value());
EXPECT_EQ(expected_error, result.error());
run_loop.Quit();
}));
run_loop.Run();
DestroyStore();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ClearAllDictionariesErrorDatabaseInitializationFailure) {
CorruptDatabaseFile();
RunClearAllDictionariesFailureTest(SQLitePersistentSharedDictionaryStore::
Error::kFailedToInitializeDatabase);
CheckStoreRecovered();
}
#if !BUILDFLAG(IS_FUCHSIA)
// MakeFileUnwritable() doesn't cause the failure on Fuchsia. So disabling the
// test on Fuchsia.
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ClearAllDictionariesErrorSqlExecutionFailure) {
CreateStore();
ClearAllDictionaries();
DestroyStore();
MakeFileUnwritable();
RunClearAllDictionariesFailureTest(SQLitePersistentSharedDictionaryStore::
Error::kFailedToInitializeDatabase);
}
#endif // !BUILDFLAG(IS_FUCHSIA)
void SQLitePersistentSharedDictionaryStoreTest::RunClearDictionariesFailureTest(
base::RepeatingCallback<bool(const GURL&)> url_matcher,
SQLitePersistentSharedDictionaryStore::Error expected_error) {
CreateStore();
base::RunLoop run_loop;
store_->ClearDictionaries(
base::Time::Now() - base::Seconds(10), base::Time::Now(),
std::move(url_matcher),
base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::UnguessableTokenSetOrError
result) {
ASSERT_FALSE(result.has_value());
EXPECT_EQ(expected_error, result.error());
run_loop.Quit();
}));
run_loop.Run();
DestroyStore();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ClearDictionariesErrorDatabaseInitializationFailure) {
CorruptDatabaseFile();
RunClearDictionariesFailureTest(base::RepeatingCallback<bool(const GURL&)>(),
SQLitePersistentSharedDictionaryStore::Error::
kFailedToInitializeDatabase);
CheckStoreRecovered();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ClearDictionariesErrorInvalidSql) {
ManipulateDatabase({"CREATE TABLE dictionaries (dummy TEST NOT NULL)"});
RunClearDictionariesFailureTest(
base::RepeatingCallback<bool(const GURL&)>(),
SQLitePersistentSharedDictionaryStore::Error::kInvalidSql);
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ClearDictionariesWithUrlMatcherErrorInvalidSql) {
ManipulateDatabase({"CREATE TABLE dictionaries (dummy TEST NOT NULL)"});
RunClearDictionariesFailureTest(
base::BindRepeating([](const GURL&) { return true; }),
SQLitePersistentSharedDictionaryStore::Error::kInvalidSql);
}
#if !BUILDFLAG(IS_FUCHSIA)
// MakeFileUnwritable() doesn't cause the failure on Fuchsia. So disabling the
// test on Fuchsia.
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ClearDictionariesErrorSqlExecutionFailure) {
CreateStore();
RegisterDictionary(isolation_key_, dictionary_info_);
DestroyStore();
MakeFileUnwritable();
RunClearDictionariesFailureTest(base::RepeatingCallback<bool(const GURL&)>(),
SQLitePersistentSharedDictionaryStore::Error::
kFailedToInitializeDatabase);
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ClearDictionariesWithUrlMatcherErrorSqlExecutionFailure) {
CreateStore();
RegisterDictionary(isolation_key_, dictionary_info_);
DestroyStore();
MakeFileUnwritable();
RunClearDictionariesFailureTest(
base::BindRepeating([](const GURL&) { return true; }),
SQLitePersistentSharedDictionaryStore::Error::
kFailedToInitializeDatabase);
}
#endif // !BUILDFLAG(IS_FUCHSIA)
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ClearDictionariesErrorFailedToGetTotalDictSize) {
CreateStore();
RegisterDictionary(isolation_key_, dictionary_info_);
DestroyStore();
ManipulateDatabase({"DELETE FROM meta WHERE key='total_dict_size'"});
RunClearDictionariesFailureTest(
base::RepeatingCallback<bool(const GURL&)>(),
SQLitePersistentSharedDictionaryStore::Error::kFailedToGetTotalDictSize);
CreateStore();
// ClearAllDictionaries() resets total_dict_size in metadata.
EXPECT_THAT(
ClearAllDictionaries(),
UnorderedElementsAreArray({dictionary_info_.disk_cache_key_token()}));
// So ClearDictionaries() should succeed.
EXPECT_TRUE(ClearDictionaries(base::Time::Now() - base::Seconds(10),
base::Time::Now(),
base::RepeatingCallback<bool(const GURL&)>())
.empty());
}
void SQLitePersistentSharedDictionaryStoreTest::
RunClearDictionariesForIsolationKeyFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error) {
CreateStore();
base::RunLoop run_loop;
store_->ClearDictionariesForIsolationKey(
isolation_key_,
base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::UnguessableTokenSetOrError
result) {
ASSERT_FALSE(result.has_value());
EXPECT_EQ(expected_error, result.error());
run_loop.Quit();
}));
run_loop.Run();
DestroyStore();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ClearDictionariesForIsolationKeyErrorDatabaseInitializationFailure) {
CorruptDatabaseFile();
RunClearDictionariesForIsolationKeyFailureTest(
SQLitePersistentSharedDictionaryStore::Error::
kFailedToInitializeDatabase);
CheckStoreRecovered();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ClearDictionariesForIsolationKeyErrorInvalidSql) {
ManipulateDatabase({"CREATE TABLE dictionaries (dummy TEST NOT NULL)"});
RunClearDictionariesForIsolationKeyFailureTest(
SQLitePersistentSharedDictionaryStore::Error::kInvalidSql);
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ClearDictionariesForIsolationKeyErrorFailedToGetTotalDictSize) {
CreateStore();
RegisterDictionary(isolation_key_, dictionary_info_);
DestroyStore();
ManipulateDatabase({"DELETE FROM meta WHERE key='total_dict_size'"});
RunClearDictionariesForIsolationKeyFailureTest(
SQLitePersistentSharedDictionaryStore::Error::kFailedToGetTotalDictSize);
CreateStore();
// ClearAllDictionaries() resets total_dict_size in metadata.
EXPECT_THAT(
ClearAllDictionaries(),
UnorderedElementsAreArray({dictionary_info_.disk_cache_key_token()}));
// So ClearDictionariesForIsolationKey() should succeed.
EXPECT_TRUE(ClearDictionariesForIsolationKey(isolation_key_).empty());
}
void SQLitePersistentSharedDictionaryStoreTest::
RunDeleteExpiredDictionariesFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error) {
CreateStore();
base::RunLoop run_loop;
store_->DeleteExpiredDictionaries(
base::Time::Now(),
base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::UnguessableTokenSetOrError
result) {
ASSERT_FALSE(result.has_value());
EXPECT_EQ(expected_error, result.error());
run_loop.Quit();
}));
run_loop.Run();
DestroyStore();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
DeleteExpiredDictionariesErrorDatabaseInitializationFailure) {
CorruptDatabaseFile();
RunDeleteExpiredDictionariesFailureTest(
SQLitePersistentSharedDictionaryStore::Error::
kFailedToInitializeDatabase);
CheckStoreRecovered();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
DeleteExpiredDictionariesErrorInvalidSql) {
ManipulateDatabase({"CREATE TABLE dictionaries (dummy TEST NOT NULL)"});
RunDeleteExpiredDictionariesFailureTest(
SQLitePersistentSharedDictionaryStore::Error::kInvalidSql);
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
DeleteExpiredDictionariesErrorFailedToGetTotalDictSize) {
CreateStore();
RegisterDictionary(isolation_key_, dictionary_info_);
DestroyStore();
ManipulateDatabase({"DELETE FROM meta WHERE key='total_dict_size'"});
// Move the clock forward by 90 seconds to make `dictionary_info_` expired.
FastForwardBy(base::Seconds(90));
RunDeleteExpiredDictionariesFailureTest(
SQLitePersistentSharedDictionaryStore::Error::kFailedToGetTotalDictSize);
CreateStore();
// ClearAllDictionaries() resets total_dict_size in metadata.
EXPECT_THAT(
ClearAllDictionaries(),
UnorderedElementsAreArray({dictionary_info_.disk_cache_key_token()}));
// So DeleteExpiredDictionaries() should succeed.
EXPECT_TRUE(DeleteExpiredDictionaries(base::Time::Now()).empty());
}
void SQLitePersistentSharedDictionaryStoreTest::RunProcessEvictionFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error) {
CreateStore();
base::RunLoop run_loop;
store_->ProcessEviction(
/*cache_max_size=*/1, /*size_low_watermark=*/1,
/*cache_max_count=*/1, /*count_low_watermark=*/1,
base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::UnguessableTokenSetOrError
result) {
ASSERT_FALSE(result.has_value());
EXPECT_EQ(expected_error, result.error());
run_loop.Quit();
}));
run_loop.Run();
DestroyStore();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ProcessEvictionErrorDatabaseInitializationFailure) {
CorruptDatabaseFile();
RunProcessEvictionFailureTest(SQLitePersistentSharedDictionaryStore::Error::
kFailedToInitializeDatabase);
CheckStoreRecovered();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ProcessEvictionErrorInvalidSql) {
CreateStore();
RegisterDictionary(isolation_key_, dictionary_info_);
DestroyStore();
// Delete the existing `dictionaries` table, and create a broken
// `dictionaries` table.
ManipulateDatabase({"DROP TABLE dictionaries",
"CREATE TABLE dictionaries (dummy TEST NOT NULL)"});
RunProcessEvictionFailureTest(
SQLitePersistentSharedDictionaryStore::Error::kInvalidSql);
}
#if !BUILDFLAG(IS_FUCHSIA)
// MakeFileUnwritable() doesn't cause the failure on Fuchsia. So disabling the
// test on Fuchsia.
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ProcessEvictionErrorSqlExecutionFailure) {
CreateStore();
RegisterDictionary(isolation_key_, dictionary_info_);
DestroyStore();
MakeFileUnwritable();
RunProcessEvictionFailureTest(SQLitePersistentSharedDictionaryStore::Error::
kFailedToInitializeDatabase);
}
#endif // !BUILDFLAG(IS_FUCHSIA)
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ProcessEvictionErrorFailedToGetTotalDictSize) {
CreateStore();
RegisterDictionary(isolation_key_, dictionary_info_);
DestroyStore();
ManipulateDatabase({"DELETE FROM meta WHERE key='total_dict_size'"});
RunProcessEvictionFailureTest(
SQLitePersistentSharedDictionaryStore::Error::kFailedToGetTotalDictSize);
CreateStore();
// ClearAllDictionaries() resets total_dict_size in metadata.
EXPECT_THAT(
ClearAllDictionaries(),
UnorderedElementsAreArray({dictionary_info_.disk_cache_key_token()}));
// So ProcessEviction() should succeed.
EXPECT_TRUE(ProcessEviction(
/*cache_max_size=*/1, /*size_low_watermark=*/1,
/*cache_max_count=*/1, /*count_low_watermark=*/1)
.empty());
}
void SQLitePersistentSharedDictionaryStoreTest::
RunGetAllDiskCacheKeyTokensFailureTest(
SQLitePersistentSharedDictionaryStore::Error expected_error) {
CreateStore();
base::RunLoop run_loop;
store_->GetAllDiskCacheKeyTokens(base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::UnguessableTokenSetOrError
result) {
ASSERT_FALSE(result.has_value());
EXPECT_EQ(expected_error, result.error());
run_loop.Quit();
}));
run_loop.Run();
DestroyStore();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
GetAllDiskCacheKeyTokensErrorDatabaseInitializationFailure) {
CorruptDatabaseFile();
RunGetAllDiskCacheKeyTokensFailureTest(
SQLitePersistentSharedDictionaryStore::Error::
kFailedToInitializeDatabase);
CheckStoreRecovered();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
GetAllDiskCacheKeyTokensErrorInvalidSql) {
ManipulateDatabase({"CREATE TABLE dictionaries (dummy TEST NOT NULL)"});
RunGetAllDiskCacheKeyTokensFailureTest(
SQLitePersistentSharedDictionaryStore::Error::kInvalidSql);
}
TEST_F(
SQLitePersistentSharedDictionaryStoreTest,
DeleteDictionariesByDiskCacheKeyTokensErrorDatabaseInitializationFailure) {
CorruptDatabaseFile();
CreateStore();
EXPECT_EQ(
SQLitePersistentSharedDictionaryStore::Error::kFailedToInitializeDatabase,
DeleteDictionariesByDiskCacheKeyTokens(
{dictionary_info_.disk_cache_key_token()}));
DestroyStore();
CheckStoreRecovered();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
DeleteDictionariesByDiskCacheKeyTokensErrorInvalidSql) {
ManipulateDatabase({"CREATE TABLE dictionaries (dummy TEST NOT NULL)"});
CreateStore();
EXPECT_EQ(SQLitePersistentSharedDictionaryStore::Error::kInvalidSql,
DeleteDictionariesByDiskCacheKeyTokens(
{dictionary_info_.disk_cache_key_token()}));
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
DeleteDictionariesByDiskCacheKeyTokensErrorFailedToGetTotalDictSize) {
CreateStore();
RegisterDictionary(isolation_key_, dictionary_info_);
DestroyStore();
ManipulateDatabase({"DELETE FROM meta WHERE key='total_dict_size'"});
CreateStore();
EXPECT_EQ(
SQLitePersistentSharedDictionaryStore::Error::kFailedToGetTotalDictSize,
DeleteDictionariesByDiskCacheKeyTokens(
{dictionary_info_.disk_cache_key_token()}));
// ClearAllDictionaries() resets total_dict_size in metadata.
EXPECT_THAT(
ClearAllDictionaries(),
UnorderedElementsAreArray({dictionary_info_.disk_cache_key_token()}));
// So DeleteDictionariesByDiskCacheKeyTokens() should succeed.
EXPECT_EQ(SQLitePersistentSharedDictionaryStore::Error::kOk,
DeleteDictionariesByDiskCacheKeyTokens(
{base::UnguessableToken::Create()}));
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
UpdateDictionaryLastFetchTimeErrorDatabaseInitializationFailure) {
CorruptDatabaseFile();
CreateStore();
EXPECT_EQ(
SQLitePersistentSharedDictionaryStore::Error::kFailedToInitializeDatabase,
UpdateDictionaryLastFetchTime(/*primary_key_in_database=*/0,
/*last_fetch_time=*/base::Time::Now()));
DestroyStore();
CheckStoreRecovered();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
UpdateDictionaryLastFetchTimeErrorInvalidSql) {
ManipulateDatabase({"CREATE TABLE dictionaries (dummy TEST NOT NULL)"});
CreateStore();
EXPECT_EQ(
SQLitePersistentSharedDictionaryStore::Error::kInvalidSql,
UpdateDictionaryLastFetchTime(/*primary_key_in_database=*/0,
/*last_fetch_time=*/base::Time::Now()));
}
#if !BUILDFLAG(IS_FUCHSIA)
// MakeFileUnwritable() doesn't cause the failure on Fuchsia. So disabling the
// test on Fuchsia.
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
UpdateDictionaryLastFetchTimeErrorSqlExecutionFailure) {
CreateStore();
auto register_dictionary_result =
RegisterDictionary(isolation_key_, dictionary_info_);
DestroyStore();
MakeFileUnwritable();
CreateStore();
EXPECT_EQ(
SQLitePersistentSharedDictionaryStore::Error::kFailedToInitializeDatabase,
UpdateDictionaryLastFetchTime(
register_dictionary_result.primary_key_in_database(),
/*last_fetch_time=*/base::Time::Now()));
}
#endif // !BUILDFLAG(IS_FUCHSIA)
TEST_F(SQLitePersistentSharedDictionaryStoreTest, InvalidHash) {
CreateStore();
auto register_dictionary_result =
RegisterDictionary(isolation_key_, dictionary_info_);
SharedDictionaryInfo expected_info = dictionary_info_;
expected_info.set_primary_key_in_database(
register_dictionary_result.primary_key_in_database());
EXPECT_THAT(GetDictionaries(isolation_key_),
ElementsAreArray({expected_info}));
DestroyStore();
ManipulateDatabase({"UPDATE dictionaries set sha256='DUMMY'"});
CreateStore();
EXPECT_TRUE(GetDictionaries(isolation_key_).empty());
EXPECT_TRUE(GetAllDictionaries().empty());
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest, InvalidToken) {
CreateStore();
auto register_dictionary_result =
RegisterDictionary(isolation_key_, dictionary_info_);
SharedDictionaryInfo expected_info = dictionary_info_;
expected_info.set_primary_key_in_database(
register_dictionary_result.primary_key_in_database());
EXPECT_THAT(GetDictionaries(isolation_key_),
ElementsAreArray({expected_info}));
DestroyStore();
// {token_low=0, token_high=0} token is treated as invalid.
ManipulateDatabase({"UPDATE dictionaries set token_low=0, token_high=0"});
CreateStore();
EXPECT_TRUE(GetDictionaries(isolation_key_).empty());
EXPECT_TRUE(GetAllDictionaries().empty());
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
GetTotalDictionarySizeCallbackNotCalledAfterStoreDeleted) {
CreateStore();
store_->GetTotalDictionarySize(base::BindLambdaForTesting(
[](base::expected<uint64_t,
SQLitePersistentSharedDictionaryStore::Error>) {
EXPECT_TRUE(false) << "Should not be reached.";
}));
store_.reset();
RunUntilIdle();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
RegisterDictionaryCallbackNotCalledAfterStoreDeleted) {
CreateStore();
store_->RegisterDictionary(
isolation_key_, dictionary_info_,
/*max_size_per_site=*/1000000,
/*max_count_per_site=*/1000,
base::BindLambdaForTesting(
[](SQLitePersistentSharedDictionaryStore::
RegisterDictionaryResultOrError result) {
EXPECT_TRUE(false) << "Should not be reached.";
}));
store_.reset();
RunUntilIdle();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
GetDictionariesCallbackNotCalledAfterStoreDeleted) {
CreateStore();
store_->GetDictionaries(
isolation_key_,
base::BindLambdaForTesting(
[](SQLitePersistentSharedDictionaryStore::DictionaryListOrError) {
EXPECT_TRUE(false) << "Should not be reached.";
}));
store_.reset();
RunUntilIdle();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
GetAllDictionariesCallbackNotCalledAfterStoreDeleted) {
CreateStore();
store_->GetAllDictionaries(base::BindLambdaForTesting(
[](SQLitePersistentSharedDictionaryStore::DictionaryMapOrError result) {
EXPECT_TRUE(false) << "Should not be reached.";
}));
store_.reset();
RunUntilIdle();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ClearAllDictionariesCallbackNotCalledAfterStoreDeleted) {
CreateStore();
store_->ClearAllDictionaries(base::BindLambdaForTesting(
[](SQLitePersistentSharedDictionaryStore::UnguessableTokenSetOrError
result) { EXPECT_TRUE(false) << "Should not be reached."; }));
store_.reset();
RunUntilIdle();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ClearDictionariesCallbackNotCalledAfterStoreDeleted) {
CreateStore();
store_->ClearDictionaries(
base::Time::Now() - base::Seconds(1), base::Time::Now(),
base::RepeatingCallback<bool(const GURL&)>(),
base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::UnguessableTokenSetOrError
result) { EXPECT_TRUE(false) << "Should not be reached."; }));
store_.reset();
RunUntilIdle();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
DeleteExpiredDictionariesCallbackNotCalledAfterStoreDeleted) {
CreateStore();
store_->DeleteExpiredDictionaries(
base::Time::Now(),
base::BindLambdaForTesting(
[&](SQLitePersistentSharedDictionaryStore::UnguessableTokenSetOrError
result) { EXPECT_TRUE(false) << "Should not be reached."; }));
store_.reset();
RunUntilIdle();
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest, ClearDictionaries) {
CreateStore();
auto token1 = base::UnguessableToken::Create();
SharedDictionaryInfo dict1 = SharedDictionaryInfo(
GURL("https://a.example/dict"),
/*last_fetch_time=*/base::Time::Now() - base::Seconds(4),
/*response_time=*/base::Time::Now() - base::Seconds(4),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/1000, SHA256HashValue({{0x00, 0x01}}),
/*disk_cache_key_token=*/token1,
/*primary_key_in_database=*/std::nullopt);
auto result1 = RegisterDictionary(isolation_key_, dict1);
dict1.set_primary_key_in_database(result1.primary_key_in_database());
auto token2 = base::UnguessableToken::Create();
SharedDictionaryInfo dict2 = SharedDictionaryInfo(
GURL("https://b.example/dict"),
/*last_fetch_time=*/base::Time::Now() - base::Seconds(3),
/*response_time=*/base::Time::Now() - base::Seconds(3),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/3000, SHA256HashValue({{0x00, 0x02}}),
/*disk_cache_key_token=*/token2,
/*primary_key_in_database=*/std::nullopt);
auto result2 = RegisterDictionary(isolation_key_, dict2);
dict2.set_primary_key_in_database(result2.primary_key_in_database());
auto token3 = base::UnguessableToken::Create();
SharedDictionaryInfo dict3 = SharedDictionaryInfo(
GURL("https://c.example/dict"),
/*last_fetch_time=*/base::Time::Now() - base::Seconds(2),
/*response_time=*/base::Time::Now() - base::Seconds(2),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/5000, SHA256HashValue({{0x00, 0x03}}),
/*disk_cache_key_token=*/token3,
/*primary_key_in_database=*/std::nullopt);
auto result3 = RegisterDictionary(isolation_key_, dict3);
dict3.set_primary_key_in_database(result3.primary_key_in_database());
auto token4 = base::UnguessableToken::Create();
SharedDictionaryInfo dict4 = SharedDictionaryInfo(
GURL("https://d.example/dict"),
/*last_fetch_time=*/base::Time::Now() - base::Seconds(1),
/*response_time=*/base::Time::Now() - base::Seconds(1),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/7000, SHA256HashValue({{0x00, 0x04}}),
/*disk_cache_key_token=*/token4,
/*primary_key_in_database=*/std::nullopt);
auto result4 = RegisterDictionary(isolation_key_, dict4);
dict4.set_primary_key_in_database(result4.primary_key_in_database());
// No matching dictionaries to be deleted.
EXPECT_TRUE(ClearDictionaries(base::Time::Now() - base::Seconds(200),
base::Time::Now() - base::Seconds(4),
base::RepeatingCallback<bool(const GURL&)>())
.empty());
std::set<base::UnguessableToken> tokens =
ClearDictionaries(base::Time::Now() - base::Seconds(3),
base::Time::Now() - base::Seconds(1),
base::RepeatingCallback<bool(const GURL&)>());
// The dict2 which res_time is "now - 3 sec" and the dict3
// which res_time is "now - 2 sec" must be deleted.
EXPECT_THAT(tokens, UnorderedElementsAreArray({token2, token3}));
// Check the remaining dictionaries.
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key_,
UnorderedElementsAreArray({dict1, dict4}))));
// Check the total size of remaining dictionaries.
EXPECT_EQ(dict1.size() + dict4.size(), GetTotalDictionarySize());
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ClearDictionariesWithUrlMatcher) {
CreateStore();
auto isolation_key1 =
CreateIsolationKey("https://a1.example/", "https://a2.example/");
auto token1 = base::UnguessableToken::Create();
SharedDictionaryInfo dict1 = SharedDictionaryInfo(
GURL("https://a3.example/dict"),
/*last_fetch_time=*/base::Time::Now() - base::Seconds(4),
/*response_time=*/base::Time::Now() - base::Seconds(4),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/1000, SHA256HashValue({{0x00, 0x01}}),
/*disk_cache_key_token=*/token1,
/*primary_key_in_database=*/std::nullopt);
auto result1 = RegisterDictionary(isolation_key1, dict1);
dict1.set_primary_key_in_database(result1.primary_key_in_database());
auto isolation_key2 =
CreateIsolationKey("https://b1.example/", "https://b2.example/");
auto token2 = base::UnguessableToken::Create();
SharedDictionaryInfo dict2 = SharedDictionaryInfo(
GURL("https://b3.example/dict"),
/*last_fetch_time=*/base::Time::Now() - base::Seconds(3),
/*response_time=*/base::Time::Now() - base::Seconds(3),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/3000, SHA256HashValue({{0x00, 0x02}}),
/*disk_cache_key_token=*/token2,
/*primary_key_in_database=*/std::nullopt);
auto result2 = RegisterDictionary(isolation_key2, dict2);
dict2.set_primary_key_in_database(result2.primary_key_in_database());
auto isolation_key3 =
CreateIsolationKey("https://c1.example/", "https://c2.example/");
auto token3 = base::UnguessableToken::Create();
SharedDictionaryInfo dict3 = SharedDictionaryInfo(
GURL("https://c3.example/dict"),
/*last_fetch_time=*/base::Time::Now() - base::Seconds(2),
/*response_time=*/base::Time::Now() - base::Seconds(2),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/5000, SHA256HashValue({{0x00, 0x03}}),
/*disk_cache_key_token=*/token3,
/*primary_key_in_database=*/std::nullopt);
auto result3 = RegisterDictionary(isolation_key3, dict3);
dict3.set_primary_key_in_database(result3.primary_key_in_database());
auto isolation_key4 =
CreateIsolationKey("https://d1.example/", "https://d2.example/");
auto token4 = base::UnguessableToken::Create();
SharedDictionaryInfo dict4 = SharedDictionaryInfo(
GURL("https://d3.example/dict"),
/*last_fetch_time=*/base::Time::Now() - base::Seconds(1),
/*response_time=*/base::Time::Now() - base::Seconds(1),
/*expiration*/ base::Seconds(100), "/pattern*", /*match_dest_string=*/"",
/*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/7000, SHA256HashValue({{0x00, 0x04}}),
/*disk_cache_key_token=*/token4,
/*primary_key_in_database=*/std::nullopt);
auto result4 = RegisterDictionary(isolation_key4, dict4);
dict4.set_primary_key_in_database(result4.primary_key_in_database());
// No matching dictionaries to be deleted.
EXPECT_TRUE(ClearDictionaries(base::Time::Now() - base::Seconds(200),
base::Time::Now() - base::Seconds(4),
base::BindRepeating([](const GURL&) {
EXPECT_TRUE(false)
<< "Should not be reached.";
return true;
}))
.empty());
std::set<GURL> checked_urls;
EXPECT_TRUE(
ClearDictionaries(base::Time::Now() - base::Seconds(3),
base::Time::Now() - base::Seconds(1),
base::BindLambdaForTesting([&](const GURL& url) {
checked_urls.insert(url);
return false;
}))
.empty());
// The dict2 which last_used_time is "now - 3 sec" and the dict3
// which last_used_time is "now - 2 sec" must be selected and the macher is
// called with those dictionaries frame_origin, top_frame_site and host.
EXPECT_THAT(checked_urls,
UnorderedElementsAreArray(
{GURL("https://b1.example/"), GURL("https://b2.example/"),
GURL("https://b3.example/"), GURL("https://c1.example/"),
GURL("https://c2.example/"), GURL("https://c3.example/")}));
// Deletes dict3.
std::set<base::UnguessableToken> tokens =
ClearDictionaries(base::Time::Now() - base::Seconds(3),
base::Time::Now() - base::Seconds(1),
base::BindRepeating([](const GURL& url) {
return url == GURL("https://c3.example/");
}));
EXPECT_THAT(tokens, ElementsAreArray({token3}));
// Check the remaining dictionaries.
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key1, ElementsAreArray({dict1})),
Pair(isolation_key2, ElementsAreArray({dict2})),
Pair(isolation_key4, ElementsAreArray({dict4}))));
// Check the total size of remaining dictionaries.
EXPECT_EQ(dict1.size() + dict2.size() + dict4.size(),
GetTotalDictionarySize());
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ClearDictionariesForIsolationKeyEmptyStore) {
CreateStore();
EXPECT_TRUE(ClearDictionariesForIsolationKey(isolation_key_).empty());
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ClearDictionariesForIsolation) {
CreateStore();
auto isolation_key1 =
CreateIsolationKey("https://a1.example/", "https://a2.example/");
auto token1 = base::UnguessableToken::Create();
SharedDictionaryInfo dict1 =
SharedDictionaryInfo(GURL("https://a1.example/dict"),
/*last_fetch_time=*/base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/1000, SHA256HashValue({{0x00, 0x01}}),
/*disk_cache_key_token=*/token1,
/*primary_key_in_database=*/std::nullopt);
auto result1 = RegisterDictionary(isolation_key1, dict1);
dict1.set_primary_key_in_database(result1.primary_key_in_database());
// Same frame origin, different top frame site.
auto isolation_key2 =
CreateIsolationKey("https://a1.example/", "https://a3.example/");
auto token2 = base::UnguessableToken::Create();
SharedDictionaryInfo dict2 =
SharedDictionaryInfo(GURL("https://a2.example/dict"),
/*last_fetch_time=*/base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/2000, SHA256HashValue({{0x00, 0x02}}),
/*disk_cache_key_token=*/token2,
/*primary_key_in_database=*/std::nullopt);
auto result2 = RegisterDictionary(isolation_key2, dict2);
dict2.set_primary_key_in_database(result2.primary_key_in_database());
// Different frame origin, same top frame site.
auto isolation_key3 =
CreateIsolationKey("https://a4.example/", "https://a2.example/");
auto token3 = base::UnguessableToken::Create();
SharedDictionaryInfo dict3 =
SharedDictionaryInfo(GURL("https://a3.example/dict"),
/*last_fetch_time=*/base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/4000, SHA256HashValue({{0x00, 0x03}}),
/*disk_cache_key_token=*/token3,
/*primary_key_in_database=*/std::nullopt);
auto result3 = RegisterDictionary(isolation_key3, dict3);
dict3.set_primary_key_in_database(result3.primary_key_in_database());
// Different frame origin, different top frame site.
auto isolation_key4 =
CreateIsolationKey("https://a4.example/", "https://a5.example/");
auto token4 = base::UnguessableToken::Create();
SharedDictionaryInfo dict4 =
SharedDictionaryInfo(GURL("https://a4.example/dict"),
/*last_fetch_time=*/base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/8000, SHA256HashValue({{0x00, 0x04}}),
/*disk_cache_key_token=*/token4,
/*primary_key_in_database=*/std::nullopt);
auto result4 = RegisterDictionary(isolation_key4, dict4);
dict4.set_primary_key_in_database(result4.primary_key_in_database());
// Deletes dictionaries for `isolation_key_`. The result should be empty.
EXPECT_TRUE(ClearDictionariesForIsolationKey(isolation_key_).empty());
// Deletes dictionaries for `isolation_key1`.
EXPECT_THAT(ClearDictionariesForIsolationKey(isolation_key1),
ElementsAreArray({token1}));
// Check the remaining dictionaries.
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key2, ElementsAreArray({dict2})),
Pair(isolation_key3, ElementsAreArray({dict3})),
Pair(isolation_key4, ElementsAreArray({dict4}))));
// Check the total size of remaining dictionaries.
EXPECT_EQ(dict2.size() + dict3.size() + dict4.size(),
GetTotalDictionarySize());
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ClearDictionariesForIsolationMultipleDictionaries) {
CreateStore();
auto isolation_key1 = CreateIsolationKey("https://a1.example/");
auto token1_1 = base::UnguessableToken::Create();
SharedDictionaryInfo dict1_1 =
SharedDictionaryInfo(GURL("https://a1.example/dict1"),
/*last_fetch_time=*/base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern1*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/1000, SHA256HashValue({{0x00, 0x01}}),
/*disk_cache_key_token=*/token1_1,
/*primary_key_in_database=*/std::nullopt);
auto result1_1 = RegisterDictionary(isolation_key1, dict1_1);
dict1_1.set_primary_key_in_database(result1_1.primary_key_in_database());
auto token1_2 = base::UnguessableToken::Create();
SharedDictionaryInfo dict1_2 =
SharedDictionaryInfo(GURL("https://a1.example/dict1"),
/*last_fetch_time=*/base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern2*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/2000, SHA256HashValue({{0x00, 0x02}}),
/*disk_cache_key_token=*/token1_2,
/*primary_key_in_database=*/std::nullopt);
auto result1_2 = RegisterDictionary(isolation_key1, dict1_2);
dict1_2.set_primary_key_in_database(result1_2.primary_key_in_database());
auto isolation_key2 = CreateIsolationKey("https://a2.example/");
auto token2 = base::UnguessableToken::Create();
SharedDictionaryInfo dict2 =
SharedDictionaryInfo(GURL("https://a2.example/dict"),
/*last_fetch_time=*/base::Time::Now(),
/*response_time=*/base::Time::Now(),
/*expiration*/ base::Seconds(100), "/pattern*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ base::Time::Now(),
/*size=*/4000, SHA256HashValue({{0x00, 0x03}}),
/*disk_cache_key_token=*/token2,
/*primary_key_in_database=*/std::nullopt);
auto result2 = RegisterDictionary(isolation_key2, dict2);
dict2.set_primary_key_in_database(result2.primary_key_in_database());
// Deletes dictionaries for `isolation_key1`.
EXPECT_THAT(ClearDictionariesForIsolationKey(isolation_key1),
UnorderedElementsAreArray({token1_1, token1_2}));
// Check the remaining dictionaries.
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key2, ElementsAreArray({dict2}))));
// Check the total size of remaining dictionaries.
EXPECT_EQ(dict2.size(), GetTotalDictionarySize());
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest, DeleteExpiredDictionaries) {
CreateStore();
const base::Time now = base::Time::Now();
auto token1 = base::UnguessableToken::Create();
SharedDictionaryInfo dict1 =
SharedDictionaryInfo(GURL("https://a.example/dict"),
/*last_fetch_time=*/now, /*response_time=*/now,
/*expiration*/ base::Seconds(100), "/pattern*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ now,
/*size=*/1000, SHA256HashValue({{0x00, 0x01}}),
/*disk_cache_key_token=*/token1,
/*primary_key_in_database=*/std::nullopt);
auto result1 = RegisterDictionary(isolation_key_, dict1);
dict1.set_primary_key_in_database(result1.primary_key_in_database());
auto token2 = base::UnguessableToken::Create();
SharedDictionaryInfo dict2 =
SharedDictionaryInfo(GURL("https://b.example/dict"),
/*last_fetch_time=*/now + base::Seconds(1),
/*response_time=*/now + base::Seconds(1),
/*expiration*/ base::Seconds(99), "/pattern*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ now,
/*size=*/3000, SHA256HashValue({{0x00, 0x02}}),
/*disk_cache_key_token=*/token2,
/*primary_key_in_database=*/std::nullopt);
auto result2 = RegisterDictionary(isolation_key_, dict2);
dict2.set_primary_key_in_database(result2.primary_key_in_database());
auto token3 = base::UnguessableToken::Create();
SharedDictionaryInfo dict3 =
SharedDictionaryInfo(GURL("https://c.example/dict"),
/*last_fetch_time=*/now + base::Seconds(1),
/*response_time=*/now + base::Seconds(1),
/*expiration*/ base::Seconds(100), "/pattern*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ now,
/*size=*/5000, SHA256HashValue({{0x00, 0x03}}),
/*disk_cache_key_token=*/token3,
/*primary_key_in_database=*/std::nullopt);
auto result3 = RegisterDictionary(isolation_key_, dict3);
dict3.set_primary_key_in_database(result3.primary_key_in_database());
auto token4 = base::UnguessableToken::Create();
SharedDictionaryInfo dict4 =
SharedDictionaryInfo(GURL("https://d.example/dict"),
/*last_fetch_time=*/now + base::Seconds(2),
/*response_time=*/now + base::Seconds(2),
/*expiration*/ base::Seconds(99), "/pattern*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ now,
/*size=*/7000, SHA256HashValue({{0x00, 0x04}}),
/*disk_cache_key_token=*/token4,
/*primary_key_in_database=*/std::nullopt);
auto result4 = RegisterDictionary(isolation_key_, dict4);
dict4.set_primary_key_in_database(result4.primary_key_in_database());
// No matching dictionaries to be deleted.
EXPECT_TRUE(DeleteExpiredDictionaries(now + base::Seconds(99)).empty());
std::set<base::UnguessableToken> tokens =
DeleteExpiredDictionaries(now + base::Seconds(100));
// The dict1 and dict2 must be deleted.
EXPECT_THAT(tokens, UnorderedElementsAreArray({token1, token2}));
// Check the remaining dictionaries.
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key_,
UnorderedElementsAreArray({dict3, dict4}))));
// Check the total size of remaining dictionaries.
EXPECT_EQ(dict3.size() + dict4.size(), GetTotalDictionarySize());
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest, ProcessEvictionNotExceeded) {
CreateStore();
auto [dict1, dict2, dict3, dict4] =
RegisterSharedDictionariesForProcessEvictionTest(store_.get(),
isolation_key_);
// The current status:
// dict1: size=1000 last_used_time=now
// dict3: size=5000 last_used_time=now+2
// dict4: size=7000 last_used_time=now+3
// dict2: size=3000 last_used_time=now+4
// No matching dictionaries to be deleted.
EXPECT_TRUE(ProcessEviction(16000, 15000, 10, 9).empty());
// Check the remaining dictionaries.
EXPECT_THAT(
GetAllDictionaries(),
ElementsAre(Pair(isolation_key_, UnorderedElementsAreArray(
{dict1, dict2, dict3, dict4}))));
// Check the total size of remaining dictionaries.
EXPECT_EQ(dict1.size() + dict2.size() + dict3.size() + dict4.size(),
GetTotalDictionarySize());
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest, ProcessEvictionSizeExceeded) {
CreateStore();
auto [dict1, dict2, dict3, dict4] =
RegisterSharedDictionariesForProcessEvictionTest(store_.get(),
isolation_key_);
// The current status:
// dict1: size=1000 last_used_time=now
// dict3: size=5000 last_used_time=now+2
// dict4: size=7000 last_used_time=now+3
// dict2: size=3000 last_used_time=now+4
std::set<base::UnguessableToken> tokens =
ProcessEviction(15000, 10000, 10, 9);
// The dict1 and dict3 must be deleted.
EXPECT_THAT(tokens,
UnorderedElementsAreArray({dict1.disk_cache_key_token(),
dict3.disk_cache_key_token()}));
// Check the remaining dictionaries.
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key_,
UnorderedElementsAreArray({dict4, dict2}))));
// Check the total size of remaining dictionaries.
EXPECT_EQ(dict4.size() + dict2.size(), GetTotalDictionarySize());
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ProcessEvictionSizeExceededEvictiedUntilCountLowWatermark) {
CreateStore();
auto [dict1, dict2, dict3, dict4] =
RegisterSharedDictionariesForProcessEvictionTest(store_.get(),
isolation_key_);
// The current status:
// dict1: size=1000 last_used_time=now
// dict3: size=5000 last_used_time=now+2
// dict4: size=7000 last_used_time=now+3
// dict2: size=3000 last_used_time=now+4
std::set<base::UnguessableToken> tokens =
ProcessEviction(15000, 10000, 10, 1);
// The dict1 and dict3 and dict4 must be deleted.
EXPECT_THAT(tokens,
UnorderedElementsAreArray({dict1.disk_cache_key_token(),
dict3.disk_cache_key_token(),
dict4.disk_cache_key_token()}));
// Check the remaining dictionaries.
EXPECT_THAT(
GetAllDictionaries(),
ElementsAre(Pair(isolation_key_, UnorderedElementsAreArray({dict2}))));
// Check the total size of remaining dictionaries.
EXPECT_EQ(dict2.size(), GetTotalDictionarySize());
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ProcessEvictionCountExceeded) {
CreateStore();
auto [dict1, dict2, dict3, dict4] =
RegisterSharedDictionariesForProcessEvictionTest(store_.get(),
isolation_key_);
// The current status:
// dict1: size=1000 last_used_time=now
// dict3: size=5000 last_used_time=now+2
// dict4: size=7000 last_used_time=now+3
// dict2: size=3000 last_used_time=now+4
std::set<base::UnguessableToken> tokens = ProcessEviction(20000, 20000, 3, 2);
// The dict1 and dict3 must be deleted.
EXPECT_THAT(tokens,
UnorderedElementsAreArray({dict1.disk_cache_key_token(),
dict3.disk_cache_key_token()}));
// Check the remaining dictionaries.
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key_,
UnorderedElementsAreArray({dict4, dict2}))));
// Check the total size of remaining dictionaries.
EXPECT_EQ(dict4.size() + dict2.size(), GetTotalDictionarySize());
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest,
ProcessEvictionCountExceededEvictedUntilSizeLowWaterMark) {
CreateStore();
auto [dict1, dict2, dict3, dict4] =
RegisterSharedDictionariesForProcessEvictionTest(store_.get(),
isolation_key_);
// The current status:
// dict1: size=1000 last_used_time=now
// dict3: size=5000 last_used_time=now+2
// dict4: size=7000 last_used_time=now+3
// dict2: size=3000 last_used_time=now+4
std::set<base::UnguessableToken> tokens = ProcessEviction(20000, 3000, 3, 2);
// The dict1 and dict3 and dict4 must be deleted.
EXPECT_THAT(tokens,
UnorderedElementsAreArray({dict1.disk_cache_key_token(),
dict3.disk_cache_key_token(),
dict4.disk_cache_key_token()}));
// Check the remaining dictionaries.
EXPECT_THAT(
GetAllDictionaries(),
ElementsAre(Pair(isolation_key_, UnorderedElementsAreArray({dict2}))));
// Check the total size of remaining dictionaries.
EXPECT_EQ(dict2.size(), GetTotalDictionarySize());
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest, ProcessEvictionZeroMaxSize) {
CreateStore();
auto [dict1, dict2, dict3, dict4] =
RegisterSharedDictionariesForProcessEvictionTest(store_.get(),
isolation_key_);
// The current status:
// dict1: size=1000 last_used_time=now
// dict3: size=5000 last_used_time=now+2
// dict4: size=7000 last_used_time=now+3
// dict2: size=3000 last_used_time=now+4
EXPECT_TRUE(ProcessEviction(0, 0, 4, 2).empty());
std::set<base::UnguessableToken> tokens = ProcessEviction(0, 0, 3, 2);
// The dict1 and dict3 and dict4 must be deleted.
EXPECT_THAT(tokens,
UnorderedElementsAreArray({dict1.disk_cache_key_token(),
dict3.disk_cache_key_token()}));
// Check the remaining dictionaries.
EXPECT_THAT(GetAllDictionaries(),
ElementsAre(Pair(isolation_key_,
UnorderedElementsAreArray({dict2, dict4}))));
// Check the total size of remaining dictionaries.
EXPECT_EQ(dict2.size() + dict4.size(), GetTotalDictionarySize());
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest, ProcessEvictionDeletesAll) {
CreateStore();
const base::Time now = base::Time::Now();
auto token1 = base::UnguessableToken::Create();
SharedDictionaryInfo dict1 =
SharedDictionaryInfo(GURL("https://a.example/dict"),
/*last_fetch_time=*/now, /*response_time=*/now,
/*expiration*/ base::Seconds(100), "/pattern*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ now,
/*size=*/1000, SHA256HashValue({{0x00, 0x01}}),
/*disk_cache_key_token=*/token1,
/*primary_key_in_database=*/std::nullopt);
auto result1 = RegisterDictionary(isolation_key_, dict1);
dict1.set_primary_key_in_database(result1.primary_key_in_database());
auto token2 = base::UnguessableToken::Create();
SharedDictionaryInfo dict2 =
SharedDictionaryInfo(GURL("https://b.example/dict"),
/*last_fetch_time=*/now, /*response_time=*/now,
/*expiration*/ base::Seconds(100), "/pattern*",
/*match_dest_string=*/"", /*id=*/"",
/*last_used_time*/ now + base::Seconds(1),
/*size=*/3000, SHA256HashValue({{0x00, 0x02}}),
/*disk_cache_key_token=*/token2,
/*primary_key_in_database=*/std::nullopt);
auto result2 = RegisterDictionary(isolation_key_, dict2);
dict2.set_primary_key_in_database(result2.primary_key_in_database());
// The current status:
// dict1: size=1000 last_used_time=now
// dict2: size=3000 last_used_time=now+1
std::set<base::UnguessableToken> tokens = ProcessEviction(1000, 900, 10, 9);
// The dict1 and dict2 must be deleted.
EXPECT_THAT(tokens, UnorderedElementsAreArray({token1, token2}));
EXPECT_TRUE(GetAllDictionaries().empty());
// Check the total size of remaining dictionaries.
EXPECT_EQ(0u, GetTotalDictionarySize());
}
TEST_F(SQLitePersistentSharedDictionaryStoreTest, GetAllDiskCacheKeyTokens) {
CreateStore();
EXPECT_TRUE(GetAllDiskCacheKeyTokens().empty());
auto token1 = base::UnguessableToken::Create();
SharedDictionaryInfo dict1 = SharedDictionaryInfo(
GURL("https://a.example/dict"),
/*last_fetch_time=*/base::Time::Now() - base::Seconds(4),
/*response_time=*/base::Time::Now()