blob: ac7b59e5dea98747c701ca39d98a17fe26f76ccc [file] [log] [blame]
// Copyright 2016 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 "content/browser/leveldb_wrapper_impl.h"
#include "base/atomic_ref_count.h"
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/task_runner_util.h"
#include "base/task_scheduler/post_task.h"
#include "base/test/test_simple_task_runner.h"
#include "base/threading/thread.h"
#include "components/leveldb/public/cpp/util.h"
#include "components/leveldb/public/interfaces/leveldb.mojom.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/test/fake_leveldb_database.h"
#include "mojo/public/cpp/bindings/associated_binding.h"
#include "mojo/public/cpp/bindings/strong_associated_binding.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
using CacheMode = LevelDBWrapperImpl::CacheMode;
using DatabaseError = leveldb::mojom::DatabaseError;
const char* kTestSource = "source";
const size_t kTestSizeLimit = 512;
std::string ToString(const std::vector<uint8_t>& input) {
return leveldb::Uint8VectorToStdString(input);
}
std::vector<uint8_t> ToBytes(const std::string& input) {
return leveldb::StdStringToUint8Vector(input);
}
class InternalIncrementalBarrier {
public:
InternalIncrementalBarrier(base::OnceClosure done_closure)
: num_callbacks_left_(1), done_closure_(std::move(done_closure)) {}
void Dec() {
// This is the same as in BarrierClosure.
DCHECK(!num_callbacks_left_.IsZero());
if (!num_callbacks_left_.Decrement()) {
base::OnceClosure done = std::move(done_closure_);
delete this;
std::move(done).Run();
}
}
base::OnceClosure Inc() {
num_callbacks_left_.Increment();
return base::BindOnce(&InternalIncrementalBarrier::Dec,
base::Unretained(this));
}
private:
base::AtomicRefCount num_callbacks_left_;
base::OnceClosure done_closure_;
DISALLOW_COPY_AND_ASSIGN(InternalIncrementalBarrier);
};
// The callbacks returned by Get might get called after destruction of this
// class (and thus the done_closure), so there needs to be an internal class
// to hold the final callback & manage the refcount.
class IncrementalBarrier {
public:
explicit IncrementalBarrier(base::OnceClosure done_closure)
: internal_barrier_(
new InternalIncrementalBarrier(std::move(done_closure))) {}
~IncrementalBarrier() { internal_barrier_->Dec(); }
base::OnceClosure Get() { return internal_barrier_->Inc(); }
private:
InternalIncrementalBarrier* internal_barrier_; // self-deleting
DISALLOW_COPY_AND_ASSIGN(IncrementalBarrier);
};
class GetAllCallback : public mojom::LevelDBWrapperGetAllCallback {
public:
static mojom::LevelDBWrapperGetAllCallbackAssociatedPtrInfo CreateAndBind(
bool* result,
base::OnceClosure callback) {
mojom::LevelDBWrapperGetAllCallbackAssociatedPtr ptr;
auto request = mojo::MakeRequestAssociatedWithDedicatedPipe(&ptr);
mojo::MakeStrongAssociatedBinding(
base::WrapUnique(new GetAllCallback(result, std::move(callback))),
std::move(request));
return ptr.PassInterface();
}
private:
GetAllCallback(bool* result, base::OnceClosure callback)
: result_(result), callback_(std::move(callback)) {}
void Complete(bool success) override {
*result_ = success;
if (callback_)
std::move(callback_).Run();
}
bool* result_;
base::OnceClosure callback_;
};
class MockDelegate : public LevelDBWrapperImpl::Delegate {
public:
MockDelegate() {}
~MockDelegate() override {}
void OnNoBindings() override {}
std::vector<leveldb::mojom::BatchedOperationPtr> PrepareToCommit() override {
return std::vector<leveldb::mojom::BatchedOperationPtr>();
}
void DidCommit(DatabaseError error) override {
if (error != DatabaseError::OK)
LOG(ERROR) << "error committing!";
if (committed_)
std::move(committed_).Run();
}
void OnMapLoaded(DatabaseError error) override { map_load_count_++; }
std::vector<LevelDBWrapperImpl::Change> FixUpData(
const LevelDBWrapperImpl::ValueMap& data) override {
return std::move(mock_changes_);
}
int map_load_count() const { return map_load_count_; }
void set_mock_changes(std::vector<LevelDBWrapperImpl::Change> changes) {
mock_changes_ = std::move(changes);
}
void SetDidCommitCallback(base::OnceClosure committed) {
committed_ = std::move(committed);
}
private:
int map_load_count_ = 0;
std::vector<LevelDBWrapperImpl::Change> mock_changes_;
base::OnceClosure committed_;
};
void GetCallback(base::OnceClosure callback,
bool* success_out,
std::vector<uint8_t>* value_out,
bool success,
const std::vector<uint8_t>& value) {
*success_out = success;
*value_out = value;
std::move(callback).Run();
}
base::OnceCallback<void(bool, const std::vector<uint8_t>&)> MakeGetCallback(
base::OnceClosure callback,
bool* success_out,
std::vector<uint8_t>* value_out) {
return base::BindOnce(&GetCallback, std::move(callback), success_out,
value_out);
}
void GetAllDataCallback(leveldb::mojom::DatabaseError* status_out,
std::vector<mojom::KeyValuePtr>* data_out,
leveldb::mojom::DatabaseError status,
std::vector<mojom::KeyValuePtr> data) {
*status_out = status;
*data_out = std::move(data);
}
base::OnceCallback<void(leveldb::mojom::DatabaseError status,
std::vector<mojom::KeyValuePtr> data)>
MakeGetAllCallback(leveldb::mojom::DatabaseError* status_out,
std::vector<mojom::KeyValuePtr>* data_out) {
return base::BindOnce(&GetAllDataCallback, status_out, data_out);
}
void SuccessCallback(base::OnceClosure callback,
bool* success_out,
bool success) {
*success_out = success;
std::move(callback).Run();
}
base::OnceCallback<void(bool)> MakeSuccessCallback(base::OnceClosure callback,
bool* success_out) {
return base::BindOnce(&SuccessCallback, std::move(callback), success_out);
}
void NoOpSuccessCallback(bool success) {}
LevelDBWrapperImpl::Options GetDefaultTestingOptions(CacheMode cache_mode) {
LevelDBWrapperImpl::Options options;
options.max_size = kTestSizeLimit;
options.default_commit_delay = base::TimeDelta::FromSeconds(5);
options.max_bytes_per_hour = 10 * 1024 * 1024;
options.max_commits_per_hour = 60;
options.cache_mode = cache_mode;
return options;
}
} // namespace
class LevelDBWrapperImplTest : public testing::Test,
public mojom::LevelDBObserver {
public:
struct Observation {
enum { kAdd, kChange, kDelete, kDeleteAll, kSendOldValue } type;
std::string key;
std::string old_value;
std::string new_value;
std::string source;
bool should_send_old_value;
};
LevelDBWrapperImplTest() : db_(&mock_data_), observer_binding_(this) {
auto request = mojo::MakeRequest(&level_db_database_ptr_);
db_.Bind(std::move(request));
LevelDBWrapperImpl::Options options =
GetDefaultTestingOptions(CacheMode::KEYS_ONLY_WHEN_POSSIBLE);
level_db_wrapper_ = std::make_unique<LevelDBWrapperImpl>(
level_db_database_ptr_.get(), test_prefix_, &delegate_, options);
set_mock_data(test_prefix_ + test_key1_, test_value1_);
set_mock_data(test_prefix_ + test_key2_, test_value2_);
set_mock_data("123", "baddata");
level_db_wrapper_->Bind(mojo::MakeRequest(&level_db_wrapper_ptr_));
mojom::LevelDBObserverAssociatedPtrInfo ptr_info;
observer_binding_.Bind(mojo::MakeRequest(&ptr_info));
level_db_wrapper_ptr_->AddObserver(std::move(ptr_info));
}
~LevelDBWrapperImplTest() override {}
void set_mock_data(const std::string& key, const std::string& value) {
mock_data_[ToBytes(key)] = ToBytes(value);
}
void set_mock_data(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& value) {
mock_data_[key] = value;
}
bool has_mock_data(const std::string& key) {
return mock_data_.find(ToBytes(key)) != mock_data_.end();
}
std::string get_mock_data(const std::string& key) {
return has_mock_data(key) ? ToString(mock_data_[ToBytes(key)]) : "";
}
void clear_mock_data() { mock_data_.clear(); }
mojom::LevelDBWrapper* wrapper() { return level_db_wrapper_ptr_.get(); }
LevelDBWrapperImpl* wrapper_impl() { return level_db_wrapper_.get(); }
bool GetSync(mojom::LevelDBWrapper* wrapper,
const std::vector<uint8_t>& key,
std::vector<uint8_t>* result) {
bool success = false;
base::RunLoop loop;
wrapper->Get(key, MakeGetCallback(loop.QuitClosure(), &success, result));
loop.Run();
return success;
}
bool PutSync(mojom::LevelDBWrapper* wrapper,
const std::vector<uint8_t>& key,
const std::vector<uint8_t>& value,
const base::Optional<std::vector<uint8_t>>& client_old_value,
std::string source = kTestSource) {
bool success = false;
base::RunLoop loop;
wrapper->Put(key, value, client_old_value, source,
MakeSuccessCallback(loop.QuitClosure(), &success));
loop.Run();
return success;
}
bool DeleteSync(
mojom::LevelDBWrapper* wrapper,
const std::vector<uint8_t>& key,
const base::Optional<std::vector<uint8_t>>& client_old_value) {
bool success = false;
base::RunLoop loop;
wrapper->Delete(key, client_old_value, test_source_,
MakeSuccessCallback(loop.QuitClosure(), &success));
loop.Run();
return success;
}
bool DeleteAllSync(mojom::LevelDBWrapper* wrapper) {
bool success = false;
base::RunLoop loop;
wrapper->DeleteAll(test_source_,
MakeSuccessCallback(loop.QuitClosure(), &success));
loop.Run();
return success;
}
bool GetSync(const std::vector<uint8_t>& key, std::vector<uint8_t>* result) {
return GetSync(wrapper(), key, result);
}
bool PutSync(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& value,
const base::Optional<std::vector<uint8_t>>& client_old_value,
std::string source = kTestSource) {
return PutSync(wrapper(), key, value, client_old_value, source);
}
bool DeleteSync(
const std::vector<uint8_t>& key,
const base::Optional<std::vector<uint8_t>>& client_old_value) {
return DeleteSync(wrapper(), key, client_old_value);
}
bool DeleteAllSync() { return DeleteAllSync(wrapper()); }
std::string GetSyncStrUsingGetAll(LevelDBWrapperImpl* wrapper_impl,
const std::string& key) {
leveldb::mojom::DatabaseError status;
std::vector<mojom::KeyValuePtr> data;
bool done = false;
base::RunLoop loop;
// Testing the 'Sync' mojo version is a big headache involving 3 threads, so
// just test the async version.
wrapper_impl->GetAll(
GetAllCallback::CreateAndBind(&done, loop.QuitClosure()),
MakeGetAllCallback(&status, &data));
loop.Run();
if (!done)
return "";
if (status != leveldb::mojom::DatabaseError::OK)
return "";
for (const auto& key_value : data) {
if (key_value->key == ToBytes(key)) {
return ToString(key_value->value);
}
}
return "";
}
void BlockingCommit() { BlockingCommit(&delegate_, level_db_wrapper_.get()); }
void BlockingCommit(MockDelegate* delegate, LevelDBWrapperImpl* wrapper) {
while (wrapper->has_pending_load_tasks() ||
wrapper->has_changes_to_commit()) {
base::RunLoop loop;
delegate->SetDidCommitCallback(loop.QuitClosure());
wrapper->ScheduleImmediateCommit();
loop.Run();
}
}
const std::vector<Observation>& observations() { return observations_; }
MockDelegate* delegate() { return &delegate_; }
void should_record_send_old_value_observations(bool value) {
should_record_send_old_value_observations_ = value;
}
protected:
const std::string test_prefix_ = "abc";
const std::string test_key1_ = "def";
const std::string test_key2_ = "123";
const std::string test_value1_ = "defdata";
const std::string test_value2_ = "123data";
const std::string test_copy_prefix1_ = "www";
const std::string test_copy_prefix2_ = "xxx";
const std::string test_copy_prefix3_ = "yyy";
const std::string test_source_ = kTestSource;
const std::vector<uint8_t> test_prefix_bytes_ = ToBytes(test_prefix_);
const std::vector<uint8_t> test_key1_bytes_ = ToBytes(test_key1_);
const std::vector<uint8_t> test_key2_bytes_ = ToBytes(test_key2_);
const std::vector<uint8_t> test_value1_bytes_ = ToBytes(test_value1_);
const std::vector<uint8_t> test_value2_bytes_ = ToBytes(test_value2_);
private:
// LevelDBObserver:
void KeyAdded(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& value,
const std::string& source) override {
observations_.push_back(
{Observation::kAdd, ToString(key), "", ToString(value), source, false});
}
void KeyChanged(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& new_value,
const std::vector<uint8_t>& old_value,
const std::string& source) override {
observations_.push_back({Observation::kChange, ToString(key),
ToString(old_value), ToString(new_value), source,
false});
}
void KeyDeleted(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& old_value,
const std::string& source) override {
observations_.push_back({Observation::kDelete, ToString(key),
ToString(old_value), "", source, false});
}
void AllDeleted(const std::string& source) override {
observations_.push_back(
{Observation::kDeleteAll, "", "", "", source, false});
}
void ShouldSendOldValueOnMutations(bool value) override {
if (should_record_send_old_value_observations_)
observations_.push_back(
{Observation::kSendOldValue, "", "", "", "", value});
}
TestBrowserThreadBundle thread_bundle_;
std::map<std::vector<uint8_t>, std::vector<uint8_t>> mock_data_;
FakeLevelDBDatabase db_;
leveldb::mojom::LevelDBDatabasePtr level_db_database_ptr_;
MockDelegate delegate_;
std::unique_ptr<LevelDBWrapperImpl> level_db_wrapper_;
mojom::LevelDBWrapperPtr level_db_wrapper_ptr_;
mojo::AssociatedBinding<mojom::LevelDBObserver> observer_binding_;
std::vector<Observation> observations_;
bool should_record_send_old_value_observations_ = false;
};
class LevelDBWrapperImplParamTest
: public LevelDBWrapperImplTest,
public testing::WithParamInterface<CacheMode> {
public:
LevelDBWrapperImplParamTest() {}
~LevelDBWrapperImplParamTest() override {}
};
INSTANTIATE_TEST_CASE_P(LevelDBWrapperImplTest,
LevelDBWrapperImplParamTest,
testing::Values(CacheMode::KEYS_ONLY_WHEN_POSSIBLE,
CacheMode::KEYS_AND_VALUES));
TEST_F(LevelDBWrapperImplTest, GetLoadedFromMap) {
wrapper_impl()->SetCacheModeForTesting(CacheMode::KEYS_AND_VALUES);
std::vector<uint8_t> result;
EXPECT_TRUE(GetSync(test_key2_bytes_, &result));
EXPECT_EQ(test_value2_bytes_, result);
EXPECT_FALSE(GetSync(ToBytes("x"), &result));
}
TEST_F(LevelDBWrapperImplTest, GetFromPutOverwrite) {
wrapper_impl()->SetCacheModeForTesting(CacheMode::KEYS_AND_VALUES);
std::vector<uint8_t> key = test_key2_bytes_;
std::vector<uint8_t> value = ToBytes("foo");
base::RunLoop loop;
bool put_success = false;
std::vector<uint8_t> result;
bool get_success = false;
{
IncrementalBarrier barrier(loop.QuitClosure());
wrapper()->Put(key, value, test_value2_bytes_, test_source_,
MakeSuccessCallback(barrier.Get(), &put_success));
wrapper()->Get(key, MakeGetCallback(barrier.Get(), &get_success, &result));
}
loop.Run();
EXPECT_TRUE(put_success);
EXPECT_TRUE(get_success);
EXPECT_EQ(value, result);
}
TEST_F(LevelDBWrapperImplTest, GetFromPutNewKey) {
wrapper_impl()->SetCacheModeForTesting(CacheMode::KEYS_AND_VALUES);
std::vector<uint8_t> key = ToBytes("newkey");
std::vector<uint8_t> value = ToBytes("foo");
EXPECT_TRUE(PutSync(key, value, base::nullopt));
std::vector<uint8_t> result;
EXPECT_TRUE(GetSync(key, &result));
EXPECT_EQ(value, result);
}
TEST_F(LevelDBWrapperImplTest, PutLoadsValuesAfterCacheModeUpgrade) {
std::vector<uint8_t> key = ToBytes("newkey");
std::vector<uint8_t> value1 = ToBytes("foo");
std::vector<uint8_t> value2 = ToBytes("bar");
ASSERT_EQ(CacheMode::KEYS_ONLY_WHEN_POSSIBLE, wrapper_impl()->cache_mode());
// Do a put to load the key-only cache.
EXPECT_TRUE(PutSync(key, value1, base::nullopt));
EXPECT_EQ(LevelDBWrapperImpl::MapState::LOADED_KEYS_ONLY,
wrapper_impl()->map_state_);
// Change cache mode.
wrapper_impl()->SetCacheModeForTesting(CacheMode::KEYS_AND_VALUES);
// Loading new map isn't necessary yet.
EXPECT_EQ(LevelDBWrapperImpl::MapState::LOADED_KEYS_ONLY,
wrapper_impl()->map_state_);
// Do another put and check that the map has been upgraded
EXPECT_TRUE(PutSync(key, value2, value1));
EXPECT_EQ(LevelDBWrapperImpl::MapState::LOADED_KEYS_AND_VALUES,
wrapper_impl()->map_state_);
}
TEST_P(LevelDBWrapperImplParamTest, GetAll) {
wrapper_impl()->SetCacheModeForTesting(GetParam());
DatabaseError status;
std::vector<mojom::KeyValuePtr> data;
bool result = false;
base::RunLoop loop;
// Testing the 'Sync' mojo version is a big headache involving 3 threads, so
// just test the async version.
wrapper_impl()->GetAll(
GetAllCallback::CreateAndBind(&result, loop.QuitClosure()),
MakeGetAllCallback(&status, &data));
loop.Run();
EXPECT_EQ(leveldb::mojom::DatabaseError::OK, status);
EXPECT_EQ(2u, data.size());
EXPECT_TRUE(result);
}
TEST_P(LevelDBWrapperImplParamTest, CommitPutToDB) {
wrapper_impl()->SetCacheModeForTesting(GetParam());
std::string key1 = test_key2_;
std::string value1 = "foo";
std::string key2 = test_prefix_;
std::string value2 = "data abc";
base::RunLoop loop;
bool put_success1 = false;
bool put_success2 = false;
bool put_success3 = false;
{
IncrementalBarrier barrier(loop.QuitClosure());
wrapper()->Put(ToBytes(key1), ToBytes(value1), test_value2_bytes_,
test_source_,
MakeSuccessCallback(barrier.Get(), &put_success1));
wrapper()->Put(ToBytes(key2), ToBytes("old value"), base::nullopt,
test_source_,
MakeSuccessCallback(barrier.Get(), &put_success2));
wrapper()->Put(ToBytes(key2), ToBytes(value2), ToBytes("old value"),
test_source_,
MakeSuccessCallback(barrier.Get(), &put_success3));
}
loop.Run();
EXPECT_TRUE(put_success1);
EXPECT_TRUE(put_success2);
EXPECT_TRUE(put_success3);
EXPECT_FALSE(has_mock_data(test_prefix_ + key2));
BlockingCommit();
EXPECT_TRUE(has_mock_data(test_prefix_ + key1));
EXPECT_EQ(value1, get_mock_data(test_prefix_ + key1));
EXPECT_TRUE(has_mock_data(test_prefix_ + key2));
EXPECT_EQ(value2, get_mock_data(test_prefix_ + key2));
}
TEST_P(LevelDBWrapperImplParamTest, PutObservations) {
wrapper_impl()->SetCacheModeForTesting(GetParam());
std::string key = "new_key";
std::string value1 = "foo";
std::string value2 = "data abc";
std::string source1 = "source1";
std::string source2 = "source2";
EXPECT_TRUE(PutSync(ToBytes(key), ToBytes(value1), base::nullopt, source1));
ASSERT_EQ(1u, observations().size());
EXPECT_EQ(Observation::kAdd, observations()[0].type);
EXPECT_EQ(key, observations()[0].key);
EXPECT_EQ(value1, observations()[0].new_value);
EXPECT_EQ(source1, observations()[0].source);
EXPECT_TRUE(PutSync(ToBytes(key), ToBytes(value2), ToBytes(value1), source2));
ASSERT_EQ(2u, observations().size());
EXPECT_EQ(Observation::kChange, observations()[1].type);
EXPECT_EQ(key, observations()[1].key);
EXPECT_EQ(value1, observations()[1].old_value);
EXPECT_EQ(value2, observations()[1].new_value);
EXPECT_EQ(source2, observations()[1].source);
// Same put should not cause another observation.
EXPECT_TRUE(PutSync(ToBytes(key), ToBytes(value2), ToBytes(value2), source2));
ASSERT_EQ(2u, observations().size());
}
TEST_P(LevelDBWrapperImplParamTest, DeleteNonExistingKey) {
wrapper_impl()->SetCacheModeForTesting(GetParam());
EXPECT_TRUE(DeleteSync(ToBytes("doesn't exist"), std::vector<uint8_t>()));
EXPECT_EQ(0u, observations().size());
}
TEST_P(LevelDBWrapperImplParamTest, DeleteExistingKey) {
wrapper_impl()->SetCacheModeForTesting(GetParam());
std::string key = "newkey";
std::string value = "foo";
set_mock_data(test_prefix_ + key, value);
EXPECT_TRUE(DeleteSync(ToBytes(key), ToBytes(value)));
ASSERT_EQ(1u, observations().size());
EXPECT_EQ(Observation::kDelete, observations()[0].type);
EXPECT_EQ(key, observations()[0].key);
EXPECT_EQ(value, observations()[0].old_value);
EXPECT_EQ(test_source_, observations()[0].source);
EXPECT_TRUE(has_mock_data(test_prefix_ + key));
BlockingCommit();
EXPECT_FALSE(has_mock_data(test_prefix_ + key));
}
TEST_P(LevelDBWrapperImplParamTest, DeleteAllWithoutLoadedMap) {
wrapper_impl()->SetCacheModeForTesting(GetParam());
std::string key = "newkey";
std::string value = "foo";
std::string dummy_key = "foobar";
set_mock_data(dummy_key, value);
set_mock_data(test_prefix_ + key, value);
EXPECT_TRUE(DeleteAllSync());
ASSERT_EQ(1u, observations().size());
EXPECT_EQ(Observation::kDeleteAll, observations()[0].type);
EXPECT_EQ(test_source_, observations()[0].source);
EXPECT_TRUE(has_mock_data(test_prefix_ + key));
EXPECT_TRUE(has_mock_data(dummy_key));
BlockingCommit();
EXPECT_FALSE(has_mock_data(test_prefix_ + key));
EXPECT_TRUE(has_mock_data(dummy_key));
// Deleting all again should still work, but not cause an observation.
EXPECT_TRUE(DeleteAllSync());
ASSERT_EQ(1u, observations().size());
// And now we've deleted all, writing something the quota size should work.
EXPECT_TRUE(PutSync(std::vector<uint8_t>(kTestSizeLimit, 'b'),
std::vector<uint8_t>(), base::nullopt));
}
TEST_P(LevelDBWrapperImplParamTest, DeleteAllWithLoadedMap) {
wrapper_impl()->SetCacheModeForTesting(GetParam());
std::string key = "newkey";
std::string value = "foo";
std::string dummy_key = "foobar";
set_mock_data(dummy_key, value);
EXPECT_TRUE(PutSync(ToBytes(key), ToBytes(value), base::nullopt));
EXPECT_TRUE(DeleteAllSync());
ASSERT_EQ(2u, observations().size());
EXPECT_EQ(Observation::kDeleteAll, observations()[1].type);
EXPECT_EQ(test_source_, observations()[1].source);
EXPECT_TRUE(has_mock_data(dummy_key));
BlockingCommit();
EXPECT_FALSE(has_mock_data(test_prefix_ + key));
EXPECT_TRUE(has_mock_data(dummy_key));
}
TEST_P(LevelDBWrapperImplParamTest, DeleteAllWithPendingMapLoad) {
wrapper_impl()->SetCacheModeForTesting(GetParam());
std::string key = "newkey";
std::string value = "foo";
std::string dummy_key = "foobar";
set_mock_data(dummy_key, value);
wrapper()->Put(ToBytes(key), ToBytes(value), base::nullopt, kTestSource,
base::BindOnce(&NoOpSuccessCallback));
EXPECT_TRUE(DeleteAllSync());
ASSERT_EQ(2u, observations().size());
EXPECT_EQ(Observation::kDeleteAll, observations()[1].type);
EXPECT_EQ(test_source_, observations()[1].source);
EXPECT_TRUE(has_mock_data(dummy_key));
BlockingCommit();
EXPECT_FALSE(has_mock_data(test_prefix_ + key));
EXPECT_TRUE(has_mock_data(dummy_key));
}
TEST_P(LevelDBWrapperImplParamTest, DeleteAllWithoutLoadedEmptyMap) {
wrapper_impl()->SetCacheModeForTesting(GetParam());
clear_mock_data();
EXPECT_TRUE(DeleteAllSync());
ASSERT_EQ(0u, observations().size());
}
TEST_F(LevelDBWrapperImplParamTest, PutOverQuotaLargeValue) {
wrapper_impl()->SetCacheModeForTesting(
LevelDBWrapperImpl::CacheMode::KEYS_AND_VALUES);
std::vector<uint8_t> key = ToBytes("newkey");
std::vector<uint8_t> value(kTestSizeLimit, 4);
EXPECT_FALSE(PutSync(key, value, base::nullopt));
value.resize(kTestSizeLimit / 2);
EXPECT_TRUE(PutSync(key, value, base::nullopt));
}
TEST_F(LevelDBWrapperImplParamTest, PutOverQuotaLargeKey) {
wrapper_impl()->SetCacheModeForTesting(
LevelDBWrapperImpl::CacheMode::KEYS_AND_VALUES);
std::vector<uint8_t> key(kTestSizeLimit, 'a');
std::vector<uint8_t> value = ToBytes("newvalue");
EXPECT_FALSE(PutSync(key, value, base::nullopt));
key.resize(kTestSizeLimit / 2);
EXPECT_TRUE(PutSync(key, value, base::nullopt));
}
TEST_F(LevelDBWrapperImplParamTest, PutWhenAlreadyOverQuota) {
wrapper_impl()->SetCacheModeForTesting(
LevelDBWrapperImpl::CacheMode::KEYS_AND_VALUES);
std::string key = "largedata";
std::vector<uint8_t> value(kTestSizeLimit, 4);
std::vector<uint8_t> old_value = value;
set_mock_data(test_prefix_ + key, ToString(value));
// Put with same data should succeed.
EXPECT_TRUE(PutSync(ToBytes(key), value, base::nullopt));
// Put with same data size should succeed.
value[1] = 13;
EXPECT_TRUE(PutSync(ToBytes(key), value, old_value));
// Adding a new key when already over quota should not succeed.
EXPECT_FALSE(PutSync(ToBytes("newkey"), {1, 2, 3}, base::nullopt));
// Reducing size should also succeed.
old_value = value;
value.resize(kTestSizeLimit / 2);
EXPECT_TRUE(PutSync(ToBytes(key), value, old_value));
// Increasing size again should succeed, as still under the limit.
old_value = value;
value.resize(value.size() + 1);
EXPECT_TRUE(PutSync(ToBytes(key), value, old_value));
// But increasing back to original size should fail.
old_value = value;
value.resize(kTestSizeLimit);
EXPECT_FALSE(PutSync(ToBytes(key), value, old_value));
}
TEST_F(LevelDBWrapperImplParamTest, PutWhenAlreadyOverQuotaBecauseOfLargeKey) {
wrapper_impl()->SetCacheModeForTesting(
LevelDBWrapperImpl::CacheMode::KEYS_AND_VALUES);
std::vector<uint8_t> key(kTestSizeLimit, 'x');
std::vector<uint8_t> value = ToBytes("value");
std::vector<uint8_t> old_value = value;
set_mock_data(test_prefix_ + ToString(key), ToString(value));
// Put with same data size should succeed.
value[0] = 'X';
EXPECT_TRUE(PutSync(key, value, old_value));
// Reducing size should also succeed.
old_value = value;
value.clear();
EXPECT_TRUE(PutSync(key, value, old_value));
// Increasing size should fail.
old_value = value;
value.resize(1, 'a');
EXPECT_FALSE(PutSync(key, value, old_value));
}
TEST_P(LevelDBWrapperImplParamTest, PutAfterPurgeMemory) {
wrapper_impl()->SetCacheModeForTesting(GetParam());
std::vector<uint8_t> result;
const auto key = test_key2_bytes_;
const auto value = test_value2_bytes_;
EXPECT_TRUE(PutSync(key, value, value));
EXPECT_EQ(delegate()->map_load_count(), 1);
// Adding again doesn't load map again.
EXPECT_TRUE(PutSync(key, value, value));
EXPECT_EQ(delegate()->map_load_count(), 1);
wrapper_impl()->PurgeMemory();
// Now adding should still work, and load map again.
EXPECT_TRUE(PutSync(key, value, value));
EXPECT_EQ(delegate()->map_load_count(), 2);
}
TEST_P(LevelDBWrapperImplParamTest, PurgeMemoryWithPendingChanges) {
wrapper_impl()->SetCacheModeForTesting(GetParam());
std::vector<uint8_t> key = test_key2_bytes_;
std::vector<uint8_t> value = ToBytes("foo");
EXPECT_TRUE(PutSync(key, value, test_value2_bytes_));
EXPECT_EQ(delegate()->map_load_count(), 1);
// Purge memory, and read. Should not actually have purged, so should not have
// triggered a load.
wrapper_impl()->PurgeMemory();
EXPECT_TRUE(PutSync(key, value, value));
EXPECT_EQ(delegate()->map_load_count(), 1);
}
TEST_P(LevelDBWrapperImplParamTest, FixUpData) {
wrapper_impl()->SetCacheModeForTesting(GetParam());
std::vector<LevelDBWrapperImpl::Change> changes;
changes.push_back(std::make_pair(test_key1_bytes_, ToBytes("foo")));
changes.push_back(std::make_pair(test_key2_bytes_, base::nullopt));
changes.push_back(std::make_pair(test_prefix_bytes_, ToBytes("bla")));
delegate()->set_mock_changes(std::move(changes));
leveldb::mojom::DatabaseError status;
std::vector<mojom::KeyValuePtr> data;
bool result = false;
base::RunLoop loop;
// Testing the 'Sync' mojo version is a big headache involving 3 threads, so
// just test the async version.
wrapper_impl()->GetAll(
GetAllCallback::CreateAndBind(&result, loop.QuitClosure()),
MakeGetAllCallback(&status, &data));
loop.Run();
EXPECT_EQ(leveldb::mojom::DatabaseError::OK, status);
ASSERT_EQ(2u, data.size());
EXPECT_EQ(test_prefix_, ToString(data[0]->key));
EXPECT_EQ("bla", ToString(data[0]->value));
EXPECT_EQ(test_key1_, ToString(data[1]->key));
EXPECT_EQ("foo", ToString(data[1]->value));
EXPECT_FALSE(has_mock_data(test_prefix_ + test_key2_));
EXPECT_EQ("foo", get_mock_data(test_prefix_ + test_key1_));
EXPECT_EQ("bla", get_mock_data(test_prefix_ + test_prefix_));
}
TEST_F(LevelDBWrapperImplTest, SetOnlyKeysWithoutDatabase) {
std::vector<uint8_t> key = test_key2_bytes_;
std::vector<uint8_t> value = ToBytes("foo");
MockDelegate delegate;
LevelDBWrapperImpl level_db_wrapper(
nullptr, test_prefix_, &delegate,
GetDefaultTestingOptions(CacheMode::KEYS_ONLY_WHEN_POSSIBLE));
mojom::LevelDBWrapperPtr level_db_wrapper_ptr;
level_db_wrapper.Bind(mojo::MakeRequest(&level_db_wrapper_ptr));
// Setting only keys mode is noop.
level_db_wrapper.SetCacheModeForTesting(CacheMode::KEYS_ONLY_WHEN_POSSIBLE);
EXPECT_FALSE(level_db_wrapper.initialized());
EXPECT_EQ(CacheMode::KEYS_AND_VALUES, level_db_wrapper.cache_mode());
// Put and Get can work synchronously without reload.
bool put_callback_called = false;
level_db_wrapper.Put(key, value, base::nullopt, "source",
base::BindOnce(
[](bool* put_callback_called, bool success) {
EXPECT_TRUE(success);
*put_callback_called = true;
},
&put_callback_called));
EXPECT_TRUE(put_callback_called);
std::vector<uint8_t> expected_value;
level_db_wrapper.Get(
key, base::BindOnce(
[](std::vector<uint8_t>* expected_value, bool success,
const std::vector<uint8_t>& value) {
EXPECT_TRUE(success);
*expected_value = value;
},
&expected_value));
EXPECT_EQ(expected_value, value);
}
TEST_P(LevelDBWrapperImplParamTest, CommitOnDifferentCacheModes) {
wrapper_impl()->SetCacheModeForTesting(GetParam());
std::vector<uint8_t> key = test_key2_bytes_;
std::vector<uint8_t> value = ToBytes("foo");
std::vector<uint8_t> value2 = ToBytes("foobar");
// The initial map always has values, so a nullopt is fine for the old value.
ASSERT_TRUE(PutSync(key, value, base::nullopt));
ASSERT_TRUE(wrapper_impl()->commit_batch_);
if (GetParam() == CacheMode::KEYS_AND_VALUES) {
EXPECT_TRUE(wrapper_impl()->commit_batch_->changed_values.empty());
auto* changes = &wrapper_impl()->commit_batch_->changed_keys;
ASSERT_EQ(1u, changes->size());
EXPECT_EQ(key, *changes->begin());
} else {
EXPECT_TRUE(wrapper_impl()->commit_batch_->changed_keys.empty());
auto* changes = &wrapper_impl()->commit_batch_->changed_values;
ASSERT_EQ(1u, changes->size());
auto it = changes->begin();
EXPECT_EQ(key, it->first);
EXPECT_EQ(value, it->second);
}
BlockingCommit();
EXPECT_EQ("foo", get_mock_data(test_prefix_ + test_key2_));
if (GetParam() == CacheMode::KEYS_AND_VALUES)
EXPECT_EQ(2u, wrapper_impl()->keys_values_map_.size());
else
EXPECT_EQ(2u, wrapper_impl()->keys_only_map_.size());
ASSERT_TRUE(PutSync(key, value, value));
EXPECT_FALSE(wrapper_impl()->commit_batch_);
ASSERT_TRUE(PutSync(key, value2, value));
ASSERT_TRUE(wrapper_impl()->commit_batch_);
if (GetParam() == CacheMode::KEYS_AND_VALUES) {
auto* changes = &wrapper_impl()->commit_batch_->changed_keys;
EXPECT_EQ(1u, changes->size());
auto it = changes->find(key);
ASSERT_NE(it, changes->end());
} else {
auto* changes = &wrapper_impl()->commit_batch_->changed_values;
EXPECT_EQ(1u, changes->size());
auto it = changes->find(key);
ASSERT_NE(it, changes->end());
EXPECT_EQ(value2, it->second);
}
clear_mock_data();
EXPECT_TRUE(wrapper_impl()->has_changes_to_commit());
BlockingCommit();
EXPECT_EQ("foobar", get_mock_data(test_prefix_ + test_key2_));
EXPECT_FALSE(wrapper_impl()->has_changes_to_commit());
}
TEST_F(LevelDBWrapperImplTest, GetAllWhenCacheOnlyKeys) {
std::vector<uint8_t> key = test_key2_bytes_;
std::vector<uint8_t> value = ToBytes("foo");
std::vector<uint8_t> value2 = ToBytes("foobar");
// Go to load state only keys.
ASSERT_TRUE(PutSync(key, value, base::nullopt));
BlockingCommit();
ASSERT_TRUE(PutSync(key, value2, value));
EXPECT_TRUE(wrapper_impl()->has_changes_to_commit());
leveldb::mojom::DatabaseError status;
std::vector<mojom::KeyValuePtr> data;
bool result = false;
base::RunLoop loop;
bool put_result1 = false;
bool put_result2 = false;
{
IncrementalBarrier barrier(loop.QuitClosure());
wrapper_impl()->Put(key, value, value2, test_source_,
MakeSuccessCallback(barrier.Get(), &put_result1));
wrapper_impl()->GetAll(
GetAllCallback::CreateAndBind(&result, barrier.Get()),
MakeGetAllCallback(&status, &data));
wrapper_impl()->Put(key, value2, value, test_source_,
MakeSuccessCallback(barrier.Get(), &put_result2));
}
// GetAll triggers a commit when it's switching map types.
EXPECT_TRUE(put_result1);
EXPECT_EQ("foo", get_mock_data(test_prefix_ + test_key2_));
EXPECT_FALSE(put_result2);
EXPECT_FALSE(result);
loop.Run();
EXPECT_TRUE(result);
EXPECT_TRUE(put_result1);
EXPECT_EQ(2u, data.size());
EXPECT_TRUE(
data[1]->Equals(mojom::KeyValue(test_key1_bytes_, test_value1_bytes_)))
<< ToString(data[1]->value) << " vs expected " << test_value1_;
EXPECT_TRUE(data[0]->Equals(mojom::KeyValue(key, value)))
<< ToString(data[0]->value) << " vs expected " << ToString(value);
EXPECT_EQ(leveldb::mojom::DatabaseError::OK, status);
// The last "put" isn't committed yet.
EXPECT_EQ("foo", get_mock_data(test_prefix_ + test_key2_));
ASSERT_TRUE(wrapper_impl()->has_changes_to_commit());
BlockingCommit();
EXPECT_EQ("foobar", get_mock_data(test_prefix_ + test_key2_));
}
TEST_F(LevelDBWrapperImplTest, GetAllAfterSetCacheMode) {
std::vector<uint8_t> key = test_key2_bytes_;
std::vector<uint8_t> value = ToBytes("foo");
std::vector<uint8_t> value2 = ToBytes("foobar");
// Go to load state only keys.
ASSERT_TRUE(PutSync(key, value, base::nullopt));
BlockingCommit();
EXPECT_TRUE(wrapper_impl()->map_state_ ==
LevelDBWrapperImpl::MapState::LOADED_KEYS_ONLY);
ASSERT_TRUE(PutSync(key, value2, value));
EXPECT_TRUE(wrapper_impl()->has_changes_to_commit());
wrapper_impl()->SetCacheModeForTesting(CacheMode::KEYS_AND_VALUES);
// Cache isn't cleared when commit batch exists.
EXPECT_TRUE(wrapper_impl()->map_state_ ==
LevelDBWrapperImpl::MapState::LOADED_KEYS_ONLY);
base::RunLoop loop;
bool put_success = false;
leveldb::mojom::DatabaseError status;
std::vector<mojom::KeyValuePtr> data;
bool get_all_success = false;
bool delete_success = false;
{
IncrementalBarrier barrier(loop.QuitClosure());
wrapper_impl()->Put(key, value, value2, test_source_,
MakeSuccessCallback(barrier.Get(), &put_success));
// Put task triggers database upgrade, so there are no more changes
// to commit.
EXPECT_FALSE(wrapper_impl()->has_changes_to_commit());
EXPECT_TRUE(wrapper_impl()->has_pending_load_tasks());
wrapper_impl()->GetAll(
GetAllCallback::CreateAndBind(&get_all_success, barrier.Get()),
MakeGetAllCallback(&status, &data));
// This Delete() should not affect the value returned by GetAll().
wrapper_impl()->Delete(key, value, test_source_,
MakeSuccessCallback(barrier.Get(), &delete_success));
}
loop.Run();
EXPECT_EQ(2u, data.size());
EXPECT_TRUE(
data[1]->Equals(mojom::KeyValue(test_key1_bytes_, test_value1_bytes_)))
<< ToString(data[1]->value) << " vs expected " << test_value1_;
EXPECT_TRUE(data[0]->Equals(mojom::KeyValue(key, value)))
<< ToString(data[0]->value) << " vs expected " << ToString(value2);
EXPECT_EQ(leveldb::mojom::DatabaseError::OK, status);
EXPECT_TRUE(put_success);
EXPECT_TRUE(get_all_success);
EXPECT_TRUE(delete_success);
// GetAll shouldn't trigger a commit before it runs now because the value
// map should be loading.
EXPECT_EQ("foobar", get_mock_data(test_prefix_ + test_key2_));
ASSERT_TRUE(wrapper_impl()->has_changes_to_commit());
BlockingCommit();
EXPECT_FALSE(has_mock_data(test_prefix_ + test_key2_));
}
TEST_F(LevelDBWrapperImplTest, SetCacheModeConsistent) {
std::vector<uint8_t> key = test_key2_bytes_;
std::vector<uint8_t> value = ToBytes("foo");
std::vector<uint8_t> value2 = ToBytes("foobar");
EXPECT_FALSE(wrapper_impl()->IsMapLoaded());
EXPECT_TRUE(wrapper_impl()->cache_mode() ==
CacheMode::KEYS_ONLY_WHEN_POSSIBLE);
// Clear the database before the wrapper loads data.
clear_mock_data();
EXPECT_TRUE(PutSync(key, value, base::nullopt));
EXPECT_TRUE(wrapper_impl()->has_changes_to_commit());
BlockingCommit();
EXPECT_TRUE(PutSync(key, value2, value));
EXPECT_TRUE(wrapper_impl()->has_changes_to_commit());
// Setting cache mode does not reload the cache till it is required.
wrapper_impl()->SetCacheModeForTesting(CacheMode::KEYS_AND_VALUES);
EXPECT_EQ(LevelDBWrapperImpl::MapState::LOADED_KEYS_ONLY,
wrapper_impl()->map_state_);
// Put operation should change the mode.
EXPECT_TRUE(PutSync(key, value, value2));
EXPECT_TRUE(wrapper_impl()->has_changes_to_commit());
EXPECT_EQ(LevelDBWrapperImpl::MapState::LOADED_KEYS_AND_VALUES,
wrapper_impl()->map_state_);
std::vector<uint8_t> result;
EXPECT_TRUE(GetSync(key, &result));
EXPECT_EQ(value, result);
EXPECT_EQ(LevelDBWrapperImpl::MapState::LOADED_KEYS_AND_VALUES,
wrapper_impl()->map_state_);
BlockingCommit();
// Test that the map will unload correctly
EXPECT_TRUE(PutSync(key, value2, value));
wrapper_impl()->SetCacheModeForTesting(CacheMode::KEYS_ONLY_WHEN_POSSIBLE);
EXPECT_EQ(LevelDBWrapperImpl::MapState::LOADED_KEYS_ONLY,
wrapper_impl()->map_state_);
BlockingCommit();
EXPECT_EQ(LevelDBWrapperImpl::MapState::LOADED_KEYS_ONLY,
wrapper_impl()->map_state_);
// Test the map will unload right away when there are no changes.
wrapper_impl()->SetCacheModeForTesting(CacheMode::KEYS_AND_VALUES);
EXPECT_TRUE(GetSync(key, &result));
EXPECT_EQ(LevelDBWrapperImpl::MapState::LOADED_KEYS_AND_VALUES,
wrapper_impl()->map_state_);
wrapper_impl()->SetCacheModeForTesting(CacheMode::KEYS_ONLY_WHEN_POSSIBLE);
EXPECT_EQ(LevelDBWrapperImpl::MapState::LOADED_KEYS_ONLY,
wrapper_impl()->map_state_);
}
TEST_F(LevelDBWrapperImplTest, SendOldValueObservations) {
should_record_send_old_value_observations(true);
wrapper_impl()->SetCacheModeForTesting(CacheMode::KEYS_AND_VALUES);
// Flush tasks on mojo thread to observe callback.
EXPECT_TRUE(DeleteSync(ToBytes("doesn't exist"), base::nullopt));
wrapper_impl()->SetCacheModeForTesting(CacheMode::KEYS_ONLY_WHEN_POSSIBLE);
// Flush tasks on mojo thread to observe callback.
EXPECT_TRUE(DeleteSync(ToBytes("doesn't exist"), base::nullopt));
ASSERT_EQ(2u, observations().size());
EXPECT_EQ(Observation::kSendOldValue, observations()[0].type);
EXPECT_FALSE(observations()[0].should_send_old_value);
EXPECT_EQ(Observation::kSendOldValue, observations()[1].type);
EXPECT_TRUE(observations()[1].should_send_old_value);
}
TEST_P(LevelDBWrapperImplParamTest, PrefixForking) {
std::string value3 = "value3";
std::string value4 = "value4";
std::string value5 = "value5";
// In order to test the interaction between forking and mojo calls where
// forking can happen in between a request and reply to the wrapper mojo
// service, all calls are done on the 'impl' object itself.
// Operations in the same run cycle:
// Fork 1 created from original
// Put on fork 1
// Fork 2 create from fork 1
// Put on fork 1
// Put on original
// Fork 3 created from original
std::unique_ptr<LevelDBWrapperImpl> fork1;
MockDelegate fork1_delegate;
std::unique_ptr<LevelDBWrapperImpl> fork2;
MockDelegate fork2_delegate;
std::unique_ptr<LevelDBWrapperImpl> fork3;
MockDelegate fork3_delegate;
auto options = GetDefaultTestingOptions(GetParam());
bool put_success1 = false;
bool put_success2 = false;
bool put_success3 = false;
base::RunLoop loop;
{
IncrementalBarrier barrier(loop.QuitClosure());
// Create fork 1.
fork1 = wrapper_impl()->ForkToNewPrefix(test_copy_prefix1_, &fork1_delegate,
options);
// Do a put on fork 1 and create fork 2.
// Note - these are 'skipping' the mojo layer, which is why the fork isn't
// scheduled.
fork1->Put(test_key2_bytes_, ToBytes(value4), test_value2_bytes_,
test_source_, MakeSuccessCallback(barrier.Get(), &put_success1));
fork2 =
fork1->ForkToNewPrefix(test_copy_prefix2_, &fork2_delegate, options);
fork1->Put(test_key2_bytes_, ToBytes(value5), ToBytes(value4), test_source_,
MakeSuccessCallback(barrier.Get(), &put_success2));
// Do a put on original and create fork 3, which is key-only.
wrapper_impl()->Put(test_key1_bytes_, ToBytes(value3), test_value1_bytes_,
test_source_,
MakeSuccessCallback(barrier.Get(), &put_success3));
fork3 = wrapper_impl()->ForkToNewPrefix(
test_copy_prefix3_, &fork3_delegate,
GetDefaultTestingOptions(CacheMode::KEYS_ONLY_WHEN_POSSIBLE));
}
loop.Run();
EXPECT_TRUE(put_success1);
EXPECT_TRUE(put_success2);
EXPECT_TRUE(fork2.get());
EXPECT_TRUE(fork3.get());
EXPECT_EQ(value3, GetSyncStrUsingGetAll(wrapper_impl(), test_key1_));
EXPECT_EQ(test_value1_, GetSyncStrUsingGetAll(fork1.get(), test_key1_));
EXPECT_EQ(test_value1_, GetSyncStrUsingGetAll(fork2.get(), test_key1_));
EXPECT_EQ(value3, GetSyncStrUsingGetAll(fork3.get(), test_key1_));
EXPECT_EQ(test_value2_, GetSyncStrUsingGetAll(wrapper_impl(), test_key2_));
EXPECT_EQ(value5, GetSyncStrUsingGetAll(fork1.get(), test_key2_));
EXPECT_EQ(value4, GetSyncStrUsingGetAll(fork2.get(), test_key2_));
EXPECT_EQ(test_value2_, GetSyncStrUsingGetAll(fork3.get(), test_key2_));
BlockingCommit(delegate(), wrapper_impl());
BlockingCommit(&fork1_delegate, fork1.get());
// test_key1_ values.
EXPECT_EQ(value3, get_mock_data(test_prefix_ + test_key1_));
EXPECT_EQ(test_value1_, get_mock_data(test_copy_prefix1_ + test_key1_));
EXPECT_EQ(test_value1_, get_mock_data(test_copy_prefix2_ + test_key1_));
EXPECT_EQ(value3, get_mock_data(test_copy_prefix3_ + test_key1_));
// test_key2_ values.
EXPECT_EQ(test_value2_, get_mock_data(test_prefix_ + test_key2_));
EXPECT_EQ(value5, get_mock_data(test_copy_prefix1_ + test_key2_));
EXPECT_EQ(value4, get_mock_data(test_copy_prefix2_ + test_key2_));
EXPECT_EQ(test_value2_, get_mock_data(test_copy_prefix3_ + test_key2_));
}
TEST_P(LevelDBWrapperImplParamTest, PrefixForkAfterLoad) {
const std::string kValue = "foo";
const std::vector<uint8_t> kValueVec = ToBytes(kValue);
// Do a sync put so the map loads.
EXPECT_TRUE(PutSync(test_key1_bytes_, kValueVec, base::nullopt));
// Execute the fork.
MockDelegate fork1_delegate;
std::unique_ptr<LevelDBWrapperImpl> fork1 =
wrapper_impl()->ForkToNewPrefix(test_copy_prefix1_, &fork1_delegate,
GetDefaultTestingOptions(GetParam()));
// Check our forked state.
EXPECT_EQ(kValue, GetSyncStrUsingGetAll(fork1.get(), test_key1_));
BlockingCommit(delegate(), wrapper_impl());
EXPECT_EQ(kValue, get_mock_data(test_copy_prefix1_ + test_key1_));
}
namespace {
std::string GetNewPrefix(int* i) {
std::string prefix = "prefix-" + base::Int64ToString(*i) + "-";
(*i)++;
return prefix;
}
struct FuzzState {
base::Optional<std::vector<uint8_t>> val1;
base::Optional<std::vector<uint8_t>> val2;
};
} // namespace
TEST_F(LevelDBWrapperImplTest, PrefixForkingPsuedoFuzzer) {
const std::string kKey1 = "key1";
const std::vector<uint8_t> kKey1Vec = ToBytes(kKey1);
const std::string kKey2 = "key2";
const std::vector<uint8_t> kKey2Vec = ToBytes(kKey2);
const int kTotalWrappers = 1000;
// This tests tries to throw all possible enumartions of operations and
// forking at wrappers. The purpose is to hit all edge cases possible to
// expose any loading bugs.
std::vector<FuzzState> states(kTotalWrappers);
std::vector<std::unique_ptr<LevelDBWrapperImpl>> wrappers(kTotalWrappers);
std::vector<MockDelegate> delegates(kTotalWrappers);
std::list<bool> successes;
int curr_prefix = 0;
base::RunLoop loop;
{
IncrementalBarrier barrier(loop.QuitClosure());
for (int64_t i = 0; i < kTotalWrappers; i++) {
FuzzState& state = states[i];
if (!wrappers[i]) {
wrappers[i] = wrapper_impl()->ForkToNewPrefix(
GetNewPrefix(&curr_prefix), &delegates[i],
GetDefaultTestingOptions(CacheMode::KEYS_ONLY_WHEN_POSSIBLE));
}
int64_t forks = i;
if ((i % 5 == 0 || i % 6 == 0) && forks + 1 < kTotalWrappers) {
forks++;
states[forks] = state;
wrappers[forks] = wrappers[i]->ForkToNewPrefix(
GetNewPrefix(&curr_prefix), &delegates[forks],
GetDefaultTestingOptions(CacheMode::KEYS_AND_VALUES));
}
if (i % 13 == 0) {
FuzzState old_state = state;
state.val1 = base::nullopt;
successes.push_back(false);
wrappers[i]->Delete(
kKey1Vec, old_state.val1, test_source_,
MakeSuccessCallback(barrier.Get(), &successes.back()));
}
if (i % 4 == 0) {
FuzzState old_state = state;
state.val2 = base::make_optional<std::vector<uint8_t>>(
{static_cast<uint8_t>(i)});
successes.push_back(false);
wrappers[i]->Put(kKey2Vec, state.val2.value(), old_state.val2,
test_source_,
MakeSuccessCallback(barrier.Get(), &successes.back()));
}
if (i % 3 == 0) {
FuzzState old_state = state;
state.val1 = base::make_optional<std::vector<uint8_t>>(
{static_cast<uint8_t>(i + 5)});
successes.push_back(false);
wrappers[i]->Put(kKey1Vec, state.val1.value(), old_state.val1,
test_source_,
MakeSuccessCallback(barrier.Get(), &successes.back()));
}
if (i % 11 == 0) {
state.val1 = base::nullopt;
state.val2 = base::nullopt;
successes.push_back(false);
wrappers[i]->DeleteAll(
test_source_,
MakeSuccessCallback(barrier.Get(), &successes.back()));
}
if (i % 2 == 0 && forks + 1 < kTotalWrappers) {
CacheMode mode = i % 3 == 0 ? CacheMode::KEYS_AND_VALUES
: CacheMode::KEYS_ONLY_WHEN_POSSIBLE;
forks++;
states[forks] = state;
wrappers[forks] = wrappers[i]->ForkToNewPrefix(
GetNewPrefix(&curr_prefix), &delegates[forks],
GetDefaultTestingOptions(mode));
}
if (i % 3 == 0) {
FuzzState old_state = state;
state.val1 = base::make_optional<std::vector<uint8_t>>(
{static_cast<uint8_t>(i + 9)});
successes.push_back(false);
wrappers[i]->Put(kKey1Vec, state.val1.value(), old_state.val1,
test_source_,
MakeSuccessCallback(barrier.Get(), &successes.back()));
}
}
}
loop.Run();
// This section checks that we get the correct values when we query the
// wrappers (which may or may not be maintaining their own cache).
for (size_t i = 0; i < kTotalWrappers; i++) {
FuzzState& state = states[i];
std::vector<uint8_t> result;
// Note: this will cause all keys-only wrappers to commit.
std::string result1 = GetSyncStrUsingGetAll(wrappers[i].get(), kKey1);
std::string result2 = GetSyncStrUsingGetAll(wrappers[i].get(), kKey2);
EXPECT_EQ(!!state.val1, !result1.empty()) << i;
if (state.val1)
EXPECT_EQ(state.val1.value(), ToBytes(result1));
EXPECT_EQ(!!state.val2, !result2.empty()) << i;
if (state.val2)
EXPECT_EQ(state.val2.value(), ToBytes(result2)) << i;
}
// This section verifies that all wrappers have committed their changes to
// the database.
ASSERT_EQ(wrappers.size(), delegates.size());
size_t half = kTotalWrappers / 2;
for (size_t i = 0; i < half; i++) {
BlockingCommit(&delegates[i], wrappers[i].get());
}
for (size_t i = kTotalWrappers - 1; i >= half; i--) {
BlockingCommit(&delegates[i], wrappers[i].get());
}
// This section checks the data in the database itself to verify all wrappers
// committed changes correctly.
for (size_t i = 0; i < kTotalWrappers; ++i) {
FuzzState& state = states[i];
std::vector<uint8_t> prefix = wrappers[i]->prefix();
std::string key1 = ToString(prefix) + kKey1;
std::string key2 = ToString(prefix) + kKey2;
EXPECT_EQ(!!state.val1, has_mock_data(key1));
if (state.val1)
EXPECT_EQ(ToString(state.val1.value()), get_mock_data(key1));
EXPECT_EQ(!!state.val2, has_mock_data(key2));
if (state.val2)
EXPECT_EQ(ToString(state.val2.value()), get_mock_data(key2));
EXPECT_FALSE(wrappers[i]->has_pending_load_tasks()) << i;
}
}
} // namespace content