summaryrefslogtreecommitdiffstats
path: root/components/leveldb_proto
diff options
context:
space:
mode:
authormathp@chromium.org <mathp@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-18 16:22:30 +0000
committermathp@chromium.org <mathp@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-18 16:22:30 +0000
commit2b894b8dae83b4bada9199713504aeb52cdfc8ed (patch)
treef527ed157c42675be946635c08a81288863b4ef5 /components/leveldb_proto
parentab4f95a66b2e01a329562d2f7a2b4445b0e4ee47 (diff)
downloadchromium_src-2b894b8dae83b4bada9199713504aeb52cdfc8ed.zip
chromium_src-2b894b8dae83b4bada9199713504aeb52cdfc8ed.tar.gz
chromium_src-2b894b8dae83b4bada9199713504aeb52cdfc8ed.tar.bz2
Extract protobuf database into a new 'leveldb_proto' component
Code extracted from components/dom_distiller/core/dom_distiller_database.* Slight API change: callers to UpdateEntries now have to pass a vector of (string, proto) as key and value, instead of just a vector of protos where key is derived. Ran clang-format on the files I touched so you may see some diffs. Note: Implementations are in proto_database_impl.h and fake_db.h for proper linking. BUG=385747 TBR=jochen,dgrogan TEST=DomDistiller*,ProtoDatabaseImplTest Review URL: https://codereview.chromium.org/330833002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@278096 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'components/leveldb_proto')
-rw-r--r--components/leveldb_proto/DEPS3
-rw-r--r--components/leveldb_proto/OWNERS2
-rw-r--r--components/leveldb_proto/README6
-rw-r--r--components/leveldb_proto/leveldb_database.cc93
-rw-r--r--components/leveldb_proto/leveldb_database.h42
-rw-r--r--components/leveldb_proto/proto_database.h52
-rw-r--r--components/leveldb_proto/proto_database_impl.h204
-rw-r--r--components/leveldb_proto/proto_database_impl_unittest.cc397
-rw-r--r--components/leveldb_proto/testing/fake_db.h146
-rw-r--r--components/leveldb_proto/testing/proto/test.proto20
10 files changed, 965 insertions, 0 deletions
diff --git a/components/leveldb_proto/DEPS b/components/leveldb_proto/DEPS
new file mode 100644
index 0000000..19109d2
--- /dev/null
+++ b/components/leveldb_proto/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+third_party/leveldatabase/src/include",
+]
diff --git a/components/leveldb_proto/OWNERS b/components/leveldb_proto/OWNERS
new file mode 100644
index 0000000..480350b
--- /dev/null
+++ b/components/leveldb_proto/OWNERS
@@ -0,0 +1,2 @@
+cjhopman@chromium.org
+mathp@chromium.org
diff --git a/components/leveldb_proto/README b/components/leveldb_proto/README
new file mode 100644
index 0000000..6d255eb
--- /dev/null
+++ b/components/leveldb_proto/README
@@ -0,0 +1,6 @@
+This component provides a database that can be used to store Protocol Buffers.
+
+It is based on LevelDB[1]. In this implementation, keys are strings chosen by
+the developer, and values are serialized Protocol Buffers.
+
+[1] https://code.google.com/p/leveldb/
diff --git a/components/leveldb_proto/leveldb_database.cc b/components/leveldb_proto/leveldb_database.cc
new file mode 100644
index 0000000..811ae1e
--- /dev/null
+++ b/components/leveldb_proto/leveldb_database.cc
@@ -0,0 +1,93 @@
+// Copyright 2014 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/leveldb_database.h"
+
+#include <string>
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "third_party/leveldatabase/src/include/leveldb/iterator.h"
+#include "third_party/leveldatabase/src/include/leveldb/options.h"
+#include "third_party/leveldatabase/src/include/leveldb/slice.h"
+#include "third_party/leveldatabase/src/include/leveldb/status.h"
+#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
+
+namespace leveldb_proto {
+
+LevelDB::LevelDB() {}
+
+LevelDB::~LevelDB() { DFAKE_SCOPED_LOCK(thread_checker_); }
+
+bool LevelDB::Init(const base::FilePath& database_dir) {
+ DFAKE_SCOPED_LOCK(thread_checker_);
+
+ leveldb::Options options;
+ options.create_if_missing = true;
+ options.max_open_files = 0; // Use minimum.
+
+ std::string path = database_dir.AsUTF8Unsafe();
+
+ leveldb::DB* db = NULL;
+ leveldb::Status status = leveldb::DB::Open(options, path, &db);
+ if (status.IsCorruption()) {
+ base::DeleteFile(database_dir, true);
+ status = leveldb::DB::Open(options, path, &db);
+ }
+
+ if (status.ok()) {
+ CHECK(db);
+ db_.reset(db);
+ return true;
+ }
+
+ LOG(WARNING) << "Unable to open " << database_dir.value() << ": "
+ << status.ToString();
+ return false;
+}
+
+bool LevelDB::Save(
+ const std::vector<std::pair<std::string, std::string> >& entries_to_save,
+ const std::vector<std::string>& keys_to_remove) {
+ DFAKE_SCOPED_LOCK(thread_checker_);
+
+ leveldb::WriteBatch updates;
+ for (std::vector<std::pair<std::string, std::string> >::const_iterator it =
+ entries_to_save.begin();
+ it != entries_to_save.end(); ++it) {
+ updates.Put(leveldb::Slice(it->first), leveldb::Slice(it->second));
+ }
+ for (std::vector<std::string>::const_iterator it = keys_to_remove.begin();
+ it != keys_to_remove.end(); ++it) {
+ updates.Delete(leveldb::Slice(*it));
+ }
+
+ leveldb::WriteOptions options;
+ options.sync = true;
+ leveldb::Status status = db_->Write(options, &updates);
+ if (status.ok()) return true;
+
+ DLOG(WARNING) << "Failed writing leveldb_proto entries: "
+ << status.ToString();
+ return false;
+}
+
+bool LevelDB::Load(std::vector<std::string>* entries) {
+ DFAKE_SCOPED_LOCK(thread_checker_);
+
+ leveldb::ReadOptions options;
+ scoped_ptr<leveldb::Iterator> db_iterator(db_->NewIterator(options));
+ for (db_iterator->SeekToFirst(); db_iterator->Valid(); db_iterator->Next()) {
+ leveldb::Slice value_slice = db_iterator->value();
+ std::string entry(value_slice.data(), value_slice.size());
+ entries->push_back(entry);
+ }
+ return true;
+}
+
+} // namespace leveldb_proto
diff --git a/components/leveldb_proto/leveldb_database.h b/components/leveldb_proto/leveldb_database.h
new file mode 100644
index 0000000..16d3c30
--- /dev/null
+++ b/components/leveldb_proto/leveldb_database.h
@@ -0,0 +1,42 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_LEVELDB_PROTO_LEVELDB_DATABASE_H_
+#define COMPONENTS_LEVELDB_PROTO_LEVELDB_DATABASE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread_collision_warner.h"
+
+namespace leveldb {
+class DB;
+} // namespace leveldb
+
+namespace leveldb_proto {
+
+// Interacts with the LevelDB third party module.
+// Once constructed, function calls and destruction should all occur on the
+// same thread (not necessarily the same as the constructor).
+class LevelDB {
+ public:
+ LevelDB();
+ virtual ~LevelDB();
+
+ virtual bool Init(const base::FilePath& database_dir);
+ virtual bool Save(
+ const std::vector<std::pair<std::string, std::string> >& pairs_to_save,
+ const std::vector<std::string>& keys_to_remove);
+ virtual bool Load(std::vector<std::string>* entries);
+
+ private:
+ DFAKE_MUTEX(thread_checker_);
+ scoped_ptr<leveldb::DB> db_;
+};
+
+} // namespace leveldb_proto
+
+#endif // COMPONENTS_LEVELDB_PROTO_LEVELDB_DATABASE_H_
diff --git a/components/leveldb_proto/proto_database.h b/components/leveldb_proto/proto_database.h
new file mode 100644
index 0000000..34ee799
--- /dev/null
+++ b/components/leveldb_proto/proto_database.h
@@ -0,0 +1,52 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_LEVELDB_PROTO_PROTO_DATABASE_H_
+#define COMPONENTS_LEVELDB_PROTO_PROTO_DATABASE_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace leveldb_proto {
+
+// Interface for classes providing persistent storage of Protocol Buffer
+// entries (T must be a Proto type extending MessageLite).
+template <typename T>
+class ProtoDatabase {
+ public:
+ typedef base::Callback<void(bool success)> InitCallback;
+ typedef base::Callback<void(bool success)> UpdateCallback;
+ typedef base::Callback<void(bool success, scoped_ptr<std::vector<T> >)>
+ LoadCallback;
+ // A list of key-value (string, T) tuples.
+ typedef std::vector<std::pair<std::string, T> > KeyEntryVector;
+
+ virtual ~ProtoDatabase() {}
+
+ // Asynchronously initializes the object. |callback| will be invoked on the
+ // calling thread when complete.
+ virtual void Init(const base::FilePath& database_dir,
+ InitCallback callback) = 0;
+
+ // Asynchronously saves |entries_to_save| and deletes entries from
+ // |keys_to_remove| from the database. |callback| will be invoked on the
+ // calling thread when complete.
+ virtual void UpdateEntries(
+ scoped_ptr<KeyEntryVector> entries_to_save,
+ scoped_ptr<std::vector<std::string> > keys_to_remove,
+ UpdateCallback callback) = 0;
+
+ // Asynchronously loads all entries from the database and invokes |callback|
+ // when complete.
+ virtual void LoadEntries(LoadCallback callback) = 0;
+};
+
+} // namespace leveldb_proto
+
+#endif // COMPONENTS_LEVELDB_PROTO_PROTO_DATABASE_H_
diff --git a/components/leveldb_proto/proto_database_impl.h b/components/leveldb_proto/proto_database_impl.h
new file mode 100644
index 0000000..1b1c740
--- /dev/null
+++ b/components/leveldb_proto/proto_database_impl.h
@@ -0,0 +1,204 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_LEVELDB_PROTO_PROTO_DATABASE_IMPL_H_
+#define COMPONENTS_LEVELDB_PROTO_PROTO_DATABASE_IMPL_H_
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/string_util.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "base/threading/thread_checker.h"
+#include "components/leveldb_proto/leveldb_database.h"
+#include "components/leveldb_proto/proto_database.h"
+
+namespace leveldb_proto {
+
+typedef std::vector<std::pair<std::string, std::string> > KeyValueVector;
+typedef std::vector<std::string> KeyVector;
+
+// When the ProtoDatabaseImpl instance is deleted, in-progress asynchronous
+// operations will be completed and the corresponding callbacks will be called.
+// Construction/calls/destruction should all happen on the same thread.
+template <typename T>
+class ProtoDatabaseImpl : public ProtoDatabase<T> {
+ public:
+ // All blocking calls/disk access will happen on the provided |task_runner|.
+ explicit ProtoDatabaseImpl(
+ scoped_refptr<base::SequencedTaskRunner> task_runner);
+
+ virtual ~ProtoDatabaseImpl();
+
+ // ProtoDatabase implementation.
+ // TODO(cjhopman): Perhaps Init() shouldn't be exposed to users and not just
+ // part of the constructor
+ virtual void Init(const base::FilePath& database_dir,
+ typename ProtoDatabase<T>::InitCallback callback) OVERRIDE;
+ virtual void UpdateEntries(
+ scoped_ptr<typename ProtoDatabase<T>::KeyEntryVector> entries_to_save,
+ scoped_ptr<KeyVector> keys_to_remove,
+ typename ProtoDatabase<T>::UpdateCallback callback) OVERRIDE;
+ virtual void LoadEntries(
+ typename ProtoDatabase<T>::LoadCallback callback) OVERRIDE;
+
+ // Allow callers to provide their own Database implementation.
+ void InitWithDatabase(scoped_ptr<LevelDB> database,
+ const base::FilePath& database_dir,
+ typename ProtoDatabase<T>::InitCallback callback);
+
+ private:
+ base::ThreadChecker thread_checker_;
+
+ // Used to run blocking tasks in-order.
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ scoped_ptr<LevelDB> db_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProtoDatabaseImpl);
+};
+
+namespace {
+
+template <typename T>
+void RunInitCallback(typename ProtoDatabase<T>::InitCallback callback,
+ const bool* success) {
+ callback.Run(*success);
+}
+
+template <typename T>
+void RunUpdateCallback(typename ProtoDatabase<T>::UpdateCallback callback,
+ const bool* success) {
+ callback.Run(*success);
+}
+
+template <typename T>
+void RunLoadCallback(typename ProtoDatabase<T>::LoadCallback callback,
+ const bool* success, scoped_ptr<std::vector<T> > entries) {
+ callback.Run(*success, entries.Pass());
+}
+
+void InitFromTaskRunner(LevelDB* database, const base::FilePath& database_dir,
+ bool* success) {
+ DCHECK(success);
+
+ // TODO(cjhopman): Histogram for database size.
+ *success = database->Init(database_dir);
+}
+
+template <typename T>
+void UpdateEntriesFromTaskRunner(
+ LevelDB* database,
+ scoped_ptr<typename ProtoDatabase<T>::KeyEntryVector> entries_to_save,
+ scoped_ptr<KeyVector> keys_to_remove, bool* success) {
+ DCHECK(success);
+ // Serialize the values from Proto to string before passing on to database.
+ KeyValueVector pairs_to_save;
+ for (typename ProtoDatabase<T>::KeyEntryVector::iterator it =
+ entries_to_save->begin();
+ it != entries_to_save->end(); ++it) {
+ pairs_to_save.push_back(
+ std::make_pair(it->first, it->second.SerializeAsString()));
+ }
+ *success = database->Save(pairs_to_save, *keys_to_remove);
+}
+
+template <typename T>
+void LoadEntriesFromTaskRunner(LevelDB* database, std::vector<T>* entries,
+ bool* success) {
+ DCHECK(success);
+ DCHECK(entries);
+
+ entries->clear();
+ std::vector<std::string> loaded_entries;
+ *success = database->Load(&loaded_entries);
+ for (std::vector<std::string>::iterator it = loaded_entries.begin();
+ it != loaded_entries.end(); ++it) {
+ T entry;
+ if (!entry.ParseFromString(*it)) {
+ DLOG(WARNING) << "Unable to parse leveldb_proto entry " << *it;
+ // TODO(cjhopman): Decide what to do about un-parseable entries.
+ }
+ entries->push_back(entry);
+ }
+}
+
+} // namespace
+
+template <typename T>
+ProtoDatabaseImpl<T>::ProtoDatabaseImpl(
+ scoped_refptr<base::SequencedTaskRunner> task_runner)
+ : task_runner_(task_runner) {}
+
+template <typename T>
+ProtoDatabaseImpl<T>::~ProtoDatabaseImpl() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!task_runner_->DeleteSoon(FROM_HERE, db_.release())) {
+ DLOG(WARNING) << "DOM distiller database will not be deleted.";
+ }
+}
+
+template <typename T>
+void ProtoDatabaseImpl<T>::Init(
+ const base::FilePath& database_dir,
+ typename ProtoDatabase<T>::InitCallback callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ InitWithDatabase(scoped_ptr<LevelDB>(new LevelDB()), database_dir, callback);
+}
+
+template <typename T>
+void ProtoDatabaseImpl<T>::InitWithDatabase(
+ scoped_ptr<LevelDB> database, const base::FilePath& database_dir,
+ typename ProtoDatabase<T>::InitCallback callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!db_);
+ DCHECK(database);
+ db_.reset(database.release());
+ bool* success = new bool(false);
+ task_runner_->PostTaskAndReply(
+ FROM_HERE, base::Bind(InitFromTaskRunner, base::Unretained(db_.get()),
+ database_dir, success),
+ base::Bind(RunInitCallback<T>, callback, base::Owned(success)));
+}
+
+template <typename T>
+void ProtoDatabaseImpl<T>::UpdateEntries(
+ scoped_ptr<typename ProtoDatabase<T>::KeyEntryVector> entries_to_save,
+ scoped_ptr<KeyVector> keys_to_remove,
+ typename ProtoDatabase<T>::UpdateCallback callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ bool* success = new bool(false);
+ task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(UpdateEntriesFromTaskRunner<T>, base::Unretained(db_.get()),
+ base::Passed(&entries_to_save), base::Passed(&keys_to_remove),
+ success),
+ base::Bind(RunUpdateCallback<T>, callback, base::Owned(success)));
+}
+
+template <typename T>
+void ProtoDatabaseImpl<T>::LoadEntries(
+ typename ProtoDatabase<T>::LoadCallback callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ bool* success = new bool(false);
+
+ scoped_ptr<std::vector<T> > entries(new std::vector<T>());
+ // Get this pointer before entries is base::Passed() so we can use it below.
+ std::vector<T>* entries_ptr = entries.get();
+
+ task_runner_->PostTaskAndReply(
+ FROM_HERE, base::Bind(LoadEntriesFromTaskRunner<T>,
+ base::Unretained(db_.get()), entries_ptr, success),
+ base::Bind(RunLoadCallback<T>, callback, base::Owned(success),
+ base::Passed(&entries)));
+}
+
+} // namespace leveldb_proto
+
+#endif // COMPONENTS_LEVELDB_PROTO_PROTO_DATABASE_IMPL_H_
diff --git a/components/leveldb_proto/proto_database_impl_unittest.cc b/components/leveldb_proto/proto_database_impl_unittest.cc
new file mode 100644
index 0000000..073328b
--- /dev/null
+++ b/components/leveldb_proto/proto_database_impl_unittest.cc
@@ -0,0 +1,397 @@
+// Copyright 2014 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/proto_database_impl.h"
+
+#include <map>
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
+#include "base/threading/thread.h"
+#include "components/leveldb_proto/leveldb_database.h"
+#include "components/leveldb_proto/testing/proto/test.pb.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::MessageLoop;
+using base::ScopedTempDir;
+using testing::Invoke;
+using testing::Return;
+using testing::_;
+
+namespace leveldb_proto {
+
+namespace {
+
+typedef std::map<std::string, TestProto> EntryMap;
+
+class MockDB : public LevelDB {
+ public:
+ MOCK_METHOD1(Init, bool(const base::FilePath&));
+ MOCK_METHOD2(Save, bool(const KeyValueVector&, const KeyVector&));
+ MOCK_METHOD1(Load, bool(std::vector<std::string>*));
+
+ MockDB() {
+ ON_CALL(*this, Init(_)).WillByDefault(Return(true));
+ ON_CALL(*this, Save(_, _)).WillByDefault(Return(true));
+ ON_CALL(*this, Load(_)).WillByDefault(Return(true));
+ }
+};
+
+class MockDatabaseCaller {
+ public:
+ MOCK_METHOD1(InitCallback, void(bool));
+ MOCK_METHOD1(SaveCallback, void(bool));
+ void LoadCallback(bool success, scoped_ptr<std::vector<TestProto> > entries) {
+ LoadCallback1(success, entries.get());
+ }
+ MOCK_METHOD2(LoadCallback1, void(bool, std::vector<TestProto>*));
+};
+
+} // namespace
+
+EntryMap GetSmallModel() {
+ EntryMap model;
+
+ model["0"].set_id("0");
+ model["0"].set_data("http://foo.com/1");
+
+ model["1"].set_id("1");
+ model["1"].set_data("http://bar.com/all");
+
+ model["2"].set_id("2");
+ model["2"].set_data("http://baz.com/1");
+
+ return model;
+}
+
+void ExpectEntryPointersEquals(EntryMap expected,
+ const std::vector<TestProto>& actual) {
+ EXPECT_EQ(expected.size(), actual.size());
+ for (size_t i = 0; i < actual.size(); i++) {
+ EntryMap::iterator expected_it = expected.find(actual[i].id());
+ EXPECT_TRUE(expected_it != expected.end());
+ std::string serialized_expected = expected_it->second.SerializeAsString();
+ std::string serialized_actual = actual[i].SerializeAsString();
+ EXPECT_EQ(serialized_expected, serialized_actual);
+ expected.erase(expected_it);
+ }
+}
+
+class ProtoDatabaseImplTest : public testing::Test {
+ public:
+ virtual void SetUp() {
+ main_loop_.reset(new MessageLoop());
+ db_.reset(
+ new ProtoDatabaseImpl<TestProto>(main_loop_->message_loop_proxy()));
+ }
+
+ virtual void TearDown() {
+ db_.reset();
+ base::RunLoop().RunUntilIdle();
+ main_loop_.reset();
+ }
+
+ scoped_ptr<ProtoDatabaseImpl<TestProto> > db_;
+ scoped_ptr<MessageLoop> main_loop_;
+};
+
+// Test that ProtoDatabaseImpl calls Init on the underlying database and that
+// the caller's InitCallback is called with the correct value.
+TEST_F(ProtoDatabaseImplTest, TestDBInitSuccess) {
+ base::FilePath path(FILE_PATH_LITERAL("/fake/path"));
+
+ MockDB* mock_db = new MockDB();
+ EXPECT_CALL(*mock_db, Init(path)).WillOnce(Return(true));
+
+ MockDatabaseCaller caller;
+ EXPECT_CALL(caller, InitCallback(true));
+
+ db_->InitWithDatabase(
+ scoped_ptr<LevelDB>(mock_db), base::FilePath(path),
+ base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller)));
+
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(ProtoDatabaseImplTest, TestDBInitFailure) {
+ base::FilePath path(FILE_PATH_LITERAL("/fake/path"));
+
+ MockDB* mock_db = new MockDB();
+ EXPECT_CALL(*mock_db, Init(path)).WillOnce(Return(false));
+
+ MockDatabaseCaller caller;
+ EXPECT_CALL(caller, InitCallback(false));
+
+ db_->InitWithDatabase(
+ scoped_ptr<LevelDB>(mock_db), base::FilePath(path),
+ base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller)));
+
+ base::RunLoop().RunUntilIdle();
+}
+
+ACTION_P(AppendLoadEntries, model) {
+ std::vector<std::string>* output = arg0;
+ for (EntryMap::const_iterator it = model.begin(); it != model.end(); ++it) {
+ output->push_back(it->second.SerializeAsString());
+ }
+ return true;
+}
+
+ACTION_P(VerifyLoadEntries, expected) {
+ std::vector<TestProto>* actual = arg1;
+ ExpectEntryPointersEquals(expected, *actual);
+}
+
+// Test that ProtoDatabaseImpl calls Load on the underlying database and that
+// the caller's LoadCallback is called with the correct success value. Also
+// confirms that on success, the expected entries are passed to the caller's
+// LoadCallback.
+TEST_F(ProtoDatabaseImplTest, TestDBLoadSuccess) {
+ base::FilePath path(FILE_PATH_LITERAL("/fake/path"));
+
+ MockDB* mock_db = new MockDB();
+ MockDatabaseCaller caller;
+ EntryMap model = GetSmallModel();
+
+ EXPECT_CALL(*mock_db, Init(_));
+ EXPECT_CALL(caller, InitCallback(_));
+ db_->InitWithDatabase(
+ scoped_ptr<LevelDB>(mock_db), base::FilePath(path),
+ base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller)));
+
+ EXPECT_CALL(*mock_db, Load(_)).WillOnce(AppendLoadEntries(model));
+ EXPECT_CALL(caller, LoadCallback1(true, _))
+ .WillOnce(VerifyLoadEntries(testing::ByRef(model)));
+ db_->LoadEntries(
+ base::Bind(&MockDatabaseCaller::LoadCallback, base::Unretained(&caller)));
+
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(ProtoDatabaseImplTest, TestDBLoadFailure) {
+ base::FilePath path(FILE_PATH_LITERAL("/fake/path"));
+
+ MockDB* mock_db = new MockDB();
+ MockDatabaseCaller caller;
+
+ EXPECT_CALL(*mock_db, Init(_));
+ EXPECT_CALL(caller, InitCallback(_));
+ db_->InitWithDatabase(
+ scoped_ptr<LevelDB>(mock_db), base::FilePath(path),
+ base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller)));
+
+ EXPECT_CALL(*mock_db, Load(_)).WillOnce(Return(false));
+ EXPECT_CALL(caller, LoadCallback1(false, _));
+ db_->LoadEntries(
+ base::Bind(&MockDatabaseCaller::LoadCallback, base::Unretained(&caller)));
+
+ base::RunLoop().RunUntilIdle();
+}
+
+ACTION_P(VerifyUpdateEntries, expected) {
+ const KeyValueVector actual = arg0;
+ // Create a vector of TestProto from |actual| to reuse the comparison
+ // function.
+ std::vector<TestProto> extracted_entries;
+ for (KeyValueVector::const_iterator it = actual.begin(); it != actual.end();
+ ++it) {
+ TestProto entry;
+ entry.ParseFromString(it->second);
+ extracted_entries.push_back(entry);
+ }
+ ExpectEntryPointersEquals(expected, extracted_entries);
+ return true;
+}
+
+// Test that ProtoDatabaseImpl calls Save on the underlying database with the
+// correct entries to save and that the caller's SaveCallback is called with the
+// correct success value.
+TEST_F(ProtoDatabaseImplTest, TestDBSaveSuccess) {
+ base::FilePath path(FILE_PATH_LITERAL("/fake/path"));
+
+ MockDB* mock_db = new MockDB();
+ MockDatabaseCaller caller;
+ EntryMap model = GetSmallModel();
+
+ EXPECT_CALL(*mock_db, Init(_));
+ EXPECT_CALL(caller, InitCallback(_));
+ db_->InitWithDatabase(
+ scoped_ptr<LevelDB>(mock_db), base::FilePath(path),
+ base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller)));
+
+ scoped_ptr<ProtoDatabase<TestProto>::KeyEntryVector> entries(
+ new ProtoDatabase<TestProto>::KeyEntryVector());
+ for (EntryMap::iterator it = model.begin(); it != model.end(); ++it) {
+ entries->push_back(std::make_pair(it->second.id(), it->second));
+ }
+ scoped_ptr<KeyVector> keys_to_remove(new KeyVector());
+
+ EXPECT_CALL(*mock_db, Save(_, _)).WillOnce(VerifyUpdateEntries(model));
+ EXPECT_CALL(caller, SaveCallback(true));
+ db_->UpdateEntries(
+ entries.Pass(), keys_to_remove.Pass(),
+ base::Bind(&MockDatabaseCaller::SaveCallback, base::Unretained(&caller)));
+
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(ProtoDatabaseImplTest, TestDBSaveFailure) {
+ base::FilePath path(FILE_PATH_LITERAL("/fake/path"));
+
+ MockDB* mock_db = new MockDB();
+ MockDatabaseCaller caller;
+ scoped_ptr<ProtoDatabase<TestProto>::KeyEntryVector> entries(
+ new ProtoDatabase<TestProto>::KeyEntryVector());
+ scoped_ptr<KeyVector> keys_to_remove(new KeyVector());
+
+ EXPECT_CALL(*mock_db, Init(_));
+ EXPECT_CALL(caller, InitCallback(_));
+ db_->InitWithDatabase(
+ scoped_ptr<LevelDB>(mock_db), base::FilePath(path),
+ base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller)));
+
+ EXPECT_CALL(*mock_db, Save(_, _)).WillOnce(Return(false));
+ EXPECT_CALL(caller, SaveCallback(false));
+ db_->UpdateEntries(
+ entries.Pass(), keys_to_remove.Pass(),
+ base::Bind(&MockDatabaseCaller::SaveCallback, base::Unretained(&caller)));
+
+ base::RunLoop().RunUntilIdle();
+}
+
+// Test that ProtoDatabaseImpl calls Save on the underlying database with the
+// correct entries to delete and that the caller's SaveCallback is called with
+// the correct success value.
+TEST_F(ProtoDatabaseImplTest, TestDBRemoveSuccess) {
+ base::FilePath path(FILE_PATH_LITERAL("/fake/path"));
+
+ MockDB* mock_db = new MockDB();
+ MockDatabaseCaller caller;
+ EntryMap model = GetSmallModel();
+
+ EXPECT_CALL(*mock_db, Init(_));
+ EXPECT_CALL(caller, InitCallback(_));
+ db_->InitWithDatabase(
+ scoped_ptr<LevelDB>(mock_db), base::FilePath(path),
+ base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller)));
+
+ scoped_ptr<ProtoDatabase<TestProto>::KeyEntryVector> entries(
+ new ProtoDatabase<TestProto>::KeyEntryVector());
+ scoped_ptr<KeyVector> keys_to_remove(new KeyVector());
+ for (EntryMap::iterator it = model.begin(); it != model.end(); ++it) {
+ keys_to_remove->push_back(it->second.id());
+ }
+
+ KeyVector keys_copy(*keys_to_remove.get());
+ EXPECT_CALL(*mock_db, Save(_, keys_copy)).WillOnce(Return(true));
+ EXPECT_CALL(caller, SaveCallback(true));
+ db_->UpdateEntries(
+ entries.Pass(), keys_to_remove.Pass(),
+ base::Bind(&MockDatabaseCaller::SaveCallback, base::Unretained(&caller)));
+
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(ProtoDatabaseImplTest, TestDBRemoveFailure) {
+ base::FilePath path(FILE_PATH_LITERAL("/fake/path"));
+
+ MockDB* mock_db = new MockDB();
+ MockDatabaseCaller caller;
+ scoped_ptr<ProtoDatabase<TestProto>::KeyEntryVector> entries(
+ new ProtoDatabase<TestProto>::KeyEntryVector());
+ scoped_ptr<KeyVector> keys_to_remove(new KeyVector());
+
+ EXPECT_CALL(*mock_db, Init(_));
+ EXPECT_CALL(caller, InitCallback(_));
+ db_->InitWithDatabase(
+ scoped_ptr<LevelDB>(mock_db), base::FilePath(path),
+ base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller)));
+
+ EXPECT_CALL(*mock_db, Save(_, _)).WillOnce(Return(false));
+ EXPECT_CALL(caller, SaveCallback(false));
+ db_->UpdateEntries(
+ entries.Pass(), keys_to_remove.Pass(),
+ base::Bind(&MockDatabaseCaller::SaveCallback, base::Unretained(&caller)));
+
+ base::RunLoop().RunUntilIdle();
+}
+
+// This tests that normal usage of the real database does not cause any
+// threading violations.
+TEST(ProtoDatabaseImplThreadingTest, TestDBDestruction) {
+ base::MessageLoop main_loop;
+
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ base::Thread db_thread("dbthread");
+ ASSERT_TRUE(db_thread.Start());
+
+ scoped_ptr<ProtoDatabaseImpl<TestProto> > db(
+ new ProtoDatabaseImpl<TestProto>(db_thread.message_loop_proxy()));
+
+ MockDatabaseCaller caller;
+ EXPECT_CALL(caller, InitCallback(_));
+ db->Init(temp_dir.path(), base::Bind(&MockDatabaseCaller::InitCallback,
+ base::Unretained(&caller)));
+
+ db.reset();
+
+ base::RunLoop run_loop;
+ db_thread.message_loop_proxy()->PostTaskAndReply(
+ FROM_HERE, base::Bind(base::DoNothing), run_loop.QuitClosure());
+ run_loop.Run();
+}
+
+// Test that the LevelDB properly saves entries and that load returns the saved
+// entries. If |close_after_save| is true, the database will be closed after
+// saving and then re-opened to ensure that the data is properly persisted.
+void TestLevelDBSaveAndLoad(bool close_after_save) {
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ EntryMap model = GetSmallModel();
+
+ KeyValueVector save_entries;
+ std::vector<std::string> load_entries;
+ KeyVector remove_keys;
+
+ for (EntryMap::iterator it = model.begin(); it != model.end(); ++it) {
+ save_entries.push_back(
+ std::make_pair(it->second.id(), it->second.SerializeAsString()));
+ }
+
+ scoped_ptr<LevelDB> db(new LevelDB());
+ EXPECT_TRUE(db->Init(temp_dir.path()));
+ EXPECT_TRUE(db->Save(save_entries, remove_keys));
+
+ if (close_after_save) {
+ db.reset(new LevelDB());
+ EXPECT_TRUE(db->Init(temp_dir.path()));
+ }
+
+ EXPECT_TRUE(db->Load(&load_entries));
+ // Convert the strings back to TestProto.
+ std::vector<TestProto> loaded_protos;
+ for (std::vector<std::string>::iterator it = load_entries.begin();
+ it != load_entries.end(); ++it) {
+ TestProto entry;
+ entry.ParseFromString(*it);
+ loaded_protos.push_back(entry);
+ }
+ ExpectEntryPointersEquals(model, loaded_protos);
+}
+
+TEST(ProtoDatabaseImplLevelDBTest, TestDBSaveAndLoad) {
+ TestLevelDBSaveAndLoad(false);
+}
+
+TEST(ProtoDatabaseImplLevelDBTest, TestDBCloseAndReopen) {
+ TestLevelDBSaveAndLoad(true);
+}
+
+} // namespace leveldb_proto
diff --git a/components/leveldb_proto/testing/fake_db.h b/components/leveldb_proto/testing/fake_db.h
new file mode 100644
index 0000000..8e57e0e
--- /dev/null
+++ b/components/leveldb_proto/testing/fake_db.h
@@ -0,0 +1,146 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_LEVELDB_PROTO_TESTING_FAKE_DB_H_
+#define COMPONENTS_LEVELDB_PROTO_TESTING_FAKE_DB_H_
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/leveldb_proto/proto_database.h"
+
+namespace leveldb_proto {
+namespace test {
+
+template <typename T>
+class FakeDB : public ProtoDatabase<T> {
+ typedef base::Callback<void(bool)> Callback;
+
+ public:
+ typedef typename base::hash_map<std::string, T> EntryMap;
+
+ explicit FakeDB(EntryMap* db);
+ virtual ~FakeDB();
+
+ virtual void Init(const base::FilePath& database_dir,
+ typename ProtoDatabase<T>::InitCallback callback)
+ OVERRIDE;
+
+ virtual void UpdateEntries(
+ scoped_ptr<typename ProtoDatabase<T>::KeyEntryVector> entries_to_save,
+ scoped_ptr<std::vector<std::string> > keys_to_remove,
+ typename ProtoDatabase<T>::UpdateCallback callback) OVERRIDE;
+
+ virtual void LoadEntries(typename ProtoDatabase<T>::LoadCallback callback)
+ OVERRIDE;
+ base::FilePath& GetDirectory();
+
+ void InitCallback(bool success);
+
+ void LoadCallback(bool success);
+
+ void UpdateCallback(bool success);
+
+ static base::FilePath DirectoryForTestDB();
+
+ private:
+ static void RunLoadCallback(
+ typename ProtoDatabase<T>::LoadCallback callback,
+ scoped_ptr<typename std::vector<T> > entries,
+ bool success);
+
+ base::FilePath dir_;
+ EntryMap* db_;
+
+ Callback init_callback_;
+ Callback load_callback_;
+ Callback update_callback_;
+};
+
+template <typename T>
+FakeDB<T>::FakeDB(EntryMap* db)
+ : db_(db) {}
+
+template <typename T>
+FakeDB<T>::~FakeDB() {}
+
+template <typename T>
+void FakeDB<T>::Init(const base::FilePath& database_dir,
+ typename ProtoDatabase<T>::InitCallback callback) {
+ dir_ = database_dir;
+ init_callback_ = callback;
+}
+
+template <typename T>
+void FakeDB<T>::UpdateEntries(
+ scoped_ptr<typename ProtoDatabase<T>::KeyEntryVector> entries_to_save,
+ scoped_ptr<std::vector<std::string> > keys_to_remove,
+ typename ProtoDatabase<T>::UpdateCallback callback) {
+ for (typename ProtoDatabase<T>::KeyEntryVector::iterator it =
+ entries_to_save->begin();
+ it != entries_to_save->end(); ++it) {
+ (*db_)[it->first] = it->second;
+ }
+ for (std::vector<std::string>::iterator it = keys_to_remove->begin();
+ it != keys_to_remove->end(); ++it) {
+ (*db_).erase(*it);
+ }
+ update_callback_ = callback;
+}
+
+template <typename T>
+void FakeDB<T>::LoadEntries(typename ProtoDatabase<T>::LoadCallback callback) {
+ scoped_ptr<std::vector<T> > entries(new std::vector<T>());
+ for (typename EntryMap::iterator it = db_->begin(); it != db_->end(); ++it) {
+ entries->push_back(it->second);
+ }
+ load_callback_ =
+ base::Bind(RunLoadCallback, callback, base::Passed(&entries));
+}
+
+template <typename T>
+base::FilePath& FakeDB<T>::GetDirectory() {
+ return dir_;
+}
+
+template <typename T>
+void FakeDB<T>::InitCallback(bool success) {
+ init_callback_.Run(success);
+ init_callback_.Reset();
+}
+
+template <typename T>
+void FakeDB<T>::LoadCallback(bool success) {
+ load_callback_.Run(success);
+ load_callback_.Reset();
+}
+
+template <typename T>
+void FakeDB<T>::UpdateCallback(bool success) {
+ update_callback_.Run(success);
+ update_callback_.Reset();
+}
+
+// static
+template <typename T>
+void FakeDB<T>::RunLoadCallback(
+ typename ProtoDatabase<T>::LoadCallback callback,
+ scoped_ptr<typename std::vector<T> > entries, bool success) {
+ callback.Run(success, entries.Pass());
+}
+
+// static
+template <typename T>
+base::FilePath FakeDB<T>::DirectoryForTestDB() {
+ return base::FilePath(FILE_PATH_LITERAL("/fake/path"));
+}
+
+} // namespace test
+} // namespace leveldb_proto
+
+#endif // COMPONENTS_LEVELDB_PROTO_TESTING_FAKE_DB_H_
diff --git a/components/leveldb_proto/testing/proto/test.proto b/components/leveldb_proto/testing/proto/test.proto
new file mode 100644
index 0000000..8498105
--- /dev/null
+++ b/components/leveldb_proto/testing/proto/test.proto
@@ -0,0 +1,20 @@
+// Copyright 2014 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.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package leveldb_proto;
+
+// A proto to test leveldb_proto::ProtoDatabaseImpl.
+//
+// Next tag: 3
+message TestProto {
+ // Arbitary id.
+ optional string id = 1;
+
+ // Arbitrary data.
+ optional string data = 2;
+}