summaryrefslogtreecommitdiffstats
path: root/extensions/browser/value_store
diff options
context:
space:
mode:
authorjamescook@chromium.org <jamescook@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-01 00:34:00 +0000
committerjamescook@chromium.org <jamescook@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-01 00:34:00 +0000
commit47b870f6f82d7b1f05ddffb0349d1431cb181426 (patch)
tree64537612db9d86a1d0d8b447d169f51ee9d9bbb1 /extensions/browser/value_store
parent31463d3a32678cb95e4d6e53cd942780f81c1082 (diff)
downloadchromium_src-47b870f6f82d7b1f05ddffb0349d1431cb181426.zip
chromium_src-47b870f6f82d7b1f05ddffb0349d1431cb181426.tar.gz
chromium_src-47b870f6f82d7b1f05ddffb0349d1431cb181426.tar.bz2
Move chrome/browser/value_store to extensions/value_store
The extensions storage API implementation is going to be moved to src/extensions so that app_shell can use it. It uses value_store extensively, so that has to move out of src/chrome first. BUG=348058 TEST=unit_tests TBR=zea@chromium.org for renaming a header used in chrome/browser/sync TBR=mpcomplete@chromium.org for releasing him from ownership of this module :-) Review URL: https://codereview.chromium.org/184863002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@254294 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'extensions/browser/value_store')
-rw-r--r--extensions/browser/value_store/leveldb_value_store.cc440
-rw-r--r--extensions/browser/value_store/leveldb_value_store.h99
-rw-r--r--extensions/browser/value_store/leveldb_value_store_unittest.cc184
-rw-r--r--extensions/browser/value_store/testing_value_store.cc138
-rw-r--r--extensions/browser/value_store/testing_value_store.h62
-rw-r--r--extensions/browser/value_store/testing_value_store_unittest.cc24
-rw-r--r--extensions/browser/value_store/value_store.cc45
-rw-r--r--extensions/browser/value_store/value_store.h221
-rw-r--r--extensions/browser/value_store/value_store_change.cc55
-rw-r--r--extensions/browser/value_store/value_store_change.h60
-rw-r--r--extensions/browser/value_store/value_store_change_unittest.cc96
-rw-r--r--extensions/browser/value_store/value_store_frontend.cc143
-rw-r--r--extensions/browser/value_store/value_store_frontend.h57
-rw-r--r--extensions/browser/value_store/value_store_frontend_unittest.cc114
-rw-r--r--extensions/browser/value_store/value_store_unittest.cc481
-rw-r--r--extensions/browser/value_store/value_store_unittest.h72
-rw-r--r--extensions/browser/value_store/value_store_util.cc21
-rw-r--r--extensions/browser/value_store/value_store_util.h29
18 files changed, 2341 insertions, 0 deletions
diff --git a/extensions/browser/value_store/leveldb_value_store.cc b/extensions/browser/value_store/leveldb_value_store.cc
new file mode 100644
index 0000000..58a8cb5
--- /dev/null
+++ b/extensions/browser/value_store/leveldb_value_store.cc
@@ -0,0 +1,440 @@
+// 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 "extensions/browser/value_store/leveldb_value_store.h"
+
+#include "base/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/sys_string_conversions.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/value_store/value_store_util.h"
+#include "third_party/leveldatabase/src/include/leveldb/iterator.h"
+#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
+
+namespace util = value_store_util;
+using content::BrowserThread;
+
+namespace {
+
+const char kInvalidJson[] = "Invalid JSON";
+
+// Scoped leveldb snapshot which releases the snapshot on destruction.
+class ScopedSnapshot {
+ public:
+ explicit ScopedSnapshot(leveldb::DB* db)
+ : db_(db), snapshot_(db->GetSnapshot()) {}
+
+ ~ScopedSnapshot() {
+ db_->ReleaseSnapshot(snapshot_);
+ }
+
+ const leveldb::Snapshot* get() {
+ return snapshot_;
+ }
+
+ private:
+ leveldb::DB* db_;
+ const leveldb::Snapshot* snapshot_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedSnapshot);
+};
+
+} // namespace
+
+LeveldbValueStore::LeveldbValueStore(const base::FilePath& db_path)
+ : db_path_(db_path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ scoped_ptr<Error> open_error = EnsureDbIsOpen();
+ if (open_error)
+ LOG(WARNING) << open_error->message;
+}
+
+LeveldbValueStore::~LeveldbValueStore() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ // Delete the database from disk if it's empty (but only if we managed to
+ // open it!). This is safe on destruction, assuming that we have exclusive
+ // access to the database.
+ if (db_ && IsEmpty())
+ DeleteDbFile();
+}
+
+size_t LeveldbValueStore::GetBytesInUse(const std::string& key) {
+ // Let SettingsStorageQuotaEnforcer implement this.
+ NOTREACHED() << "Not implemented";
+ return 0;
+}
+
+size_t LeveldbValueStore::GetBytesInUse(
+ const std::vector<std::string>& keys) {
+ // Let SettingsStorageQuotaEnforcer implement this.
+ NOTREACHED() << "Not implemented";
+ return 0;
+}
+
+size_t LeveldbValueStore::GetBytesInUse() {
+ // Let SettingsStorageQuotaEnforcer implement this.
+ NOTREACHED() << "Not implemented";
+ return 0;
+}
+
+ValueStore::ReadResult LeveldbValueStore::Get(const std::string& key) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ scoped_ptr<Error> open_error = EnsureDbIsOpen();
+ if (open_error)
+ return MakeReadResult(open_error.Pass());
+
+ scoped_ptr<base::Value> setting;
+ scoped_ptr<Error> error = ReadFromDb(leveldb::ReadOptions(), key, &setting);
+ if (error)
+ return MakeReadResult(error.Pass());
+
+ base::DictionaryValue* settings = new base::DictionaryValue();
+ if (setting)
+ settings->SetWithoutPathExpansion(key, setting.release());
+ return MakeReadResult(make_scoped_ptr(settings));
+}
+
+ValueStore::ReadResult LeveldbValueStore::Get(
+ const std::vector<std::string>& keys) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ scoped_ptr<Error> open_error = EnsureDbIsOpen();
+ if (open_error)
+ return MakeReadResult(open_error.Pass());
+
+ leveldb::ReadOptions options;
+ scoped_ptr<base::DictionaryValue> settings(new base::DictionaryValue());
+
+ // All interaction with the db is done on the same thread, so snapshotting
+ // isn't strictly necessary. This is just defensive.
+ ScopedSnapshot snapshot(db_.get());
+ options.snapshot = snapshot.get();
+ for (std::vector<std::string>::const_iterator it = keys.begin();
+ it != keys.end(); ++it) {
+ scoped_ptr<base::Value> setting;
+ scoped_ptr<Error> error = ReadFromDb(options, *it, &setting);
+ if (error)
+ return MakeReadResult(error.Pass());
+ if (setting)
+ settings->SetWithoutPathExpansion(*it, setting.release());
+ }
+
+ return MakeReadResult(settings.Pass());
+}
+
+ValueStore::ReadResult LeveldbValueStore::Get() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ scoped_ptr<Error> open_error = EnsureDbIsOpen();
+ if (open_error)
+ return MakeReadResult(open_error.Pass());
+
+ base::JSONReader json_reader;
+ leveldb::ReadOptions options = leveldb::ReadOptions();
+ // All interaction with the db is done on the same thread, so snapshotting
+ // isn't strictly necessary. This is just defensive.
+ scoped_ptr<base::DictionaryValue> settings(new base::DictionaryValue());
+
+ ScopedSnapshot snapshot(db_.get());
+ options.snapshot = snapshot.get();
+ scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options));
+ for (it->SeekToFirst(); it->Valid(); it->Next()) {
+ std::string key = it->key().ToString();
+ base::Value* value = json_reader.ReadToValue(it->value().ToString());
+ if (!value) {
+ return MakeReadResult(
+ Error::Create(CORRUPTION, kInvalidJson, util::NewKey(key)));
+ }
+ settings->SetWithoutPathExpansion(key, value);
+ }
+
+ if (it->status().IsNotFound()) {
+ NOTREACHED() << "IsNotFound() but iterating over all keys?!";
+ return MakeReadResult(settings.Pass());
+ }
+
+ if (!it->status().ok())
+ return MakeReadResult(ToValueStoreError(it->status(), util::NoKey()));
+
+ return MakeReadResult(settings.Pass());
+}
+
+ValueStore::WriteResult LeveldbValueStore::Set(
+ WriteOptions options, const std::string& key, const base::Value& value) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ scoped_ptr<Error> open_error = EnsureDbIsOpen();
+ if (open_error)
+ return MakeWriteResult(open_error.Pass());
+
+ leveldb::WriteBatch batch;
+ scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
+ scoped_ptr<Error> batch_error =
+ AddToBatch(options, key, value, &batch, changes.get());
+ if (batch_error)
+ return MakeWriteResult(batch_error.Pass());
+
+ scoped_ptr<Error> write_error = WriteToDb(&batch);
+ return write_error ? MakeWriteResult(write_error.Pass())
+ : MakeWriteResult(changes.Pass());
+}
+
+ValueStore::WriteResult LeveldbValueStore::Set(
+ WriteOptions options, const base::DictionaryValue& settings) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ scoped_ptr<Error> open_error = EnsureDbIsOpen();
+ if (open_error)
+ return MakeWriteResult(open_error.Pass());
+
+ leveldb::WriteBatch batch;
+ scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
+
+ for (base::DictionaryValue::Iterator it(settings);
+ !it.IsAtEnd(); it.Advance()) {
+ scoped_ptr<Error> batch_error =
+ AddToBatch(options, it.key(), it.value(), &batch, changes.get());
+ if (batch_error)
+ return MakeWriteResult(batch_error.Pass());
+ }
+
+ scoped_ptr<Error> write_error = WriteToDb(&batch);
+ return write_error ? MakeWriteResult(write_error.Pass())
+ : MakeWriteResult(changes.Pass());
+}
+
+ValueStore::WriteResult LeveldbValueStore::Remove(const std::string& key) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ return Remove(std::vector<std::string>(1, key));
+}
+
+ValueStore::WriteResult LeveldbValueStore::Remove(
+ const std::vector<std::string>& keys) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ scoped_ptr<Error> open_error = EnsureDbIsOpen();
+ if (open_error)
+ return MakeWriteResult(open_error.Pass());
+
+ leveldb::WriteBatch batch;
+ scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
+
+ for (std::vector<std::string>::const_iterator it = keys.begin();
+ it != keys.end(); ++it) {
+ scoped_ptr<base::Value> old_value;
+ scoped_ptr<Error> read_error =
+ ReadFromDb(leveldb::ReadOptions(), *it, &old_value);
+ if (read_error)
+ return MakeWriteResult(read_error.Pass());
+
+ if (old_value) {
+ changes->push_back(ValueStoreChange(*it, old_value.release(), NULL));
+ batch.Delete(*it);
+ }
+ }
+
+ leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
+ if (!status.ok() && !status.IsNotFound())
+ return MakeWriteResult(ToValueStoreError(status, util::NoKey()));
+ return MakeWriteResult(changes.Pass());
+}
+
+ValueStore::WriteResult LeveldbValueStore::Clear() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
+
+ ReadResult read_result = Get();
+ if (read_result->HasError())
+ return MakeWriteResult(read_result->PassError());
+
+ base::DictionaryValue& whole_db = read_result->settings();
+ while (!whole_db.empty()) {
+ std::string next_key = base::DictionaryValue::Iterator(whole_db).key();
+ scoped_ptr<base::Value> next_value;
+ whole_db.RemoveWithoutPathExpansion(next_key, &next_value);
+ changes->push_back(
+ ValueStoreChange(next_key, next_value.release(), NULL));
+ }
+
+ DeleteDbFile();
+ return MakeWriteResult(changes.Pass());
+}
+
+bool LeveldbValueStore::Restore() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ ReadResult result = Get();
+ std::string previous_key;
+ while (result->IsCorrupted()) {
+ // If we don't have a specific corrupted key, or we've tried and failed to
+ // clear this specific key, or we fail to restore the key, then wipe the
+ // whole database.
+ if (!result->error().key.get() || *result->error().key == previous_key ||
+ !RestoreKey(*result->error().key)) {
+ DeleteDbFile();
+ result = Get();
+ break;
+ }
+
+ // Otherwise, re-Get() the database to check if there is still any
+ // corruption.
+ previous_key = *result->error().key;
+ result = Get();
+ }
+
+ // If we still have an error, it means we've tried deleting the database file,
+ // and failed. There's nothing more we can do.
+ return !result->IsCorrupted();
+}
+
+bool LeveldbValueStore::RestoreKey(const std::string& key) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ ReadResult result = Get(key);
+ if (result->IsCorrupted()) {
+ leveldb::WriteBatch batch;
+ batch.Delete(key);
+ scoped_ptr<ValueStore::Error> error = WriteToDb(&batch);
+ // If we can't delete the key, the restore failed.
+ if (error.get())
+ return false;
+ result = Get(key);
+ }
+
+ // The restore succeeded if there is no corruption error.
+ return !result->IsCorrupted();
+}
+
+bool LeveldbValueStore::WriteToDbForTest(leveldb::WriteBatch* batch) {
+ return !WriteToDb(batch).get();
+}
+
+scoped_ptr<ValueStore::Error> LeveldbValueStore::EnsureDbIsOpen() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ if (db_)
+ return util::NoError();
+
+ leveldb::Options options;
+ options.max_open_files = 0; // Use minimum.
+ options.create_if_missing = true;
+
+ leveldb::DB* db = NULL;
+ leveldb::Status status =
+ leveldb::DB::Open(options, db_path_.AsUTF8Unsafe(), &db);
+ if (!status.ok())
+ return ToValueStoreError(status, util::NoKey());
+
+ CHECK(db);
+ db_.reset(db);
+ return util::NoError();
+}
+
+scoped_ptr<ValueStore::Error> LeveldbValueStore::ReadFromDb(
+ leveldb::ReadOptions options,
+ const std::string& key,
+ scoped_ptr<base::Value>* setting) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ DCHECK(setting);
+
+ std::string value_as_json;
+ leveldb::Status s = db_->Get(options, key, &value_as_json);
+
+ if (s.IsNotFound()) {
+ // Despite there being no value, it was still a success. Check this first
+ // because ok() is false on IsNotFound.
+ return util::NoError();
+ }
+
+ if (!s.ok())
+ return ToValueStoreError(s, util::NewKey(key));
+
+ base::Value* value = base::JSONReader().ReadToValue(value_as_json);
+ if (!value)
+ return Error::Create(CORRUPTION, kInvalidJson, util::NewKey(key));
+
+ setting->reset(value);
+ return util::NoError();
+}
+
+scoped_ptr<ValueStore::Error> LeveldbValueStore::AddToBatch(
+ ValueStore::WriteOptions options,
+ const std::string& key,
+ const base::Value& value,
+ leveldb::WriteBatch* batch,
+ ValueStoreChangeList* changes) {
+ bool write_new_value = true;
+
+ if (!(options & NO_GENERATE_CHANGES)) {
+ scoped_ptr<base::Value> old_value;
+ scoped_ptr<Error> read_error =
+ ReadFromDb(leveldb::ReadOptions(), key, &old_value);
+ if (read_error)
+ return read_error.Pass();
+ if (!old_value || !old_value->Equals(&value)) {
+ changes->push_back(
+ ValueStoreChange(key, old_value.release(), value.DeepCopy()));
+ } else {
+ write_new_value = false;
+ }
+ }
+
+ if (write_new_value) {
+ std::string value_as_json;
+ base::JSONWriter::Write(&value, &value_as_json);
+ batch->Put(key, value_as_json);
+ }
+
+ return util::NoError();
+}
+
+scoped_ptr<ValueStore::Error> LeveldbValueStore::WriteToDb(
+ leveldb::WriteBatch* batch) {
+ leveldb::Status status = db_->Write(leveldb::WriteOptions(), batch);
+ return status.ok() ? util::NoError()
+ : ToValueStoreError(status, util::NoKey());
+}
+
+bool LeveldbValueStore::IsEmpty() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions()));
+
+ it->SeekToFirst();
+ bool is_empty = !it->Valid();
+ if (!it->status().ok()) {
+ LOG(ERROR) << "Checking DB emptiness failed: " << it->status().ToString();
+ return false;
+ }
+ return is_empty;
+}
+
+void LeveldbValueStore::DeleteDbFile() {
+ db_.reset(); // release any lock on the directory
+ if (!base::DeleteFile(db_path_, true /* recursive */)) {
+ LOG(WARNING) << "Failed to delete LeveldbValueStore database at " <<
+ db_path_.value();
+ }
+}
+
+scoped_ptr<ValueStore::Error> LeveldbValueStore::ToValueStoreError(
+ const leveldb::Status& status,
+ scoped_ptr<std::string> key) {
+ CHECK(!status.ok());
+ CHECK(!status.IsNotFound()); // not an error
+
+ std::string message = status.ToString();
+ // The message may contain |db_path_|, which may be considered sensitive
+ // data, and those strings are passed to the extension, so strip it out.
+ ReplaceSubstringsAfterOffset(&message, 0u, db_path_.AsUTF8Unsafe(), "...");
+
+ return Error::Create(CORRUPTION, message, key.Pass());
+}
diff --git a/extensions/browser/value_store/leveldb_value_store.h b/extensions/browser/value_store/leveldb_value_store.h
new file mode 100644
index 0000000..ec2a0e0
--- /dev/null
+++ b/extensions/browser/value_store/leveldb_value_store.h
@@ -0,0 +1,99 @@
+// 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 EXTENSIONS_BROWSER_VALUE_STORE_LEVELDB_VALUE_STORE_H_
+#define EXTENSIONS_BROWSER_VALUE_STORE_LEVELDB_VALUE_STORE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "extensions/browser/value_store/value_store.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+
+// Value store area, backed by a leveldb database.
+// All methods must be run on the FILE thread.
+class LeveldbValueStore : public ValueStore {
+ public:
+ // Creates a database bound to |path|. The underlying database won't be
+ // opened (i.e. may not be created) until one of the get/set/etc methods are
+ // called - this is because opening the database may fail, and extensions
+ // need to be notified of that, but we don't want to permanently give up.
+ //
+ // Must be created on the FILE thread.
+ explicit LeveldbValueStore(const base::FilePath& path);
+
+ // Must be deleted on the FILE thread.
+ virtual ~LeveldbValueStore();
+
+ // ValueStore implementation.
+ virtual size_t GetBytesInUse(const std::string& key) OVERRIDE;
+ virtual size_t GetBytesInUse(const std::vector<std::string>& keys) OVERRIDE;
+ virtual size_t GetBytesInUse() OVERRIDE;
+ virtual ReadResult Get(const std::string& key) OVERRIDE;
+ virtual ReadResult Get(const std::vector<std::string>& keys) OVERRIDE;
+ virtual ReadResult Get() OVERRIDE;
+ virtual WriteResult Set(
+ WriteOptions options,
+ const std::string& key,
+ const base::Value& value) OVERRIDE;
+ virtual WriteResult Set(
+ WriteOptions options, const base::DictionaryValue& values) OVERRIDE;
+ virtual WriteResult Remove(const std::string& key) OVERRIDE;
+ virtual WriteResult Remove(const std::vector<std::string>& keys) OVERRIDE;
+ virtual WriteResult Clear() OVERRIDE;
+ virtual bool Restore() OVERRIDE;
+ virtual bool RestoreKey(const std::string& key) OVERRIDE;
+
+ // Write directly to the backing levelDB. Only used for testing to cause
+ // corruption in the database.
+ bool WriteToDbForTest(leveldb::WriteBatch* batch);
+
+ private:
+ // Tries to open the database if it hasn't been opened already.
+ scoped_ptr<ValueStore::Error> EnsureDbIsOpen();
+
+ // Reads a setting from the database.
+ scoped_ptr<ValueStore::Error> ReadFromDb(
+ leveldb::ReadOptions options,
+ const std::string& key,
+ // Will be reset() with the result, if any.
+ scoped_ptr<base::Value>* setting);
+
+ // Adds a setting to a WriteBatch, and logs the change in |changes|. For use
+ // with WriteToDb.
+ scoped_ptr<ValueStore::Error> AddToBatch(ValueStore::WriteOptions options,
+ const std::string& key,
+ const base::Value& value,
+ leveldb::WriteBatch* batch,
+ ValueStoreChangeList* changes);
+
+ // Commits the changes in |batch| to the database.
+ scoped_ptr<ValueStore::Error> WriteToDb(leveldb::WriteBatch* batch);
+
+ // Converts an error leveldb::Status to a ValueStore::Error. Returns a
+ // scoped_ptr for convenience; the result will always be non-empty.
+ scoped_ptr<ValueStore::Error> ToValueStoreError(
+ const leveldb::Status& status,
+ scoped_ptr<std::string> key);
+
+ // Removes the on-disk database at |db_path_|. Any file system locks should
+ // be released before calling this method.
+ void DeleteDbFile();
+
+ // Returns whether the database is empty.
+ bool IsEmpty();
+
+ // The location of the leveldb backend.
+ const base::FilePath db_path_;
+
+ // leveldb backend.
+ scoped_ptr<leveldb::DB> db_;
+
+ DISALLOW_COPY_AND_ASSIGN(LeveldbValueStore);
+};
+
+#endif // EXTENSIONS_BROWSER_VALUE_STORE_LEVELDB_VALUE_STORE_H_
diff --git a/extensions/browser/value_store/leveldb_value_store_unittest.cc b/extensions/browser/value_store/leveldb_value_store_unittest.cc
new file mode 100644
index 0000000..8582e1e
--- /dev/null
+++ b/extensions/browser/value_store/leveldb_value_store_unittest.cc
@@ -0,0 +1,184 @@
+// 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 "extensions/browser/value_store/value_store_unittest.h"
+
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/values.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "extensions/browser/value_store/leveldb_value_store.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
+
+namespace {
+
+ValueStore* Param(const base::FilePath& file_path) {
+ return new LeveldbValueStore(file_path);
+}
+
+} // namespace
+
+INSTANTIATE_TEST_CASE_P(
+ LeveldbValueStore,
+ ValueStoreTest,
+ testing::Values(&Param));
+
+class LeveldbValueStoreUnitTest : public testing::Test {
+ public:
+ LeveldbValueStoreUnitTest() {}
+ virtual ~LeveldbValueStoreUnitTest() {}
+
+ protected:
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(database_dir_.CreateUniqueTempDir());
+ OpenStore();
+ ASSERT_FALSE(store_->Get()->HasError());
+ }
+
+ virtual void TearDown() OVERRIDE {
+ store_->Clear();
+ store_.reset();
+ }
+
+ void CloseStore() { store_.reset(); }
+
+ void OpenStore() { store_.reset(new LeveldbValueStore(database_path())); }
+
+ LeveldbValueStore* store() { return store_.get(); }
+ const base::FilePath& database_path() { return database_dir_.path(); }
+
+ private:
+ scoped_ptr<LeveldbValueStore> store_;
+ base::ScopedTempDir database_dir_;
+
+ content::TestBrowserThreadBundle thread_bundle_;
+};
+
+// Check that we can restore a single corrupted key in the LeveldbValueStore.
+TEST_F(LeveldbValueStoreUnitTest, RestoreKeyTest) {
+ const char kNotCorruptKey[] = "not-corrupt";
+ const char kValue[] = "value";
+
+ // Insert a valid pair.
+ scoped_ptr<base::Value> value(base::Value::CreateStringValue(kValue));
+ ASSERT_FALSE(
+ store()->Set(ValueStore::DEFAULTS, kNotCorruptKey, *value)->HasError());
+
+ // Insert a corrupt pair.
+ const char kCorruptKey[] = "corrupt";
+ leveldb::WriteBatch batch;
+ batch.Put(kCorruptKey, "[{(.*+\"\'\\");
+ ASSERT_TRUE(store()->WriteToDbForTest(&batch));
+
+ // Verify corruption.
+ ValueStore::ReadResult result = store()->Get(kCorruptKey);
+ ASSERT_TRUE(result->HasError());
+ ASSERT_EQ(ValueStore::CORRUPTION, result->error().code);
+
+ // Restore and verify.
+ ASSERT_TRUE(store()->RestoreKey(kCorruptKey));
+ result = store()->Get(kCorruptKey);
+ EXPECT_FALSE(result->HasError());
+ EXPECT_TRUE(result->settings().empty());
+
+ // Verify that the valid pair is still present.
+ result = store()->Get(kNotCorruptKey);
+ EXPECT_FALSE(result->HasError());
+ EXPECT_TRUE(result->settings().HasKey(kNotCorruptKey));
+ std::string value_string;
+ EXPECT_TRUE(result->settings().GetString(kNotCorruptKey, &value_string));
+ EXPECT_EQ(kValue, value_string);
+}
+
+// Test that the Restore() method does not just delete the entire database
+// (unless absolutely necessary), and instead only removes corrupted keys.
+TEST_F(LeveldbValueStoreUnitTest, RestoreDoesMinimumNecessary) {
+ const char* kNotCorruptKeys[] = {"a", "n", "z"};
+ const size_t kNotCorruptKeysSize = 3u;
+ const char kCorruptKey1[] = "f";
+ const char kCorruptKey2[] = "s";
+ const char kValue[] = "value";
+ const char kCorruptValue[] = "[{(.*+\"\'\\";
+
+ // Insert a collection of non-corrupted pairs.
+ scoped_ptr<base::Value> value(base::Value::CreateStringValue(kValue));
+ for (size_t i = 0; i < kNotCorruptKeysSize; ++i) {
+ ASSERT_FALSE(store()
+ ->Set(ValueStore::DEFAULTS, kNotCorruptKeys[i], *value)
+ ->HasError());
+ }
+
+ // Insert a few corrupted pairs.
+ leveldb::WriteBatch batch;
+ batch.Put(kCorruptKey1, kCorruptValue);
+ batch.Put(kCorruptKey2, kCorruptValue);
+ ASSERT_TRUE(store()->WriteToDbForTest(&batch));
+
+ // Verify that we broke it, and then fix it.
+ ValueStore::ReadResult result = store()->Get();
+ ASSERT_TRUE(result->HasError());
+ ASSERT_EQ(ValueStore::CORRUPTION, result->error().code);
+
+ ASSERT_TRUE(store()->Restore());
+
+ // We should still have all valid pairs present in the database.
+ std::string value_string;
+ for (size_t i = 0; i < kNotCorruptKeysSize; ++i) {
+ result = store()->Get(kNotCorruptKeys[i]);
+ EXPECT_FALSE(result->HasError());
+ EXPECT_TRUE(result->settings().HasKey(kNotCorruptKeys[i]));
+ EXPECT_TRUE(
+ result->settings().GetString(kNotCorruptKeys[i], &value_string));
+ EXPECT_EQ(kValue, value_string);
+ }
+}
+
+// Test that the LeveldbValueStore can recover in the case of a CATastrophic
+// failure and we have total corruption. In this case, the database is plagued
+// by LolCats.
+// Full corruption has been known to happen occasionally in strange edge cases,
+// such as after users use Windows Restore. We can't prevent it, but we need to
+// be able to handle it smoothly.
+TEST_F(LeveldbValueStoreUnitTest, RestoreFullDatabase) {
+ const std::string kLolCats("I can haz leveldb filez?");
+ const char* kNotCorruptKeys[] = {"a", "n", "z"};
+ const size_t kNotCorruptKeysSize = 3u;
+ const char kValue[] = "value";
+
+ // Generate a database.
+ scoped_ptr<base::Value> value(base::Value::CreateStringValue(kValue));
+ for (size_t i = 0; i < kNotCorruptKeysSize; ++i) {
+ ASSERT_FALSE(store()
+ ->Set(ValueStore::DEFAULTS, kNotCorruptKeys[i], *value)
+ ->HasError());
+ }
+
+ // Close it (so we remove the lock), and replace all files with LolCats.
+ CloseStore();
+ base::FileEnumerator enumerator(
+ database_path(), true /* recursive */, base::FileEnumerator::FILES);
+ for (base::FilePath file = enumerator.Next(); !file.empty();
+ file = enumerator.Next()) {
+ // WriteFile() failure is a result of -1.
+ ASSERT_NE(file_util::WriteFile(file, kLolCats.c_str(), kLolCats.length()),
+ -1);
+ }
+ OpenStore();
+
+ // We should definitely have an error.
+ ValueStore::ReadResult result = store()->Get();
+ ASSERT_TRUE(result->HasError());
+ ASSERT_EQ(ValueStore::CORRUPTION, result->error().code);
+
+ ASSERT_TRUE(store()->Restore());
+ result = store()->Get();
+ EXPECT_FALSE(result->HasError());
+ // We couldn't recover anything, but we should be in a sane state again.
+ EXPECT_EQ(0u, result->settings().size());
+}
diff --git a/extensions/browser/value_store/testing_value_store.cc b/extensions/browser/value_store/testing_value_store.cc
new file mode 100644
index 0000000..f4002de
--- /dev/null
+++ b/extensions/browser/value_store/testing_value_store.cc
@@ -0,0 +1,138 @@
+// 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 "extensions/browser/value_store/testing_value_store.h"
+
+#include "base/logging.h"
+
+namespace {
+
+const char kGenericErrorMessage[] = "TestingValueStore configured to error";
+
+} // namespace
+
+TestingValueStore::TestingValueStore()
+ : read_count_(0), write_count_(0), error_code_(OK) {}
+
+TestingValueStore::~TestingValueStore() {}
+
+size_t TestingValueStore::GetBytesInUse(const std::string& key) {
+ // Let SettingsStorageQuotaEnforcer implement this.
+ NOTREACHED() << "Not implemented";
+ return 0;
+}
+
+size_t TestingValueStore::GetBytesInUse(
+ const std::vector<std::string>& keys) {
+ // Let SettingsStorageQuotaEnforcer implement this.
+ NOTREACHED() << "Not implemented";
+ return 0;
+}
+
+size_t TestingValueStore::GetBytesInUse() {
+ // Let SettingsStorageQuotaEnforcer implement this.
+ NOTREACHED() << "Not implemented";
+ return 0;
+}
+
+ValueStore::ReadResult TestingValueStore::Get(const std::string& key) {
+ return Get(std::vector<std::string>(1, key));
+}
+
+ValueStore::ReadResult TestingValueStore::Get(
+ const std::vector<std::string>& keys) {
+ read_count_++;
+ if (error_code_ != OK)
+ return MakeReadResult(TestingError());
+
+ base::DictionaryValue* settings = new base::DictionaryValue();
+ for (std::vector<std::string>::const_iterator it = keys.begin();
+ it != keys.end(); ++it) {
+ base::Value* value = NULL;
+ if (storage_.GetWithoutPathExpansion(*it, &value)) {
+ settings->SetWithoutPathExpansion(*it, value->DeepCopy());
+ }
+ }
+ return MakeReadResult(make_scoped_ptr(settings));
+}
+
+ValueStore::ReadResult TestingValueStore::Get() {
+ read_count_++;
+ if (error_code_ != OK)
+ return MakeReadResult(TestingError());
+ return MakeReadResult(make_scoped_ptr(storage_.DeepCopy()));
+}
+
+ValueStore::WriteResult TestingValueStore::Set(
+ WriteOptions options, const std::string& key, const base::Value& value) {
+ base::DictionaryValue settings;
+ settings.SetWithoutPathExpansion(key, value.DeepCopy());
+ return Set(options, settings);
+}
+
+ValueStore::WriteResult TestingValueStore::Set(
+ WriteOptions options, const base::DictionaryValue& settings) {
+ write_count_++;
+ if (error_code_ != OK)
+ return MakeWriteResult(TestingError());
+
+ scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
+ for (base::DictionaryValue::Iterator it(settings);
+ !it.IsAtEnd(); it.Advance()) {
+ base::Value* old_value = NULL;
+ if (!storage_.GetWithoutPathExpansion(it.key(), &old_value) ||
+ !old_value->Equals(&it.value())) {
+ changes->push_back(
+ ValueStoreChange(
+ it.key(),
+ old_value ? old_value->DeepCopy() : old_value,
+ it.value().DeepCopy()));
+ storage_.SetWithoutPathExpansion(it.key(), it.value().DeepCopy());
+ }
+ }
+ return MakeWriteResult(changes.Pass());
+}
+
+ValueStore::WriteResult TestingValueStore::Remove(const std::string& key) {
+ return Remove(std::vector<std::string>(1, key));
+}
+
+ValueStore::WriteResult TestingValueStore::Remove(
+ const std::vector<std::string>& keys) {
+ write_count_++;
+ if (error_code_ != OK)
+ return MakeWriteResult(TestingError());
+
+ scoped_ptr<ValueStoreChangeList> changes(new ValueStoreChangeList());
+ for (std::vector<std::string>::const_iterator it = keys.begin();
+ it != keys.end(); ++it) {
+ scoped_ptr<base::Value> old_value;
+ if (storage_.RemoveWithoutPathExpansion(*it, &old_value)) {
+ changes->push_back(ValueStoreChange(*it, old_value.release(), NULL));
+ }
+ }
+ return MakeWriteResult(changes.Pass());
+}
+
+ValueStore::WriteResult TestingValueStore::Clear() {
+ std::vector<std::string> keys;
+ for (base::DictionaryValue::Iterator it(storage_);
+ !it.IsAtEnd(); it.Advance()) {
+ keys.push_back(it.key());
+ }
+ return Remove(keys);
+}
+
+bool TestingValueStore::Restore() {
+ return true;
+}
+
+bool TestingValueStore::RestoreKey(const std::string& key) {
+ return true;
+}
+
+scoped_ptr<ValueStore::Error> TestingValueStore::TestingError() {
+ return make_scoped_ptr(new ValueStore::Error(
+ error_code_, kGenericErrorMessage, scoped_ptr<std::string>()));
+}
diff --git a/extensions/browser/value_store/testing_value_store.h b/extensions/browser/value_store/testing_value_store.h
new file mode 100644
index 0000000..dec5c2c
--- /dev/null
+++ b/extensions/browser/value_store/testing_value_store.h
@@ -0,0 +1,62 @@
+// 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 EXTENSIONS_BROWSER_VALUE_STORE_TESTING_VALUE_STORE_H_
+#define EXTENSIONS_BROWSER_VALUE_STORE_TESTING_VALUE_STORE_H_
+
+#include "base/compiler_specific.h"
+#include "extensions/browser/value_store/value_store.h"
+
+// ValueStore for testing, with an in-memory storage but the ability to
+// optionally fail all operations.
+class TestingValueStore : public ValueStore {
+ public:
+ TestingValueStore();
+ virtual ~TestingValueStore();
+
+ // Sets the error code for requests. If OK, errors won't be thrown.
+ // Defaults to OK.
+ void set_error_code(ErrorCode error_code) { error_code_ = error_code; }
+
+ // Accessors for the number of reads/writes done by this value store. Each
+ // Get* operation (except for the BytesInUse ones) counts as one read, and
+ // each Set*/Remove/Clear operation counts as one write. This is useful in
+ // tests seeking to assert that some number of reads/writes to their
+ // underlying value store have (or have not) happened.
+ int read_count() { return read_count_; }
+ int write_count() { return write_count_; }
+
+ // ValueStore implementation.
+ virtual size_t GetBytesInUse(const std::string& key) OVERRIDE;
+ virtual size_t GetBytesInUse(const std::vector<std::string>& keys) OVERRIDE;
+ virtual size_t GetBytesInUse() OVERRIDE;
+ virtual ReadResult Get(const std::string& key) OVERRIDE;
+ virtual ReadResult Get(const std::vector<std::string>& keys) OVERRIDE;
+ virtual ReadResult Get() OVERRIDE;
+ virtual WriteResult Set(
+ WriteOptions options,
+ const std::string& key,
+ const base::Value& value) OVERRIDE;
+ virtual WriteResult Set(
+ WriteOptions options, const base::DictionaryValue& values) OVERRIDE;
+ virtual WriteResult Remove(const std::string& key) OVERRIDE;
+ virtual WriteResult Remove(const std::vector<std::string>& keys) OVERRIDE;
+ virtual WriteResult Clear() OVERRIDE;
+ // TestingValueStores can't get corrupted (they're all in-memory), so these
+ // just return true.
+ virtual bool Restore() OVERRIDE;
+ virtual bool RestoreKey(const std::string& key) OVERRIDE;
+
+ private:
+ scoped_ptr<ValueStore::Error> TestingError();
+
+ base::DictionaryValue storage_;
+ int read_count_;
+ int write_count_;
+ ErrorCode error_code_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestingValueStore);
+};
+
+#endif // EXTENSIONS_BROWSER_VALUE_STORE_TESTING_VALUE_STORE_H_
diff --git a/extensions/browser/value_store/testing_value_store_unittest.cc b/extensions/browser/value_store/testing_value_store_unittest.cc
new file mode 100644
index 0000000..487512c
--- /dev/null
+++ b/extensions/browser/value_store/testing_value_store_unittest.cc
@@ -0,0 +1,24 @@
+// 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 "extensions/browser/value_store/value_store_unittest.h"
+
+#include "extensions/browser/value_store/testing_value_store.h"
+
+namespace extensions {
+
+namespace {
+
+ValueStore* Param(const base::FilePath& file_path) {
+ return new TestingValueStore();
+}
+
+} // namespace
+
+INSTANTIATE_TEST_CASE_P(
+ TestingValueStore,
+ ValueStoreTest,
+ testing::Values(&Param));
+
+} // namespace extensions
diff --git a/extensions/browser/value_store/value_store.cc b/extensions/browser/value_store/value_store.cc
new file mode 100644
index 0000000..0b87b5f
--- /dev/null
+++ b/extensions/browser/value_store/value_store.cc
@@ -0,0 +1,45 @@
+// 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 "extensions/browser/value_store/value_store.h"
+
+#include "base/logging.h"
+
+// Implementation of Error.
+
+ValueStore::Error::Error(ErrorCode code,
+ const std::string& message,
+ scoped_ptr<std::string> key)
+ : code(code), message(message), key(key.Pass()) {}
+
+ValueStore::Error::~Error() {}
+
+// Implementation of ReadResultType.
+
+ValueStore::ReadResultType::ReadResultType(
+ scoped_ptr<base::DictionaryValue> settings) : settings_(settings.Pass()) {
+ CHECK(settings_);
+}
+
+ValueStore::ReadResultType::ReadResultType(scoped_ptr<Error> error)
+ : error_(error.Pass()) {
+ CHECK(error_);
+}
+
+ValueStore::ReadResultType::~ReadResultType() {}
+
+// Implementation of WriteResultType.
+
+ValueStore::WriteResultType::WriteResultType(
+ scoped_ptr<ValueStoreChangeList> changes)
+ : changes_(changes.Pass()) {
+ CHECK(changes_);
+}
+
+ValueStore::WriteResultType::WriteResultType(scoped_ptr<Error> error)
+ : error_(error.Pass()) {
+ CHECK(error_);
+}
+
+ValueStore::WriteResultType::~WriteResultType() {}
diff --git a/extensions/browser/value_store/value_store.h b/extensions/browser/value_store/value_store.h
new file mode 100644
index 0000000..f0d5cc0
--- /dev/null
+++ b/extensions/browser/value_store/value_store.h
@@ -0,0 +1,221 @@
+// 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 EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_H_
+#define EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "extensions/browser/value_store/value_store_change.h"
+
+// Interface for a storage area for Value objects.
+class ValueStore {
+ public:
+ // Error codes returned from storage methods.
+ enum ErrorCode {
+ OK,
+
+ // The failure was due to some kind of database corruption. Depending on
+ // what is corrupted, some part of the database may be recoverable.
+ //
+ // For example, if the on-disk representation of leveldb is corrupted, it's
+ // likely the whole database will need to be wiped and started again.
+ //
+ // If a single key has been committed with an invalid JSON representation,
+ // just that key can be deleted without affecting the rest of the database.
+ CORRUPTION,
+
+ // The failure was due to the store being read-only (for example, policy).
+ READ_ONLY,
+
+ // The failure was due to the store running out of space.
+ QUOTA_EXCEEDED,
+
+ // Any other error.
+ OTHER_ERROR,
+ };
+
+ // Bundles an ErrorCode with further metadata.
+ struct Error {
+ Error(ErrorCode code,
+ const std::string& message,
+ scoped_ptr<std::string> key);
+ ~Error();
+
+ static scoped_ptr<Error> Create(ErrorCode code,
+ const std::string& message,
+ scoped_ptr<std::string> key) {
+ return make_scoped_ptr(new Error(code, message, key.Pass()));
+ }
+
+ // The error code.
+ const ErrorCode code;
+
+ // Message associated with the error.
+ const std::string message;
+
+ // The key associated with the error, if any. Use a scoped_ptr here
+ // because empty-string is a valid key.
+ //
+ // TODO(kalman): add test(s) for an empty key.
+ const scoped_ptr<std::string> key;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Error);
+ };
+
+ // The result of a read operation (Get).
+ class ReadResultType {
+ public:
+ explicit ReadResultType(scoped_ptr<base::DictionaryValue> settings);
+ explicit ReadResultType(scoped_ptr<Error> error);
+ ~ReadResultType();
+
+ bool HasError() const { return error_; }
+
+ bool IsCorrupted() const {
+ return error_.get() && error_->code == CORRUPTION;
+ }
+
+ // Gets the settings read from the storage. Note that this represents
+ // the root object. If you request the value for key "foo", that value will
+ // be in |settings|.|foo|.
+ //
+ // Must only be called if there is no error.
+ base::DictionaryValue& settings() { return *settings_; }
+ scoped_ptr<base::DictionaryValue> PassSettings() {
+ return settings_.Pass();
+ }
+
+ // Only call if HasError is true.
+ const Error& error() const { return *error_; }
+ scoped_ptr<Error> PassError() { return error_.Pass(); }
+
+ private:
+ scoped_ptr<base::DictionaryValue> settings_;
+ scoped_ptr<Error> error_;
+
+ DISALLOW_COPY_AND_ASSIGN(ReadResultType);
+ };
+ typedef scoped_ptr<ReadResultType> ReadResult;
+
+ // The result of a write operation (Set/Remove/Clear).
+ class WriteResultType {
+ public:
+ explicit WriteResultType(scoped_ptr<ValueStoreChangeList> changes);
+ explicit WriteResultType(scoped_ptr<Error> error);
+ ~WriteResultType();
+
+ bool HasError() const { return error_; }
+
+ // Gets the list of changes to the settings which resulted from the write.
+ // Won't be present if the NO_GENERATE_CHANGES WriteOptions was given.
+ // Only call if HasError is false.
+ ValueStoreChangeList& changes() { return *changes_; }
+ scoped_ptr<ValueStoreChangeList> PassChanges() { return changes_.Pass(); }
+
+ // Only call if HasError is true.
+ const Error& error() const { return *error_; }
+ scoped_ptr<Error> PassError() { return error_.Pass(); }
+
+ private:
+ scoped_ptr<ValueStoreChangeList> changes_;
+ scoped_ptr<Error> error_;
+
+ DISALLOW_COPY_AND_ASSIGN(WriteResultType);
+ };
+ typedef scoped_ptr<WriteResultType> WriteResult;
+
+ // Options for write operations.
+ enum WriteOptionsValues {
+ // Callers should usually use this.
+ DEFAULTS = 0,
+
+ // Ignore any quota restrictions.
+ IGNORE_QUOTA = 1<<1,
+
+ // Don't generate the changes for a WriteResult.
+ NO_GENERATE_CHANGES = 1<<2,
+ };
+ typedef int WriteOptions;
+
+ virtual ~ValueStore() {}
+
+ // Helpers for making a Read/WriteResult.
+ template<typename T>
+ static ReadResult MakeReadResult(scoped_ptr<T> arg) {
+ return ReadResult(new ReadResultType(arg.Pass()));
+ }
+
+ template<typename T>
+ static WriteResult MakeWriteResult(scoped_ptr<T> arg) {
+ return WriteResult(new WriteResultType(arg.Pass()));
+ }
+
+ // Gets the amount of space being used by a single value, in bytes.
+ // Note: The GetBytesInUse methods are only used by extension settings at the
+ // moment. If these become more generally useful, the
+ // SettingsStorageQuotaEnforcer and WeakUnlimitedSettingsStorage classes
+ // should be moved to the value_store directory.
+ virtual size_t GetBytesInUse(const std::string& key) = 0;
+
+ // Gets the total amount of space being used by multiple values, in bytes.
+ virtual size_t GetBytesInUse(const std::vector<std::string>& keys) = 0;
+
+ // Gets the total amount of space being used by this storage area, in bytes.
+ virtual size_t GetBytesInUse() = 0;
+
+ // Gets a single value from storage.
+ virtual ReadResult Get(const std::string& key) = 0;
+
+ // Gets multiple values from storage.
+ virtual ReadResult Get(const std::vector<std::string>& keys) = 0;
+
+ // Gets all values from storage.
+ virtual ReadResult Get() = 0;
+
+ // Sets a single key to a new value.
+ virtual WriteResult Set(WriteOptions options,
+ const std::string& key,
+ const base::Value& value) = 0;
+
+ // Sets multiple keys to new values.
+ virtual WriteResult Set(
+ WriteOptions options, const base::DictionaryValue& values) = 0;
+
+ // Removes a key from the storage.
+ virtual WriteResult Remove(const std::string& key) = 0;
+
+ // Removes multiple keys from the storage.
+ virtual WriteResult Remove(const std::vector<std::string>& keys) = 0;
+
+ // Clears the storage.
+ virtual WriteResult Clear() = 0;
+
+ // In the event of corruption, the ValueStore should be able to restore
+ // itself. This means deleting local corrupted files. If only a few keys are
+ // corrupted, then some of the database may be saved. If the full database is
+ // corrupted, this will erase it in its entirety.
+ // Returns true on success, false on failure. The only way this will fail is
+ // if we also cannot delete the database file.
+ // Note: This method may be expensive; some implementations may need to read
+ // the entire database to restore. Use sparingly.
+ // Note: This method (and the following RestoreKey()) are rude, and do not
+ // make any logs, track changes, or other generally polite things. Please do
+ // not use these as substitutes for Clear() and Remove().
+ virtual bool Restore() = 0;
+
+ // Similar to Restore(), but for only a particular key. If the key is corrupt,
+ // this will forcefully remove the key. It does not look at the database on
+ // the whole, which makes it faster, but does not guarantee there is no
+ // additional corruption.
+ // Returns true on success, and false on failure. If false, the next step is
+ // probably to Restore() the whole database.
+ virtual bool RestoreKey(const std::string& key) = 0;
+};
+
+#endif // EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_H_
diff --git a/extensions/browser/value_store/value_store_change.cc b/extensions/browser/value_store/value_store_change.cc
new file mode 100644
index 0000000..822c9b9
--- /dev/null
+++ b/extensions/browser/value_store/value_store_change.cc
@@ -0,0 +1,55 @@
+// 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 "extensions/browser/value_store/value_store_change.h"
+
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+
+// static
+std::string ValueStoreChange::ToJson(
+ const ValueStoreChangeList& changes) {
+ base::DictionaryValue changes_value;
+ for (ValueStoreChangeList::const_iterator it = changes.begin();
+ it != changes.end(); ++it) {
+ base::DictionaryValue* change_value = new base::DictionaryValue();
+ if (it->old_value()) {
+ change_value->Set("oldValue", it->old_value()->DeepCopy());
+ }
+ if (it->new_value()) {
+ change_value->Set("newValue", it->new_value()->DeepCopy());
+ }
+ changes_value.SetWithoutPathExpansion(it->key(), change_value);
+ }
+ std::string json;
+ base::JSONWriter::Write(&changes_value, &json);
+ return json;
+}
+
+ValueStoreChange::ValueStoreChange(
+ const std::string& key, base::Value* old_value, base::Value* new_value)
+ : inner_(new Inner(key, old_value, new_value)) {}
+
+ValueStoreChange::~ValueStoreChange() {}
+
+const std::string& ValueStoreChange::key() const {
+ DCHECK(inner_.get());
+ return inner_->key_;
+}
+
+const base::Value* ValueStoreChange::old_value() const {
+ DCHECK(inner_.get());
+ return inner_->old_value_.get();
+}
+
+const base::Value* ValueStoreChange::new_value() const {
+ DCHECK(inner_.get());
+ return inner_->new_value_.get();
+}
+
+ValueStoreChange::Inner::Inner(
+ const std::string& key, base::Value* old_value, base::Value* new_value)
+ : key_(key), old_value_(old_value), new_value_(new_value) {}
+
+ValueStoreChange::Inner::~Inner() {}
diff --git a/extensions/browser/value_store/value_store_change.h b/extensions/browser/value_store/value_store_change.h
new file mode 100644
index 0000000..4c03cc3
--- /dev/null
+++ b/extensions/browser/value_store/value_store_change.h
@@ -0,0 +1,60 @@
+// 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 EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_CHANGE_H_
+#define EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_CHANGE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+
+class ValueStoreChange;
+typedef std::vector<ValueStoreChange> ValueStoreChangeList;
+
+// A change to a setting. Safe/efficient to copy.
+class ValueStoreChange {
+ public:
+ // Converts an ValueStoreChangeList into JSON of the form:
+ // { "foo": { "key": "foo", "oldValue": "bar", "newValue": "baz" } }
+ static std::string ToJson(const ValueStoreChangeList& changes);
+
+ // Ownership of |old_value| and |new_value| taken.
+ ValueStoreChange(
+ const std::string& key, base::Value* old_value, base::Value* new_value);
+
+ ~ValueStoreChange();
+
+ // Gets the key of the setting which changed.
+ const std::string& key() const;
+
+ // Gets the value of the setting before the change, or NULL if there was no
+ // old value.
+ const base::Value* old_value() const;
+
+ // Gets the value of the setting after the change, or NULL if there is no new
+ // value.
+ const base::Value* new_value() const;
+
+ private:
+ class Inner : public base::RefCountedThreadSafe<Inner> {
+ public:
+ Inner(
+ const std::string& key, base::Value* old_value, base::Value* new_value);
+
+ const std::string key_;
+ const scoped_ptr<base::Value> old_value_;
+ const scoped_ptr<base::Value> new_value_;
+
+ private:
+ friend class base::RefCountedThreadSafe<Inner>;
+ virtual ~Inner();
+ };
+
+ scoped_refptr<Inner> inner_;
+};
+
+#endif // EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_CHANGE_H_
diff --git a/extensions/browser/value_store/value_store_change_unittest.cc b/extensions/browser/value_store/value_store_change_unittest.cc
new file mode 100644
index 0000000..5fb75b7
--- /dev/null
+++ b/extensions/browser/value_store/value_store_change_unittest.cc
@@ -0,0 +1,96 @@
+// 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 "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "extensions/browser/value_store/value_store_change.h"
+#include "extensions/common/value_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::DictionaryValue;
+using base::Value;
+using extensions::DictionaryBuilder;
+using extensions::ListBuilder;
+
+namespace {
+
+TEST(ValueStoreChangeTest, NullOldValue) {
+ ValueStoreChange change("key", NULL, base::Value::CreateStringValue("value"));
+
+ EXPECT_EQ("key", change.key());
+ EXPECT_EQ(NULL, change.old_value());
+ {
+ scoped_ptr<base::Value> expected(base::Value::CreateStringValue("value"));
+ EXPECT_TRUE(change.new_value()->Equals(expected.get()));
+ }
+}
+
+TEST(ValueStoreChangeTest, NullNewValue) {
+ ValueStoreChange change("key", base::Value::CreateStringValue("value"), NULL);
+
+ EXPECT_EQ("key", change.key());
+ {
+ scoped_ptr<base::Value> expected(base::Value::CreateStringValue("value"));
+ EXPECT_TRUE(change.old_value()->Equals(expected.get()));
+ }
+ EXPECT_EQ(NULL, change.new_value());
+}
+
+TEST(ValueStoreChangeTest, NonNullValues) {
+ ValueStoreChange change("key",
+ base::Value::CreateStringValue("old_value"),
+ base::Value::CreateStringValue("new_value"));
+
+ EXPECT_EQ("key", change.key());
+ {
+ scoped_ptr<base::Value> expected(
+ base::Value::CreateStringValue("old_value"));
+ EXPECT_TRUE(change.old_value()->Equals(expected.get()));
+ }
+ {
+ scoped_ptr<base::Value> expected(
+ base::Value::CreateStringValue("new_value"));
+ EXPECT_TRUE(change.new_value()->Equals(expected.get()));
+ }
+}
+
+TEST(ValueStoreChangeTest, ToJson) {
+ // Create a mildly complicated structure that has dots in it.
+ scoped_ptr<base::DictionaryValue> value = DictionaryBuilder()
+ .Set("key", "value")
+ .Set("key.with.dots", "value.with.dots")
+ .Set("tricked", DictionaryBuilder()
+ .Set("you", "nodots"))
+ .Set("tricked.you", "with.dots")
+ .Build();
+
+ ValueStoreChangeList change_list;
+ change_list.push_back(
+ ValueStoreChange("key", value->DeepCopy(), value->DeepCopy()));
+ change_list.push_back(
+ ValueStoreChange("key.with.dots", value->DeepCopy(), value->DeepCopy()));
+
+ std::string json = ValueStoreChange::ToJson(change_list);
+ scoped_ptr<base::Value> from_json(base::JSONReader::Read(json));
+ ASSERT_TRUE(from_json.get());
+
+ DictionaryBuilder v1(*value);
+ DictionaryBuilder v2(*value);
+ DictionaryBuilder v3(*value);
+ DictionaryBuilder v4(*value);
+ scoped_ptr<base::DictionaryValue> expected_from_json = DictionaryBuilder()
+ .Set("key", DictionaryBuilder()
+ .Set("oldValue", v1)
+ .Set("newValue", v2))
+ .Set("key.with.dots", DictionaryBuilder()
+ .Set("oldValue", v3)
+ .Set("newValue", v4))
+ .Build();
+
+ EXPECT_TRUE(from_json->Equals(expected_from_json.get()));
+}
+
+} // namespace
diff --git a/extensions/browser/value_store/value_store_frontend.cc b/extensions/browser/value_store/value_store_frontend.cc
new file mode 100644
index 0000000..6ef11cc
--- /dev/null
+++ b/extensions/browser/value_store/value_store_frontend.cc
@@ -0,0 +1,143 @@
+// 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 "extensions/browser/value_store/value_store_frontend.h"
+
+#include "base/bind.h"
+#include "base/debug/trace_event.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/value_store/leveldb_value_store.h"
+
+using content::BrowserThread;
+
+class ValueStoreFrontend::Backend : public base::RefCountedThreadSafe<Backend> {
+ public:
+ Backend() : storage_(NULL) {}
+
+ void Init(const base::FilePath& db_path) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ DCHECK(!storage_);
+ TRACE_EVENT0("ValueStoreFrontend::Backend", "Init");
+ db_path_ = db_path;
+ storage_ = new LeveldbValueStore(db_path);
+ }
+
+ // This variant is useful for testing (using a mock ValueStore).
+ void InitWithStore(scoped_ptr<ValueStore> storage) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ DCHECK(!storage_);
+ storage_ = storage.release();
+ }
+
+ void Get(const std::string& key,
+ const ValueStoreFrontend::ReadCallback& callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ ValueStore::ReadResult result = storage_->Get(key);
+
+ // Extract the value from the ReadResult and pass ownership of it to the
+ // callback.
+ scoped_ptr<base::Value> value;
+ if (!result->HasError()) {
+ result->settings().RemoveWithoutPathExpansion(key, &value);
+ } else {
+ LOG(WARNING) << "Reading " << key << " from " << db_path_.value()
+ << " failed: " << result->error().message;
+ }
+
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&ValueStoreFrontend::Backend::RunCallback,
+ this, callback, base::Passed(&value)));
+ }
+
+ void Set(const std::string& key, scoped_ptr<base::Value> value) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ // We don't need the old value, so skip generating changes.
+ storage_->Set(ValueStore::IGNORE_QUOTA | ValueStore::NO_GENERATE_CHANGES,
+ key, *value.get());
+ }
+
+ void Remove(const std::string& key) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ storage_->Remove(key);
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<Backend>;
+
+ virtual ~Backend() {
+ if (BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
+ delete storage_;
+ } else {
+ BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, storage_);
+ }
+ }
+
+ void RunCallback(const ValueStoreFrontend::ReadCallback& callback,
+ scoped_ptr<base::Value> value) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ callback.Run(value.Pass());
+ }
+
+ // The actual ValueStore that handles persisting the data to disk. Used
+ // exclusively on the FILE thread.
+ ValueStore* storage_;
+
+ base::FilePath db_path_;
+
+ DISALLOW_COPY_AND_ASSIGN(Backend);
+};
+
+ValueStoreFrontend::ValueStoreFrontend()
+ : backend_(new Backend()) {
+}
+
+ValueStoreFrontend::ValueStoreFrontend(const base::FilePath& db_path)
+ : backend_(new Backend()) {
+ Init(db_path);
+}
+
+ValueStoreFrontend::ValueStoreFrontend(scoped_ptr<ValueStore> value_store)
+ : backend_(new Backend()) {
+ BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
+ base::Bind(&ValueStoreFrontend::Backend::InitWithStore,
+ backend_, base::Passed(&value_store)));
+}
+
+ValueStoreFrontend::~ValueStoreFrontend() {
+ DCHECK(CalledOnValidThread());
+}
+
+void ValueStoreFrontend::Init(const base::FilePath& db_path) {
+ BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
+ base::Bind(&ValueStoreFrontend::Backend::Init,
+ backend_, db_path));
+}
+
+void ValueStoreFrontend::Get(const std::string& key,
+ const ReadCallback& callback) {
+ DCHECK(CalledOnValidThread());
+
+ BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
+ base::Bind(&ValueStoreFrontend::Backend::Get,
+ backend_, key, callback));
+}
+
+void ValueStoreFrontend::Set(const std::string& key,
+ scoped_ptr<base::Value> value) {
+ DCHECK(CalledOnValidThread());
+
+ BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
+ base::Bind(&ValueStoreFrontend::Backend::Set,
+ backend_, key, base::Passed(&value)));
+}
+
+void ValueStoreFrontend::Remove(const std::string& key) {
+ DCHECK(CalledOnValidThread());
+
+ BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
+ base::Bind(&ValueStoreFrontend::Backend::Remove,
+ backend_, key));
+}
diff --git a/extensions/browser/value_store/value_store_frontend.h b/extensions/browser/value_store/value_store_frontend.h
new file mode 100644
index 0000000..1b93c7f
--- /dev/null
+++ b/extensions/browser/value_store/value_store_frontend.h
@@ -0,0 +1,57 @@
+// 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 EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_FRONTEND_H_
+#define EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_FRONTEND_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/values.h"
+#include "extensions/browser/value_store/value_store.h"
+
+namespace base {
+class FilePath;
+}
+
+// A frontend for a LeveldbValueStore, for use on the UI thread.
+class ValueStoreFrontend
+ : public base::SupportsWeakPtr<ValueStoreFrontend>,
+ public base::NonThreadSafe {
+ public:
+ typedef base::Callback<void(scoped_ptr<base::Value>)> ReadCallback;
+
+ ValueStoreFrontend();
+ explicit ValueStoreFrontend(const base::FilePath& db_path);
+ // This variant is useful for testing (using a mock ValueStore).
+ explicit ValueStoreFrontend(scoped_ptr<ValueStore> value_store);
+ ~ValueStoreFrontend();
+
+ void Init(const base::FilePath& db_path);
+
+ // Retrieves a value from the database asynchronously, passing a copy to
+ // |callback| when ready. NULL is passed if no matching entry is found.
+ void Get(const std::string& key, const ReadCallback& callback);
+
+ // Sets a value with the given key.
+ void Set(const std::string& key, scoped_ptr<base::Value> value);
+
+ // Removes the value with the given key.
+ void Remove(const std::string& key);
+
+ private:
+ class Backend;
+
+ // A helper class to manage lifetime of the backing ValueStore, which lives
+ // on the FILE thread.
+ scoped_refptr<Backend> backend_;
+
+ DISALLOW_COPY_AND_ASSIGN(ValueStoreFrontend);
+};
+
+#endif // EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_FRONTEND_H_
diff --git a/extensions/browser/value_store/value_store_frontend_unittest.cc b/extensions/browser/value_store/value_store_frontend_unittest.cc
new file mode 100644
index 0000000..3d81a1d
--- /dev/null
+++ b/extensions/browser/value_store/value_store_frontend_unittest.cc
@@ -0,0 +1,114 @@
+// 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 "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "chrome/common/chrome_paths.h"
+#include "content/public/test/test_browser_thread.h"
+#include "extensions/browser/value_store/value_store_frontend.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::BrowserThread;
+
+class ValueStoreFrontendTest : public testing::Test {
+ public:
+ ValueStoreFrontendTest()
+ : ui_thread_(BrowserThread::UI, base::MessageLoop::current()),
+ file_thread_(BrowserThread::FILE, base::MessageLoop::current()) {
+ }
+
+ virtual void SetUp() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ base::FilePath test_data_dir;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir));
+ base::FilePath src_db(test_data_dir.AppendASCII("value_store_db"));
+ db_path_ = temp_dir_.path().AppendASCII("temp_db");
+ base::CopyDirectory(src_db, db_path_, true);
+
+ ResetStorage();
+ }
+
+ virtual void TearDown() {
+ base::MessageLoop::current()->RunUntilIdle(); // wait for storage to delete
+ storage_.reset();
+ }
+
+ // Reset the value store, reloading the DB from disk.
+ void ResetStorage() {
+ storage_.reset(new ValueStoreFrontend(db_path_));
+ }
+
+ bool Get(const std::string& key, scoped_ptr<base::Value>* output) {
+ storage_->Get(key, base::Bind(&ValueStoreFrontendTest::GetAndWait,
+ base::Unretained(this), output));
+ base::MessageLoop::current()->Run(); // wait for GetAndWait
+ return !!output->get();
+ }
+
+ protected:
+ void GetAndWait(scoped_ptr<base::Value>* output,
+ scoped_ptr<base::Value> result) {
+ *output = result.Pass();
+ base::MessageLoop::current()->Quit();
+ }
+
+ scoped_ptr<ValueStoreFrontend> storage_;
+ base::ScopedTempDir temp_dir_;
+ base::FilePath db_path_;
+ base::MessageLoop message_loop_;
+ content::TestBrowserThread ui_thread_;
+ content::TestBrowserThread file_thread_;
+};
+
+TEST_F(ValueStoreFrontendTest, GetExistingData) {
+ scoped_ptr<base::Value> value;
+ ASSERT_FALSE(Get("key0", &value));
+
+ // Test existing keys in the DB.
+ {
+ ASSERT_TRUE(Get("key1", &value));
+ std::string result;
+ ASSERT_TRUE(value->GetAsString(&result));
+ EXPECT_EQ("value1", result);
+ }
+
+ {
+ ASSERT_TRUE(Get("key2", &value));
+ int result;
+ ASSERT_TRUE(value->GetAsInteger(&result));
+ EXPECT_EQ(2, result);
+ }
+}
+
+TEST_F(ValueStoreFrontendTest, ChangesPersistAfterReload) {
+ storage_->Set("key0",
+ scoped_ptr<base::Value>(base::Value::CreateIntegerValue(0)));
+ storage_->Set("key1",
+ scoped_ptr<base::Value>(base::Value::CreateStringValue("new1")));
+ storage_->Remove("key2");
+
+ // Reload the DB and test our changes.
+ ResetStorage();
+
+ scoped_ptr<base::Value> value;
+ {
+ ASSERT_TRUE(Get("key0", &value));
+ int result;
+ ASSERT_TRUE(value->GetAsInteger(&result));
+ EXPECT_EQ(0, result);
+ }
+
+ {
+ ASSERT_TRUE(Get("key1", &value));
+ std::string result;
+ ASSERT_TRUE(value->GetAsString(&result));
+ EXPECT_EQ("new1", result);
+ }
+
+ ASSERT_FALSE(Get("key2", &value));
+}
diff --git a/extensions/browser/value_store/value_store_unittest.cc b/extensions/browser/value_store/value_store_unittest.cc
new file mode 100644
index 0000000..a859570
--- /dev/null
+++ b/extensions/browser/value_store/value_store_unittest.cc
@@ -0,0 +1,481 @@
+// 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 "extensions/browser/value_store/value_store_unittest.h"
+
+#include "base/json/json_writer.h"
+#include "base/memory/linked_ptr.h"
+#include "base/values.h"
+
+using content::BrowserThread;
+
+namespace {
+
+// To save typing ValueStore::DEFAULTS everywhere.
+const ValueStore::WriteOptions DEFAULTS = ValueStore::DEFAULTS;
+
+// Gets the pretty-printed JSON for a value.
+std::string GetJSON(const base::Value& value) {
+ std::string json;
+ base::JSONWriter::WriteWithOptions(&value,
+ base::JSONWriter::OPTIONS_PRETTY_PRINT,
+ &json);
+ return json;
+}
+
+} // namespace
+
+// Compares two possibly NULL values for equality, filling |error| with an
+// appropriate error message if they're different.
+bool ValuesEqual(const base::Value* expected,
+ const base::Value* actual,
+ std::string* error) {
+ if (expected == actual) {
+ return true;
+ }
+ if (expected && !actual) {
+ *error = "Expected: " + GetJSON(*expected) + ", actual: NULL";
+ return false;
+ }
+ if (actual && !expected) {
+ *error = "Expected: NULL, actual: " + GetJSON(*actual);
+ return false;
+ }
+ if (!expected->Equals(actual)) {
+ *error =
+ "Expected: " + GetJSON(*expected) + ", actual: " + GetJSON(*actual);
+ return false;
+ }
+ return true;
+}
+
+// Returns whether the read result of a storage operation has the expected
+// settings.
+testing::AssertionResult SettingsEq(
+ const char* _1, const char* _2,
+ const base::DictionaryValue& expected,
+ ValueStore::ReadResult actual_result) {
+ if (actual_result->HasError()) {
+ return testing::AssertionFailure() <<
+ "Result has error: " << actual_result->error().message;
+ }
+
+ std::string error;
+ if (!ValuesEqual(&expected, &actual_result->settings(), &error)) {
+ return testing::AssertionFailure() << error;
+ }
+
+ return testing::AssertionSuccess();
+}
+
+// Returns whether the write result of a storage operation has the expected
+// changes.
+testing::AssertionResult ChangesEq(
+ const char* _1, const char* _2,
+ const ValueStoreChangeList& expected,
+ ValueStore::WriteResult actual_result) {
+ if (actual_result->HasError()) {
+ return testing::AssertionFailure() <<
+ "Result has error: " << actual_result->error().message;
+ }
+
+ const ValueStoreChangeList& actual = actual_result->changes();
+ if (expected.size() != actual.size()) {
+ return testing::AssertionFailure() <<
+ "Actual has wrong size, expecting " << expected.size() <<
+ " but was " << actual.size();
+ }
+
+ std::map<std::string, linked_ptr<ValueStoreChange> > expected_as_map;
+ for (ValueStoreChangeList::const_iterator it = expected.begin();
+ it != expected.end(); ++it) {
+ expected_as_map[it->key()] =
+ linked_ptr<ValueStoreChange>(new ValueStoreChange(*it));
+ }
+
+ std::set<std::string> keys_seen;
+
+ for (ValueStoreChangeList::const_iterator it = actual.begin();
+ it != actual.end(); ++it) {
+ if (keys_seen.count(it->key())) {
+ return testing::AssertionFailure() <<
+ "Multiple changes seen for key: " << it->key();
+ }
+ keys_seen.insert(it->key());
+
+ if (!expected_as_map.count(it->key())) {
+ return testing::AssertionFailure() <<
+ "Actual has unexpected change for key: " << it->key();
+ }
+
+ ValueStoreChange expected_change = *expected_as_map[it->key()];
+ std::string error;
+ if (!ValuesEqual(expected_change.new_value(), it->new_value(), &error)) {
+ return testing::AssertionFailure() <<
+ "New value for " << it->key() << " was unexpected: " << error;
+ }
+ if (!ValuesEqual(expected_change.old_value(), it->old_value(), &error)) {
+ return testing::AssertionFailure() <<
+ "Old value for " << it->key() << " was unexpected: " << error;
+ }
+ }
+
+ return testing::AssertionSuccess();
+}
+
+ValueStoreTest::ValueStoreTest()
+ : key1_("foo"),
+ key2_("bar"),
+ key3_("baz"),
+ empty_dict_(new base::DictionaryValue()),
+ dict1_(new base::DictionaryValue()),
+ dict3_(new base::DictionaryValue()),
+ dict12_(new base::DictionaryValue()),
+ dict123_(new base::DictionaryValue()),
+ ui_thread_(BrowserThread::UI, base::MessageLoop::current()),
+ file_thread_(BrowserThread::FILE, base::MessageLoop::current()) {
+ val1_.reset(base::Value::CreateStringValue(key1_ + "Value"));
+ val2_.reset(base::Value::CreateStringValue(key2_ + "Value"));
+ val3_.reset(base::Value::CreateStringValue(key3_ + "Value"));
+
+ list1_.push_back(key1_);
+ list2_.push_back(key2_);
+ list3_.push_back(key3_);
+ list12_.push_back(key1_);
+ list12_.push_back(key2_);
+ list13_.push_back(key1_);
+ list13_.push_back(key3_);
+ list123_.push_back(key1_);
+ list123_.push_back(key2_);
+ list123_.push_back(key3_);
+
+ set1_.insert(list1_.begin(), list1_.end());
+ set2_.insert(list2_.begin(), list2_.end());
+ set3_.insert(list3_.begin(), list3_.end());
+ set12_.insert(list12_.begin(), list12_.end());
+ set13_.insert(list13_.begin(), list13_.end());
+ set123_.insert(list123_.begin(), list123_.end());
+
+ dict1_->Set(key1_, val1_->DeepCopy());
+ dict3_->Set(key3_, val3_->DeepCopy());
+ dict12_->Set(key1_, val1_->DeepCopy());
+ dict12_->Set(key2_, val2_->DeepCopy());
+ dict123_->Set(key1_, val1_->DeepCopy());
+ dict123_->Set(key2_, val2_->DeepCopy());
+ dict123_->Set(key3_, val3_->DeepCopy());
+}
+
+ValueStoreTest::~ValueStoreTest() {}
+
+void ValueStoreTest::SetUp() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ storage_.reset((GetParam())(temp_dir_.path().AppendASCII("dbName")));
+ ASSERT_TRUE(storage_.get());
+}
+
+void ValueStoreTest::TearDown() {
+ storage_.reset();
+}
+
+TEST_P(ValueStoreTest, GetWhenEmpty) {
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(empty_list_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(list123_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get());
+}
+
+TEST_P(ValueStoreTest, GetWithSingleValue) {
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key1_, NULL, val1_->DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq,
+ changes, storage_->Set(DEFAULTS, key1_, *val1_));
+ }
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict1_, storage_->Get(key1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key2_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key3_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(empty_list_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict1_, storage_->Get(list123_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict1_, storage_->Get());
+}
+
+TEST_P(ValueStoreTest, GetWithMultipleValues) {
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key1_, NULL, val1_->DeepCopy()));
+ changes.push_back(ValueStoreChange(key2_, NULL, val2_->DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Set(DEFAULTS, *dict12_));
+ }
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict1_, storage_->Get(key1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key3_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(empty_list_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict12_, storage_->Get(list123_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict12_, storage_->Get());
+}
+
+TEST_P(ValueStoreTest, RemoveWhenEmpty) {
+ EXPECT_PRED_FORMAT2(ChangesEq, ValueStoreChangeList(),
+ storage_->Remove(key1_));
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(list1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get());
+}
+
+TEST_P(ValueStoreTest, RemoveWithSingleValue) {
+ storage_->Set(DEFAULTS, *dict1_);
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key1_, val1_->DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Remove(key1_));
+ }
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key2_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(list1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(list12_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get());
+}
+
+TEST_P(ValueStoreTest, RemoveWithMultipleValues) {
+ storage_->Set(DEFAULTS, *dict123_);
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key3_, val3_->DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Remove(key3_));
+ }
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict1_, storage_->Get(key1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key3_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(empty_list_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict1_, storage_->Get(list1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict12_, storage_->Get(list12_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict1_, storage_->Get(list13_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict12_, storage_->Get(list123_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict12_, storage_->Get());
+
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key1_, val1_->DeepCopy(), NULL));
+ changes.push_back(ValueStoreChange(key2_, val2_->DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Remove(list12_));
+ }
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key3_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(empty_list_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(list1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(list12_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(list13_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(list123_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get());
+}
+
+TEST_P(ValueStoreTest, SetWhenOverwriting) {
+ storage_->Set(DEFAULTS, key1_, *val2_);
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(
+ ValueStoreChange(key1_, val2_->DeepCopy(), val1_->DeepCopy()));
+ changes.push_back(ValueStoreChange(key2_, NULL, val2_->DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Set(DEFAULTS, *dict12_));
+ }
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict1_, storage_->Get(key1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key3_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(empty_list_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict1_, storage_->Get(list1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict12_, storage_->Get(list12_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict1_, storage_->Get(list13_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict12_, storage_->Get(list123_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *dict12_, storage_->Get());
+}
+
+TEST_P(ValueStoreTest, ClearWhenEmpty) {
+ EXPECT_PRED_FORMAT2(ChangesEq, ValueStoreChangeList(), storage_->Clear());
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(empty_list_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(list123_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get());
+}
+
+TEST_P(ValueStoreTest, ClearWhenNotEmpty) {
+ storage_->Set(DEFAULTS, *dict12_);
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key1_, val1_->DeepCopy(), NULL));
+ changes.push_back(ValueStoreChange(key2_, val2_->DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Clear());
+ }
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(key1_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(empty_list_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(list123_));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get());
+}
+
+// Dots should be allowed in key names; they shouldn't be interpreted as
+// indexing into a dictionary.
+TEST_P(ValueStoreTest, DotsInKeyNames) {
+ std::string dot_key("foo.bar");
+ base::StringValue dot_value("baz.qux");
+ std::vector<std::string> dot_list;
+ dot_list.push_back(dot_key);
+ base::DictionaryValue dot_dict;
+ dot_dict.SetWithoutPathExpansion(dot_key, dot_value.DeepCopy());
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(dot_key));
+
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(
+ ValueStoreChange(dot_key, NULL, dot_value.DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq,
+ changes, storage_->Set(DEFAULTS, dot_key, dot_value));
+ }
+ EXPECT_PRED_FORMAT2(ChangesEq,
+ ValueStoreChangeList(), storage_->Set(DEFAULTS, dot_key, dot_value));
+
+ EXPECT_PRED_FORMAT2(SettingsEq, dot_dict, storage_->Get(dot_key));
+
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(
+ ValueStoreChange(dot_key, dot_value.DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Remove(dot_key));
+ }
+ EXPECT_PRED_FORMAT2(ChangesEq,
+ ValueStoreChangeList(), storage_->Remove(dot_key));
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(
+ ValueStoreChange(dot_key, NULL, dot_value.DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Set(DEFAULTS, dot_dict));
+ }
+
+ EXPECT_PRED_FORMAT2(SettingsEq, dot_dict, storage_->Get(dot_list));
+ EXPECT_PRED_FORMAT2(SettingsEq, dot_dict, storage_->Get());
+
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(
+ ValueStoreChange(dot_key, dot_value.DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Remove(dot_list));
+ }
+
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get(dot_key));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get());
+}
+
+TEST_P(ValueStoreTest, DotsInKeyNamesWithDicts) {
+ base::DictionaryValue outer_dict;
+ base::DictionaryValue* inner_dict = new base::DictionaryValue();
+ outer_dict.Set("foo", inner_dict);
+ inner_dict->SetString("bar", "baz");
+
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(
+ ValueStoreChange("foo", NULL, inner_dict->DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq,
+ changes, storage_->Set(DEFAULTS, outer_dict));
+ }
+
+ EXPECT_PRED_FORMAT2(SettingsEq, outer_dict, storage_->Get("foo"));
+ EXPECT_PRED_FORMAT2(SettingsEq, *empty_dict_, storage_->Get("foo.bar"));
+}
+
+TEST_P(ValueStoreTest, ComplexChangedKeysScenarios) {
+ // Test:
+ // - Setting over missing/changed/same keys, combinations.
+ // - Removing over missing and present keys, combinations.
+ // - Clearing.
+ std::vector<std::string> complex_list;
+ base::DictionaryValue complex_changed_dict;
+
+ storage_->Set(DEFAULTS, key1_, *val1_);
+ EXPECT_PRED_FORMAT2(ChangesEq,
+ ValueStoreChangeList(), storage_->Set(DEFAULTS, key1_, *val1_));
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(
+ key1_, val1_->DeepCopy(), val2_->DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq,
+ changes, storage_->Set(DEFAULTS, key1_, *val2_));
+ }
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key1_, val2_->DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Remove(key1_));
+ EXPECT_PRED_FORMAT2(ChangesEq,
+ ValueStoreChangeList(), storage_->Remove(key1_));
+ }
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key1_, NULL, val1_->DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq,
+ changes, storage_->Set(DEFAULTS, key1_, *val1_));
+ }
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key1_, val1_->DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Clear());
+ EXPECT_PRED_FORMAT2(ChangesEq, ValueStoreChangeList(), storage_->Clear());
+ }
+
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key1_, NULL, val1_->DeepCopy()));
+ changes.push_back(ValueStoreChange(key2_, NULL, val2_->DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Set(DEFAULTS, *dict12_));
+ EXPECT_PRED_FORMAT2(ChangesEq,
+ ValueStoreChangeList(), storage_->Set(DEFAULTS, *dict12_));
+ }
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key3_, NULL, val3_->DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Set(DEFAULTS, *dict123_));
+ }
+ {
+ base::DictionaryValue to_set;
+ to_set.Set(key1_, val2_->DeepCopy());
+ to_set.Set(key2_, val2_->DeepCopy());
+ to_set.Set("asdf", val1_->DeepCopy());
+ to_set.Set("qwerty", val3_->DeepCopy());
+
+ ValueStoreChangeList changes;
+ changes.push_back(
+ ValueStoreChange(key1_, val1_->DeepCopy(), val2_->DeepCopy()));
+ changes.push_back(ValueStoreChange("asdf", NULL, val1_->DeepCopy()));
+ changes.push_back(
+ ValueStoreChange("qwerty", NULL, val3_->DeepCopy()));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Set(DEFAULTS, to_set));
+ }
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key1_, val2_->DeepCopy(), NULL));
+ changes.push_back(ValueStoreChange(key2_, val2_->DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Remove(list12_));
+ }
+ {
+ std::vector<std::string> to_remove;
+ to_remove.push_back(key1_);
+ to_remove.push_back("asdf");
+
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange("asdf", val1_->DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Remove(to_remove));
+ }
+ {
+ ValueStoreChangeList changes;
+ changes.push_back(ValueStoreChange(key3_, val3_->DeepCopy(), NULL));
+ changes.push_back(
+ ValueStoreChange("qwerty", val3_->DeepCopy(), NULL));
+ EXPECT_PRED_FORMAT2(ChangesEq, changes, storage_->Clear());
+ EXPECT_PRED_FORMAT2(ChangesEq, ValueStoreChangeList(), storage_->Clear());
+ }
+}
diff --git a/extensions/browser/value_store/value_store_unittest.h b/extensions/browser/value_store/value_store_unittest.h
new file mode 100644
index 0000000..6360a5a
--- /dev/null
+++ b/extensions/browser/value_store/value_store_unittest.h
@@ -0,0 +1,72 @@
+// 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 EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_UNITTEST_H_
+#define EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_UNITTEST_H_
+
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "content/public/test/test_browser_thread.h"
+#include "extensions/browser/value_store/value_store.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Parameter type for the value-parameterized tests.
+typedef ValueStore* (*ValueStoreTestParam)(const base::FilePath& file_path);
+
+// Test fixture for ValueStore tests. Tests are defined in
+// settings_storage_unittest.cc with configurations for both cached
+// and non-cached leveldb storage, and cached no-op storage.
+class ValueStoreTest : public testing::TestWithParam<ValueStoreTestParam> {
+ public:
+ ValueStoreTest();
+ virtual ~ValueStoreTest();
+
+ virtual void SetUp() OVERRIDE;
+ virtual void TearDown() OVERRIDE;
+
+ protected:
+ scoped_ptr<ValueStore> storage_;
+
+ std::string key1_;
+ std::string key2_;
+ std::string key3_;
+
+ scoped_ptr<base::Value> val1_;
+ scoped_ptr<base::Value> val2_;
+ scoped_ptr<base::Value> val3_;
+
+ std::vector<std::string> empty_list_;
+ std::vector<std::string> list1_;
+ std::vector<std::string> list2_;
+ std::vector<std::string> list3_;
+ std::vector<std::string> list12_;
+ std::vector<std::string> list13_;
+ std::vector<std::string> list123_;
+
+ std::set<std::string> empty_set_;
+ std::set<std::string> set1_;
+ std::set<std::string> set2_;
+ std::set<std::string> set3_;
+ std::set<std::string> set12_;
+ std::set<std::string> set13_;
+ std::set<std::string> set123_;
+
+ scoped_ptr<base::DictionaryValue> empty_dict_;
+ scoped_ptr<base::DictionaryValue> dict1_;
+ scoped_ptr<base::DictionaryValue> dict3_;
+ scoped_ptr<base::DictionaryValue> dict12_;
+ scoped_ptr<base::DictionaryValue> dict123_;
+
+ private:
+ base::ScopedTempDir temp_dir_;
+
+ // Need these so that the DCHECKs for running on FILE or UI threads pass.
+ base::MessageLoop message_loop_;
+ content::TestBrowserThread ui_thread_;
+ content::TestBrowserThread file_thread_;
+};
+
+#endif // EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_UNITTEST_H_
diff --git a/extensions/browser/value_store/value_store_util.cc b/extensions/browser/value_store/value_store_util.cc
new file mode 100644
index 0000000..67a3a00
--- /dev/null
+++ b/extensions/browser/value_store/value_store_util.cc
@@ -0,0 +1,21 @@
+// 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 "extensions/browser/value_store/value_store_util.h"
+
+namespace value_store_util {
+
+scoped_ptr<std::string> NewKey(const std::string& key) {
+ return make_scoped_ptr(new std::string(key));
+}
+
+scoped_ptr<std::string> NoKey() {
+ return scoped_ptr<std::string>();
+}
+
+scoped_ptr<ValueStore::Error> NoError() {
+ return scoped_ptr<ValueStore::Error>();
+}
+
+} // namespace value_store_util
diff --git a/extensions/browser/value_store/value_store_util.h b/extensions/browser/value_store/value_store_util.h
new file mode 100644
index 0000000..ae3b37f
--- /dev/null
+++ b/extensions/browser/value_store/value_store_util.h
@@ -0,0 +1,29 @@
+// 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 EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_UTIL_H_
+#define EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_UTIL_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "extensions/browser/value_store/value_store.h"
+
+namespace value_store_util {
+
+// Returns a copy of |key| as a scoped_ptr. Useful for creating keys for
+// ValueStore::Error.
+scoped_ptr<std::string> NewKey(const std::string& key);
+
+// Returns an empty scoped_ptr. Useful for creating empty keys for
+// ValueStore::Error.
+scoped_ptr<std::string> NoKey();
+
+// Return an empty Error. Useful for creating ValueStore::Error-less
+// ValueStore::Read/WriteResults.
+scoped_ptr<ValueStore::Error> NoError();
+
+} // namespace value_store_util
+
+#endif // EXTENSIONS_BROWSER_VALUE_STORE_VALUE_STORE_UTIL_H_