blob: 09263f53fa0c8a831f0d53b07ccfbec9ec078667 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/leveldb_proto/internal/shared_proto_database_client.h"
#include <set>
#include <string>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/debug/stack_trace.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/test/task_environment.h"
#include "components/leveldb_proto/internal/proto_leveldb_wrapper.h"
#include "components/leveldb_proto/internal/shared_proto_database.h"
#include "components/leveldb_proto/public/proto_database.h"
#include "components/leveldb_proto/public/shared_proto_database_client_list.h"
#include "components/leveldb_proto/testing/proto/test_db.pb.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace leveldb_proto {
class SharedProtoDatabaseClientTest : public testing::Test {
public:
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
db_ = base::WrapRefCounted(
new SharedProtoDatabase("client", temp_dir_.GetPath()));
}
void TearDown() override {
if (db_)
db_->Shutdown();
}
protected:
scoped_refptr<SharedProtoDatabase> db() { return db_; }
base::ScopedTempDir* temp_dir() { return &temp_dir_; }
LevelDB* GetLevelDB() const { return db_->GetLevelDBForTesting(); }
std::unique_ptr<SharedProtoDatabaseClient> GetClient(
ProtoDbType db_type,
bool create_if_missing,
Callbacks::InitStatusCallback callback,
SharedDBMetadataProto::MigrationStatus expected_migration_status =
SharedDBMetadataProto::MIGRATION_NOT_ATTEMPTED) {
return db_->GetClientForTesting(
db_type, create_if_missing,
base::BindOnce(
[](SharedDBMetadataProto::MigrationStatus expected_migration_status,
Callbacks::InitStatusCallback callback, Enums::InitStatus status,
SharedDBMetadataProto::MigrationStatus migration_status) {
EXPECT_EQ(expected_migration_status, migration_status);
std::move(callback).Run(status);
},
expected_migration_status, std::move(callback)));
}
std::unique_ptr<SharedProtoDatabaseClient> GetClientAndWait(
ProtoDbType db_type,
bool create_if_missing,
Enums::InitStatus* status,
SharedDBMetadataProto::MigrationStatus expected_migration_status =
SharedDBMetadataProto::MIGRATION_NOT_ATTEMPTED) {
base::RunLoop loop;
auto client =
GetClient(db_type, create_if_missing,
base::BindOnce(
[](Enums::InitStatus* status_out,
base::OnceClosure closure, Enums::InitStatus status) {
*status_out = status;
std::move(closure).Run();
},
status, loop.QuitClosure()),
expected_migration_status);
loop.Run();
return client;
}
bool ContainsKeys(const leveldb_proto::KeyVector& db_keys,
const leveldb_proto::KeyVector& keys,
ProtoDbType db_type) {
std::set<std::string> key_set(db_keys.begin(), db_keys.end());
for (auto& key : keys) {
auto full_key =
db_type == ProtoDbType::LAST
? key
: SharedProtoDatabaseClient::PrefixForDatabase(db_type) + key;
if (key_set.find(full_key) == key_set.end())
return false;
}
return true;
}
bool ContainsEntries(const leveldb_proto::KeyVector& db_keys,
const ValueVector& entries,
ProtoDbType db_type) {
std::set<std::string> entry_id_set;
for (auto& entry : entries)
entry_id_set.insert(entry);
// Entry IDs don't include the full prefix, so we don't look for that here.
auto prefix = SharedProtoDatabaseClient::PrefixForDatabase(db_type);
for (auto& key : db_keys) {
if (entry_id_set.find(prefix + key) == entry_id_set.end())
return false;
}
return true;
}
void UpdateEntries(SharedProtoDatabaseClient* client,
const leveldb_proto::KeyVector& keys,
const leveldb_proto::KeyVector& keys_to_remove,
bool expect_success) {
auto entries =
std::make_unique<ProtoDatabase<std::string>::KeyEntryVector>();
for (auto& key : keys) {
std::string value;
value = client->prefix_ + key;
entries->emplace_back(std::make_pair(key, std::move(value)));
}
auto entries_to_remove = std::make_unique<leveldb_proto::KeyVector>();
for (auto& key : keys_to_remove)
entries_to_remove->push_back(key);
base::RunLoop update_entries_loop;
client->UpdateEntries(
std::move(entries), std::move(entries_to_remove),
base::BindOnce(
[](base::OnceClosure signal, bool expect_success, bool success) {
ASSERT_EQ(success, expect_success);
std::move(signal).Run();
},
update_entries_loop.QuitClosure(), expect_success));
update_entries_loop.Run();
}
void UpdateEntriesWithRemoveFilter(SharedProtoDatabaseClient* client,
const leveldb_proto::KeyVector& keys,
const KeyFilter& delete_key_filter,
bool expect_success) {
auto entries =
std::make_unique<ProtoDatabase<std::string>::KeyEntryVector>();
for (auto& key : keys) {
std::string value;
value = client->prefix_ + key;
entries->emplace_back(std::make_pair(key, std::move(value)));
}
base::RunLoop update_entries_loop;
client->UpdateEntriesWithRemoveFilter(
std::move(entries), delete_key_filter,
base::BindOnce(
[](base::OnceClosure signal, bool expect_success, bool success) {
ASSERT_EQ(success, expect_success);
std::move(signal).Run();
},
update_entries_loop.QuitClosure(), expect_success));
update_entries_loop.Run();
}
// This fills in for all the LoadEntriesX functions because they all call this
// one.
void LoadEntriesWithFilter(SharedProtoDatabaseClient* client,
const KeyFilter& key_filter,
const leveldb::ReadOptions& options,
const std::string& target_prefix,
bool expect_success,
std::unique_ptr<ValueVector>* entries) {
base::RunLoop load_entries_loop;
client->LoadEntriesWithFilter(
key_filter, options, target_prefix,
base::BindOnce(
[](std::unique_ptr<ValueVector>* entries_ptr,
base::OnceClosure signal, bool expect_success, bool success,
std::unique_ptr<ValueVector> entries) {
ASSERT_EQ(success, expect_success);
entries_ptr->reset(entries.release());
std::move(signal).Run();
},
entries, load_entries_loop.QuitClosure(), expect_success));
load_entries_loop.Run();
}
void LoadKeysAndEntriesInRange(SharedProtoDatabaseClient* client,
const std::string& start,
const std::string& end,
bool expect_success,
std::unique_ptr<KeyValueMap>* entries) {
base::RunLoop load_entries_in_range_loop;
client->LoadKeysAndEntriesInRange(
start, end,
base::BindOnce(
[](std::unique_ptr<KeyValueMap>* entries_ptr,
base::OnceClosure signal, bool expect_success, bool success,
std::unique_ptr<KeyValueMap> entries) {
ASSERT_EQ(success, expect_success);
*entries_ptr = std::move(entries);
std::move(signal).Run();
},
entries, load_entries_in_range_loop.QuitClosure(), expect_success));
load_entries_in_range_loop.Run();
}
void LoadKeysAndEntriesWhile(SharedProtoDatabaseClient* client,
const std::string& start,
const KeyIteratorController controller,
bool expect_success,
std::unique_ptr<KeyValueMap>* entries) {
base::RunLoop load_entries_in_range_loop;
client->LoadKeysAndEntriesWhile(
start, controller,
base::BindOnce(
[](std::unique_ptr<KeyValueMap>* entries_ptr,
base::OnceClosure signal, bool expect_success, bool success,
std::unique_ptr<KeyValueMap> entries) {
ASSERT_EQ(success, expect_success);
*entries_ptr = std::move(entries);
std::move(signal).Run();
},
entries, load_entries_in_range_loop.QuitClosure(), expect_success));
load_entries_in_range_loop.Run();
}
void LoadKeys(SharedProtoDatabaseClient* client,
bool expect_success,
std::unique_ptr<KeyVector>* keys) {
base::RunLoop load_keys_loop;
client->LoadKeys(base::BindOnce(
[](std::unique_ptr<KeyVector>* keys_ptr, base::OnceClosure signal,
bool expect_success, bool success, std::unique_ptr<KeyVector> keys) {
ASSERT_EQ(success, expect_success);
keys_ptr->reset(keys.release());
std::move(signal).Run();
},
keys, load_keys_loop.QuitClosure(), expect_success));
load_keys_loop.Run();
}
std::unique_ptr<std::string> GetEntry(SharedProtoDatabaseClient* client,
const std::string& key,
bool expect_success) {
std::unique_ptr<std::string> entry;
base::RunLoop get_key_loop;
client->GetEntry(
key, base::BindOnce(
[](std::unique_ptr<std::string>* entry_ptr,
base::OnceClosure signal, bool expect_success, bool success,
std::unique_ptr<std::string> entry) {
ASSERT_EQ(success, expect_success);
entry_ptr->reset(entry.release());
std::move(signal).Run();
},
&entry, get_key_loop.QuitClosure(), expect_success));
get_key_loop.Run();
return entry;
}
void Destroy(SharedProtoDatabaseClient* client, bool expect_success) {
base::RunLoop destroy_loop;
client->Destroy(base::BindOnce(
[](bool expect_success, base::OnceClosure signal, bool success) {
ASSERT_EQ(success, expect_success);
std::move(signal).Run();
},
expect_success, destroy_loop.QuitClosure()));
destroy_loop.Run();
}
// Sets the obsolete client list to given list, runs clean up tasks and waits
// for them to complete.
void DestroyObsoleteClientsAndWait(const ProtoDbType* client_list) {
SharedProtoDatabaseClient::SetObsoleteClientListForTesting(client_list);
base::RunLoop wait_loop;
Callbacks::UpdateCallback wait_callback = base::BindOnce(
[](base::OnceClosure closure, bool success) {
EXPECT_TRUE(success);
std::move(closure).Run();
},
wait_loop.QuitClosure());
SharedProtoDatabaseClient::DestroyObsoleteSharedProtoDatabaseClients(
std::make_unique<ProtoLevelDBWrapper>(
db_->database_task_runner_for_testing(), GetLevelDB()),
std::move(wait_callback));
wait_loop.Run();
SharedProtoDatabaseClient::SetObsoleteClientListForTesting(nullptr);
}
void UpdateMetadataAsync(
SharedProtoDatabaseClient* client,
SharedDBMetadataProto::MigrationStatus migration_status) {
base::RunLoop wait_loop;
Callbacks::UpdateCallback wait_callback = base::BindOnce(
[](base::OnceClosure closure, bool success) {
EXPECT_TRUE(success);
std::move(closure).Run();
},
wait_loop.QuitClosure());
client->set_migration_status(migration_status);
SharedProtoDatabaseClient::UpdateClientMetadataAsync(
client->parent_db_, client->prefix_, migration_status,
std::move(wait_callback));
wait_loop.Run();
}
private:
base::ScopedTempDir temp_dir_;
base::test::TaskEnvironment task_environment_;
scoped_refptr<SharedProtoDatabase> db_;
};
TEST_F(SharedProtoDatabaseClientTest, InitSuccess) {
auto status = Enums::InitStatus::kError;
auto client = GetClientAndWait(ProtoDbType::TEST_DATABASE0,
true /* create_if_missing */, &status);
ASSERT_EQ(status, Enums::InitStatus::kOK);
client->Init("client", base::BindOnce([](Enums::InitStatus status) {
ASSERT_EQ(status, Enums::InitStatus::kOK);
}));
}
TEST_F(SharedProtoDatabaseClientTest, InitFail) {
auto status = Enums::InitStatus::kError;
auto client = GetClientAndWait(ProtoDbType::TEST_DATABASE0,
false /* create_if_missing */, &status);
ASSERT_EQ(status, Enums::InitStatus::kInvalidOperation);
client->Init("client", base::BindOnce([](Enums::InitStatus status) {
ASSERT_EQ(status, Enums::InitStatus::kError);
}));
}
// Ensure that our LevelDB contains the properly prefixed entries and also
// removes prefixed entries correctly.
TEST_F(SharedProtoDatabaseClientTest, UpdateEntriesAppropriatePrefix) {
auto status = Enums::InitStatus::kError;
auto client = GetClientAndWait(ProtoDbType::TEST_DATABASE0,
true /* create_if_missing */, &status);
ASSERT_EQ(status, Enums::InitStatus::kOK);
KeyVector key_list = {"entry1", "entry2", "entry3"};
UpdateEntries(client.get(), key_list, leveldb_proto::KeyVector(), true);
// Make sure those entries are in the LevelDB with the appropriate prefix.
KeyVector keys;
LevelDB* db = GetLevelDB();
db->LoadKeys(&keys);
ASSERT_EQ(keys.size(), 3U);
ASSERT_TRUE(ContainsKeys(keys, key_list, ProtoDbType::TEST_DATABASE0));
auto remove_keys = {key_list[0]};
// Now try to delete one entry.
UpdateEntries(client.get(), leveldb_proto::KeyVector(), remove_keys, true);
keys.clear();
db->LoadKeys(&keys);
ASSERT_EQ(keys.size(), 2U);
ASSERT_TRUE(ContainsKeys(keys, {key_list[1], key_list[2]},
ProtoDbType::TEST_DATABASE0));
}
TEST_F(SharedProtoDatabaseClientTest,
UpdateEntries_DeletesCorrectClientEntries) {
auto status = Enums::InitStatus::kError;
auto client_a = GetClientAndWait(ProtoDbType::TEST_DATABASE0,
true /* create_if_missing */, &status);
ASSERT_EQ(status, Enums::InitStatus::kOK);
auto client_b = GetClientAndWait(ProtoDbType::TEST_DATABASE2,
true /* create_if_missing */, &status);
ASSERT_EQ(status, Enums::InitStatus::kOK);
KeyVector key_list = {"entry1", "entry2", "entry3"};
UpdateEntries(client_a.get(), key_list, leveldb_proto::KeyVector(), true);
UpdateEntries(client_b.get(), key_list, leveldb_proto::KeyVector(), true);
KeyVector keys;
LevelDB* db = GetLevelDB();
db->LoadKeys(&keys);
ASSERT_EQ(keys.size(), 6U);
ASSERT_TRUE(ContainsKeys(keys, key_list, ProtoDbType::TEST_DATABASE0));
ASSERT_TRUE(ContainsKeys(keys, key_list, ProtoDbType::TEST_DATABASE2));
// Now delete from client_b and ensure only client A's values exist.
UpdateEntries(client_b.get(), leveldb_proto::KeyVector(), key_list, true);
keys.clear();
db->LoadKeys(&keys);
ASSERT_EQ(keys.size(), 3U);
ASSERT_TRUE(ContainsKeys(keys, key_list, ProtoDbType::TEST_DATABASE0));
}
TEST_F(SharedProtoDatabaseClientTest,
UpdateEntriesWithRemoveFilter_DeletesCorrectEntries) {
auto status = Enums::InitStatus::kError;
auto client = GetClientAndWait(ProtoDbType::TEST_DATABASE0,
true /* create_if_missing */, &status);
ASSERT_EQ(status, Enums::InitStatus::kOK);
KeyVector key_list = {"entry1", "entry2", "testentry3"};
UpdateEntries(client.get(), key_list, leveldb_proto::KeyVector(), true);
KeyVector keys;
LevelDB* db = GetLevelDB();
db->LoadKeys(&keys);
ASSERT_EQ(keys.size(), 3U);
UpdateEntriesWithRemoveFilter(client.get(), leveldb_proto::KeyVector(),
base::BindRepeating([](const std::string& key) {
return base::StartsWith(
key, "test",
base::CompareCase::INSENSITIVE_ASCII);
}),
true);
keys.clear();
db->LoadKeys(&keys);
ASSERT_EQ(keys.size(), 2U);
// Make sure "testentry3" was removed.
ASSERT_TRUE(
ContainsKeys(keys, {"entry1", "entry2"}, ProtoDbType::TEST_DATABASE0));
}
TEST_F(SharedProtoDatabaseClientTest, LoadEntriesWithFilter) {
auto status = Enums::InitStatus::kError;
auto client_a = GetClientAndWait(ProtoDbType::TEST_DATABASE0,
true /* create_if_missing */, &status);
ASSERT_EQ(status, Enums::InitStatus::kOK);
auto client_b = GetClientAndWait(ProtoDbType::TEST_DATABASE2,
true /* create_if_missing */, &status);
ASSERT_EQ(status, Enums::InitStatus::kOK);
KeyVector key_list_a = {"entry123", "entry2123", "testentry3"};
UpdateEntries(client_a.get(), key_list_a, leveldb_proto::KeyVector(), true);
KeyVector key_list_b = {"testentry124", "entry2124", "testentry4"};
UpdateEntries(client_b.get(), key_list_b, leveldb_proto::KeyVector(), true);
std::unique_ptr<ValueVector> entries;
LoadEntriesWithFilter(client_a.get(), leveldb_proto::KeyFilter(),
leveldb::ReadOptions(), std::string(), true, &entries);
ASSERT_EQ(entries->size(), 3U);
ASSERT_TRUE(
ContainsEntries(key_list_a, *entries, ProtoDbType::TEST_DATABASE0));
LoadEntriesWithFilter(client_b.get(), leveldb_proto::KeyFilter(),
leveldb::ReadOptions(), std::string(), true, &entries);
ASSERT_EQ(entries->size(), 3U);
ASSERT_TRUE(
ContainsEntries(key_list_b, *entries, ProtoDbType::TEST_DATABASE2));
// Now test the actual filtering functionality.
LoadEntriesWithFilter(
client_b.get(), base::BindRepeating([](const std::string& key) {
// Strips the entries that start with "test" from |key_list_b|.
return !base::StartsWith(key, "test",
base::CompareCase::INSENSITIVE_ASCII);
}),
leveldb::ReadOptions(), std::string(), true, &entries);
ASSERT_EQ(entries->size(), 1U);
ASSERT_TRUE(
ContainsEntries({"entry2124"}, *entries, ProtoDbType::TEST_DATABASE2));
}
TEST_F(SharedProtoDatabaseClientTest, LoadKeysAndEntriesInRange) {
auto status = Enums::InitStatus::kError;
auto client_a = GetClientAndWait(ProtoDbType::TEST_DATABASE0,
true /* create_if_missing */, &status);
ASSERT_EQ(status, Enums::InitStatus::kOK);
auto client_b = GetClientAndWait(ProtoDbType::TEST_DATABASE2,
true /* create_if_missing */, &status);
ASSERT_EQ(status, Enums::InitStatus::kOK);
KeyVector key_list_a = {"entry0", "entry1", "entry2", "entry3",
"entry3notinrange", "entry4", "entry5"};
UpdateEntries(client_a.get(), key_list_a, leveldb_proto::KeyVector(), true);
KeyVector key_list_b = {"entry2", "entry3", "entry4"};
UpdateEntries(client_b.get(), key_list_b, leveldb_proto::KeyVector(), true);
std::unique_ptr<KeyValueMap> keys_and_entries_a;
LoadKeysAndEntriesInRange(client_a.get(), "entry1", "entry3", true,
&keys_and_entries_a);
std::unique_ptr<KeyValueMap> keys_and_entries_b;
LoadKeysAndEntriesInRange(client_b.get(), "entry0", "entry1", true,
&keys_and_entries_b);
ValueVector entries;
for (auto& pair : *keys_and_entries_a) {
entries.push_back(pair.second);
}
ASSERT_EQ(keys_and_entries_a->size(), 3U);
ASSERT_EQ(keys_and_entries_b->size(), 0U);
ASSERT_TRUE(ContainsEntries({"entry1", "entry2", "entry3"}, entries,
ProtoDbType::TEST_DATABASE0));
}
TEST_F(SharedProtoDatabaseClientTest, LoadKeysAndEntriesWhile) {
auto status = Enums::InitStatus::kError;
auto client_a = GetClientAndWait(ProtoDbType::TEST_DATABASE0,
true /* create_if_missing */, &status);
ASSERT_EQ(status, Enums::InitStatus::kOK);
auto client_b = GetClientAndWait(ProtoDbType::TEST_DATABASE2,
true /* create_if_missing */, &status);
ASSERT_EQ(status, Enums::InitStatus::kOK);
KeyVector key_list_a = {"entry0", "entry1", "entry2", "entry3",
"entry3notinrange", "entry4", "entry5"};
UpdateEntries(client_a.get(), key_list_a, leveldb_proto::KeyVector(), true);
KeyVector key_list_b = {"entry2", "entry3", "entry4"};
UpdateEntries(client_b.get(), key_list_b, leveldb_proto::KeyVector(), true);
{
KeyIteratorController controller_a =
base::BindRepeating([](const std::string& key) {
if (key >= std::string("entry3"))
return Enums::kLoadAndStop;
return Enums::kLoadAndContinue;
});
std::unique_ptr<KeyValueMap> keys_and_entries_a;
LoadKeysAndEntriesWhile(client_a.get(), "entry1", controller_a, true,
&keys_and_entries_a);
ValueVector entries;
for (auto& pair : *keys_and_entries_a) {
entries.push_back(pair.second);
}
ASSERT_EQ(keys_and_entries_a->size(), 3U);
ASSERT_TRUE(ContainsEntries({"entry1", "entry2", "entry3"}, entries,
ProtoDbType::TEST_DATABASE0));
}
{
KeyIteratorController controller_b =
base::BindRepeating([](const std::string& key) {
if (key > std::string("entry1"))
return Enums::kSkipAndStop;
return Enums::kLoadAndContinue;
});
std::unique_ptr<KeyValueMap> keys_and_entries_b;
LoadKeysAndEntriesWhile(client_b.get(), "entry0", controller_b, true,
&keys_and_entries_b);
ASSERT_EQ(keys_and_entries_b->size(), 0U);
}
{
KeyIteratorController controller_c = base::BindRepeating(
[](const std::string& key) { return Enums::kLoadAndContinue; });
std::unique_ptr<KeyValueMap> keys_and_entries_c;
LoadKeysAndEntriesWhile(client_a.get(), "", controller_c, true,
&keys_and_entries_c);
ValueVector entries;
for (auto& pair : *keys_and_entries_c) {
entries.push_back(pair.second);
}
ASSERT_EQ(keys_and_entries_c->size(), key_list_a.size());
ASSERT_TRUE(
ContainsEntries(key_list_a, entries, ProtoDbType::TEST_DATABASE0));
}
}
TEST_F(SharedProtoDatabaseClientTest, LoadKeys) {
auto status = Enums::InitStatus::kError;
auto client_a = GetClientAndWait(ProtoDbType::TEST_DATABASE0,
true /* create_if_missing */, &status);
ASSERT_EQ(status, Enums::InitStatus::kOK);
auto client_b = GetClientAndWait(ProtoDbType::TEST_DATABASE2,
true /* create_if_missing */, &status);
ASSERT_EQ(status, Enums::InitStatus::kOK);
KeyVector key_list_a = {"entry123", "entry2123", "testentry3", "testing"};
UpdateEntries(client_a.get(), key_list_a, leveldb_proto::KeyVector(), true);
KeyVector key_list_b = {"testentry124", "entry2124", "testentry4"};
UpdateEntries(client_b.get(), key_list_b, leveldb_proto::KeyVector(), true);
std::unique_ptr<KeyVector> keys;
LoadKeys(client_a.get(), true, &keys);
ASSERT_EQ(keys->size(), 4U);
ASSERT_TRUE(ContainsKeys(*keys, key_list_a, ProtoDbType::LAST));
LoadKeys(client_b.get(), true, &keys);
ASSERT_EQ(keys->size(), 3U);
ASSERT_TRUE(ContainsKeys(*keys, key_list_b, ProtoDbType::LAST));
}
TEST_F(SharedProtoDatabaseClientTest, GetEntry) {
auto status = Enums::InitStatus::kError;
auto client_a = GetClientAndWait(ProtoDbType::TEST_DATABASE0,
true /* create_if_missing */, &status);
ASSERT_EQ(status, Enums::InitStatus::kOK);
auto client_b = GetClientAndWait(ProtoDbType::TEST_DATABASE2,
true /* create_if_missing */, &status);
ASSERT_EQ(status, Enums::InitStatus::kOK);
KeyVector key_list = {"a", "b", "c"};
// Add the same entries to both because we want to make sure we only get the
// entries from the proper client.
UpdateEntries(client_a.get(), key_list, leveldb_proto::KeyVector(), true);
UpdateEntries(client_b.get(), key_list, leveldb_proto::KeyVector(), true);
auto a_prefix =
SharedProtoDatabaseClient::PrefixForDatabase(ProtoDbType::TEST_DATABASE0);
auto b_prefix =
SharedProtoDatabaseClient::PrefixForDatabase(ProtoDbType::TEST_DATABASE2);
for (auto& key : key_list) {
auto entry = GetEntry(client_a.get(), key, true);
ASSERT_EQ(*entry, a_prefix + key);
entry = GetEntry(client_b.get(), key, true);
ASSERT_EQ(*entry, b_prefix + key);
}
}
TEST_F(SharedProtoDatabaseClientTest, TestCleanupObsoleteClients) {
auto status = Enums::InitStatus::kError;
auto client_a = GetClientAndWait(ProtoDbType::TEST_DATABASE0,
true /* create_if_missing */, &status);
ASSERT_EQ(status, Enums::InitStatus::kOK);
auto client_b = GetClientAndWait(ProtoDbType::TEST_DATABASE1,
true /* create_if_missing */, &status);
ASSERT_EQ(status, Enums::InitStatus::kOK);
auto client_c = GetClientAndWait(ProtoDbType::TEST_DATABASE2,
true /* create_if_missing */, &status);
ASSERT_EQ(status, Enums::InitStatus::kOK);
KeyVector test_keys = {"a", "b", "c"};
UpdateEntries(client_a.get(), test_keys, leveldb_proto::KeyVector(), true);
UpdateEntries(client_b.get(), test_keys, leveldb_proto::KeyVector(), true);
UpdateEntries(client_c.get(), test_keys, leveldb_proto::KeyVector(), true);
// Check that the original list does not clear any data from test DBs.
DestroyObsoleteClientsAndWait(nullptr /* client_list */);
KeyVector keys;
LevelDB* db = GetLevelDB();
db->LoadKeys(&keys);
EXPECT_EQ(keys.size(), test_keys.size() * 3);
// Mark some DBs obsolete.
const ProtoDbType kObsoleteList1[] = {ProtoDbType::TEST_DATABASE0,
ProtoDbType::TEST_DATABASE1,
ProtoDbType::LAST};
DestroyObsoleteClientsAndWait(kObsoleteList1);
keys.clear();
db->LoadKeys(&keys);
EXPECT_EQ(keys.size(), test_keys.size());
EXPECT_FALSE(ContainsKeys(keys, test_keys, ProtoDbType::TEST_DATABASE0));
EXPECT_FALSE(ContainsKeys(keys, test_keys, ProtoDbType::TEST_DATABASE1));
EXPECT_TRUE(ContainsKeys(keys, test_keys, ProtoDbType::TEST_DATABASE2));
// Make all the DBs obsolete.
const ProtoDbType kObsoleteList2[] = {
ProtoDbType::TEST_DATABASE0, ProtoDbType::TEST_DATABASE1,
ProtoDbType::TEST_DATABASE2, ProtoDbType::LAST};
DestroyObsoleteClientsAndWait(kObsoleteList2);
// Nothing should remain.
keys.clear();
db->LoadKeys(&keys);
EXPECT_EQ(keys.size(), 0U);
}
TEST_F(SharedProtoDatabaseClientTest, TestDestroy) {
auto status = Enums::InitStatus::kError;
auto client_a = GetClientAndWait(ProtoDbType::TEST_DATABASE0,
true /* create_if_missing */, &status);
ASSERT_EQ(status, Enums::InitStatus::kOK);
auto client_b = GetClientAndWait(ProtoDbType::TEST_DATABASE2,
true /* create_if_missing */, &status);
ASSERT_EQ(status, Enums::InitStatus::kOK);
KeyVector key_list = {"a", "b", "c"};
// Add the same entries to both because we want to make sure we only destroy
// the entries from the proper client.
UpdateEntries(client_a.get(), key_list, leveldb_proto::KeyVector(), true);
UpdateEntries(client_b.get(), key_list, leveldb_proto::KeyVector(), true);
// Delete only client A.
KeyVector keys;
LevelDB* db = GetLevelDB();
db->LoadKeys(&keys);
ASSERT_EQ(keys.size(), key_list.size() * 2);
ASSERT_TRUE(ContainsKeys(keys, key_list, ProtoDbType::TEST_DATABASE0));
ASSERT_TRUE(ContainsKeys(keys, key_list, ProtoDbType::TEST_DATABASE2));
Destroy(client_a.get(), true);
// Make sure only client B remains and delete client B.
keys.clear();
db->LoadKeys(&keys);
ASSERT_EQ(keys.size(), key_list.size());
ASSERT_TRUE(ContainsKeys(keys, key_list, ProtoDbType::TEST_DATABASE2));
Destroy(client_b.get(), true);
// Nothing should remain.
keys.clear();
db->LoadKeys(&keys);
ASSERT_EQ(keys.size(), 0U);
}
TEST_F(SharedProtoDatabaseClientTest, UpdateClientMetadataAsync) {
auto status = Enums::InitStatus::kError;
auto client_a = GetClientAndWait(ProtoDbType::TEST_DATABASE0,
true /* create_if_missing */, &status);
EXPECT_EQ(status, Enums::InitStatus::kOK);
EXPECT_EQ(SharedDBMetadataProto::MIGRATION_NOT_ATTEMPTED,
client_a->migration_status());
auto client_b = GetClientAndWait(ProtoDbType::TEST_DATABASE1,
true /* create_if_missing */, &status);
EXPECT_EQ(status, Enums::InitStatus::kOK);
EXPECT_EQ(SharedDBMetadataProto::MIGRATION_NOT_ATTEMPTED,
client_b->migration_status());
auto client_c = GetClientAndWait(ProtoDbType::TEST_DATABASE2,
true /* create_if_missing */, &status);
EXPECT_EQ(status, Enums::InitStatus::kOK);
EXPECT_EQ(SharedDBMetadataProto::MIGRATION_NOT_ATTEMPTED,
client_c->migration_status());
UpdateMetadataAsync(client_a.get(),
SharedDBMetadataProto::MIGRATE_TO_SHARED_SUCCESSFUL);
UpdateMetadataAsync(
client_b.get(),
SharedDBMetadataProto::MIGRATE_TO_UNIQUE_SHARED_TO_BE_DELETED);
client_a.reset();
client_b.reset();
client_c.reset();
auto client_d = GetClientAndWait(
ProtoDbType::TEST_DATABASE0, true /* create_if_missing */, &status,
SharedDBMetadataProto::MIGRATE_TO_SHARED_SUCCESSFUL);
EXPECT_EQ(status, Enums::InitStatus::kOK);
auto client_e = GetClientAndWait(
ProtoDbType::TEST_DATABASE1, true /* create_if_missing */, &status,
SharedDBMetadataProto::MIGRATE_TO_UNIQUE_SHARED_TO_BE_DELETED);
EXPECT_EQ(status, Enums::InitStatus::kOK);
auto client_f = GetClientAndWait(ProtoDbType::TEST_DATABASE2,
true /* create_if_missing */, &status);
EXPECT_EQ(status, Enums::InitStatus::kOK);
UpdateMetadataAsync(client_d.get(),
SharedDBMetadataProto::MIGRATION_NOT_ATTEMPTED);
UpdateMetadataAsync(client_e.get(),
SharedDBMetadataProto::MIGRATION_NOT_ATTEMPTED);
}
} // namespace leveldb_proto