// Copyright 2014 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 "components/sync_driver/non_ui_data_type_controller.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/threading/thread.h" #include "base/tracked_objects.h" #include "components/sync_driver/data_type_controller_mock.h" #include "components/sync_driver/generic_change_processor_factory.h" #include "components/sync_driver/non_ui_data_type_controller_mock.h" #include "sync/api/fake_syncable_service.h" #include "sync/api/sync_change.h" #include "sync/internal_api/public/engine/model_safe_worker.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace sync_driver { namespace { using base::WaitableEvent; using syncer::AUTOFILL_PROFILE; using testing::_; using testing::AtLeast; using testing::DoAll; using testing::InvokeWithoutArgs; using testing::Mock; using testing::Return; using testing::SetArgumentPointee; using testing::StrictMock; ACTION_P(WaitOnEvent, event) { event->Wait(); } ACTION_P(SignalEvent, event) { event->Signal(); } ACTION_P(SaveChangeProcessor, scoped_change_processor) { scoped_change_processor->reset(arg2); } ACTION_P(GetWeakPtrToSyncableService, syncable_service) { // Have to do this within an Action to ensure it's not evaluated on the wrong // thread. return syncable_service->AsWeakPtr(); } class SharedChangeProcessorMock : public SharedChangeProcessor { public: SharedChangeProcessorMock() {} MOCK_METHOD6(Connect, base::WeakPtr( SyncApiComponentFactory*, GenericChangeProcessorFactory*, syncer::UserShare*, DataTypeErrorHandler*, syncer::ModelType, const base::WeakPtr&)); MOCK_METHOD0(Disconnect, bool()); MOCK_METHOD2(ProcessSyncChanges, syncer::SyncError(const tracked_objects::Location&, const syncer::SyncChangeList&)); MOCK_CONST_METHOD2(GetAllSyncDataReturnError, syncer::SyncError(syncer::ModelType, syncer::SyncDataList*)); MOCK_METHOD0(GetSyncCount, int()); MOCK_METHOD1(SyncModelHasUserCreatedNodes, bool(bool*)); MOCK_METHOD0(CryptoReadyIfNecessary, bool()); MOCK_CONST_METHOD1(GetDataTypeContext, bool(std::string*)); protected: virtual ~SharedChangeProcessorMock() {} MOCK_METHOD2(OnUnrecoverableError, void(const tracked_objects::Location&, const std::string&)); private: DISALLOW_COPY_AND_ASSIGN(SharedChangeProcessorMock); }; class NonUIDataTypeControllerFake : public NonUIDataTypeController { public: NonUIDataTypeControllerFake( SyncApiComponentFactory* sync_factory, NonUIDataTypeControllerMock* mock, SharedChangeProcessor* change_processor, const DisableTypeCallback& disable_callback, scoped_refptr backend_loop) : NonUIDataTypeController( base::MessageLoopProxy::current(), base::Closure(), disable_callback, sync_factory), blocked_(false), mock_(mock), change_processor_(change_processor), backend_loop_(backend_loop) {} virtual syncer::ModelType type() const OVERRIDE { return AUTOFILL_PROFILE; } virtual syncer::ModelSafeGroup model_safe_group() const OVERRIDE { return syncer::GROUP_DB; } // Prevent tasks from being posted on the backend thread until // UnblockBackendTasks() is called. void BlockBackendTasks() { blocked_ = true; } // Post pending tasks on the backend thread and start allowing tasks // to be posted on the backend thread again. void UnblockBackendTasks() { blocked_ = false; for (std::vector::const_iterator it = pending_tasks_.begin(); it != pending_tasks_.end(); ++it) { PostTaskOnBackendThread(it->from_here, it->task); } pending_tasks_.clear(); } virtual SharedChangeProcessor* CreateSharedChangeProcessor() OVERRIDE { return change_processor_; } protected: virtual bool PostTaskOnBackendThread( const tracked_objects::Location& from_here, const base::Closure& task) OVERRIDE { if (blocked_) { pending_tasks_.push_back(PendingTask(from_here, task)); return true; } else { return backend_loop_->PostTask(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 StopModels() OVERRIDE { mock_->StopModels(); } virtual void RecordAssociationTime(base::TimeDelta time) OVERRIDE { mock_->RecordAssociationTime(time); } virtual void RecordStartFailure(DataTypeController::StartResult result) OVERRIDE { mock_->RecordStartFailure(result); } private: virtual ~NonUIDataTypeControllerFake() {} DISALLOW_COPY_AND_ASSIGN(NonUIDataTypeControllerFake); struct PendingTask { PendingTask(const tracked_objects::Location& from_here, const base::Closure& task) : from_here(from_here), task(task) {} tracked_objects::Location from_here; base::Closure task; }; bool blocked_; std::vector pending_tasks_; NonUIDataTypeControllerMock* mock_; scoped_refptr change_processor_; scoped_refptr backend_loop_; }; class SyncNonUIDataTypeControllerTest : public testing::Test { public: SyncNonUIDataTypeControllerTest() : backend_thread_("dbthread"), disable_callback_invoked_(false) {} virtual void SetUp() OVERRIDE { backend_thread_.Start(); change_processor_ = new SharedChangeProcessorMock(); // All of these are refcounted, so don't need to be released. dtc_mock_ = new StrictMock(); DataTypeController::DisableTypeCallback disable_callback = base::Bind(&SyncNonUIDataTypeControllerTest::DisableTypeCallback, base::Unretained(this)); non_ui_dtc_ = new NonUIDataTypeControllerFake(NULL, dtc_mock_.get(), change_processor_, disable_callback, backend_thread_.message_loop_proxy()); } virtual void TearDown() OVERRIDE { backend_thread_.Stop(); } void WaitForDTC() { WaitableEvent done(true, false); backend_thread_.message_loop_proxy()->PostTask( FROM_HERE, base::Bind(&SyncNonUIDataTypeControllerTest::SignalDone, &done)); done.TimedWait(TestTimeouts::action_timeout()); if (!done.IsSignaled()) { ADD_FAILURE() << "Timed out waiting for DB thread to finish."; } base::MessageLoop::current()->RunUntilIdle(); } protected: void SetStartExpectations() { EXPECT_CALL(*dtc_mock_.get(), StartModels()).WillOnce(Return(true)); EXPECT_CALL(model_load_callback_, Run(_, _)); } void SetAssociateExpectations() { EXPECT_CALL(*change_processor_.get(), Connect(_, _, _, _, _, _)) .WillOnce(GetWeakPtrToSyncableService(&syncable_service_)); EXPECT_CALL(*change_processor_.get(), CryptoReadyIfNecessary()) .WillOnce(Return(true)); EXPECT_CALL(*change_processor_.get(), SyncModelHasUserCreatedNodes(_)) .WillOnce(DoAll(SetArgumentPointee<0>(true), Return(true))); EXPECT_CALL(*change_processor_.get(), GetAllSyncDataReturnError(_,_)) .WillOnce(Return(syncer::SyncError())); EXPECT_CALL(*change_processor_.get(), GetSyncCount()).WillOnce(Return(0)); EXPECT_CALL(*dtc_mock_.get(), RecordAssociationTime(_)); } void SetActivateExpectations(DataTypeController::StartResult result) { EXPECT_CALL(start_callback_, Run(result,_,_)); } void SetStopExpectations() { EXPECT_CALL(*dtc_mock_.get(), StopModels()); EXPECT_CALL(*change_processor_.get(), Disconnect()).WillOnce(Return(true)); } void SetStartFailExpectations(DataTypeController::StartResult result) { EXPECT_CALL(*dtc_mock_.get(), StopModels()).Times(AtLeast(1)); EXPECT_CALL(*dtc_mock_.get(), RecordStartFailure(result)); EXPECT_CALL(start_callback_, Run(result, _, _)); } void Start() { non_ui_dtc_->LoadModels( base::Bind(&ModelLoadCallbackMock::Run, base::Unretained(&model_load_callback_))); non_ui_dtc_->StartAssociating( base::Bind(&StartCallbackMock::Run, base::Unretained(&start_callback_))); } static void SignalDone(WaitableEvent* done) { done->Signal(); } void DisableTypeCallback(const tracked_objects::Location& location, const std::string& message) { disable_callback_invoked_ = true; non_ui_dtc_->Stop(); } base::MessageLoopForUI message_loop_; base::Thread backend_thread_; StartCallbackMock start_callback_; ModelLoadCallbackMock model_load_callback_; // Must be destroyed after non_ui_dtc_. syncer::FakeSyncableService syncable_service_; scoped_refptr non_ui_dtc_; scoped_refptr dtc_mock_; scoped_refptr change_processor_; scoped_ptr saved_change_processor_; bool disable_callback_invoked_; }; TEST_F(SyncNonUIDataTypeControllerTest, StartOk) { SetStartExpectations(); SetAssociateExpectations(); SetActivateExpectations(DataTypeController::OK); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_ui_dtc_->state()); Start(); WaitForDTC(); EXPECT_EQ(DataTypeController::RUNNING, non_ui_dtc_->state()); } TEST_F(SyncNonUIDataTypeControllerTest, StartFirstRun) { SetStartExpectations(); EXPECT_CALL(*change_processor_.get(), Connect(_, _, _, _, _, _)) .WillOnce(GetWeakPtrToSyncableService(&syncable_service_)); EXPECT_CALL(*change_processor_.get(), CryptoReadyIfNecessary()) .WillOnce(Return(true)); EXPECT_CALL(*change_processor_.get(), SyncModelHasUserCreatedNodes(_)) .WillOnce(DoAll(SetArgumentPointee<0>(false), Return(true))); EXPECT_CALL(*change_processor_.get(), GetAllSyncDataReturnError(_,_)) .WillOnce(Return(syncer::SyncError())); EXPECT_CALL(*dtc_mock_.get(), RecordAssociationTime(_)); SetActivateExpectations(DataTypeController::OK_FIRST_RUN); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_ui_dtc_->state()); Start(); WaitForDTC(); EXPECT_EQ(DataTypeController::RUNNING, non_ui_dtc_->state()); } // Start the DTC and have StartModels() return false. Then, stop the // DTC without finishing model startup. It should stop cleanly. TEST_F(SyncNonUIDataTypeControllerTest, AbortDuringStartModels) { EXPECT_CALL(*dtc_mock_.get(), StartModels()).WillOnce(Return(false)); EXPECT_CALL(*dtc_mock_.get(), StopModels()); EXPECT_CALL(model_load_callback_, Run(_, _)); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_ui_dtc_->state()); non_ui_dtc_->LoadModels( base::Bind(&ModelLoadCallbackMock::Run, base::Unretained(&model_load_callback_))); WaitForDTC(); EXPECT_EQ(DataTypeController::MODEL_STARTING, non_ui_dtc_->state()); non_ui_dtc_->Stop(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_ui_dtc_->state()); } // Start the DTC and have MergeDataAndStartSyncing() return an error. // The DTC should become disabled, and the DTC should still stop // cleanly. TEST_F(SyncNonUIDataTypeControllerTest, StartAssociationFailed) { SetStartExpectations(); EXPECT_CALL(*change_processor_.get(), Connect(_, _, _, _, _, _)) .WillOnce(GetWeakPtrToSyncableService(&syncable_service_)); EXPECT_CALL(*change_processor_.get(), CryptoReadyIfNecessary()) .WillOnce(Return(true)); EXPECT_CALL(*change_processor_.get(), SyncModelHasUserCreatedNodes(_)) .WillOnce(DoAll(SetArgumentPointee<0>(true), Return(true))); EXPECT_CALL(*change_processor_.get(), GetAllSyncDataReturnError(_,_)) .WillOnce(Return(syncer::SyncError())); 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_ui_dtc_->state()); syncable_service_.set_merge_data_and_start_syncing_error( syncer::SyncError(FROM_HERE, syncer::SyncError::DATATYPE_ERROR, "Sync Error", non_ui_dtc_->type())); Start(); WaitForDTC(); EXPECT_EQ(DataTypeController::DISABLED, non_ui_dtc_->state()); non_ui_dtc_->Stop(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_ui_dtc_->state()); } TEST_F(SyncNonUIDataTypeControllerTest, StartAssociationTriggersUnrecoverableError) { SetStartExpectations(); SetStartFailExpectations(DataTypeController::UNRECOVERABLE_ERROR); // Set up association to fail with an unrecoverable error. EXPECT_CALL(*change_processor_.get(), Connect(_, _, _, _, _, _)) .WillOnce(GetWeakPtrToSyncableService(&syncable_service_)); EXPECT_CALL(*change_processor_.get(), CryptoReadyIfNecessary()) .WillRepeatedly(Return(true)); EXPECT_CALL(*change_processor_.get(), SyncModelHasUserCreatedNodes(_)) .WillRepeatedly(DoAll(SetArgumentPointee<0>(false), Return(false))); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_ui_dtc_->state()); Start(); WaitForDTC(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_ui_dtc_->state()); } TEST_F(SyncNonUIDataTypeControllerTest, StartAssociationCryptoNotReady) { SetStartExpectations(); SetStartFailExpectations(DataTypeController::NEEDS_CRYPTO); // Set up association to fail with a NEEDS_CRYPTO error. EXPECT_CALL(*change_processor_.get(), Connect(_, _, _, _, _, _)) .WillOnce(GetWeakPtrToSyncableService(&syncable_service_)); EXPECT_CALL(*change_processor_.get(), CryptoReadyIfNecessary()) .WillRepeatedly(Return(false)); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_ui_dtc_->state()); Start(); WaitForDTC(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_ui_dtc_->state()); } // Trigger a Stop() call when we check if the model associator has user created // nodes. TEST_F(SyncNonUIDataTypeControllerTest, AbortDuringAssociation) { WaitableEvent wait_for_db_thread_pause(false, false); WaitableEvent pause_db_thread(false, false); SetStartExpectations(); SetStartFailExpectations(DataTypeController::ABORTED); EXPECT_CALL(*change_processor_.get(), Connect(_, _, _, _, _, _)) .WillOnce(GetWeakPtrToSyncableService(&syncable_service_)); EXPECT_CALL(*change_processor_.get(), CryptoReadyIfNecessary()) .WillOnce(Return(true)); EXPECT_CALL(*change_processor_.get(), SyncModelHasUserCreatedNodes(_)) .WillOnce(DoAll(SignalEvent(&wait_for_db_thread_pause), WaitOnEvent(&pause_db_thread), SetArgumentPointee<0>(true), Return(true))); EXPECT_CALL(*change_processor_.get(), GetAllSyncDataReturnError(_,_)) .WillOnce( Return(syncer::SyncError(FROM_HERE, syncer::SyncError::DATATYPE_ERROR, "Disconnected.", AUTOFILL_PROFILE))); EXPECT_CALL(*change_processor_.get(), Disconnect()) .WillOnce(DoAll(SignalEvent(&pause_db_thread), Return(true))); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_ui_dtc_->state()); Start(); wait_for_db_thread_pause.Wait(); non_ui_dtc_->Stop(); WaitForDTC(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_ui_dtc_->state()); } // Start the DTC while the backend tasks are blocked. Then stop the DTC before // the backend tasks get a chance to run. TEST_F(SyncNonUIDataTypeControllerTest, StartAfterSyncShutdown) { non_ui_dtc_->BlockBackendTasks(); SetStartExpectations(); // We don't expect StopSyncing to be called because local_service_ will never // have been set. EXPECT_CALL(*change_processor_.get(), Disconnect()).WillOnce(Return(true)); EXPECT_CALL(*dtc_mock_.get(), StopModels()); EXPECT_CALL(*dtc_mock_.get(), RecordStartFailure(DataTypeController::ABORTED)); EXPECT_CALL(start_callback_, Run(DataTypeController::ABORTED, _, _)); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_ui_dtc_->state()); Start(); non_ui_dtc_->Stop(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_ui_dtc_->state()); Mock::VerifyAndClearExpectations(change_processor_.get()); Mock::VerifyAndClearExpectations(dtc_mock_.get()); EXPECT_CALL(*change_processor_.get(), Connect(_, _, _, _, _, _)) .WillOnce(Return(base::WeakPtr())); non_ui_dtc_->UnblockBackendTasks(); WaitForDTC(); } TEST_F(SyncNonUIDataTypeControllerTest, Stop) { SetStartExpectations(); SetAssociateExpectations(); SetActivateExpectations(DataTypeController::OK); SetStopExpectations(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_ui_dtc_->state()); Start(); WaitForDTC(); EXPECT_EQ(DataTypeController::RUNNING, non_ui_dtc_->state()); non_ui_dtc_->Stop(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_ui_dtc_->state()); } // Start the DTC then block its backend tasks. While its backend // tasks are blocked, stop and start it again, then unblock its // backend tasks. The (delayed) running of the backend tasks from the // stop after the restart shouldn't cause any problems. TEST_F(SyncNonUIDataTypeControllerTest, StopStart) { SetStartExpectations(); SetAssociateExpectations(); SetActivateExpectations(DataTypeController::OK); SetStopExpectations(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_ui_dtc_->state()); Start(); WaitForDTC(); EXPECT_EQ(DataTypeController::RUNNING, non_ui_dtc_->state()); non_ui_dtc_->BlockBackendTasks(); non_ui_dtc_->Stop(); SetStartExpectations(); SetAssociateExpectations(); SetActivateExpectations(DataTypeController::OK); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_ui_dtc_->state()); Start(); non_ui_dtc_->UnblockBackendTasks(); WaitForDTC(); EXPECT_EQ(DataTypeController::RUNNING, non_ui_dtc_->state()); } TEST_F(SyncNonUIDataTypeControllerTest, OnSingleDatatypeUnrecoverableError) { SetStartExpectations(); SetAssociateExpectations(); SetActivateExpectations(DataTypeController::OK); SetStopExpectations(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_ui_dtc_->state()); Start(); WaitForDTC(); EXPECT_EQ(DataTypeController::RUNNING, non_ui_dtc_->state()); // This should cause non_ui_dtc_->Stop() to be called. backend_thread_.message_loop_proxy()->PostTask(FROM_HERE, base::Bind( &NonUIDataTypeControllerFake:: OnSingleDatatypeUnrecoverableError, non_ui_dtc_.get(), FROM_HERE, std::string("Test"))); WaitForDTC(); EXPECT_EQ(DataTypeController::NOT_RUNNING, non_ui_dtc_->state()); EXPECT_TRUE(disable_callback_invoked_); } } // namespace } // namespace sync_driver