// 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 "testing/gtest/include/gtest/gtest.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/callback.h" #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/synchronization/waitable_event.h" #include "base/test/test_timeouts.h" #include "base/tracked_objects.h" #include "chrome/browser/sync/glue/non_frontend_data_type_controller.h" #include "chrome/browser/sync/glue/non_frontend_data_type_controller_mock.h" #include "chrome/browser/sync/profile_sync_components_factory_mock.h" #include "chrome/browser/sync/profile_sync_service_mock.h" #include "chrome/test/base/profile_mock.h" #include "components/sync_driver/change_processor_mock.h" #include "components/sync_driver/data_type_controller_mock.h" #include "components/sync_driver/model_associator_mock.h" #include "content/public/test/test_browser_thread.h" #include "sync/internal_api/public/engine/model_safe_worker.h" using base::WaitableEvent; using syncer::GROUP_DB; using browser_sync::NonFrontendDataTypeController; using browser_sync::NonFrontendDataTypeControllerMock; using sync_driver::ChangeProcessorMock; using sync_driver::DataTypeController; using sync_driver::ModelAssociatorMock; using sync_driver::ModelLoadCallbackMock; using sync_driver::StartCallbackMock; using content::BrowserThread; using testing::_; using testing::DoAll; using testing::InvokeWithoutArgs; using testing::Return; using testing::SetArgumentPointee; using testing::StrictMock; ACTION_P(WaitOnEvent, event) { event->Wait(); } ACTION_P(SignalEvent, event) { event->Signal(); } class NonFrontendDataTypeControllerFake : public NonFrontendDataTypeController { public: NonFrontendDataTypeControllerFake( ProfileSyncComponentsFactory* profile_sync_factory, Profile* profile, ProfileSyncService* sync_service, NonFrontendDataTypeControllerMock* mock) : NonFrontendDataTypeController(base::MessageLoopProxy::current(), base::Closure(), profile_sync_factory, profile, sync_service), mock_(mock) {} virtual syncer::ModelType type() const OVERRIDE { return syncer::BOOKMARKS; } virtual syncer::ModelSafeGroup model_safe_group() const OVERRIDE { return syncer::GROUP_DB; } private: virtual ~NonFrontendDataTypeControllerFake() {} virtual ProfileSyncComponentsFactory::SyncComponents CreateSyncComponents() OVERRIDE { return profile_sync_factory()-> CreateBookmarkSyncComponents(profile_sync_service(), this); } virtual bool PostTaskOnBackendThread( const tracked_objects::Location& from_here, const base::Closure& task) OVERRIDE { return BrowserThread::PostTask(BrowserThread::DB, from_here, task); } // We mock the following methods because their default implementations do // nothing, but we still want to make sure they're called appropriately. virtual bool StartModels() OVERRIDE { return mock_->StartModels(); } virtual void RecordUnrecoverableError( const tracked_objects::Location& from_here, const std::string& message) OVERRIDE { mock_->RecordUnrecoverableError(from_here, message); } virtual void RecordAssociationTime(base::TimeDelta time) OVERRIDE { mock_->RecordAssociationTime(time); } virtual void RecordStartFailure( DataTypeController::ConfigureResult result) OVERRIDE { mock_->RecordStartFailure(result); } virtual void DisconnectProcessor( sync_driver::ChangeProcessor* processor) OVERRIDE{ mock_->DisconnectProcessor(processor); } private: NonFrontendDataTypeControllerMock* mock_; }; class SyncNonFrontendDataTypeControllerTest : public testing::Test { public: SyncNonFrontendDataTypeControllerTest() : ui_thread_(BrowserThread::UI, &message_loop_), db_thread_(BrowserThread::DB), service_(&profile_), model_associator_(NULL), change_processor_(NULL) {} virtual void SetUp() { db_thread_.Start(); profile_sync_factory_.reset(new ProfileSyncComponentsFactoryMock()); // All of these are refcounted, so don't need to be released. dtc_mock_ = new StrictMock(); non_frontend_dtc_ = new NonFrontendDataTypeControllerFake(profile_sync_factory_.get(), &profile_, &service_, dtc_mock_.get()); } virtual void TearDown() { if (non_frontend_dtc_->state() != NonFrontendDataTypeController::NOT_RUNNING) { non_frontend_dtc_->Stop(); } db_thread_.Stop(); } protected: void SetStartExpectations() { EXPECT_CALL(*dtc_mock_.get(), StartModels()).WillOnce(Return(true)); EXPECT_CALL(model_load_callback_, Run(_, _)); model_associator_ = new ModelAssociatorMock(); change_processor_ = new ChangeProcessorMock(); EXPECT_CALL(*profile_sync_factory_, CreateBookmarkSyncComponents(_, _)). WillOnce(Return(ProfileSyncComponentsFactory::SyncComponents( model_associator_, change_processor_))); } void SetAssociateExpectations() { EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). WillOnce(Return(true)); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillOnce(DoAll(SetArgumentPointee<0>(true), Return(true))); EXPECT_CALL(*model_associator_, AssociateModels(_, _)). WillOnce(Return(syncer::SyncError())); EXPECT_CALL(*dtc_mock_.get(), RecordAssociationTime(_)); } void SetActivateExpectations(DataTypeController::ConfigureResult result) { EXPECT_CALL(start_callback_, Run(result, _, _)); } void SetStopExpectations() { EXPECT_CALL(*dtc_mock_.get(), DisconnectProcessor(_)); EXPECT_CALL(service_, DeactivateDataType(_)); EXPECT_CALL(*model_associator_, DisassociateModels()). WillOnce(Return(syncer::SyncError())); } void SetStartFailExpectations(DataTypeController::ConfigureResult result) { if (DataTypeController::IsUnrecoverableResult(result)) EXPECT_CALL(*dtc_mock_.get(), RecordUnrecoverableError(_, _)); if (model_associator_) { EXPECT_CALL(*model_associator_, DisassociateModels()). WillOnce(Return(syncer::SyncError())); } EXPECT_CALL(*dtc_mock_.get(), RecordStartFailure(result)); EXPECT_CALL(start_callback_, Run(result, _, _)); } static void SignalDone(WaitableEvent* done) { done->Signal(); } void WaitForDTC() { WaitableEvent done(true, false); BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, base::Bind(&SyncNonFrontendDataTypeControllerTest::SignalDone, &done)); done.TimedWait(TestTimeouts::action_timeout()); if (!done.IsSignaled()) { ADD_FAILURE() << "Timed out waiting for DB thread to finish."; } base::MessageLoop::current()->RunUntilIdle(); } void Start() { non_frontend_dtc_->LoadModels( base::Bind(&ModelLoadCallbackMock::Run, base::Unretained(&model_load_callback_))); non_frontend_dtc_->StartAssociating( base::Bind(&StartCallbackMock::Run, base::Unretained(&start_callback_))); } base::MessageLoopForUI message_loop_; content::TestBrowserThread ui_thread_; content::TestBrowserThread db_thread_; scoped_refptr non_frontend_dtc_; scoped_ptr profile_sync_factory_; scoped_refptr dtc_mock_; ProfileMock profile_; ProfileSyncServiceMock service_; ModelAssociatorMock* model_associator_; ChangeProcessorMock* change_processor_; StartCallbackMock start_callback_; ModelLoadCallbackMock model_load_callback_; }; TEST_F(SyncNonFrontendDataTypeControllerTest, StartOk) { SetStartExpectations(); SetAssociateExpectations(); SetActivateExpectations(DataTypeController::OK); SetStopExpectations(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_frontend_dtc_->state()); Start(); WaitForDTC(); EXPECT_EQ(DataTypeController::RUNNING, non_frontend_dtc_->state()); } TEST_F(SyncNonFrontendDataTypeControllerTest, StartFirstRun) { SetStartExpectations(); EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). WillOnce(Return(true)); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillOnce(DoAll(SetArgumentPointee<0>(false), Return(true))); EXPECT_CALL(*model_associator_, AssociateModels(_, _)). WillOnce(Return(syncer::SyncError())); EXPECT_CALL(*dtc_mock_.get(), RecordAssociationTime(_)); SetActivateExpectations(DataTypeController::OK_FIRST_RUN); SetStopExpectations(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_frontend_dtc_->state()); Start(); WaitForDTC(); EXPECT_EQ(DataTypeController::RUNNING, non_frontend_dtc_->state()); } TEST_F(SyncNonFrontendDataTypeControllerTest, StartAssociationFailed) { SetStartExpectations(); EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). WillOnce(Return(true)); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillOnce(DoAll(SetArgumentPointee<0>(true), Return(true))); EXPECT_CALL(*model_associator_, AssociateModels(_, _)). WillOnce( Return(syncer::SyncError(FROM_HERE, syncer::SyncError::DATATYPE_ERROR, "Error", syncer::BOOKMARKS))); EXPECT_CALL(*dtc_mock_.get(), RecordAssociationTime(_)); SetStartFailExpectations(DataTypeController::ASSOCIATION_FAILED); // Set up association to fail with an association failed error. EXPECT_EQ(DataTypeController::NOT_RUNNING, non_frontend_dtc_->state()); Start(); WaitForDTC(); EXPECT_EQ(DataTypeController::DISABLED, non_frontend_dtc_->state()); } TEST_F(SyncNonFrontendDataTypeControllerTest, StartAssociationTriggersUnrecoverableError) { SetStartExpectations(); SetStartFailExpectations(DataTypeController::UNRECOVERABLE_ERROR); // Set up association to fail with an unrecoverable error. EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). WillRepeatedly(Return(true)); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillRepeatedly(DoAll(SetArgumentPointee<0>(false), Return(false))); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_frontend_dtc_->state()); Start(); WaitForDTC(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_frontend_dtc_->state()); } TEST_F(SyncNonFrontendDataTypeControllerTest, StartAssociationCryptoNotReady) { SetStartExpectations(); SetStartFailExpectations(DataTypeController::NEEDS_CRYPTO); // Set up association to fail with a NEEDS_CRYPTO error. EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). WillRepeatedly(Return(false)); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_frontend_dtc_->state()); Start(); WaitForDTC(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_frontend_dtc_->state()); } // Trigger a Stop() call when we check if the model associator has user created // nodes. TEST_F(SyncNonFrontendDataTypeControllerTest, AbortDuringAssociationInactive) { WaitableEvent wait_for_db_thread_pause(false, false); WaitableEvent pause_db_thread(false, false); SetStartExpectations(); EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). WillOnce(Return(true)); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillOnce(DoAll( SignalEvent(&wait_for_db_thread_pause), WaitOnEvent(&pause_db_thread), SetArgumentPointee<0>(true), Return(true))); EXPECT_CALL(*model_associator_, AbortAssociation()).WillOnce( SignalEvent(&pause_db_thread)); EXPECT_CALL(*model_associator_, AssociateModels(_, _)). WillOnce(Return(syncer::SyncError())); SetStopExpectations(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_frontend_dtc_->state()); Start(); wait_for_db_thread_pause.Wait(); non_frontend_dtc_->Stop(); WaitForDTC(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_frontend_dtc_->state()); } // Same as above but abort during the Activate call. TEST_F(SyncNonFrontendDataTypeControllerTest, AbortDuringAssociationActivated) { WaitableEvent wait_for_association_starts(false, false); WaitableEvent wait_for_dtc_stop(false, false); SetStartExpectations(); EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). WillOnce(Return(true)); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillOnce(DoAll( SetArgumentPointee<0>(true), Return(true))); EXPECT_CALL(*model_associator_, AbortAssociation()); EXPECT_CALL(*model_associator_, AssociateModels(_, _)). WillOnce(DoAll( SignalEvent(&wait_for_association_starts), WaitOnEvent(&wait_for_dtc_stop), Return(syncer::SyncError()))); SetStopExpectations(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_frontend_dtc_->state()); Start(); wait_for_association_starts.Wait(); non_frontend_dtc_->Stop(); wait_for_dtc_stop.Signal(); WaitForDTC(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_frontend_dtc_->state()); } TEST_F(SyncNonFrontendDataTypeControllerTest, Stop) { SetStartExpectations(); SetAssociateExpectations(); SetActivateExpectations(DataTypeController::OK); SetStopExpectations(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_frontend_dtc_->state()); Start(); WaitForDTC(); EXPECT_EQ(DataTypeController::RUNNING, non_frontend_dtc_->state()); non_frontend_dtc_->Stop(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_frontend_dtc_->state()); } // Disabled due to http://crbug.com/388367 TEST_F(SyncNonFrontendDataTypeControllerTest, DISABLED_OnSingleDataTypeUnrecoverableError) { SetStartExpectations(); SetAssociateExpectations(); SetActivateExpectations(DataTypeController::OK); EXPECT_CALL(*dtc_mock_.get(), RecordUnrecoverableError(_, "Test")); EXPECT_CALL(service_, DisableDatatype(_)) .WillOnce(InvokeWithoutArgs(non_frontend_dtc_.get(), &NonFrontendDataTypeController::Stop)); SetStopExpectations(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_frontend_dtc_->state()); Start(); WaitForDTC(); EXPECT_EQ(DataTypeController::RUNNING, non_frontend_dtc_->state()); // This should cause non_frontend_dtc_->Stop() to be called. syncer::SyncError error(FROM_HERE, syncer::SyncError::DATATYPE_ERROR, "error", non_frontend_dtc_->type()); BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, base::Bind( &NonFrontendDataTypeControllerFake::OnSingleDataTypeUnrecoverableError, non_frontend_dtc_.get(), error)); WaitForDTC(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_frontend_dtc_->state()); }