| Index: chrome/browser/extensions/extension_settings_sync_unittest.cc
|
| diff --git a/chrome/browser/extensions/extension_settings_sync_unittest.cc b/chrome/browser/extensions/extension_settings_sync_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..289ab2388e8a301353d4c1e504dc6eee1eebafa7
|
| --- /dev/null
|
| +++ b/chrome/browser/extensions/extension_settings_sync_unittest.cc
|
| @@ -0,0 +1,473 @@
|
| +// Copyright (c) 2011 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 "testing/gtest/include/gtest/gtest.h"
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/file_util.h"
|
| +#include "base/json/json_reader.h"
|
| +#include "base/json/json_writer.h"
|
| +#include "base/memory/ref_counted.h"
|
| +#include "base/memory/scoped_ptr.h"
|
| +#include "base/message_loop.h"
|
| +#include "base/task.h"
|
| +#include "chrome/browser/extensions/extension_settings.h"
|
| +#include "chrome/browser/extensions/extension_settings_storage_cache.h"
|
| +#include "chrome/browser/extensions/extension_settings_noop_storage.h"
|
| +#include "chrome/browser/extensions/extension_settings_sync_helper.h"
|
| +#include "chrome/browser/extensions/syncable_extension_settings_storage.h"
|
| +#include "chrome/browser/sync/api/sync_change_processor.h"
|
| +#include "content/browser/browser_thread.h"
|
| +
|
| +namespace {
|
| +
|
| +// Define macro to get the __LINE__ expansion.
|
| +#define NEW_CALLBACK(expected) \
|
| + (new AssertEqualsCallback((expected), __LINE__))
|
| +
|
| +// SyncChangeProcessor which just records the changes made, accessed after
|
| +// being converted to the more useful ExtensionSettingSyncData via changes().
|
| +class MockSyncChangeProcessor : public SyncChangeProcessor {
|
| + public:
|
| + virtual SyncError ProcessSyncChanges(
|
| + const tracked_objects::Location& from_here,
|
| + const SyncChangeList& change_list) OVERRIDE {
|
| + for (SyncChangeList::const_iterator it = change_list.begin();
|
| + it != change_list.end(); ++it) {
|
| + ExtensionSettingsSyncData data(*it);
|
| + DCHECK(data.value() != NULL);
|
| + changes_.push_back(data);
|
| + }
|
| + return SyncError();
|
| + }
|
| +
|
| + const ExtensionSettingsSyncDataList& changes() { return changes_; }
|
| +
|
| + void ClearChanges() {
|
| + changes_.clear();
|
| + }
|
| +
|
| + // Returns the only change for a given extension setting. If there is not
|
| + // exactly 1 change for that key, a test assertion will fail.
|
| + ExtensionSettingsSyncData GetOnlyChange(
|
| + const std::string& extension_id, const std::string& key) {
|
| + ExtensionSettingsSyncDataList matching_changes;
|
| + for (ExtensionSettingsSyncDataList::iterator it = changes_.begin();
|
| + it != changes_.end(); ++it) {
|
| + if (it->extension_id() == extension_id && it->key() == key) {
|
| + matching_changes.push_back(*it);
|
| + }
|
| + }
|
| + DCHECK_EQ(1u, matching_changes.size()) <<
|
| + "Not exactly 1 change for " << extension_id << "/" << key <<
|
| + " (out of " << changes_.size() << ")";
|
| + return matching_changes[0];
|
| + }
|
| +
|
| + private:
|
| + ExtensionSettingsSyncDataList changes_;
|
| +};
|
| +
|
| +// Callback from storage methods which performs the test assertions.
|
| +// TODO(kalman): share this with extension_settings_storage_unittest somehow.
|
| +class AssertEqualsCallback : public ExtensionSettingsStorage::Callback {
|
| + public:
|
| + AssertEqualsCallback(DictionaryValue* expected, int line)
|
| + : expected_(expected), line_(line), called_(false) {
|
| + }
|
| +
|
| + ~AssertEqualsCallback() {
|
| + // Need to DCHECK since ASSERT_* can't be used from destructors.
|
| + DCHECK(called_);
|
| + }
|
| +
|
| + virtual void OnSuccess(DictionaryValue* actual) OVERRIDE {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| + ASSERT_FALSE(called_) << "Callback has already been called";
|
| + called_ = true;
|
| + if (expected_ == NULL) {
|
| + ASSERT_TRUE(actual == NULL) << "Values are different:\n" <<
|
| + "Line: " << line_ << "\n" <<
|
| + "Expected: NULL\n" <<
|
| + "Got: " << GetJson(actual);
|
| + } else {
|
| + ASSERT_TRUE(expected_->Equals(actual)) << "Values are different:\n" <<
|
| + "Line: " << line_ << "\n" <<
|
| + "Expected: " << GetJson(expected_) <<
|
| + "Got: " << GetJson(actual);
|
| + delete actual;
|
| + }
|
| + }
|
| +
|
| + virtual void OnFailure(const std::string& message) OVERRIDE {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| + ASSERT_FALSE(called_) << "Callback has already been called";
|
| + called_ = true;
|
| + // No tests allow failure (yet).
|
| + ASSERT_TRUE(false) << "Callback failed on line " << line_;
|
| + }
|
| +
|
| + private:
|
| + std::string GetJson(Value* value) {
|
| + std::string json;
|
| + base::JSONWriter::Write(value, true, &json);
|
| + return json;
|
| + }
|
| +
|
| + DictionaryValue* expected_;
|
| + int line_;
|
| + bool called_;
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +class ExtensionSettingsSyncUnittest : public testing::Test {
|
| + public:
|
| + virtual void SetUp() {
|
| + ui_message_loop_.reset(new MessageLoopForUI());
|
| + // Use the same message loop for the UI and FILE threads, giving a test
|
| + // pattern where storage API calls get posted to the same message loop (the
|
| + // current one), then all run with MessageLoop::current()->RunAllPending().
|
| + ui_thread_.reset(
|
| + new BrowserThread(BrowserThread::UI, MessageLoop::current()));
|
| + file_thread_.reset(
|
| + new BrowserThread(BrowserThread::FILE, MessageLoop::current()));
|
| + sync_.reset(new MockSyncChangeProcessor());
|
| +
|
| + FilePath temp_dir;
|
| + file_util::CreateNewTempDirectory(FilePath::StringType(), &temp_dir);
|
| + settings_ = new ExtensionSettings(temp_dir);
|
| + }
|
| +
|
| + protected:
|
| + void GetStorage(
|
| + const std::string& extension_id,
|
| + SyncableExtensionSettingsStorage** storage) {
|
| + settings_->GetStorage(
|
| + extension_id,
|
| + base::Bind(
|
| + &ExtensionSettingsSyncUnittest::AssignStorage,
|
| + base::Unretained(this),
|
| + storage));
|
| + MessageLoop::current()->RunAllPending();
|
| + }
|
| +
|
| + scoped_ptr<MockSyncChangeProcessor> sync_;
|
| + scoped_refptr<ExtensionSettings> settings_;
|
| +
|
| + private:
|
| + void AssignStorage(
|
| + SyncableExtensionSettingsStorage** assign_to,
|
| + SyncableExtensionSettingsStorage* storage) {
|
| + DCHECK(storage != NULL);
|
| + *assign_to = storage;
|
| + }
|
| +
|
| + scoped_ptr<MessageLoopForUI> ui_message_loop_;
|
| + scoped_ptr<BrowserThread> ui_thread_;
|
| + scoped_ptr<BrowserThread> file_thread_;
|
| +};
|
| +
|
| +TEST_F(ExtensionSettingsSyncUnittest, NoDataDoesNotInvokeSync) {
|
| + SyncableExtensionSettingsStorage* storage1;
|
| + SyncableExtensionSettingsStorage* storage2;
|
| + DictionaryValue empty_dict;
|
| +
|
| + // Have one extension created before sync is set up, the other created after.
|
| + GetStorage("storage1", &storage1);
|
| + settings_->MergeDataAndStartSyncing(
|
| + syncable::EXTENSION_SETTINGS,
|
| + SyncDataList(),
|
| + sync_.get());
|
| + MessageLoop::current()->RunAllPending();
|
| + GetStorage("storage2", &storage2);
|
| +
|
| + settings_->StopSyncing(syncable::EXTENSION_SETTINGS);
|
| + MessageLoop::current()->RunAllPending();
|
| +
|
| + ASSERT_EQ(0u, sync_->changes().size());
|
| +}
|
| +
|
| +TEST_F(ExtensionSettingsSyncUnittest, InSyncDataDoesNotInvokeSync) {
|
| + StringValue value1("fooValue");
|
| + ListValue value2;
|
| + value2.Append(StringValue::CreateStringValue("barValue"));
|
| +
|
| + SyncableExtensionSettingsStorage* storage1;
|
| + SyncableExtensionSettingsStorage* storage2;
|
| +
|
| + GetStorage("storage1", &storage1);
|
| + GetStorage("storage2", &storage2);
|
| +
|
| + storage1->Set("foo", value1, new ExtensionSettingsStorage::NoopCallback());
|
| + storage2->Set("bar", value2, new ExtensionSettingsStorage::NoopCallback());
|
| + MessageLoop::current()->RunAllPending();
|
| +
|
| + SyncDataList sync_data;
|
| + sync_data.push_back(ExtensionSettingsSyncHelper::CreateData(
|
| + "storage1", "foo", value1));
|
| + sync_data.push_back(ExtensionSettingsSyncHelper::CreateData(
|
| + "storage2", "bar", value2));
|
| +
|
| + settings_->MergeDataAndStartSyncing(
|
| + syncable::EXTENSION_SETTINGS, sync_data, sync_.get());
|
| + MessageLoop::current()->RunAllPending();
|
| + settings_->StopSyncing(syncable::EXTENSION_SETTINGS);
|
| + MessageLoop::current()->RunAllPending();
|
| +
|
| + // Already in sync, so no changes.
|
| + ASSERT_EQ(0u, sync_->changes().size());
|
| +}
|
| +
|
| +TEST_F(ExtensionSettingsSyncUnittest, LocalDataWithNoSyncDataIsPushedToSync) {
|
| + StringValue value1("fooValue");
|
| + ListValue value2;
|
| + value2.Append(StringValue::CreateStringValue("barValue"));
|
| +
|
| + SyncableExtensionSettingsStorage* storage1;
|
| + SyncableExtensionSettingsStorage* storage2;
|
| +
|
| + GetStorage("storage1", &storage1);
|
| + GetStorage("storage2", &storage2);
|
| +
|
| + storage1->Set("foo", value1, new ExtensionSettingsStorage::NoopCallback());
|
| + storage2->Set("bar", value2, new ExtensionSettingsStorage::NoopCallback());
|
| + MessageLoop::current()->RunAllPending();
|
| +
|
| + settings_->MergeDataAndStartSyncing(
|
| + syncable::EXTENSION_SETTINGS, SyncDataList(), sync_.get());
|
| + MessageLoop::current()->RunAllPending();
|
| +
|
| + // All settings should have been pushed to sync.
|
| + ASSERT_EQ(2u, sync_->changes().size());
|
| + ExtensionSettingsSyncData change = sync_->GetOnlyChange("storage1", "foo");
|
| + ASSERT_EQ(SyncChange::ACTION_ADD, change.change_type());
|
| + ASSERT_TRUE(value1.Equals(change.value()));
|
| + change = sync_->GetOnlyChange("storage2", "bar");
|
| + ASSERT_EQ(SyncChange::ACTION_ADD, change.change_type());
|
| + ASSERT_TRUE(value2.Equals(change.value()));
|
| +
|
| + settings_->StopSyncing(syncable::EXTENSION_SETTINGS);
|
| + MessageLoop::current()->RunAllPending();
|
| +}
|
| +
|
| +TEST_F(ExtensionSettingsSyncUnittest, AnySyncDataOverwritesLocalData) {
|
| + StringValue value1("fooValue");
|
| + ListValue value2;
|
| + value2.Append(StringValue::CreateStringValue("barValue"));
|
| +
|
| + SyncableExtensionSettingsStorage* storage1;
|
| + SyncableExtensionSettingsStorage* storage2;
|
| +
|
| + // Pre-populate one of the storage areas.
|
| + GetStorage("storage1", &storage1);
|
| + storage1->Set(
|
| + "overwriteMe", value1, new ExtensionSettingsStorage::NoopCallback());
|
| + MessageLoop::current()->RunAllPending();
|
| +
|
| + SyncDataList sync_data;
|
| + sync_data.push_back(ExtensionSettingsSyncHelper::CreateData(
|
| + "storage1", "foo", value1));
|
| + sync_data.push_back(ExtensionSettingsSyncHelper::CreateData(
|
| + "storage2", "bar", value2));
|
| + settings_->MergeDataAndStartSyncing(
|
| + syncable::EXTENSION_SETTINGS, sync_data, sync_.get());
|
| + MessageLoop::current()->RunAllPending();
|
| +
|
| + GetStorage("storage2", &storage2);
|
| +
|
| + // All changes should be local, so no sync changes.
|
| + ASSERT_EQ(0u, sync_->changes().size());
|
| +
|
| + // Sync settings should have been pushed to local settings.
|
| + DictionaryValue settings1;
|
| + settings1.Set("foo", value1.DeepCopy());
|
| + DictionaryValue settings2;
|
| + settings2.Set("bar", value2.DeepCopy());
|
| + storage1->Get(NEW_CALLBACK(&settings1));
|
| + storage2->Get(NEW_CALLBACK(&settings2));
|
| + MessageLoop::current()->RunAllPending();
|
| +
|
| + settings_->StopSyncing(syncable::EXTENSION_SETTINGS);
|
| + MessageLoop::current()->RunAllPending();
|
| +}
|
| +
|
| +TEST_F(ExtensionSettingsSyncUnittest, ProcessSyncChanges) {
|
| + StringValue value1("fooValue");
|
| + ListValue value2;
|
| + value2.Append(StringValue::CreateStringValue("barValue"));
|
| +
|
| + SyncableExtensionSettingsStorage* storage1;
|
| + SyncableExtensionSettingsStorage* storage2;
|
| + // Maintain dictionaries mirrored to the expected values of the settings in
|
| + // each storage area.
|
| + // TODO rename these.
|
| + DictionaryValue settings1, settings2;
|
| +
|
| + // Make storage1 initialised from local data, storage2 initialised from sync.
|
| + GetStorage("storage1", &storage1);
|
| + GetStorage("storage2", &storage2);
|
| +
|
| + storage1->Set("foo", value1, new ExtensionSettingsStorage::NoopCallback());
|
| + settings1.Set("foo", value1.DeepCopy());
|
| + MessageLoop::current()->RunAllPending();
|
| +
|
| + SyncDataList sync_data;
|
| + sync_data.push_back(ExtensionSettingsSyncHelper::CreateData(
|
| + "storage2", "bar", value2));
|
| + settings2.Set("bar", value2.DeepCopy());
|
| +
|
| + settings_->MergeDataAndStartSyncing(
|
| + syncable::EXTENSION_SETTINGS, sync_data, sync_.get());
|
| + MessageLoop::current()->RunAllPending();
|
| +
|
| + // Make sync add some settings.
|
| + SyncChangeList change_list;
|
| + change_list.push_back(ExtensionSettingsSyncHelper::CreateAdd(
|
| + "storage1", "bar", value2));
|
| + settings1.Set("bar", value2.DeepCopy());
|
| + change_list.push_back(ExtensionSettingsSyncHelper::CreateAdd(
|
| + "storage2", "foo", value1));
|
| + settings2.Set("foo", value1.DeepCopy());
|
| + settings_->ProcessSyncChanges(FROM_HERE, change_list);
|
| + MessageLoop::current()->RunAllPending();
|
| +
|
| + storage1->Get(NEW_CALLBACK(&settings1));
|
| + storage2->Get(NEW_CALLBACK(&settings2));
|
| + MessageLoop::current()->RunAllPending();
|
| +
|
| + // Make sync update some settings, storage1 the new setting, storage2 the
|
| + // initial setting.
|
| + change_list.clear();
|
| + change_list.push_back(ExtensionSettingsSyncHelper::CreateUpdate(
|
| + "storage1", "bar", value2));
|
| + settings1.Set("bar", value2.DeepCopy());
|
| + change_list.push_back(ExtensionSettingsSyncHelper::CreateUpdate(
|
| + "storage2", "bar", value1));
|
| + settings2.Set("bar", value1.DeepCopy());
|
| + settings_->ProcessSyncChanges(FROM_HERE, change_list);
|
| + MessageLoop::current()->RunAllPending();
|
| +
|
| + storage1->Get(NEW_CALLBACK(&settings1));
|
| + storage2->Get(NEW_CALLBACK(&settings2));
|
| + MessageLoop::current()->RunAllPending();
|
| +
|
| + // Make sync remove some settings, storage1 the initial setting, storage2 the
|
| + // new setting.
|
| + change_list.clear();
|
| + change_list.push_back(ExtensionSettingsSyncHelper::CreateDelete(
|
| + "storage1", "foo"));
|
| + settings1.Remove("foo", NULL);
|
| + change_list.push_back(ExtensionSettingsSyncHelper::CreateDelete(
|
| + "storage2", "foo"));
|
| + settings2.Remove("foo", NULL);
|
| + settings_->ProcessSyncChanges(FROM_HERE, change_list);
|
| + MessageLoop::current()->RunAllPending();
|
| +
|
| + storage1->Get(NEW_CALLBACK(&settings1));
|
| + storage2->Get(NEW_CALLBACK(&settings2));
|
| + MessageLoop::current()->RunAllPending();
|
| +
|
| + settings_->StopSyncing(syncable::EXTENSION_SETTINGS);
|
| + MessageLoop::current()->RunAllPending();
|
| +}
|
| +
|
| +TEST_F(ExtensionSettingsSyncUnittest, PushToSync) {
|
| + StringValue value1("fooValue");
|
| + ListValue value2;
|
| + value2.Append(StringValue::CreateStringValue("barValue"));
|
| +
|
| + SyncableExtensionSettingsStorage* storage1;
|
| + SyncableExtensionSettingsStorage* storage2;
|
| + SyncableExtensionSettingsStorage* storage3;
|
| + SyncableExtensionSettingsStorage* storage4;
|
| +
|
| + // Make storage1/2 initialised from local data, storage3/4 initialised from
|
| + // sync.
|
| + GetStorage("storage1", &storage1);
|
| + GetStorage("storage2", &storage2);
|
| + GetStorage("storage3", &storage3);
|
| + GetStorage("storage4", &storage4);
|
| +
|
| + storage1->Set("foo", value1, new ExtensionSettingsStorage::NoopCallback());
|
| + storage2->Set("foo", value1, new ExtensionSettingsStorage::NoopCallback());
|
| + MessageLoop::current()->RunAllPending();
|
| +
|
| + SyncDataList sync_data;
|
| + sync_data.push_back(ExtensionSettingsSyncHelper::CreateData(
|
| + "storage3", "bar", value2));
|
| + sync_data.push_back(ExtensionSettingsSyncHelper::CreateData(
|
| + "storage4", "bar", value2));
|
| +
|
| + settings_->MergeDataAndStartSyncing(
|
| + syncable::EXTENSION_SETTINGS, sync_data, sync_.get());
|
| + MessageLoop::current()->RunAllPending();
|
| +
|
| + // Add something locally.
|
| + storage1->Set("bar", value2, new ExtensionSettingsStorage::NoopCallback());
|
| + storage2->Set("bar", value2, new ExtensionSettingsStorage::NoopCallback());
|
| + storage3->Set("foo", value1, new ExtensionSettingsStorage::NoopCallback());
|
| + storage4->Set("foo", value1, new ExtensionSettingsStorage::NoopCallback());
|
| + MessageLoop::current()->RunAllPending();
|
| +
|
| + ExtensionSettingsSyncData change = sync_->GetOnlyChange("storage1", "bar");
|
| + ASSERT_EQ(SyncChange::ACTION_ADD, change.change_type());
|
| + ASSERT_TRUE(value2.Equals(change.value()));
|
| + sync_->GetOnlyChange("storage2", "bar");
|
| + ASSERT_EQ(SyncChange::ACTION_ADD, change.change_type());
|
| + ASSERT_TRUE(value2.Equals(change.value()));
|
| + change = sync_->GetOnlyChange("storage3", "foo");
|
| + ASSERT_EQ(SyncChange::ACTION_ADD, change.change_type());
|
| + ASSERT_TRUE(value1.Equals(change.value()));
|
| + change = sync_->GetOnlyChange("storage4", "foo");
|
| + ASSERT_EQ(SyncChange::ACTION_ADD, change.change_type());
|
| + ASSERT_TRUE(value1.Equals(change.value()));
|
| +
|
| + // Change something locally, storage1/3 the new setting and storage2/4 the
|
| + // initial setting, for all combinations of local vs sync intialisation and
|
| + // new vs initial.
|
| + sync_->ClearChanges();
|
| + storage1->Set("bar", value1, new ExtensionSettingsStorage::NoopCallback());
|
| + storage2->Set("foo", value2, new ExtensionSettingsStorage::NoopCallback());
|
| + storage3->Set("bar", value1, new ExtensionSettingsStorage::NoopCallback());
|
| + storage4->Set("foo", value2, new ExtensionSettingsStorage::NoopCallback());
|
| + MessageLoop::current()->RunAllPending();
|
| +
|
| + change = sync_->GetOnlyChange("storage1", "bar");
|
| + ASSERT_EQ(SyncChange::ACTION_UPDATE, change.change_type());
|
| + ASSERT_TRUE(value1.Equals(change.value()));
|
| + change = sync_->GetOnlyChange("storage2", "foo");
|
| + ASSERT_EQ(SyncChange::ACTION_UPDATE, change.change_type());
|
| + ASSERT_TRUE(value2.Equals(change.value()));
|
| + change = sync_->GetOnlyChange("storage3", "bar");
|
| + ASSERT_EQ(SyncChange::ACTION_UPDATE, change.change_type());
|
| + ASSERT_TRUE(value1.Equals(change.value()));
|
| + change = sync_->GetOnlyChange("storage4", "foo");
|
| + ASSERT_EQ(SyncChange::ACTION_UPDATE, change.change_type());
|
| + ASSERT_TRUE(value2.Equals(change.value()));
|
| +
|
| + // Remove something locally, storage1/3 the new setting and storage2/4 the
|
| + // initial setting, for all combinations of local vs sync intialisation and
|
| + // new vs initial.
|
| + sync_->ClearChanges();
|
| + storage1->Remove("foo", new ExtensionSettingsStorage::NoopCallback());
|
| + storage2->Remove("bar", new ExtensionSettingsStorage::NoopCallback());
|
| + storage3->Remove("foo", new ExtensionSettingsStorage::NoopCallback());
|
| + storage4->Remove("bar", new ExtensionSettingsStorage::NoopCallback());
|
| + MessageLoop::current()->RunAllPending();
|
| +
|
| + change = sync_->GetOnlyChange("storage1", "foo");
|
| + ASSERT_EQ(SyncChange::ACTION_DELETE, change.change_type());
|
| + change = sync_->GetOnlyChange("storage2", "bar");
|
| + ASSERT_EQ(SyncChange::ACTION_DELETE, change.change_type());
|
| + change = sync_->GetOnlyChange("storage3", "foo");
|
| + ASSERT_EQ(SyncChange::ACTION_DELETE, change.change_type());
|
| + change = sync_->GetOnlyChange("storage4", "bar");
|
| + ASSERT_EQ(SyncChange::ACTION_DELETE, change.change_type());
|
| +
|
| + // XXX CLEAR
|
| +
|
| + settings_->StopSyncing(syncable::EXTENSION_SETTINGS);
|
| + MessageLoop::current()->RunAllPending();
|
| +}
|
|
|