| // Copyright (c) 2012 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/basictypes.h" |
| #include "base/compiler_specific.h" |
| #include "base/file_util.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/message_loop.h" |
| #include "base/values.h" |
| #include "chrome/browser/signin/signin_manager.h" |
| #include "chrome/browser/signin/signin_manager_factory.h" |
| #include "chrome/browser/signin/token_service.h" |
| #include "chrome/browser/signin/token_service_factory.h" |
| #include "chrome/browser/sync/glue/bookmark_data_type_controller.h" |
| #include "chrome/browser/sync/glue/data_type_controller.h" |
| #include "chrome/browser/sync/profile_sync_components_factory_mock.h" |
| #include "chrome/browser/sync/test_profile_sync_service.h" |
| #include "chrome/common/chrome_version_info.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/testing_pref_service.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/test/test_browser_thread.h" |
| #include "google/cacheinvalidation/include/types.h" |
| #include "google_apis/gaia/gaia_constants.h" |
| #include "sync/js/js_arg_list.h" |
| #include "sync/js/js_event_details.h" |
| #include "sync/js/js_test_util.h" |
| #include "sync/notifier/fake_invalidation_handler.h" |
| #include "sync/notifier/invalidator.h" |
| #include "sync/notifier/invalidator_test_template.h" |
| #include "sync/notifier/object_id_state_map_test_util.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "webkit/user_agent/user_agent.h" |
| |
| // TODO(akalin): Add tests here that exercise the whole |
| // ProfileSyncService/SyncBackendHost stack while mocking out as |
| // little as possible. |
| |
| namespace browser_sync { |
| |
| namespace { |
| |
| using content::BrowserThread; |
| using testing::_; |
| using testing::AtLeast; |
| using testing::AtMost; |
| using testing::Mock; |
| using testing::Return; |
| using testing::StrictMock; |
| |
| class ProfileSyncServiceTestHarness { |
| public: |
| ProfileSyncServiceTestHarness() |
| : ui_thread_(BrowserThread::UI, &ui_loop_), |
| db_thread_(BrowserThread::DB), |
| file_thread_(BrowserThread::FILE), |
| io_thread_(BrowserThread::IO) {} |
| |
| ~ProfileSyncServiceTestHarness() {} |
| |
| void SetUp() { |
| file_thread_.Start(); |
| io_thread_.StartIOThread(); |
| profile.reset(new TestingProfile()); |
| profile->CreateRequestContext(); |
| |
| // We need to set the user agent before the backend host can call |
| // webkit_glue::GetUserAgent(). |
| webkit_glue::SetUserAgent(content::GetContentClient()->GetUserAgent(), |
| false); |
| } |
| |
| void TearDown() { |
| // Kill the service before the profile. |
| service.reset(); |
| profile.reset(); |
| // Pump messages posted by the sync thread (which may end up |
| // posting on the IO thread). |
| ui_loop_.RunAllPending(); |
| io_thread_.Stop(); |
| file_thread_.Stop(); |
| // Ensure that the sync objects destruct to avoid memory leaks. |
| ui_loop_.RunAllPending(); |
| } |
| |
| // TODO(akalin): Refactor the StartSyncService*() functions below. |
| |
| void StartSyncService() { |
| StartSyncServiceAndSetInitialSyncEnded( |
| true, true, false, true, syncer::STORAGE_IN_MEMORY); |
| } |
| |
| void StartSyncServiceAndSetInitialSyncEnded( |
| bool set_initial_sync_ended, |
| bool issue_auth_token, |
| bool synchronous_sync_configuration, |
| bool sync_setup_completed, |
| syncer::StorageOption storage_option) { |
| if (!service.get()) { |
| SigninManager* signin = |
| SigninManagerFactory::GetForProfile(profile.get()); |
| signin->SetAuthenticatedUsername("test"); |
| ProfileSyncComponentsFactoryMock* factory = |
| new ProfileSyncComponentsFactoryMock(); |
| service.reset(new TestProfileSyncService( |
| factory, |
| profile.get(), |
| signin, |
| ProfileSyncService::AUTO_START, |
| true, |
| base::Closure())); |
| if (!set_initial_sync_ended) |
| service->dont_set_initial_sync_ended_on_init(); |
| if (synchronous_sync_configuration) |
| service->set_synchronous_sync_configuration(); |
| service->set_storage_option(storage_option); |
| if (!sync_setup_completed) |
| profile->GetPrefs()->SetBoolean(prefs::kSyncHasSetupCompleted, false); |
| |
| // Register the bookmark data type. |
| ON_CALL(*factory, CreateDataTypeManager(_, _, _)). |
| WillByDefault(ReturnNewDataTypeManager()); |
| |
| if (issue_auth_token) { |
| IssueTestTokens(); |
| } |
| service->Initialize(); |
| } |
| } |
| |
| void IssueTestTokens() { |
| TokenService* token_service = |
| TokenServiceFactory::GetForProfile(profile.get()); |
| token_service->IssueAuthTokenForTest( |
| GaiaConstants::kSyncService, "token1"); |
| token_service->IssueAuthTokenForTest( |
| GaiaConstants::kGaiaOAuth2LoginRefreshToken, "token2"); |
| } |
| |
| scoped_ptr<TestProfileSyncService> service; |
| scoped_ptr<TestingProfile> profile; |
| |
| private: |
| MessageLoop ui_loop_; |
| // Needed by |service|. |
| content::TestBrowserThread ui_thread_; |
| content::TestBrowserThread db_thread_; |
| // Needed by DisableAndEnableSyncTemporarily test case. |
| content::TestBrowserThread file_thread_; |
| // Needed by |service| and |profile|'s request context. |
| content::TestBrowserThread io_thread_; |
| }; |
| |
| class ProfileSyncServiceTest : public testing::Test { |
| protected: |
| virtual void SetUp() { |
| harness_.SetUp(); |
| } |
| |
| virtual void TearDown() { |
| harness_.TearDown(); |
| } |
| |
| ProfileSyncServiceTestHarness harness_; |
| }; |
| |
| TEST_F(ProfileSyncServiceTest, InitialState) { |
| SigninManager* signin = |
| SigninManagerFactory::GetForProfile(harness_.profile.get()); |
| harness_.service.reset(new TestProfileSyncService( |
| new ProfileSyncComponentsFactoryMock(), |
| harness_.profile.get(), |
| signin, |
| ProfileSyncService::MANUAL_START, |
| true, |
| base::Closure())); |
| EXPECT_TRUE( |
| harness_.service->sync_service_url().spec() == |
| ProfileSyncService::kSyncServerUrl || |
| harness_.service->sync_service_url().spec() == |
| ProfileSyncService::kDevServerUrl); |
| } |
| |
| TEST_F(ProfileSyncServiceTest, DisabledByPolicy) { |
| harness_.profile->GetTestingPrefService()->SetManagedPref( |
| prefs::kSyncManaged, |
| Value::CreateBooleanValue(true)); |
| SigninManager* signin = |
| SigninManagerFactory::GetForProfile(harness_.profile.get()); |
| harness_.service.reset(new TestProfileSyncService( |
| new ProfileSyncComponentsFactoryMock(), |
| harness_.profile.get(), |
| signin, |
| ProfileSyncService::MANUAL_START, |
| true, |
| base::Closure())); |
| harness_.service->Initialize(); |
| EXPECT_TRUE(harness_.service->IsManaged()); |
| } |
| |
| TEST_F(ProfileSyncServiceTest, AbortedByShutdown) { |
| SigninManager* signin = |
| SigninManagerFactory::GetForProfile(harness_.profile.get()); |
| signin->SetAuthenticatedUsername("test"); |
| ProfileSyncComponentsFactoryMock* factory = |
| new ProfileSyncComponentsFactoryMock(); |
| harness_.service.reset(new TestProfileSyncService( |
| factory, |
| harness_.profile.get(), |
| signin, |
| ProfileSyncService::AUTO_START, |
| true, |
| base::Closure())); |
| EXPECT_CALL(*factory, CreateDataTypeManager(_, _, _)).Times(0); |
| EXPECT_CALL(*factory, CreateBookmarkSyncComponents(_, _)). |
| Times(0); |
| harness_.service->RegisterDataTypeController( |
| new BookmarkDataTypeController(harness_.service->factory(), |
| harness_.profile.get(), |
| harness_.service.get())); |
| |
| harness_.service->Initialize(); |
| harness_.service.reset(); |
| } |
| |
| TEST_F(ProfileSyncServiceTest, DisableAndEnableSyncTemporarily) { |
| SigninManager* signin = |
| SigninManagerFactory::GetForProfile(harness_.profile.get()); |
| signin->SetAuthenticatedUsername("test"); |
| ProfileSyncComponentsFactoryMock* factory = |
| new ProfileSyncComponentsFactoryMock(); |
| harness_.service.reset(new TestProfileSyncService( |
| factory, |
| harness_.profile.get(), |
| signin, |
| ProfileSyncService::AUTO_START, |
| true, |
| base::Closure())); |
| // Register the bookmark data type. |
| EXPECT_CALL(*factory, CreateDataTypeManager(_, _, _)). |
| WillRepeatedly(ReturnNewDataTypeManager()); |
| |
| harness_.IssueTestTokens(); |
| |
| harness_.service->Initialize(); |
| EXPECT_TRUE(harness_.service->sync_initialized()); |
| EXPECT_TRUE(harness_.service->GetBackendForTest() != NULL); |
| EXPECT_FALSE( |
| harness_.profile->GetPrefs()->GetBoolean(prefs::kSyncSuppressStart)); |
| |
| harness_.service->StopAndSuppress(); |
| EXPECT_FALSE(harness_.service->sync_initialized()); |
| EXPECT_TRUE( |
| harness_.profile->GetPrefs()->GetBoolean(prefs::kSyncSuppressStart)); |
| |
| harness_.service->UnsuppressAndStart(); |
| EXPECT_TRUE(harness_.service->sync_initialized()); |
| EXPECT_FALSE( |
| harness_.profile->GetPrefs()->GetBoolean(prefs::kSyncSuppressStart)); |
| } |
| |
| TEST_F(ProfileSyncServiceTest, JsControllerHandlersBasic) { |
| harness_.StartSyncService(); |
| EXPECT_TRUE(harness_.service->sync_initialized()); |
| EXPECT_TRUE(harness_.service->GetBackendForTest() != NULL); |
| |
| syncer::JsController* js_controller = harness_.service->GetJsController(); |
| StrictMock<syncer::MockJsEventHandler> event_handler; |
| js_controller->AddJsEventHandler(&event_handler); |
| js_controller->RemoveJsEventHandler(&event_handler); |
| } |
| |
| TEST_F(ProfileSyncServiceTest, |
| JsControllerHandlersDelayedBackendInitialization) { |
| harness_.StartSyncServiceAndSetInitialSyncEnded(true, false, false, true, |
| syncer::STORAGE_IN_MEMORY); |
| |
| StrictMock<syncer::MockJsEventHandler> event_handler; |
| EXPECT_CALL(event_handler, HandleJsEvent(_, _)).Times(AtLeast(1)); |
| |
| EXPECT_EQ(NULL, harness_.service->GetBackendForTest()); |
| EXPECT_FALSE(harness_.service->sync_initialized()); |
| |
| syncer::JsController* js_controller = harness_.service->GetJsController(); |
| js_controller->AddJsEventHandler(&event_handler); |
| // Since we're doing synchronous initialization, backend should be |
| // initialized by this call. |
| harness_.IssueTestTokens(); |
| EXPECT_TRUE(harness_.service->sync_initialized()); |
| js_controller->RemoveJsEventHandler(&event_handler); |
| } |
| |
| TEST_F(ProfileSyncServiceTest, JsControllerProcessJsMessageBasic) { |
| harness_.StartSyncService(); |
| |
| StrictMock<syncer::MockJsReplyHandler> reply_handler; |
| |
| ListValue arg_list1; |
| arg_list1.Append(Value::CreateStringValue("TRANSIENT_INVALIDATION_ERROR")); |
| syncer::JsArgList args1(&arg_list1); |
| EXPECT_CALL(reply_handler, |
| HandleJsReply("getNotificationState", HasArgs(args1))); |
| |
| { |
| syncer::JsController* js_controller = harness_.service->GetJsController(); |
| js_controller->ProcessJsMessage("getNotificationState", args1, |
| reply_handler.AsWeakHandle()); |
| } |
| |
| // This forces the sync thread to process the message and reply. |
| harness_.TearDown(); |
| } |
| |
| TEST_F(ProfileSyncServiceTest, |
| JsControllerProcessJsMessageBasicDelayedBackendInitialization) { |
| harness_.StartSyncServiceAndSetInitialSyncEnded(true, false, false, true, |
| syncer::STORAGE_IN_MEMORY); |
| |
| StrictMock<syncer::MockJsReplyHandler> reply_handler; |
| |
| ListValue arg_list1; |
| arg_list1.Append(Value::CreateStringValue("TRANSIENT_INVALIDATION_ERROR")); |
| syncer::JsArgList args1(&arg_list1); |
| EXPECT_CALL(reply_handler, |
| HandleJsReply("getNotificationState", HasArgs(args1))); |
| |
| { |
| syncer::JsController* js_controller = harness_.service->GetJsController(); |
| js_controller->ProcessJsMessage("getNotificationState", |
| args1, reply_handler.AsWeakHandle()); |
| } |
| |
| harness_.IssueTestTokens(); |
| |
| // This forces the sync thread to process the message and reply. |
| harness_.TearDown(); |
| } |
| |
| // Make sure that things still work if sync is not enabled, but some old sync |
| // databases are lingering in the "Sync Data" folder. |
| TEST_F(ProfileSyncServiceTest, TestStartupWithOldSyncData) { |
| const char* nonsense1 = "reginald"; |
| const char* nonsense2 = "beartato"; |
| const char* nonsense3 = "harrison"; |
| FilePath temp_directory = |
| harness_.profile->GetPath().AppendASCII("Sync Data"); |
| FilePath sync_file1 = |
| temp_directory.AppendASCII("BookmarkSyncSettings.sqlite3"); |
| FilePath sync_file2 = temp_directory.AppendASCII("SyncData.sqlite3"); |
| FilePath sync_file3 = temp_directory.AppendASCII("nonsense_file"); |
| ASSERT_TRUE(file_util::CreateDirectory(temp_directory)); |
| ASSERT_NE(-1, |
| file_util::WriteFile(sync_file1, nonsense1, strlen(nonsense1))); |
| ASSERT_NE(-1, |
| file_util::WriteFile(sync_file2, nonsense2, strlen(nonsense2))); |
| ASSERT_NE(-1, |
| file_util::WriteFile(sync_file3, nonsense3, strlen(nonsense3))); |
| |
| harness_.StartSyncServiceAndSetInitialSyncEnded(false, false, true, false, |
| syncer::STORAGE_ON_DISK); |
| EXPECT_FALSE(harness_.service->HasSyncSetupCompleted()); |
| EXPECT_FALSE(harness_.service->sync_initialized()); |
| |
| // Since we're doing synchronous initialization, backend should be |
| // initialized by this call. |
| harness_.IssueTestTokens(); |
| |
| // Stop the service so we can read the new Sync Data files that were |
| // created. |
| harness_.service.reset(); |
| |
| // This file should have been deleted when the whole directory was nuked. |
| ASSERT_FALSE(file_util::PathExists(sync_file3)); |
| ASSERT_FALSE(file_util::PathExists(sync_file1)); |
| |
| // This will still exist, but the text should have changed. |
| ASSERT_TRUE(file_util::PathExists(sync_file2)); |
| std::string file2text; |
| ASSERT_TRUE(file_util::ReadFileToString(sync_file2, &file2text)); |
| ASSERT_NE(file2text.compare(nonsense2), 0); |
| } |
| |
| // Simulates a scenario where a database is corrupted and it is impossible to |
| // recreate it. This test is useful mainly when it is run under valgrind. Its |
| // expectations are not very interesting. |
| TEST_F(ProfileSyncServiceTest, FailToOpenDatabase) { |
| harness_.StartSyncServiceAndSetInitialSyncEnded(false, true, true, true, |
| syncer::STORAGE_INVALID); |
| |
| // The backend is not ready. Ensure the PSS knows this. |
| EXPECT_FALSE(harness_.service->sync_initialized()); |
| } |
| |
| // Register for some IDs with the ProfileSyncService, restart sync, |
| // and trigger some invalidation messages. They should still be |
| // received by the handler. |
| TEST_F(ProfileSyncServiceTest, UpdateRegisteredInvalidationIdsPersistence) { |
| harness_.StartSyncService(); |
| |
| syncer::ObjectIdSet ids; |
| ids.insert(invalidation::ObjectId(3, "id3")); |
| const syncer::ObjectIdStateMap& states = |
| syncer::ObjectIdSetToStateMap(ids, "payload"); |
| |
| syncer::FakeInvalidationHandler handler; |
| |
| harness_.service->RegisterInvalidationHandler(&handler); |
| harness_.service->UpdateRegisteredInvalidationIds(&handler, ids); |
| |
| harness_.service->StopAndSuppress(); |
| harness_.service->UnsuppressAndStart(); |
| |
| SyncBackendHostForProfileSyncTest* const backend = |
| harness_.service->GetBackendForTest(); |
| |
| backend->EmitOnInvalidatorStateChange(syncer::INVALIDATIONS_ENABLED); |
| EXPECT_EQ(syncer::INVALIDATIONS_ENABLED, handler.GetInvalidatorState()); |
| |
| backend->EmitOnIncomingInvalidation(states, syncer::REMOTE_INVALIDATION); |
| EXPECT_THAT(states, Eq(handler.GetLastInvalidationIdStateMap())); |
| EXPECT_EQ(syncer::REMOTE_INVALIDATION, handler.GetLastInvalidationSource()); |
| |
| backend->EmitOnInvalidatorStateChange(syncer::TRANSIENT_INVALIDATION_ERROR); |
| EXPECT_EQ(syncer::TRANSIENT_INVALIDATION_ERROR, |
| handler.GetInvalidatorState()); |
| } |
| |
| // Thin Invalidator wrapper around ProfileSyncService. |
| class ProfileSyncServiceInvalidator : public syncer::Invalidator { |
| public: |
| explicit ProfileSyncServiceInvalidator(ProfileSyncService* service) |
| : service_(service) {} |
| |
| virtual ~ProfileSyncServiceInvalidator() {} |
| |
| // Invalidator implementation. |
| virtual void RegisterHandler(syncer::InvalidationHandler* handler) OVERRIDE { |
| service_->RegisterInvalidationHandler(handler); |
| } |
| |
| virtual void UpdateRegisteredIds(syncer::InvalidationHandler* handler, |
| const syncer::ObjectIdSet& ids) OVERRIDE { |
| service_->UpdateRegisteredInvalidationIds(handler, ids); |
| } |
| |
| virtual void UnregisterHandler( |
| syncer::InvalidationHandler* handler) OVERRIDE { |
| service_->UnregisterInvalidationHandler(handler); |
| } |
| |
| virtual syncer::InvalidatorState GetInvalidatorState() const OVERRIDE { |
| return service_->GetInvalidatorState(); |
| } |
| |
| virtual void SetUniqueId(const std::string& unique_id) OVERRIDE { |
| // Do nothing. |
| } |
| |
| virtual void SetStateDeprecated(const std::string& state) OVERRIDE { |
| // Do nothing. |
| } |
| |
| virtual void UpdateCredentials( |
| const std::string& email, const std::string& token) OVERRIDE { |
| // Do nothing. |
| } |
| |
| virtual void SendInvalidation( |
| const syncer::ObjectIdStateMap& id_state_map) OVERRIDE { |
| // Do nothing. |
| } |
| |
| private: |
| ProfileSyncService* const service_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ProfileSyncServiceInvalidator); |
| }; |
| |
| } // namespace |
| |
| |
| // ProfileSyncServiceInvalidatorTestDelegate has to be visible from |
| // the syncer namespace (where InvalidatorTest lives). |
| class ProfileSyncServiceInvalidatorTestDelegate { |
| public: |
| ProfileSyncServiceInvalidatorTestDelegate() {} |
| |
| ~ProfileSyncServiceInvalidatorTestDelegate() { |
| DestroyInvalidator(); |
| } |
| |
| void CreateInvalidator( |
| const std::string& initial_state, |
| const base::WeakPtr<syncer::InvalidationStateTracker>& |
| invalidation_state_tracker) { |
| DCHECK(!invalidator_.get()); |
| harness_.SetUp(); |
| harness_.StartSyncService(); |
| invalidator_.reset( |
| new ProfileSyncServiceInvalidator(harness_.service.get())); |
| } |
| |
| ProfileSyncServiceInvalidator* GetInvalidator() { |
| return invalidator_.get(); |
| } |
| |
| void DestroyInvalidator() { |
| invalidator_.reset(); |
| harness_.TearDown(); |
| } |
| |
| void WaitForInvalidator() { |
| // Do nothing. |
| } |
| |
| void TriggerOnInvalidatorStateChange(syncer::InvalidatorState state) { |
| harness_.service->GetBackendForTest()->EmitOnInvalidatorStateChange(state); |
| } |
| |
| void TriggerOnIncomingInvalidation( |
| const syncer::ObjectIdStateMap& id_state_map, |
| syncer::IncomingInvalidationSource source) { |
| harness_.service->GetBackendForTest()->EmitOnIncomingInvalidation( |
| id_state_map, source); |
| } |
| |
| static bool InvalidatorHandlesDeprecatedState() { |
| return false; |
| } |
| |
| private: |
| ProfileSyncServiceTestHarness harness_; |
| scoped_ptr<ProfileSyncServiceInvalidator> invalidator_; |
| }; |
| |
| } // namespace browser_sync |
| |
| namespace syncer { |
| namespace { |
| |
| // ProfileSyncService should behave just like an invalidator. |
| INSTANTIATE_TYPED_TEST_CASE_P( |
| ProfileSyncServiceInvalidatorTest, InvalidatorTest, |
| ::browser_sync::ProfileSyncServiceInvalidatorTestDelegate); |
| |
| } // namespace |
| } // namespace syncer |