// Copyright 2016 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_model_type_controller.h" #include #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 NonUIModelTypeController. class TestNonUIModelTypeController : public NonUIModelTypeController { public: TestNonUIModelTypeController( const scoped_refptr& ui_thread, const scoped_refptr& model_task_runner, const base::Closure& error_callback, syncer::ModelType model_type, sync_driver::SyncClient* sync_client) : NonUIModelTypeController(ui_thread, error_callback, model_type, sync_client), model_task_runner_(model_task_runner) {} base::WeakPtr get_type_processor() const { return type_processor(); } bool RunOnModelThread(const tracked_objects::Location& from_here, const base::Closure& task) override { DCHECK(model_task_runner_); return model_task_runner_->PostTask(from_here, task); } private: ~TestNonUIModelTypeController() override {} scoped_refptr 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 activation_context) { enabled_types_.Put(type); activation_context->type_processor->ConnectSync( 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& 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& ready_task, const base::Callback& 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 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(std::move(activation_context)))); } 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 sync_task_runner_; }; } // namespace class NonUIModelTypeControllerTest : public testing::Test, public sync_driver::FakeSyncClient { public: NonUIModelTypeControllerTest() : 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_) {} ~NonUIModelTypeControllerTest() override {} void SetUp() override { model_thread_.Start(); model_thread_runner_ = model_thread_.task_runner(); InitializeModelTypeService(); controller_ = new TestNonUIModelTypeController( ui_loop_.task_runner(), model_thread_runner_, base::Closure(), syncer::DICTIONARY, this); InitializeTypeProcessor(); } void TearDown() override { ClearModelTypeService(); controller_ = NULL; RunQueuedUIThreadTasks(); } base::WeakPtr GetModelTypeServiceForType( syncer::ModelType type) override { return service_->AsWeakPtr(); } protected: void InitializeTypeProcessor() { if (!model_thread_runner_ || model_thread_runner_->BelongsToCurrentThread()) { // TODO(crbug.com/543407): Move the processor stuff out. type_processor_ = service_->SetUpProcessor(new syncer_v2::SharedModelTypeProcessor( syncer::DICTIONARY, service_.get())); } else { model_thread_runner_->PostTask( FROM_HERE, base::Bind(&NonUIModelTypeControllerTest::InitializeTypeProcessor, base::Unretained(this))); RunQueuedModelThreadTasks(); } } void InitializeModelTypeService() { if (!model_thread_runner_ || model_thread_runner_->BelongsToCurrentThread()) { service_.reset(new syncer_v2::FakeModelTypeService()); } else { model_thread_runner_->PostTask( FROM_HERE, base::Bind(&NonUIModelTypeControllerTest::InitializeModelTypeService, base::Unretained(this))); RunQueuedModelThreadTasks(); } } void ClearModelTypeService() { if (!model_thread_runner_ || model_thread_runner_->BelongsToCurrentThread()) { service_.reset(); } else { model_thread_runner_->PostTask( FROM_HERE, base::Bind(&NonUIModelTypeControllerTest::ClearModelTypeService, base::Unretained(this))); RunQueuedModelThreadTasks(); } } void TestTypeProcessor(bool isAllowingChanges, bool isConnected) { if (model_thread_runner_->BelongsToCurrentThread()) { EXPECT_EQ(isAllowingChanges, type_processor_->IsAllowingChanges()); EXPECT_EQ(isConnected, type_processor_->IsConnected()); } else { model_thread_runner_->PostTask( FROM_HERE, base::Bind(&NonUIModelTypeControllerTest::TestTypeProcessor, base::Unretained(this), isAllowingChanges, isConnected)); RunQueuedModelThreadTasks(); } } void OnMetadataLoaded() { if (model_thread_runner_->BelongsToCurrentThread()) { type_processor_->OnMetadataLoaded( make_scoped_ptr(new syncer_v2::MetadataBatch())); } else { model_thread_runner_->PostTask( FROM_HERE, base::Bind(&NonUIModelTypeControllerTest::OnMetadataLoaded, base::Unretained(this))); RunQueuedModelThreadTasks(); } } void LoadModels() { OnMetadataLoadedOnModelThread(); controller_->LoadModels(base::Bind( &NonUIModelTypeControllerTest::LoadModelsDone, base::Unretained(this))); if (auto_run_tasks_) { RunAllTasks(); } } void OnMetadataLoadedOnModelThread() { if (model_thread_runner_->BelongsToCurrentThread()) { if (!type_processor_->IsAllowingChanges()) { OnMetadataLoaded(); } } else { model_thread_runner_->PostTask( FROM_HERE, base::Bind( &NonUIModelTypeControllerTest::OnMetadataLoadedOnModelThread, base::Unretained(this))); RunQueuedModelThreadTasks(); } } void StartAssociating() { controller_->StartAssociating( base::Bind(&NonUIModelTypeControllerTest::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; } base::WeakPtr type_processor_; scoped_refptr 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 model_thread_runner_; scoped_refptr sync_thread_runner_; MockSyncBackend backend_; MockBackendDataTypeConfigurer configurer_; scoped_ptr service_; }; TEST_F(NonUIModelTypeControllerTest, InitialState) { EXPECT_EQ(syncer::DICTIONARY, controller_->type()); EXPECT_EQ(sync_driver::DataTypeController::NOT_RUNNING, controller_->state()); } TEST_F(NonUIModelTypeControllerTest, LoadModelsOnBackendThread) { 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(NonUIModelTypeControllerTest, LoadModelsTwice) { 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(NonUIModelTypeControllerTest, ActivateDataTypeOnBackendThread) { 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(NonUIModelTypeControllerTest, Stop) { 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