Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(5034)

Unified Diff: chrome/browser/extensions/extension_settings_sync_unittest.cc

Issue 7775008: Enable sync for the settings from the Extension Settings API. (Closed) Base URL: http://git.chromium.org/git/chromium.git@trunk
Patch Set: Reordering Created 9 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..5a46209540f2fbcc4af889db49d7ee073522d4bf
--- /dev/null
+++ b/chrome/browser/extensions/extension_settings_sync_unittest.cc
@@ -0,0 +1,531 @@
+// 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) {
+ ExtensionSettingSyncData data(*it);
+ DCHECK(data.value() != NULL);
+ changes_.push_back(data);
+ }
+ return SyncError();
+ }
+
+ const ExtensionSettingSyncDataList& 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.
+ ExtensionSettingSyncData GetOnlyChange(
+ const std::string& extension_id, const std::string& key) {
+ ExtensionSettingSyncDataList matching_changes;
+ for (ExtensionSettingSyncDataList::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:
+ ExtensionSettingSyncDataList changes_;
+};
+
+// Callback from storage methods which performs the test assertions.
+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_;
+};
+
+// Callback from storage methods which does nothing.
+class NoopCallback : public ExtensionSettingsStorage::Callback {
+ public:
+ virtual ~NoopCallback() {}
+ virtual void OnSuccess(DictionaryValue* s) OVERRIDE { delete s; }
+ virtual void OnFailure(const std::string& message) OVERRIDE {}
+};
+
+} // 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 NoopCallback());
+ storage2->Set("bar", value2, new 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 NoopCallback());
+ storage2->Set("bar", value2, new 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());
+ ExtensionSettingSyncData 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 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 expected1;
+ expected1.Set("foo", value1.DeepCopy());
+ DictionaryValue expected2;
+ expected2.Set("bar", value2.DeepCopy());
+ storage1->Get(NEW_CALLBACK(&expected1));
+ storage2->Get(NEW_CALLBACK(&expected2));
+ 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.
+ DictionaryValue expected1, expected2;
+
+ // Make storage1 initialised from local data, storage2 initialised from sync.
+ GetStorage("storage1", &storage1);
+ GetStorage("storage2", &storage2);
+
+ storage1->Set("foo", value1, new NoopCallback());
+ expected1.Set("foo", value1.DeepCopy());
+ MessageLoop::current()->RunAllPending();
+
+ SyncDataList sync_data;
+ sync_data.push_back(ExtensionSettingsSyncHelper::CreateData(
+ "storage2", "bar", value2));
+ expected2.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));
+ expected1.Set("bar", value2.DeepCopy());
+ change_list.push_back(ExtensionSettingsSyncHelper::CreateAdd(
+ "storage2", "foo", value1));
+ expected2.Set("foo", value1.DeepCopy());
+ settings_->ProcessSyncChanges(FROM_HERE, change_list);
+ MessageLoop::current()->RunAllPending();
+
+ storage1->Get(NEW_CALLBACK(&expected1));
+ storage2->Get(NEW_CALLBACK(&expected2));
+ 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));
+ expected1.Set("bar", value2.DeepCopy());
+ change_list.push_back(ExtensionSettingsSyncHelper::CreateUpdate(
+ "storage2", "bar", value1));
+ expected2.Set("bar", value1.DeepCopy());
+ settings_->ProcessSyncChanges(FROM_HERE, change_list);
+ MessageLoop::current()->RunAllPending();
+
+ storage1->Get(NEW_CALLBACK(&expected1));
+ storage2->Get(NEW_CALLBACK(&expected2));
+ 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"));
+ expected1.Remove("foo", NULL);
+ change_list.push_back(ExtensionSettingsSyncHelper::CreateDelete(
+ "storage2", "foo"));
+ expected2.Remove("foo", NULL);
+ settings_->ProcessSyncChanges(FROM_HERE, change_list);
+ MessageLoop::current()->RunAllPending();
+
+ storage1->Get(NEW_CALLBACK(&expected1));
+ storage2->Get(NEW_CALLBACK(&expected2));
+ 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 NoopCallback());
+ storage2->Set("foo", value1, new 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 NoopCallback());
+ storage2->Set("bar", value2, new NoopCallback());
+ storage3->Set("foo", value1, new NoopCallback());
+ storage4->Set("foo", value1, new NoopCallback());
+ MessageLoop::current()->RunAllPending();
+
+ ExtensionSettingSyncData 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 NoopCallback());
+ storage2->Set("foo", value2, new NoopCallback());
+ storage3->Set("bar", value1, new NoopCallback());
+ storage4->Set("foo", value2, new 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 NoopCallback());
+ storage2->Remove("bar", new NoopCallback());
+ storage3->Remove("foo", new NoopCallback());
+ storage4->Remove("bar", new NoopCallback());
+ MessageLoop::current()->RunAllPending();
+
+ ASSERT_EQ(
+ SyncChange::ACTION_DELETE,
+ sync_->GetOnlyChange("storage1", "foo").change_type());
+ ASSERT_EQ(
+ SyncChange::ACTION_DELETE,
+ sync_->GetOnlyChange("storage2", "bar").change_type());
+ ASSERT_EQ(
+ SyncChange::ACTION_DELETE,
+ sync_->GetOnlyChange("storage3", "foo").change_type());
+ ASSERT_EQ(
+ SyncChange::ACTION_DELETE,
+ sync_->GetOnlyChange("storage4", "bar").change_type());
+
+ // Remove some nonexistent settings.
+ sync_->ClearChanges();
+ storage1->Remove("foo", new NoopCallback());
+ storage2->Remove("bar", new NoopCallback());
+ storage3->Remove("foo", new NoopCallback());
+ storage4->Remove("bar", new NoopCallback());
+ MessageLoop::current()->RunAllPending();
+
+ ASSERT_EQ(0u, sync_->changes().size());
+
+ // Clear the rest of the settings. Add the removed ones back first so that
+ // more than one setting is cleared.
+ storage1->Set("foo", value1, new NoopCallback());
+ storage2->Set("bar", value2, new NoopCallback());
+ storage3->Set("foo", value1, new NoopCallback());
+ storage4->Set("bar", value2, new NoopCallback());
+ MessageLoop::current()->RunAllPending();
+
+ sync_->ClearChanges();
+ storage1->Clear(new NoopCallback());
+ storage2->Clear(new NoopCallback());
+ storage3->Clear(new NoopCallback());
+ storage4->Clear(new NoopCallback());
+ MessageLoop::current()->RunAllPending();
+
+ ASSERT_EQ(
+ SyncChange::ACTION_DELETE,
+ sync_->GetOnlyChange("storage1", "foo").change_type());
+ ASSERT_EQ(
+ SyncChange::ACTION_DELETE,
+ sync_->GetOnlyChange("storage1", "bar").change_type());
+ ASSERT_EQ(
+ SyncChange::ACTION_DELETE,
+ sync_->GetOnlyChange("storage2", "foo").change_type());
+ ASSERT_EQ(
+ SyncChange::ACTION_DELETE,
+ sync_->GetOnlyChange("storage2", "bar").change_type());
+ ASSERT_EQ(
+ SyncChange::ACTION_DELETE,
+ sync_->GetOnlyChange("storage3", "foo").change_type());
+ ASSERT_EQ(
+ SyncChange::ACTION_DELETE,
+ sync_->GetOnlyChange("storage3", "bar").change_type());
+ ASSERT_EQ(
+ SyncChange::ACTION_DELETE,
+ sync_->GetOnlyChange("storage4", "foo").change_type());
+ ASSERT_EQ(
+ SyncChange::ACTION_DELETE,
+ sync_->GetOnlyChange("storage4", "bar").change_type());
+
+ settings_->StopSyncing(syncable::EXTENSION_SETTINGS);
+ MessageLoop::current()->RunAllPending();
+}

Powered by Google App Engine
This is Rietveld 408576698