Commit d51c84ce authored by mek's avatar mek Committed by Commit bot

Start adding tests for LevelDBWrapper, and fix a bunch of bugs.

In particular this fixes:
- loading data from leveldb should strip the prefix from the keys
- writing data to leveldb should preprend the prefix to the keys
- CommitChanges should actually commit changes

BUG=586194

Review-Url: https://codereview.chromium.org/2583253002
Cr-Commit-Position: refs/heads/master@{#439849}
parent 52560eca
......@@ -124,9 +124,6 @@ class DOMStorageContextWrapper::MojoState {
mojom::LevelDBObserverPtr observer,
mojom::LevelDBWrapperRequest request);
// Maps between an origin and its prefixed LevelDB view.
std::map<url::Origin, std::unique_ptr<LevelDBWrapperImpl>> level_db_wrappers_;
service_manager::Connector* const connector_;
const base::FilePath subdirectory_;
......@@ -146,6 +143,9 @@ class DOMStorageContextWrapper::MojoState {
std::vector<base::Closure> on_database_opened_callbacks_;
// Maps between an origin and its prefixed LevelDB view.
std::map<url::Origin, std::unique_ptr<LevelDBWrapperImpl>> level_db_wrappers_;
base::WeakPtrFactory<MojoState> weak_ptr_factory_;
};
......
......@@ -55,7 +55,7 @@ LevelDBWrapperImpl::LevelDBWrapperImpl(
int max_bytes_per_hour,
int max_commits_per_hour,
const base::Closure& no_bindings_callback)
: prefix_(prefix),
: prefix_(leveldb::StdStringToUint8Vector(prefix)),
no_bindings_callback_(no_bindings_callback),
database_(database),
bytes_used_(0),
......@@ -249,7 +249,7 @@ void LevelDBWrapperImpl::LoadMap(const base::Closure& completion_callback) {
return;
// TODO(michaeln): Import from sqlite localstorage db.
database_->GetPrefixed(leveldb::StdStringToUint8Vector(prefix_),
database_->GetPrefixed(prefix_,
base::Bind(&LevelDBWrapperImpl::OnLoadComplete,
weak_ptr_factory_.GetWeakPtr()));
}
......@@ -259,8 +259,11 @@ void LevelDBWrapperImpl::OnLoadComplete(
std::vector<leveldb::mojom::KeyValuePtr> data) {
DCHECK(!map_);
map_.reset(new ValueMap);
for (auto& it : data)
(*map_)[it->key] = it->value;
for (auto& it : data) {
DCHECK_GE(it->key.size(), prefix_.size());
(*map_)[std::vector<uint8_t>(it->key.begin() + prefix_.size(),
it->key.end())] = it->value;
}
// We proceed without using a backing store, nothing will be persisted but the
// class is functional for the lifetime of the object.
......@@ -316,7 +319,7 @@ base::TimeDelta LevelDBWrapperImpl::ComputeCommitDelay() const {
void LevelDBWrapperImpl::CommitChanges() {
DCHECK(database_);
if (commit_batch_)
if (!commit_batch_)
return;
commit_rate_limiter_.add_samples(1);
......@@ -328,13 +331,15 @@ void LevelDBWrapperImpl::CommitChanges() {
leveldb::mojom::BatchedOperationPtr item =
leveldb::mojom::BatchedOperation::New();
item->type = leveldb::mojom::BatchOperationType::DELETE_PREFIXED_KEY;
item->key = leveldb::StdStringToUint8Vector(prefix_);
item->key = prefix_;
operations.push_back(std::move(item));
}
for (auto& it : commit_batch_->changed_values) {
leveldb::mojom::BatchedOperationPtr item =
leveldb::mojom::BatchedOperation::New();
item->key = std::move(it.first);
item->key.reserve(prefix_.size() + it.first.size());
item->key.insert(item->key.end(), prefix_.begin(), prefix_.end());
item->key.insert(item->key.end(), it.first.begin(), it.first.end());
if (!it.second) {
item->type = leveldb::mojom::BatchOperationType::DELETE_KEY;
} else {
......
......@@ -30,7 +30,7 @@ namespace content {
// 2) Enforces a max_size constraint.
// 3) Informs observers when values scoped by prefix are modified.
// 4) Throttles requests to avoid overwhelming the disk.
class LevelDBWrapperImpl : public mojom::LevelDBWrapper {
class CONTENT_EXPORT LevelDBWrapperImpl : public mojom::LevelDBWrapper {
public:
// |no_bindings_callback| will be called when this object has no more
// bindings.
......@@ -53,6 +53,8 @@ class LevelDBWrapperImpl : public mojom::LevelDBWrapper {
static void EnableAggressiveCommitDelay();
private:
friend class LevelDBWrapperImplTest;
using ValueMap = std::map<std::vector<uint8_t>, std::vector<uint8_t>>;
using ChangedValueMap =
std::map<std::vector<uint8_t>, base::Optional<std::vector<uint8_t>>>;
......@@ -114,7 +116,7 @@ class LevelDBWrapperImpl : public mojom::LevelDBWrapper {
void CommitChanges();
void OnCommitComplete(leveldb::mojom::DatabaseError error);
std::string prefix_;
std::vector<uint8_t> prefix_;
mojo::BindingSet<mojom::LevelDBWrapper> bindings_;
mojo::InterfacePtrSet<mojom::LevelDBObserver> observers_;
base::Closure no_bindings_callback_;
......
// 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/debug/stack_trace.h"
#include "components/leveldb/public/cpp/util.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gtest/include/gtest/gtest.h"
using leveldb::StdStringToUint8Vector;
using leveldb::Uint8VectorToStdString;
namespace content {
namespace {
const char* kTestPrefix = "abc";
const char* kTestSource = "source";
const size_t kTestSizeLimit = 512;
leveldb::mojom::KeyValuePtr CreateKeyValue(std::vector<uint8_t> key,
std::vector<uint8_t> value) {
leveldb::mojom::KeyValuePtr result = leveldb::mojom::KeyValue::New();
result->key = std::move(key);
result->value = std::move(value);
return result;
}
base::StringPiece AsStringPiece(const std::vector<uint8_t>& data) {
return base::StringPiece(reinterpret_cast<const char*>(data.data()),
data.size());
}
bool StartsWith(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& prefix) {
return base::StartsWith(AsStringPiece(key), AsStringPiece(prefix),
base::CompareCase::SENSITIVE);
}
class MockLevelDBDatabase : public leveldb::mojom::LevelDBDatabase {
public:
explicit MockLevelDBDatabase(
std::map<std::vector<uint8_t>, std::vector<uint8_t>>* mock_data)
: mock_data_(*mock_data) {}
void Put(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& value,
const PutCallback& callback) override {
FAIL();
}
void Delete(const std::vector<uint8_t>& key,
const DeleteCallback& callback) override {
FAIL();
}
void DeletePrefixed(const std::vector<uint8_t>& key_prefix,
const DeletePrefixedCallback& callback) override {
FAIL();
}
void Write(std::vector<leveldb::mojom::BatchedOperationPtr> operations,
const WriteCallback& callback) override {
for (const auto& op : operations) {
switch (op->type) {
case leveldb::mojom::BatchOperationType::PUT_KEY:
mock_data_[op->key] = *op->value;
break;
case leveldb::mojom::BatchOperationType::DELETE_KEY:
mock_data_.erase(op->key);
break;
case leveldb::mojom::BatchOperationType::DELETE_PREFIXED_KEY:
FAIL();
break;
}
}
callback.Run(leveldb::mojom::DatabaseError::OK);
}
void Get(const std::vector<uint8_t>& key,
const GetCallback& callback) override {
FAIL();
}
void GetPrefixed(const std::vector<uint8_t>& key_prefix,
const GetPrefixedCallback& callback) override {
std::vector<leveldb::mojom::KeyValuePtr> data;
for (const auto& row : mock_data_) {
if (StartsWith(row.first, key_prefix)) {
data.push_back(CreateKeyValue(row.first, row.second));
}
}
callback.Run(leveldb::mojom::DatabaseError::OK, std::move(data));
}
void GetSnapshot(const GetSnapshotCallback& callback) override { FAIL(); }
void ReleaseSnapshot(const base::UnguessableToken& snapshot) override {
FAIL();
}
void GetFromSnapshot(const base::UnguessableToken& snapshot,
const std::vector<uint8_t>& key,
const GetCallback& callback) override {
FAIL();
}
void NewIterator(const NewIteratorCallback& callback) override { FAIL(); }
void NewIteratorFromSnapshot(
const base::UnguessableToken& snapshot,
const NewIteratorFromSnapshotCallback& callback) override {
FAIL();
}
void ReleaseIterator(const base::UnguessableToken& iterator) override {
FAIL();
}
void IteratorSeekToFirst(
const base::UnguessableToken& iterator,
const IteratorSeekToFirstCallback& callback) override {
FAIL();
}
void IteratorSeekToLast(const base::UnguessableToken& iterator,
const IteratorSeekToLastCallback& callback) override {
FAIL();
}
void IteratorSeek(const base::UnguessableToken& iterator,
const std::vector<uint8_t>& target,
const IteratorSeekToLastCallback& callback) override {
FAIL();
}
void IteratorNext(const base::UnguessableToken& iterator,
const IteratorNextCallback& callback) override {
FAIL();
}
void IteratorPrev(const base::UnguessableToken& iterator,
const IteratorPrevCallback& callback) override {
FAIL();
}
private:
std::map<std::vector<uint8_t>, std::vector<uint8_t>>& mock_data_;
};
void NoOp() {}
void GetCallback(bool* called,
bool* success_out,
std::vector<uint8_t>* value_out,
bool success,
const std::vector<uint8_t>& value) {
*called = true;
*success_out = success;
*value_out = value;
}
void GetAllCallback(bool* called,
leveldb::mojom::DatabaseError* status_out,
std::vector<mojom::KeyValuePtr>* data_out,
leveldb::mojom::DatabaseError status,
std::vector<mojom::KeyValuePtr> data) {
*called = true;
*status_out = status;
*data_out = std::move(data);
}
void SuccessCallback(bool* called, bool* success_out, bool success) {
*called = true;
*success_out = success;
}
void NoOpSuccess(bool success) {}
} // namespace
class LevelDBWrapperImplTest : public testing::Test {
public:
LevelDBWrapperImplTest()
: db_(&mock_data_),
level_db_wrapper_(&db_,
kTestPrefix,
kTestSizeLimit,
base::TimeDelta::FromSeconds(5),
10 * 1024 * 1024 /* max_bytes_per_hour */,
60 /* max_commits_per_hour */,
base::Bind(&NoOp)) {
set_mock_data(std::string(kTestPrefix) + "def", "defdata");
set_mock_data(std::string(kTestPrefix) + "123", "123data");
set_mock_data("123", "baddata");
}
void set_mock_data(const std::string& key, const std::string& value) {
mock_data_[StdStringToUint8Vector(key)] = StdStringToUint8Vector(value);
}
bool has_mock_data(const std::string& key) {
return mock_data_.find(StdStringToUint8Vector(key)) != mock_data_.end();
}
std::string get_mock_data(const std::string& key) {
return has_mock_data(key)
? Uint8VectorToStdString(mock_data_[StdStringToUint8Vector(key)])
: "";
}
mojom::LevelDBWrapper* wrapper() { return &level_db_wrapper_; }
void CommitChanges() {
ASSERT_TRUE(level_db_wrapper_.commit_batch_);
level_db_wrapper_.CommitChanges();
}
private:
TestBrowserThreadBundle thread_bundle_;
std::map<std::vector<uint8_t>, std::vector<uint8_t>> mock_data_;
MockLevelDBDatabase db_;
LevelDBWrapperImpl level_db_wrapper_;
};
TEST_F(LevelDBWrapperImplTest, GetLoadedFromMap) {
bool called = false;
bool success;
std::vector<uint8_t> result;
wrapper()->Get(StdStringToUint8Vector("123"),
base::Bind(&GetCallback, &called, &success, &result));
ASSERT_TRUE(called);
EXPECT_TRUE(success);
EXPECT_EQ(StdStringToUint8Vector("123data"), result);
called = false;
wrapper()->Get(StdStringToUint8Vector("x"),
base::Bind(&GetCallback, &called, &success, &result));
ASSERT_TRUE(called);
EXPECT_FALSE(success);
}
TEST_F(LevelDBWrapperImplTest, GetFromPutOverwrite) {
std::vector<uint8_t> key = StdStringToUint8Vector("123");
std::vector<uint8_t> value = StdStringToUint8Vector("foo");
bool called = false;
bool success;
wrapper()->Put(key, value, kTestSource,
base::Bind(&SuccessCallback, &called, &success));
ASSERT_TRUE(called);
EXPECT_TRUE(success);
called = false;
std::vector<uint8_t> result;
wrapper()->Get(key, base::Bind(&GetCallback, &called, &success, &result));
ASSERT_TRUE(called);
EXPECT_TRUE(success);
EXPECT_EQ(value, result);
}
TEST_F(LevelDBWrapperImplTest, GetFromPutNewKey) {
std::vector<uint8_t> key = StdStringToUint8Vector("newkey");
std::vector<uint8_t> value = StdStringToUint8Vector("foo");
bool called = false;
bool success;
wrapper()->Put(key, value, kTestSource,
base::Bind(&SuccessCallback, &called, &success));
ASSERT_TRUE(called);
EXPECT_TRUE(success);
called = false;
std::vector<uint8_t> result;
wrapper()->Get(key, base::Bind(&GetCallback, &called, &success, &result));
ASSERT_TRUE(called);
EXPECT_TRUE(success);
EXPECT_EQ(value, result);
}
TEST_F(LevelDBWrapperImplTest, GetAll) {
bool called = false;
leveldb::mojom::DatabaseError status;
std::vector<mojom::KeyValuePtr> data;
wrapper()->GetAll(kTestSource,
base::Bind(&GetAllCallback, &called, &status, &data));
ASSERT_TRUE(called);
EXPECT_EQ(leveldb::mojom::DatabaseError::OK, status);
EXPECT_EQ(2u, data.size());
}
TEST_F(LevelDBWrapperImplTest, CommitPutToDB) {
std::string key1 = "123";
std::string value1 = "foo";
std::string key2 = "abc";
std::string value2 = "data abc";
wrapper()->Put(StdStringToUint8Vector(key1), StdStringToUint8Vector(value1),
kTestSource, base::Bind(&NoOpSuccess));
wrapper()->Put(StdStringToUint8Vector(key2),
StdStringToUint8Vector("old value"), kTestSource,
base::Bind(&NoOpSuccess));
wrapper()->Put(StdStringToUint8Vector(key2), StdStringToUint8Vector(value2),
kTestSource, base::Bind(&NoOpSuccess));
CommitChanges();
EXPECT_TRUE(has_mock_data(kTestPrefix + key1));
EXPECT_EQ(value1, get_mock_data(kTestPrefix + key1));
EXPECT_TRUE(has_mock_data(kTestPrefix + key2));
EXPECT_EQ(value2, get_mock_data(kTestPrefix + key2));
}
} // namespace content
......@@ -1097,6 +1097,7 @@ test("content_unittests") {
"../browser/indexed_db/mock_indexed_db_database_callbacks.h",
"../browser/indexed_db/mock_indexed_db_factory.cc",
"../browser/indexed_db/mock_indexed_db_factory.h",
"../browser/leveldb_wrapper_impl_unittest.cc",
"../browser/loader/async_resource_handler_unittest.cc",
"../browser/loader/async_revalidation_driver_unittest.cc",
"../browser/loader/async_revalidation_manager_unittest.cc",
......@@ -1385,6 +1386,8 @@ test("content_unittests") {
"//cc/ipc",
"//cc/surfaces",
"//components/display_compositor",
"//components/leveldb/public/cpp",
"//components/leveldb/public/interfaces",
"//components/payments:payment_app",
"//components/rappor:test_support",
"//content:resources",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment