diff options
author | jamescook@chromium.org <jamescook@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-01 00:34:00 +0000 |
---|---|---|
committer | jamescook@chromium.org <jamescook@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-01 00:34:00 +0000 |
commit | 47b870f6f82d7b1f05ddffb0349d1431cb181426 (patch) | |
tree | 64537612db9d86a1d0d8b447d169f51ee9d9bbb1 /extensions/browser/value_store | |
parent | 31463d3a32678cb95e4d6e53cd942780f81c1082 (diff) | |
download | chromium_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')
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_ |