| // 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_blocking_data_type_controller.h" |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/logging.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/test/test_simple_task_runner.h" |
| #include "base/threading/thread.h" |
| #include "components/sync_driver/backend_data_type_configurer.h" |
| #include "components/sync_driver/fake_sync_client.h" |
| #include "sync/engine/commit_queue.h" |
| #include "sync/internal_api/public/activation_context.h" |
| #include "sync/internal_api/public/shared_model_type_processor.h" |
| #include "sync/internal_api/public/test/fake_model_type_service.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace sync_driver_v2 { |
| |
| namespace { |
| |
| // Test controller derived from NonBlockingDataTypeController. |
| class TestController : public NonBlockingDataTypeController { |
| public: |
| TestController(const scoped_refptr<base::SingleThreadTaskRunner>& ui_thread, |
| const base::Closure& error_callback, |
| syncer::ModelType model_type, |
| sync_driver::SyncClient* sync_client) |
| : NonBlockingDataTypeController(ui_thread, error_callback, sync_client), |
| model_type_(model_type) {} |
| |
| // TODO(stanisc): This will likely have to change. It should be controller's |
| // job to locate the service via SyncClient, create an instance of |
| // SharedModelTypeProcessor, pass it to the service to own, and to store the |
| // weak pointer to the type processor. For now this continues using the |
| // earlier design where the controller and it's task runner are initialized |
| // from outside. |
| // Note that for the test purposes a nullptr |model_task_runner| indicates a |
| // special case of running on UI thread (see RunOnModelThread below). |
| void Initialize(const scoped_refptr<base::TaskRunner>& model_task_runner, |
| const base::WeakPtr<syncer_v2::SharedModelTypeProcessor>& |
| type_processor) { |
| model_task_runner_ = model_task_runner; |
| type_processor_ = type_processor; |
| } |
| |
| syncer::ModelType type() const override { return model_type_; } |
| |
| base::WeakPtr<syncer_v2::SharedModelTypeProcessor> type_processor() |
| const override { |
| return type_processor_; |
| } |
| |
| bool RunOnModelThread(const tracked_objects::Location& from_here, |
| const base::Closure& task) override { |
| if (model_task_runner_) { |
| return model_task_runner_->PostTask(from_here, task); |
| } else { |
| // Special case for model running on the UI thread. |
| task.Run(); |
| return true; |
| } |
| } |
| |
| private: |
| ~TestController() override {} |
| |
| syncer::ModelType model_type_; |
| base::WeakPtr<syncer_v2::SharedModelTypeProcessor> type_processor_; |
| scoped_refptr<base::TaskRunner> model_task_runner_; |
| }; |
| |
| // A no-op instance of CommitQueue. |
| class NullCommitQueue : public syncer_v2::CommitQueue { |
| public: |
| NullCommitQueue() {} |
| ~NullCommitQueue() override {} |
| |
| void EnqueueForCommit(const syncer_v2::CommitRequestDataList& list) override { |
| NOTREACHED() << "Not implemented."; |
| } |
| }; |
| |
| // A class that pretends to be the sync backend. |
| class MockSyncBackend { |
| public: |
| void Connect(syncer::ModelType type, |
| scoped_ptr<syncer_v2::ActivationContext> activation_context) { |
| enabled_types_.Put(type); |
| activation_context->type_processor->OnConnect( |
| make_scoped_ptr(new NullCommitQueue())); |
| } |
| |
| void Disconnect(syncer::ModelType type) { |
| DCHECK(enabled_types_.Has(type)); |
| enabled_types_.Remove(type); |
| } |
| |
| private: |
| syncer::ModelTypeSet enabled_types_; |
| }; |
| |
| // Fake implementation of BackendDataTypeConfigurer that pretends to be Sync |
| // backend. |
| class MockBackendDataTypeConfigurer |
| : public sync_driver::BackendDataTypeConfigurer { |
| public: |
| MockBackendDataTypeConfigurer( |
| MockSyncBackend* backend, |
| const scoped_refptr<base::TaskRunner>& sync_task_runner) |
| : backend_(backend), sync_task_runner_(sync_task_runner) {} |
| ~MockBackendDataTypeConfigurer() override {} |
| |
| syncer::ModelTypeSet ConfigureDataTypes( |
| syncer::ConfigureReason reason, |
| const DataTypeConfigStateMap& config_state_map, |
| const base::Callback<void(syncer::ModelTypeSet, syncer::ModelTypeSet)>& |
| ready_task, |
| const base::Callback<void()>& retry_callback) override { |
| NOTREACHED() << "Not implemented."; |
| return syncer::ModelTypeSet(); |
| } |
| |
| void ActivateDirectoryDataType( |
| syncer::ModelType type, |
| syncer::ModelSafeGroup group, |
| sync_driver::ChangeProcessor* change_processor) override { |
| NOTREACHED() << "Not implemented."; |
| } |
| |
| void DeactivateDirectoryDataType(syncer::ModelType type) override { |
| NOTREACHED() << "Not implemented."; |
| } |
| |
| void ActivateNonBlockingDataType( |
| syncer::ModelType type, |
| scoped_ptr<syncer_v2::ActivationContext> activation_context) override { |
| // Post on Sync thread just like the real implementation does. |
| sync_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&MockSyncBackend::Connect, base::Unretained(backend_), type, |
| base::Passed(activation_context.Pass()))); |
| } |
| |
| void DeactivateNonBlockingDataType(syncer::ModelType type) override { |
| sync_task_runner_->PostTask(FROM_HERE, |
| base::Bind(&MockSyncBackend::Disconnect, |
| base::Unretained(backend_), type)); |
| } |
| |
| private: |
| MockSyncBackend* backend_; |
| scoped_refptr<base::TaskRunner> sync_task_runner_; |
| }; |
| |
| } // namespace |
| |
| class NonBlockingDataTypeControllerTest : public testing::Test, |
| public sync_driver::FakeSyncClient { |
| public: |
| NonBlockingDataTypeControllerTest() |
| : auto_run_tasks_(true), |
| load_models_callback_called_(false), |
| association_callback_called_(false), |
| model_thread_("modelthread"), |
| sync_thread_runner_(new base::TestSimpleTaskRunner()), |
| configurer_(&backend_, sync_thread_runner_) {} |
| |
| ~NonBlockingDataTypeControllerTest() override {} |
| |
| void SetUp() override { |
| controller_ = new TestController(ui_loop_.task_runner(), base::Closure(), |
| syncer::DICTIONARY, this); |
| } |
| |
| void TearDown() override { |
| ClearTypeProcessor(); |
| controller_ = NULL; |
| RunQueuedUIThreadTasks(); |
| } |
| |
| protected: |
| void CreateTypeProcessor() { |
| // TODO(stanisc): Controller should discover the service via SyncClient. |
| type_processor_.reset( |
| new syncer_v2::SharedModelTypeProcessor(syncer::DICTIONARY, &service_)); |
| type_processor_for_ui_ = type_processor_->AsWeakPtrForUI(); |
| } |
| |
| void InitTypeProcessorOnUIThread() { |
| CreateTypeProcessor(); |
| model_thread_runner_ = ui_loop_.task_runner(); |
| // Don't pass task runner to the controller in this case. It will be making |
| // prompt calls instead of posting tasks. |
| controller_->Initialize(nullptr, type_processor_for_ui_); |
| } |
| |
| void InitTypeProcessorOnBackendThread() { |
| model_thread_.Start(); |
| model_thread_runner_ = model_thread_.task_runner(); |
| |
| // TODO(stanisc): It should be controller's job to |
| // create TypeProcessor on the model thread. |
| model_thread_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&NonBlockingDataTypeControllerTest::CreateTypeProcessor, |
| base::Unretained(this))); |
| RunQueuedModelThreadTasks(); |
| |
| controller_->Initialize(model_thread_runner_, type_processor_for_ui_); |
| } |
| |
| void ClearTypeProcessor() { |
| if (!model_thread_runner_ || |
| model_thread_runner_->BelongsToCurrentThread()) { |
| type_processor_.reset(); |
| } else { |
| model_thread_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&NonBlockingDataTypeControllerTest::ClearTypeProcessor, |
| base::Unretained(this))); |
| RunQueuedModelThreadTasks(); |
| } |
| } |
| |
| void TestTypeProcessor(bool isEnabled, bool isConnected) { |
| if (model_thread_runner_->BelongsToCurrentThread()) { |
| EXPECT_EQ(isEnabled, type_processor_->IsEnabled()); |
| EXPECT_EQ(isConnected, type_processor_->IsConnected()); |
| } else { |
| model_thread_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&NonBlockingDataTypeControllerTest::TestTypeProcessor, |
| base::Unretained(this), isEnabled, isConnected)); |
| RunQueuedModelThreadTasks(); |
| } |
| } |
| |
| void LoadModels() { |
| controller_->LoadModels( |
| base::Bind(&NonBlockingDataTypeControllerTest::LoadModelsDone, |
| base::Unretained(this))); |
| |
| if (auto_run_tasks_) { |
| RunAllTasks(); |
| } |
| } |
| |
| void StartAssociating() { |
| controller_->StartAssociating( |
| base::Bind(&NonBlockingDataTypeControllerTest::AssociationDone, |
| base::Unretained(this))); |
| // The callback is expected to be promptly called. |
| EXPECT_TRUE(association_callback_called_); |
| } |
| |
| void ActivateDataType() { |
| DCHECK(association_callback_called_); |
| controller_->ActivateDataType(&configurer_); |
| if (auto_run_tasks_) { |
| RunAllTasks(); |
| } |
| } |
| |
| void DeactivateDataTypeAndStop() { |
| controller_->DeactivateDataType(&configurer_); |
| controller_->Stop(); |
| if (auto_run_tasks_) { |
| RunAllTasks(); |
| } |
| } |
| |
| // These threads can ping-pong for a bit so we run the model thread twice. |
| void RunAllTasks() { |
| RunQueuedModelThreadTasks(); |
| RunQueuedUIThreadTasks(); |
| RunQueuedSyncThreadTasks(); |
| RunQueuedModelThreadTasks(); |
| } |
| |
| // Runs any tasks posted on UI thread. |
| void RunQueuedUIThreadTasks() { ui_loop_.RunUntilIdle(); } |
| |
| // Runs any tasks posted on model thread. |
| void RunQueuedModelThreadTasks() { |
| base::RunLoop run_loop; |
| model_thread_runner_->PostTaskAndReply( |
| FROM_HERE, base::Bind(&base::DoNothing), |
| base::Bind(&base::RunLoop::Quit, base::Unretained(&run_loop))); |
| run_loop.Run(); |
| } |
| |
| // Processes any pending connect or disconnect requests and sends |
| // responses synchronously. |
| void RunQueuedSyncThreadTasks() { sync_thread_runner_->RunUntilIdle(); } |
| |
| void SetAutoRunTasks(bool auto_run_tasks) { |
| auto_run_tasks_ = auto_run_tasks; |
| } |
| |
| void LoadModelsDone(syncer::ModelType type, syncer::SyncError error) { |
| load_models_callback_called_ = true; |
| load_models_error_ = error; |
| } |
| |
| void AssociationDone(sync_driver::DataTypeController::ConfigureResult result, |
| const syncer::SyncMergeResult& local_merge_result, |
| const syncer::SyncMergeResult& syncer_merge_result) { |
| EXPECT_EQ(sync_driver::DataTypeController::OK, result); |
| association_callback_called_ = true; |
| } |
| |
| scoped_ptr<syncer_v2::SharedModelTypeProcessor> type_processor_; |
| base::WeakPtr<syncer_v2::SharedModelTypeProcessor> type_processor_for_ui_; |
| scoped_refptr<TestController> controller_; |
| |
| bool auto_run_tasks_; |
| bool load_models_callback_called_; |
| syncer::SyncError load_models_error_; |
| bool association_callback_called_; |
| base::MessageLoopForUI ui_loop_; |
| base::Thread model_thread_; |
| scoped_refptr<base::SingleThreadTaskRunner> model_thread_runner_; |
| scoped_refptr<base::TestSimpleTaskRunner> sync_thread_runner_; |
| MockSyncBackend backend_; |
| MockBackendDataTypeConfigurer configurer_; |
| syncer_v2::FakeModelTypeService service_; |
| }; |
| |
| TEST_F(NonBlockingDataTypeControllerTest, InitialState) { |
| EXPECT_EQ(syncer::DICTIONARY, controller_->type()); |
| EXPECT_EQ(sync_driver::DataTypeController::NOT_RUNNING, controller_->state()); |
| } |
| |
| TEST_F(NonBlockingDataTypeControllerTest, LoadModelsOnUIThread) { |
| InitTypeProcessorOnUIThread(); |
| TestTypeProcessor(false, false); // not enabled, not connected. |
| LoadModels(); |
| EXPECT_EQ(sync_driver::DataTypeController::MODEL_LOADED, |
| controller_->state()); |
| EXPECT_TRUE(load_models_callback_called_); |
| EXPECT_FALSE(load_models_error_.IsSet()); |
| TestTypeProcessor(true, false); // enabled, not connected. |
| } |
| |
| TEST_F(NonBlockingDataTypeControllerTest, LoadModelsOnBackendThread) { |
| InitTypeProcessorOnBackendThread(); |
| TestTypeProcessor(false, false); // not enabled, not connected. |
| SetAutoRunTasks(false); |
| LoadModels(); |
| EXPECT_EQ(sync_driver::DataTypeController::MODEL_STARTING, |
| controller_->state()); |
| RunAllTasks(); |
| EXPECT_EQ(sync_driver::DataTypeController::MODEL_LOADED, |
| controller_->state()); |
| EXPECT_TRUE(load_models_callback_called_); |
| EXPECT_FALSE(load_models_error_.IsSet()); |
| TestTypeProcessor(true, false); // enabled, not connected. |
| } |
| |
| TEST_F(NonBlockingDataTypeControllerTest, LoadModelsTwice) { |
| InitTypeProcessorOnUIThread(); |
| LoadModels(); |
| SetAutoRunTasks(false); |
| LoadModels(); |
| EXPECT_EQ(sync_driver::DataTypeController::MODEL_LOADED, |
| controller_->state()); |
| // The second LoadModels call should set the error. |
| EXPECT_TRUE(load_models_error_.IsSet()); |
| } |
| |
| TEST_F(NonBlockingDataTypeControllerTest, ActivateDataTypeOnUIThread) { |
| InitTypeProcessorOnUIThread(); |
| LoadModels(); |
| EXPECT_EQ(sync_driver::DataTypeController::MODEL_LOADED, |
| controller_->state()); |
| |
| StartAssociating(); |
| EXPECT_EQ(sync_driver::DataTypeController::RUNNING, controller_->state()); |
| |
| ActivateDataType(); |
| TestTypeProcessor(true, true); // enabled, connected. |
| } |
| |
| TEST_F(NonBlockingDataTypeControllerTest, ActivateDataTypeOnBackendThread) { |
| InitTypeProcessorOnBackendThread(); |
| LoadModels(); |
| EXPECT_EQ(sync_driver::DataTypeController::MODEL_LOADED, |
| controller_->state()); |
| |
| StartAssociating(); |
| EXPECT_EQ(sync_driver::DataTypeController::RUNNING, controller_->state()); |
| |
| ActivateDataType(); |
| TestTypeProcessor(true, true); // enabled, connected. |
| } |
| |
| TEST_F(NonBlockingDataTypeControllerTest, Stop) { |
| InitTypeProcessorOnBackendThread(); |
| LoadModels(); |
| StartAssociating(); |
| ActivateDataType(); |
| TestTypeProcessor(true, true); // enabled, connected. |
| |
| DeactivateDataTypeAndStop(); |
| EXPECT_EQ(sync_driver::DataTypeController::NOT_RUNNING, controller_->state()); |
| TestTypeProcessor(true, false); // enabled, not connected. |
| } |
| |
| } // namespace sync_driver_v2 |