Commit ab37ca87 authored by dgrogan@chromium.org's avatar dgrogan@chromium.org

Implementation of leveldb-backed PrefStore.

This is not hooked up yet, migration code from Json-backed stores is needed, among other things.

BUG=362814

Review URL: https://codereview.chromium.org/169323003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@271602 0039d316-1c4b-4281-b951-d872f2087c98
parent aae544d7
...@@ -330,6 +330,11 @@ void JsonPrefStore::OnFileRead(scoped_ptr<base::Value> value, ...@@ -330,6 +330,11 @@ void JsonPrefStore::OnFileRead(scoped_ptr<base::Value> value,
// operation itself. // operation itself.
NOTREACHED(); NOTREACHED();
break; break;
case PREF_READ_ERROR_LEVELDB_IO:
case PREF_READ_ERROR_LEVELDB_CORRUPTION_READ_ONLY:
case PREF_READ_ERROR_LEVELDB_CORRUPTION:
// These are specific to LevelDBPrefStore.
NOTREACHED();
case PREF_READ_ERROR_MAX_ENUM: case PREF_READ_ERROR_MAX_ENUM:
NOTREACHED(); NOTREACHED();
break; break;
......
...@@ -33,6 +33,9 @@ class BASE_PREFS_EXPORT PersistentPrefStore : public WriteablePrefStore { ...@@ -33,6 +33,9 @@ class BASE_PREFS_EXPORT PersistentPrefStore : public WriteablePrefStore {
// Indicates that ReadPrefs() couldn't complete synchronously and is waiting // Indicates that ReadPrefs() couldn't complete synchronously and is waiting
// for an asynchronous task to complete first. // for an asynchronous task to complete first.
PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE = 10, PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE = 10,
PREF_READ_ERROR_LEVELDB_IO = 11,
PREF_READ_ERROR_LEVELDB_CORRUPTION_READ_ONLY = 12,
PREF_READ_ERROR_LEVELDB_CORRUPTION = 13,
PREF_READ_ERROR_MAX_ENUM PREF_READ_ERROR_MAX_ENUM
}; };
......
...@@ -313,7 +313,8 @@ void HandleReadError(PersistentPrefStore::PrefReadError error) { ...@@ -313,7 +313,8 @@ void HandleReadError(PersistentPrefStore::PrefReadError error) {
// an example problem that this can cause. // an example problem that this can cause.
// Do some diagnosis and try to avoid losing data. // Do some diagnosis and try to avoid losing data.
int message_id = 0; int message_id = 0;
if (error <= PersistentPrefStore::PREF_READ_ERROR_JSON_TYPE) { if (error <= PersistentPrefStore::PREF_READ_ERROR_JSON_TYPE ||
error == PersistentPrefStore::PREF_READ_ERROR_LEVELDB_CORRUPTION) {
message_id = IDS_PREFERENCES_CORRUPT_ERROR; message_id = IDS_PREFERENCES_CORRUPT_ERROR;
} else if (error != PersistentPrefStore::PREF_READ_ERROR_NO_FILE) { } else if (error != PersistentPrefStore::PREF_READ_ERROR_NO_FILE) {
message_id = IDS_PREFERENCES_UNREADABLE_ERROR; message_id = IDS_PREFERENCES_UNREADABLE_ERROR;
......
// 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 "chrome/browser/prefs/leveldb_pref_store.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/file_util.h"
#include "base/json/json_string_value_serializer.h"
#include "base/location.h"
#include "base/metrics/sparse_histogram.h"
#include "base/sequenced_task_runner.h"
#include "base/task_runner_util.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "third_party/leveldatabase/env_chromium.h"
#include "third_party/leveldatabase/src/include/leveldb/db.h"
#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
namespace {
enum ErrorMasks {
OPENED = 1 << 0,
DESTROYED = 1 << 1,
REPAIRED = 1 << 2,
DESTROY_FAILED = 1 << 3,
REPAIR_FAILED = 1 << 4,
IO_ERROR = 1 << 5,
DATA_LOST = 1 << 6,
ITER_NOT_OK = 1 << 7,
FILE_NOT_SPECIFIED = 1 << 8,
};
PersistentPrefStore::PrefReadError IntToPrefReadError(int error) {
DCHECK(error);
if (error == FILE_NOT_SPECIFIED)
return PersistentPrefStore::PREF_READ_ERROR_FILE_NOT_SPECIFIED;
if (error == OPENED)
return PersistentPrefStore::PREF_READ_ERROR_NONE;
if (error & IO_ERROR)
return PersistentPrefStore::PREF_READ_ERROR_LEVELDB_IO;
if (error & OPENED)
return PersistentPrefStore::PREF_READ_ERROR_LEVELDB_CORRUPTION;
return PersistentPrefStore::PREF_READ_ERROR_LEVELDB_CORRUPTION_READ_ONLY;
}
} // namespace
struct LevelDBPrefStore::ReadingResults {
ReadingResults() : error(0) {}
bool no_dir;
scoped_ptr<leveldb::DB> db;
scoped_ptr<PrefValueMap> value_map;
int error;
};
// An instance of this class is created on the UI thread but is used
// exclusively on the FILE thread.
class LevelDBPrefStore::FileThreadSerializer {
public:
explicit FileThreadSerializer(scoped_ptr<leveldb::DB> db) : db_(db.Pass()) {}
void WriteToDatabase(
std::map<std::string, std::string>* keys_to_set,
std::set<std::string>* keys_to_delete) {
DCHECK(keys_to_set->size() > 0 || keys_to_delete->size() > 0);
leveldb::WriteBatch batch;
for (std::map<std::string, std::string>::iterator iter =
keys_to_set->begin();
iter != keys_to_set->end();
iter++) {
batch.Put(iter->first, iter->second);
}
for (std::set<std::string>::iterator iter = keys_to_delete->begin();
iter != keys_to_delete->end();
iter++) {
batch.Delete(*iter);
}
leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
// DCHECK is fine; the corresponding error is ignored in JsonPrefStore.
// There's also no API available to surface the error back up to the caller.
// TODO(dgrogan): UMA?
DCHECK(status.ok()) << status.ToString();
}
private:
scoped_ptr<leveldb::DB> db_;
DISALLOW_COPY_AND_ASSIGN(FileThreadSerializer);
};
bool MoveDirectoryAside(const base::FilePath& path) {
base::FilePath bad_path = path.AppendASCII(".bad");
if (!base::Move(path, bad_path)) {
base::DeleteFile(bad_path, true);
return false;
}
return true;
}
/* static */
void LevelDBPrefStore::OpenDB(const base::FilePath& path,
ReadingResults* reading_results) {
DCHECK_EQ(0, reading_results->error);
leveldb::Options options;
options.create_if_missing = true;
leveldb::DB* db;
while (1) {
leveldb::Status status =
leveldb::DB::Open(options, path.AsUTF8Unsafe(), &db);
if (status.ok()) {
reading_results->db.reset(db);
reading_results->error |= OPENED;
break;
}
if (leveldb_env::IsIOError(status)) {
reading_results->error |= IO_ERROR;
break;
}
if (reading_results->error & DESTROYED)
break;
DCHECK(!(reading_results->error & REPAIR_FAILED));
if (!(reading_results->error & REPAIRED)) {
status = leveldb::RepairDB(path.AsUTF8Unsafe(), options);
if (status.ok()) {
reading_results->error |= REPAIRED;
continue;
}
reading_results->error |= REPAIR_FAILED;
}
if (!MoveDirectoryAside(path)) {
status = leveldb::DestroyDB(path.AsUTF8Unsafe(), options);
if (!status.ok()) {
reading_results->error |= DESTROY_FAILED;
break;
}
}
reading_results->error |= DESTROYED;
}
DCHECK(reading_results->error);
DCHECK(!((reading_results->error & OPENED) &&
(reading_results->error & DESTROY_FAILED)));
DCHECK(reading_results->error != (REPAIR_FAILED | OPENED));
}
/* static */
scoped_ptr<LevelDBPrefStore::ReadingResults> LevelDBPrefStore::DoReading(
const base::FilePath& path) {
base::ThreadRestrictions::AssertIOAllowed();
scoped_ptr<ReadingResults> reading_results(new ReadingResults);
reading_results->no_dir = !base::PathExists(path.DirName());
OpenDB(path, reading_results.get());
if (!reading_results->db) {
DCHECK(!(reading_results->error & OPENED));
return reading_results.Pass();
}
DCHECK(reading_results->error & OPENED);
reading_results->value_map.reset(new PrefValueMap);
scoped_ptr<leveldb::Iterator> it(
reading_results->db->NewIterator(leveldb::ReadOptions()));
// TODO(dgrogan): Is it really necessary to check it->status() each iteration?
for (it->SeekToFirst(); it->Valid() && it->status().ok(); it->Next()) {
const std::string value_string = it->value().ToString();
JSONStringValueSerializer deserializer(value_string);
std::string error_message;
int error_code;
base::Value* json_value =
deserializer.Deserialize(&error_code, &error_message);
if (json_value) {
reading_results->value_map->SetValue(it->key().ToString(), json_value);
} else {
DLOG(ERROR) << "Invalid json for key " << it->key().ToString()
<< ": " << error_message;
reading_results->error |= DATA_LOST;
}
}
if (!it->status().ok())
reading_results->error |= ITER_NOT_OK;
return reading_results.Pass();
}
LevelDBPrefStore::LevelDBPrefStore(
const base::FilePath& filename,
base::SequencedTaskRunner* sequenced_task_runner)
: path_(filename),
sequenced_task_runner_(sequenced_task_runner),
original_task_runner_(base::MessageLoopProxy::current()),
read_only_(false),
initialized_(false),
read_error_(PREF_READ_ERROR_NONE),
weak_ptr_factory_(this) {}
LevelDBPrefStore::~LevelDBPrefStore() {
CommitPendingWrite();
sequenced_task_runner_->DeleteSoon(FROM_HERE, serializer_.release());
}
bool LevelDBPrefStore::GetValue(const std::string& key,
const base::Value** result) const {
DCHECK(initialized_);
const base::Value* tmp = NULL;
if (!prefs_.GetValue(key, &tmp)) {
return false;
}
if (result)
*result = tmp;
return true;
}
// Callers of GetMutableValue have to also call ReportValueChanged.
bool LevelDBPrefStore::GetMutableValue(const std::string& key,
base::Value** result) {
DCHECK(initialized_);
return prefs_.GetValue(key, result);
}
void LevelDBPrefStore::AddObserver(PrefStore::Observer* observer) {
observers_.AddObserver(observer);
}
void LevelDBPrefStore::RemoveObserver(PrefStore::Observer* observer) {
observers_.RemoveObserver(observer);
}
bool LevelDBPrefStore::HasObservers() const {
return observers_.might_have_observers();
}
bool LevelDBPrefStore::IsInitializationComplete() const { return initialized_; }
void LevelDBPrefStore::PersistFromUIThread() {
if (read_only_)
return;
DCHECK(serializer_);
scoped_ptr<std::set<std::string> > keys_to_delete(new std::set<std::string>);
keys_to_delete->swap(keys_to_delete_);
scoped_ptr<std::map<std::string, std::string> > keys_to_set(
new std::map<std::string, std::string>);
keys_to_set->swap(keys_to_set_);
sequenced_task_runner_->PostTask(
FROM_HERE,
base::Bind(&LevelDBPrefStore::FileThreadSerializer::WriteToDatabase,
base::Unretained(serializer_.get()),
base::Owned(keys_to_set.release()),
base::Owned(keys_to_delete.release())));
}
void LevelDBPrefStore::ScheduleWrite() {
if (!timer_.IsRunning()) {
timer_.Start(FROM_HERE,
base::TimeDelta::FromSeconds(10),
this,
&LevelDBPrefStore::PersistFromUIThread);
}
}
void LevelDBPrefStore::SetValue(const std::string& key, base::Value* value) {
SetValueInternal(key, value, true /*notify*/);
}
void LevelDBPrefStore::SetValueSilently(const std::string& key,
base::Value* value) {
SetValueInternal(key, value, false /*notify*/);
}
static std::string Serialize(base::Value* value) {
std::string value_string;
JSONStringValueSerializer serializer(&value_string);
bool serialized_ok = serializer.Serialize(*value);
DCHECK(serialized_ok);
return value_string;
}
void LevelDBPrefStore::SetValueInternal(const std::string& key,
base::Value* value,
bool notify) {
DCHECK(initialized_);
DCHECK(value);
scoped_ptr<base::Value> new_value(value);
base::Value* old_value = NULL;
prefs_.GetValue(key, &old_value);
if (!old_value || !value->Equals(old_value)) {
std::string value_string = Serialize(value);
prefs_.SetValue(key, new_value.release());
MarkForInsertion(key, value_string);
if (notify)
NotifyObservers(key);
}
}
void LevelDBPrefStore::RemoveValue(const std::string& key) {
DCHECK(initialized_);
if (prefs_.RemoveValue(key)) {
MarkForDeletion(key);
NotifyObservers(key);
}
}
bool LevelDBPrefStore::ReadOnly() const { return read_only_; }
PersistentPrefStore::PrefReadError LevelDBPrefStore::GetReadError() const {
return read_error_;
}
PersistentPrefStore::PrefReadError LevelDBPrefStore::ReadPrefs() {
DCHECK(!initialized_);
scoped_ptr<ReadingResults> reading_results;
if (path_.empty()) {
reading_results.reset(new ReadingResults);
reading_results->error = FILE_NOT_SPECIFIED;
} else {
reading_results = DoReading(path_);
}
PrefReadError error = IntToPrefReadError(reading_results->error);
OnStorageRead(reading_results.Pass());
return error;
}
void LevelDBPrefStore::ReadPrefsAsync(ReadErrorDelegate* error_delegate) {
DCHECK_EQ(false, initialized_);
error_delegate_.reset(error_delegate);
if (path_.empty()) {
scoped_ptr<ReadingResults> reading_results(new ReadingResults);
reading_results->error = FILE_NOT_SPECIFIED;
OnStorageRead(reading_results.Pass());
return;
}
PostTaskAndReplyWithResult(sequenced_task_runner_,
FROM_HERE,
base::Bind(&LevelDBPrefStore::DoReading, path_),
base::Bind(&LevelDBPrefStore::OnStorageRead,
weak_ptr_factory_.GetWeakPtr()));
}
void LevelDBPrefStore::CommitPendingWrite() {
if (timer_.IsRunning()) {
timer_.Stop();
PersistFromUIThread();
}
}
void LevelDBPrefStore::MarkForDeletion(const std::string& key) {
if (read_only_)
return;
keys_to_delete_.insert(key);
// Need to erase in case there's a set operation in the same batch that would
// clobber this delete.
keys_to_set_.erase(key);
ScheduleWrite();
}
void LevelDBPrefStore::MarkForInsertion(const std::string& key,
const std::string& value) {
if (read_only_)
return;
keys_to_set_[key] = value;
// Need to erase in case there's a delete operation in the same batch that
// would clobber this set.
keys_to_delete_.erase(key);
ScheduleWrite();
}
void LevelDBPrefStore::ReportValueChanged(const std::string& key) {
base::Value* new_value = NULL;
DCHECK(prefs_.GetValue(key, &new_value));
std::string value_string = Serialize(new_value);
MarkForInsertion(key, value_string);
NotifyObservers(key);
}
void LevelDBPrefStore::NotifyObservers(const std::string& key) {
FOR_EACH_OBSERVER(PrefStore::Observer, observers_, OnPrefValueChanged(key));
}
void LevelDBPrefStore::OnStorageRead(
scoped_ptr<LevelDBPrefStore::ReadingResults> reading_results) {
UMA_HISTOGRAM_SPARSE_SLOWLY("LevelDBPrefStore.ReadErrors",
reading_results->error);
read_error_ = IntToPrefReadError(reading_results->error);
if (reading_results->no_dir) {
FOR_EACH_OBSERVER(
PrefStore::Observer, observers_, OnInitializationCompleted(false));
return;
}
initialized_ = true;
if (reading_results->db) {
DCHECK(reading_results->value_map);
serializer_.reset(new FileThreadSerializer(reading_results->db.Pass()));
prefs_.Swap(reading_results->value_map.get());
} else {
read_only_ = true;
}
// TODO(dgrogan): Call pref_filter_->FilterOnLoad
if (error_delegate_.get() && read_error_ != PREF_READ_ERROR_NONE)
error_delegate_->OnError(read_error_);
FOR_EACH_OBSERVER(
PrefStore::Observer, observers_, OnInitializationCompleted(true));
}
// 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 CHROME_BROWSER_PREFS_LEVELDB_PREF_STORE_H_
#define CHROME_BROWSER_PREFS_LEVELDB_PREF_STORE_H_
#include <set>
#include <string>
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/files/file_path.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/observer_list.h"
#include "base/prefs/persistent_pref_store.h"
#include "base/prefs/pref_value_map.h"
#include "base/timer/timer.h"
namespace base {
class DictionaryValue;
class SequencedTaskRunner;
class Value;
}
namespace leveldb {
class DB;
}
// A writable PrefStore implementation that is used for user preferences.
class LevelDBPrefStore : public PersistentPrefStore {
public:
// |sequenced_task_runner| is must be a shutdown-blocking task runner, ideally
// created by GetTaskRunnerForFile() method above.
LevelDBPrefStore(const base::FilePath& pref_filename,
base::SequencedTaskRunner* sequenced_task_runner);
// PrefStore overrides:
virtual bool GetValue(const std::string& key,
const base::Value** result) const OVERRIDE;
virtual void AddObserver(PrefStore::Observer* observer) OVERRIDE;
virtual void RemoveObserver(PrefStore::Observer* observer) OVERRIDE;
virtual bool HasObservers() const OVERRIDE;
virtual bool IsInitializationComplete() const OVERRIDE;
// PersistentPrefStore overrides:
virtual bool GetMutableValue(const std::string& key,
base::Value** result) OVERRIDE;
// Takes ownership of value.
virtual void SetValue(const std::string& key, base::Value* value) OVERRIDE;
virtual void SetValueSilently(const std::string& key,
base::Value* value) OVERRIDE;
virtual void RemoveValue(const std::string& key) OVERRIDE;
virtual bool ReadOnly() const OVERRIDE;
virtual PrefReadError GetReadError() const OVERRIDE;
virtual PrefReadError ReadPrefs() OVERRIDE;
virtual void ReadPrefsAsync(ReadErrorDelegate* error_delegate) OVERRIDE;
virtual void CommitPendingWrite() OVERRIDE;
virtual void ReportValueChanged(const std::string& key) OVERRIDE;
private:
struct ReadingResults;
class FileThreadSerializer;
virtual ~LevelDBPrefStore();
static scoped_ptr<ReadingResults> DoReading(const base::FilePath& path);
static void OpenDB(const base::FilePath& path,
ReadingResults* reading_results);
void OnStorageRead(scoped_ptr<ReadingResults> reading_results);
void PersistFromUIThread();
void RemoveFromUIThread(const std::string& key);
void ScheduleWrite();
void SetValueInternal(const std::string& key,
base::Value* value,
bool notify);
void NotifyObservers(const std::string& key);
void MarkForInsertion(const std::string& key, const std::string& value);
void MarkForDeletion(const std::string& key);
base::FilePath path_;
const scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner_;
const scoped_refptr<base::SequencedTaskRunner> original_task_runner_;
PrefValueMap prefs_;
bool read_only_;
ObserverList<PrefStore::Observer, true> observers_;
scoped_ptr<ReadErrorDelegate> error_delegate_;
bool initialized_;
PrefReadError read_error_;
// This object is created on the UI thread right after preferences are loaded
// from disk. A message to delete it is sent to the FILE thread by
// ~LevelDBPrefStore.
scoped_ptr<FileThreadSerializer> serializer_;
// Changes are accumulated in |keys_to_delete_| and |keys_to_set_| and are
// stored in the database according to |timer_|.
std::set<std::string> keys_to_delete_;
std::map<std::string, std::string> keys_to_set_;
base::OneShotTimer<LevelDBPrefStore> timer_;
base::WeakPtrFactory<LevelDBPrefStore> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(LevelDBPrefStore);
};
#endif // CHROME_BROWSER_PREFS_LEVELDB_PREF_STORE_H_
// 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 "chrome/browser/prefs/leveldb_pref_store.h"
#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 "base/run_loop.h"
#include "base/values.h"
#include "chrome/common/chrome_paths.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
class MockPrefStoreObserver : public PrefStore::Observer {
public:
MOCK_METHOD1(OnPrefValueChanged, void(const std::string&));
MOCK_METHOD1(OnInitializationCompleted, void(bool));
};
class MockReadErrorDelegate : public PersistentPrefStore::ReadErrorDelegate {
public:
MOCK_METHOD1(OnError, void(PersistentPrefStore::PrefReadError));
};
} // namespace
class LevelDBPrefStoreTest : public testing::Test {
protected:
virtual void SetUp() OVERRIDE {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_dir_));
data_dir_ = data_dir_.AppendASCII("prefs");
ASSERT_TRUE(PathExists(data_dir_));
}
void Open() {
pref_store_ = new LevelDBPrefStore(
temp_dir_.path(), message_loop_.message_loop_proxy().get());
EXPECT_EQ(LevelDBPrefStore::PREF_READ_ERROR_NONE, pref_store_->ReadPrefs());
}
void Close() {
pref_store_ = NULL;
base::RunLoop().RunUntilIdle();
}
void CloseAndReopen() {
Close();
Open();
}
// The path to temporary directory used to contain the test operations.
base::ScopedTempDir temp_dir_;
// The path to the directory where the test data is stored in the source tree.
base::FilePath data_dir_;
// A message loop that we can use as the file thread message loop.
base::MessageLoop message_loop_;
scoped_refptr<LevelDBPrefStore> pref_store_;
};
TEST_F(LevelDBPrefStoreTest, PutAndGet) {
Open();
const std::string key = "some.key";
pref_store_->SetValue(key, new base::FundamentalValue(5));
base::FundamentalValue orig_value(5);
const base::Value* actual_value;
EXPECT_TRUE(pref_store_->GetValue(key, &actual_value));
EXPECT_TRUE(orig_value.Equals(actual_value));
}
TEST_F(LevelDBPrefStoreTest, PutAndGetPersistent) {
Open();
const std::string key = "some.key";
pref_store_->SetValue(key, new base::FundamentalValue(5));
CloseAndReopen();
const base::Value* actual_value = NULL;
base::FundamentalValue orig_value(5);
EXPECT_TRUE(pref_store_->GetValue(key, &actual_value));
EXPECT_TRUE(orig_value.Equals(actual_value));
}
TEST_F(LevelDBPrefStoreTest, BasicObserver) {
scoped_refptr<LevelDBPrefStore> pref_store = new LevelDBPrefStore(
temp_dir_.path(), message_loop_.message_loop_proxy().get());
MockPrefStoreObserver mock_observer;
pref_store->AddObserver(&mock_observer);
EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(1);
EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, pref_store->ReadPrefs());
testing::Mock::VerifyAndClearExpectations(&mock_observer);
const std::string key = "some.key";
EXPECT_CALL(mock_observer, OnPrefValueChanged(key)).Times(1);
pref_store->SetValue(key, new base::FundamentalValue(5));
pref_store->RemoveObserver(&mock_observer);
}
TEST_F(LevelDBPrefStoreTest, SetValueSilently) {
Open();
MockPrefStoreObserver mock_observer;
pref_store_->AddObserver(&mock_observer);
const std::string key = "some.key";
EXPECT_CALL(mock_observer, OnPrefValueChanged(key)).Times(0);
pref_store_->SetValueSilently(key, new base::FundamentalValue(30));
pref_store_->RemoveObserver(&mock_observer);
CloseAndReopen();
base::FundamentalValue value(30);
const base::Value* actual_value = NULL;
EXPECT_TRUE(pref_store_->GetValue(key, &actual_value));
EXPECT_TRUE(base::Value::Equals(&value, actual_value));
}
TEST_F(LevelDBPrefStoreTest, GetMutableValue) {
Open();
const std::string key = "some.key";
base::DictionaryValue* orig_value = new base::DictionaryValue;
orig_value->SetInteger("key2", 25);
pref_store_->SetValue(key, orig_value);
base::Value* actual_value;
EXPECT_TRUE(pref_store_->GetMutableValue(key, &actual_value));
EXPECT_TRUE(orig_value->Equals(actual_value));
base::DictionaryValue* dict_value;
ASSERT_TRUE(actual_value->GetAsDictionary(&dict_value));
dict_value->SetInteger("key2", 30);
pref_store_->ReportValueChanged(key);
// Ensure the new value is stored in memory.
const base::Value* retrieved_value;
EXPECT_TRUE(pref_store_->GetValue(key, &retrieved_value));
scoped_ptr<base::DictionaryValue> golden_value(new base::DictionaryValue);
golden_value->SetInteger("key2", 30);
EXPECT_TRUE(base::Value::Equals(golden_value.get(), retrieved_value));
// Ensure the new value is persisted to disk.
CloseAndReopen();
EXPECT_TRUE(pref_store_->GetValue(key, &retrieved_value));
EXPECT_TRUE(base::Value::Equals(golden_value.get(), retrieved_value));
}
TEST_F(LevelDBPrefStoreTest, RemoveFromMemory) {
Open();
const std::string key = "some.key";
pref_store_->SetValue(key, new base::FundamentalValue(5));
MockPrefStoreObserver mock_observer;
pref_store_->AddObserver(&mock_observer);
EXPECT_CALL(mock_observer, OnPrefValueChanged(key)).Times(1);
pref_store_->RemoveValue(key);
pref_store_->RemoveObserver(&mock_observer);
const base::Value* retrieved_value;
EXPECT_FALSE(pref_store_->GetValue(key, &retrieved_value));
}
TEST_F(LevelDBPrefStoreTest, RemoveFromDisk) {
Open();
const std::string key = "some.key";
pref_store_->SetValue(key, new base::FundamentalValue(5));
CloseAndReopen();
pref_store_->RemoveValue(key);
CloseAndReopen();
const base::Value* retrieved_value;
EXPECT_FALSE(pref_store_->GetValue(key, &retrieved_value));
}
TEST_F(LevelDBPrefStoreTest, OpenAsync) {
// First set a key/value with a synchronous connection.
Open();
const std::string key = "some.key";
pref_store_->SetValue(key, new base::FundamentalValue(5));
Close();
scoped_refptr<LevelDBPrefStore> pref_store(new LevelDBPrefStore(
temp_dir_.path(), message_loop_.message_loop_proxy().get()));
MockReadErrorDelegate* delegate = new MockReadErrorDelegate;
pref_store->ReadPrefsAsync(delegate);
MockPrefStoreObserver mock_observer;
pref_store->AddObserver(&mock_observer);
EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(1);
base::RunLoop().RunUntilIdle();
pref_store->RemoveObserver(&mock_observer);
const base::Value* result;
EXPECT_TRUE(pref_store->GetValue("some.key", &result));
int int_value;
EXPECT_TRUE(result->GetAsInteger(&int_value));
EXPECT_EQ(5, int_value);
pref_store = NULL;
}
TEST_F(LevelDBPrefStoreTest, OpenAsyncError) {
// Open a connection that will lock the database.
Open();
// Try to open an async connection to the same database.
scoped_refptr<LevelDBPrefStore> pref_store(new LevelDBPrefStore(
temp_dir_.path(), message_loop_.message_loop_proxy().get()));
MockReadErrorDelegate* delegate = new MockReadErrorDelegate;
pref_store->ReadPrefsAsync(delegate);
MockPrefStoreObserver mock_observer;
pref_store->AddObserver(&mock_observer);
EXPECT_CALL(*delegate,
OnError(PersistentPrefStore::PREF_READ_ERROR_LEVELDB_IO))
.Times(1);
EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(1);
base::RunLoop().RunUntilIdle();
pref_store->RemoveObserver(&mock_observer);
EXPECT_TRUE(pref_store->ReadOnly());
EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_LEVELDB_IO,
pref_store->GetReadError());
// Sync connection to the database will be closed by the destructor.
}
TEST_F(LevelDBPrefStoreTest, RepairCorrupt) {
// Open a database where CURRENT has no newline. Ensure that repair is called
// and there is no error reading the database.
base::FilePath corrupted_dir = data_dir_.AppendASCII("corrupted_leveldb");
base::FilePath dest = temp_dir_.path().AppendASCII("corrupted_leveldb");
const bool kRecursive = true;
ASSERT_TRUE(CopyDirectory(corrupted_dir, dest, kRecursive));
pref_store_ =
new LevelDBPrefStore(dest, message_loop_.message_loop_proxy().get());
EXPECT_EQ(LevelDBPrefStore::PREF_READ_ERROR_LEVELDB_CORRUPTION,
pref_store_->ReadPrefs());
}
TEST_F(LevelDBPrefStoreTest, Values) {
Open();
pref_store_->SetValue("boolean", new base::FundamentalValue(false));
pref_store_->SetValue("integer", new base::FundamentalValue(10));
pref_store_->SetValue("double", new base::FundamentalValue(10.3));
pref_store_->SetValue("string", new base::StringValue("some string"));
base::DictionaryValue* dict_value = new base::DictionaryValue;
dict_value->Set("boolean", new base::FundamentalValue(true));
scoped_ptr<base::DictionaryValue> golden_dict_value(dict_value->DeepCopy());
pref_store_->SetValue("dictionary", dict_value);
base::ListValue* list_value = new base::ListValue;
list_value->Set(2, new base::StringValue("string in list"));
scoped_ptr<base::ListValue> golden_list_value(list_value->DeepCopy());
pref_store_->SetValue("list", list_value);
// Do something nontrivial as well.
base::DictionaryValue* compound_value = new base::DictionaryValue;
base::ListValue* outer_list = new base::ListValue;
base::ListValue* inner_list = new base::ListValue;
inner_list->Set(0, new base::FundamentalValue(5));
outer_list->Set(1, inner_list);
compound_value->Set("compound_lists", outer_list);
scoped_ptr<base::DictionaryValue> golden_compound_value(
compound_value->DeepCopy());
pref_store_->SetValue("compound_value", compound_value);
CloseAndReopen();
const base::Value* value;
EXPECT_TRUE(pref_store_->GetValue("boolean", &value));
EXPECT_TRUE(base::FundamentalValue(false).Equals(value));
EXPECT_TRUE(pref_store_->GetValue("integer", &value));
EXPECT_TRUE(base::FundamentalValue(10).Equals(value));
EXPECT_TRUE(pref_store_->GetValue("double", &value));
EXPECT_TRUE(base::FundamentalValue(10.3).Equals(value));
EXPECT_TRUE(pref_store_->GetValue("string", &value));
EXPECT_TRUE(base::StringValue("some string").Equals(value));
EXPECT_TRUE(pref_store_->GetValue("dictionary", &value));
EXPECT_TRUE(base::Value::Equals(golden_dict_value.get(), value));
EXPECT_TRUE(pref_store_->GetValue("list", &value));
EXPECT_TRUE(base::Value::Equals(golden_list_value.get(), value));
EXPECT_TRUE(pref_store_->GetValue("compound_value", &value));
EXPECT_TRUE(base::Value::Equals(golden_compound_value.get(), value));
}
...@@ -1549,6 +1549,8 @@ ...@@ -1549,6 +1549,8 @@
'browser/prefs/incognito_mode_prefs.h', 'browser/prefs/incognito_mode_prefs.h',
'browser/prefs/interceptable_pref_filter.cc', 'browser/prefs/interceptable_pref_filter.cc',
'browser/prefs/interceptable_pref_filter.h', 'browser/prefs/interceptable_pref_filter.h',
'browser/prefs/leveldb_pref_store.cc',
'browser/prefs/leveldb_pref_store.h',
'browser/prefs/pref_hash_calculator.cc', 'browser/prefs/pref_hash_calculator.cc',
'browser/prefs/pref_hash_calculator.h', 'browser/prefs/pref_hash_calculator.h',
'browser/prefs/pref_hash_filter.cc', 'browser/prefs/pref_hash_filter.cc',
......
...@@ -1154,6 +1154,7 @@ ...@@ -1154,6 +1154,7 @@
'browser/prefs/chrome_pref_service_unittest.cc', 'browser/prefs/chrome_pref_service_unittest.cc',
'browser/prefs/command_line_pref_store_unittest.cc', 'browser/prefs/command_line_pref_store_unittest.cc',
'browser/prefs/incognito_mode_prefs_unittest.cc', 'browser/prefs/incognito_mode_prefs_unittest.cc',
'browser/prefs/leveldb_pref_store_unittest.cc',
'browser/prefs/pref_hash_calculator_unittest.cc', 'browser/prefs/pref_hash_calculator_unittest.cc',
'browser/prefs/pref_hash_filter_unittest.cc', 'browser/prefs/pref_hash_filter_unittest.cc',
'browser/prefs/pref_hash_store_impl_unittest.cc', 'browser/prefs/pref_hash_store_impl_unittest.cc',
......
MANIFEST-000007
\ No newline at end of file
...@@ -8989,6 +8989,14 @@ Therefore, the affected-histogram name has to have at least one dot in it. ...@@ -8989,6 +8989,14 @@ Therefore, the affected-histogram name has to have at least one dot in it.
</summary> </summary>
</histogram> </histogram>
<histogram name="LevelDBPrefStore.ReadErrors" enum="LevelDBPrefStoreErrorCodes">
<owner>dgrogan@chromium.org</owner>
<summary>
Bitfield that indicates errors and recovery that occurred when opening a
LevelDB preferences database.
</summary>
</histogram>
<histogram name="LibraryLoader.NativeLibraryHack" enum="BooleanUsage"> <histogram name="LibraryLoader.NativeLibraryHack" enum="BooleanUsage">
<owner>feng@chromium.org</owner> <owner>feng@chromium.org</owner>
<summary> <summary>
...@@ -37275,6 +37283,36 @@ Therefore, the affected-histogram name has to have at least one dot in it. ...@@ -37275,6 +37283,36 @@ Therefore, the affected-histogram name has to have at least one dot in it.
<int value="20" label="GetChildren"/> <int value="20" label="GetChildren"/>
</enum> </enum>
<enum name="LevelDBPrefStoreErrorCodes" type="int">
<int value="1" label="OPENED"/>
<int value="5" label="REPAIRED | OPENED"/>
<int value="6" label="REPAIRED | DESTROYED"/>
<int value="7" label="REPAIRED | DESTROYED | OPENED"/>
<int value="12" label="REPAIRED | DESTROY_FAILED"/>
<int value="18" label="REPAIR_FAILED | DESTROYED"/>
<int value="19" label="REPAIR_FAILED | DESTROYED | OPENED"/>
<int value="24" label="REPAIR_FAILED | DESTROY_FAILED"/>
<int value="32" label="IO_ERROR"/>
<int value="36" label="REPAIRED | IO_ERROR"/>
<int value="38" label="REPAIRED | DESTROYED | IO_ERROR"/>
<int value="50" label="REPAIR_FAILED | DESTROYED | IO_ERROR"/>
<int value="65" label="OPENED | DATA_LOST"/>
<int value="69" label="REPAIRED | OPENED | DATA_LOST"/>
<int value="71" label="REPAIRED | DESTROYED | OPENED | DATA_LOST"/>
<int value="83" label="REPAIR_FAILED | DESTROYED | OPENED | DATA_LOST"/>
<int value="129" label="OPENED | ITER_NOT_OK"/>
<int value="133" label="REPAIRED | OPENED | ITER_NOT_OK"/>
<int value="135" label="REPAIRED | DESTROYED | OPENED | ITER_NOT_OK"/>
<int value="147" label="REPAIR_FAILED | DESTROYED | OPENED | ITER_NOT_OK"/>
<int value="193" label="OPENED | DATA_LOST | ITER_NOT_OK"/>
<int value="197" label="REPAIRED | OPENED | DATA_LOST | ITER_NOT_OK"/>
<int value="199"
label="REPAIRED | DESTROYED | OPENED | DATA_LOST | ITER_NOT_OK"/>
<int value="211"
label="REPAIR_FAILED | DESTROYED | OPENED | DATA_LOST | ITER_NOT_OK"/>
<int value="256" label="FILE_NOT_SPECIFIED"/>
</enum>
<enum name="LinkMonitorFailureType" type="int"> <enum name="LinkMonitorFailureType" type="int">
<int value="0" label="Local MAC Address Not Found"/> <int value="0" label="Local MAC Address Not Found"/>
<int value="1" label="Client Startup Failure"/> <int value="1" label="Client Startup Failure"/>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment