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(); |
+} |