| // Copyright 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 "components/sync_driver/glue/sync_backend_registrar.h" |
| |
| #include "base/location.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "components/sync_driver/change_processor_mock.h" |
| #include "components/sync_driver/fake_sync_client.h" |
| #include "components/sync_driver/glue/browser_thread_model_worker.h" |
| #include "components/sync_driver/sync_api_component_factory_mock.h" |
| #include "sync/internal_api/public/base/model_type.h" |
| #include "sync/internal_api/public/engine/passive_model_worker.h" |
| #include "sync/internal_api/public/test/test_user_share.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace browser_sync { |
| |
| namespace { |
| |
| using ::testing::_; |
| using ::testing::InSequence; |
| using ::testing::Return; |
| using ::testing::StrictMock; |
| using syncer::FIRST_REAL_MODEL_TYPE; |
| using syncer::AUTOFILL; |
| using syncer::BOOKMARKS; |
| using syncer::PREFERENCES; |
| using syncer::THEMES; |
| using syncer::NIGORI; |
| using syncer::PASSWORDS; |
| using syncer::MODEL_TYPE_COUNT; |
| using syncer::ModelTypeSet; |
| using syncer::ModelType; |
| using syncer::ModelTypeFromInt; |
| |
| void TriggerChanges(SyncBackendRegistrar* registrar, ModelType type) { |
| registrar->OnChangesApplied(type, 0, NULL, |
| syncer::ImmutableChangeRecordList()); |
| registrar->OnChangesComplete(type); |
| } |
| |
| class RegistrarSyncClient : public sync_driver::FakeSyncClient { |
| public: |
| RegistrarSyncClient( |
| const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner, |
| const scoped_refptr<base::SingleThreadTaskRunner>& db_task_runner, |
| const scoped_refptr<base::SingleThreadTaskRunner>& file_task_runner) |
| : ui_task_runner_(ui_task_runner), |
| db_task_runner_(db_task_runner), |
| file_task_runner_(file_task_runner) {} |
| |
| scoped_refptr<syncer::ModelSafeWorker> CreateModelWorkerForGroup( |
| syncer::ModelSafeGroup group, |
| syncer::WorkerLoopDestructionObserver* observer) override { |
| switch (group) { |
| case syncer::GROUP_UI: |
| return new BrowserThreadModelWorker(ui_task_runner_, group, observer); |
| case syncer::GROUP_DB: |
| return new BrowserThreadModelWorker(db_task_runner_, group, observer); |
| case syncer::GROUP_FILE: |
| return new BrowserThreadModelWorker(file_task_runner_, group, observer); |
| case syncer::GROUP_PASSIVE: |
| return new syncer::PassiveModelWorker(observer); |
| default: |
| return nullptr; |
| } |
| } |
| |
| private: |
| const scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_; |
| const scoped_refptr<base::SingleThreadTaskRunner> db_task_runner_; |
| const scoped_refptr<base::SingleThreadTaskRunner> file_task_runner_; |
| }; |
| |
| // Flaky: https://crbug.com/498238 |
| class SyncBackendRegistrarTest : public testing::Test { |
| public: |
| void TestNonUIDataTypeActivationAsync(sync_driver::ChangeProcessor* processor, |
| base::WaitableEvent* done) { |
| registrar_->ActivateDataType(AUTOFILL, |
| syncer::GROUP_DB, |
| processor, |
| test_user_share_.user_share()); |
| syncer::ModelSafeRoutingInfo expected_routing_info; |
| expected_routing_info[AUTOFILL] = syncer::GROUP_DB; |
| ExpectRoutingInfo(registrar_.get(), expected_routing_info); |
| ExpectHasProcessorsForTypes(*registrar_, ModelTypeSet(AUTOFILL)); |
| TriggerChanges(registrar_.get(), AUTOFILL); |
| done->Signal(); |
| } |
| |
| protected: |
| SyncBackendRegistrarTest() |
| : db_thread_("DBThreadForTest"), |
| file_thread_("FileThreadForTest"), |
| sync_thread_(NULL) {} |
| |
| ~SyncBackendRegistrarTest() override {} |
| |
| void SetUp() override { |
| db_thread_.StartAndWaitForTesting(); |
| file_thread_.StartAndWaitForTesting(); |
| test_user_share_.SetUp(); |
| sync_client_.reset(new RegistrarSyncClient( |
| ui_task_runner(), db_task_runner(), file_task_runner())); |
| registrar_.reset(new SyncBackendRegistrar( |
| "test", sync_client_.get(), scoped_ptr<base::Thread>(), |
| ui_task_runner(), db_task_runner(), file_task_runner())); |
| sync_thread_ = registrar_->sync_thread(); |
| } |
| |
| void TearDown() override { |
| registrar_->RequestWorkerStopOnUIThread(); |
| test_user_share_.TearDown(); |
| sync_thread_->task_runner()->PostTask( |
| FROM_HERE, base::Bind(&SyncBackendRegistrar::Shutdown, |
| base::Unretained(registrar_.release()))); |
| sync_thread_->WaitUntilThreadStarted(); |
| sync_thread_->message_loop()->RunUntilIdle(); |
| } |
| |
| void ExpectRoutingInfo( |
| SyncBackendRegistrar* registrar, |
| const syncer::ModelSafeRoutingInfo& expected_routing_info) { |
| syncer::ModelSafeRoutingInfo routing_info; |
| registrar->GetModelSafeRoutingInfo(&routing_info); |
| EXPECT_EQ(expected_routing_info, routing_info); |
| } |
| |
| void ExpectHasProcessorsForTypes(const SyncBackendRegistrar& registrar, |
| ModelTypeSet types) { |
| for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) { |
| ModelType model_type = ModelTypeFromInt(i); |
| EXPECT_EQ(types.Has(model_type), |
| registrar_->IsTypeActivatedForTest(model_type)); |
| } |
| } |
| |
| const scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner() { |
| return message_loop_.task_runner(); |
| } |
| |
| const scoped_refptr<base::SingleThreadTaskRunner> db_task_runner() { |
| return db_thread_.task_runner(); |
| } |
| |
| const scoped_refptr<base::SingleThreadTaskRunner> file_task_runner() { |
| return db_thread_.task_runner(); |
| } |
| |
| base::MessageLoop message_loop_; |
| base::Thread db_thread_; |
| base::Thread file_thread_; |
| |
| syncer::TestUserShare test_user_share_; |
| scoped_ptr<RegistrarSyncClient> sync_client_; |
| scoped_ptr<SyncBackendRegistrar> registrar_; |
| |
| base::Thread* sync_thread_; |
| }; |
| |
| TEST_F(SyncBackendRegistrarTest, ConstructorEmpty) { |
| registrar_->SetInitialTypes(ModelTypeSet()); |
| EXPECT_FALSE(registrar_->IsNigoriEnabled()); |
| { |
| std::vector<scoped_refptr<syncer::ModelSafeWorker> > workers; |
| registrar_->GetWorkers(&workers); |
| EXPECT_EQ(4u, workers.size()); |
| } |
| ExpectRoutingInfo(registrar_.get(), syncer::ModelSafeRoutingInfo()); |
| ExpectHasProcessorsForTypes(*registrar_, ModelTypeSet()); |
| } |
| |
| TEST_F(SyncBackendRegistrarTest, ConstructorNonEmpty) { |
| const ModelTypeSet initial_types(BOOKMARKS, NIGORI, PASSWORDS); |
| registrar_->SetInitialTypes(initial_types); |
| EXPECT_TRUE(registrar_->IsNigoriEnabled()); |
| { |
| std::vector<scoped_refptr<syncer::ModelSafeWorker> > workers; |
| registrar_->GetWorkers(&workers); |
| EXPECT_EQ(4u, workers.size()); |
| } |
| { |
| syncer::ModelSafeRoutingInfo expected_routing_info; |
| expected_routing_info[BOOKMARKS] = syncer::GROUP_PASSIVE; |
| expected_routing_info[NIGORI] = syncer::GROUP_PASSIVE; |
| // Passwords dropped because of no password store. |
| ExpectRoutingInfo(registrar_.get(), expected_routing_info); |
| } |
| ExpectHasProcessorsForTypes(*registrar_, ModelTypeSet()); |
| } |
| |
| TEST_F(SyncBackendRegistrarTest, ConfigureDataTypes) { |
| registrar_->SetInitialTypes(ModelTypeSet()); |
| |
| // Add. |
| const ModelTypeSet types1(BOOKMARKS, NIGORI, AUTOFILL); |
| EXPECT_TRUE( |
| registrar_->ConfigureDataTypes(types1, ModelTypeSet()).Equals(types1)); |
| { |
| syncer::ModelSafeRoutingInfo expected_routing_info; |
| expected_routing_info[BOOKMARKS] = syncer::GROUP_PASSIVE; |
| expected_routing_info[NIGORI] = syncer::GROUP_PASSIVE; |
| expected_routing_info[AUTOFILL] = syncer::GROUP_PASSIVE; |
| ExpectRoutingInfo(registrar_.get(), expected_routing_info); |
| } |
| ExpectHasProcessorsForTypes(*registrar_, ModelTypeSet()); |
| EXPECT_TRUE(types1.Equals(registrar_->GetLastConfiguredTypes())); |
| |
| // Add and remove. |
| const ModelTypeSet types2(PREFERENCES, THEMES); |
| EXPECT_TRUE(registrar_->ConfigureDataTypes(types2, types1).Equals(types2)); |
| { |
| syncer::ModelSafeRoutingInfo expected_routing_info; |
| expected_routing_info[PREFERENCES] = syncer::GROUP_PASSIVE; |
| expected_routing_info[THEMES] = syncer::GROUP_PASSIVE; |
| ExpectRoutingInfo(registrar_.get(), expected_routing_info); |
| } |
| ExpectHasProcessorsForTypes(*registrar_, ModelTypeSet()); |
| EXPECT_TRUE(types2.Equals(registrar_->GetLastConfiguredTypes())); |
| |
| // Remove. |
| EXPECT_TRUE(registrar_->ConfigureDataTypes(ModelTypeSet(), types2).Empty()); |
| ExpectRoutingInfo(registrar_.get(), syncer::ModelSafeRoutingInfo()); |
| ExpectHasProcessorsForTypes(*registrar_, ModelTypeSet()); |
| EXPECT_TRUE(ModelTypeSet().Equals(registrar_->GetLastConfiguredTypes())); |
| } |
| |
| TEST_F(SyncBackendRegistrarTest, ActivateDeactivateUIDataType) { |
| InSequence in_sequence; |
| registrar_->SetInitialTypes(ModelTypeSet()); |
| |
| // Should do nothing. |
| TriggerChanges(registrar_.get(), BOOKMARKS); |
| |
| StrictMock<sync_driver::ChangeProcessorMock> change_processor_mock; |
| EXPECT_CALL(change_processor_mock, StartImpl()); |
| EXPECT_CALL(change_processor_mock, IsRunning()) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(change_processor_mock, ApplyChangesFromSyncModel(NULL, _, _)); |
| EXPECT_CALL(change_processor_mock, IsRunning()) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(change_processor_mock, CommitChangesFromSyncModel()); |
| EXPECT_CALL(change_processor_mock, IsRunning()) |
| .WillRepeatedly(Return(false)); |
| |
| const ModelTypeSet types(BOOKMARKS); |
| EXPECT_TRUE( |
| registrar_->ConfigureDataTypes(types, ModelTypeSet()).Equals(types)); |
| registrar_->ActivateDataType(BOOKMARKS, syncer::GROUP_UI, |
| &change_processor_mock, |
| test_user_share_.user_share()); |
| { |
| syncer::ModelSafeRoutingInfo expected_routing_info; |
| expected_routing_info[BOOKMARKS] = syncer::GROUP_UI; |
| ExpectRoutingInfo(registrar_.get(), expected_routing_info); |
| } |
| ExpectHasProcessorsForTypes(*registrar_, types); |
| |
| TriggerChanges(registrar_.get(), BOOKMARKS); |
| |
| registrar_->DeactivateDataType(BOOKMARKS); |
| ExpectRoutingInfo(registrar_.get(), syncer::ModelSafeRoutingInfo()); |
| ExpectHasProcessorsForTypes(*registrar_, ModelTypeSet()); |
| |
| // Should do nothing. |
| TriggerChanges(registrar_.get(), BOOKMARKS); |
| } |
| |
| TEST_F(SyncBackendRegistrarTest, ActivateDeactivateNonUIDataType) { |
| InSequence in_sequence; |
| registrar_->SetInitialTypes(ModelTypeSet()); |
| |
| // Should do nothing. |
| TriggerChanges(registrar_.get(), AUTOFILL); |
| |
| StrictMock<sync_driver::ChangeProcessorMock> change_processor_mock; |
| EXPECT_CALL(change_processor_mock, StartImpl()); |
| EXPECT_CALL(change_processor_mock, IsRunning()) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(change_processor_mock, ApplyChangesFromSyncModel(NULL, _, _)); |
| EXPECT_CALL(change_processor_mock, IsRunning()) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(change_processor_mock, CommitChangesFromSyncModel()); |
| EXPECT_CALL(change_processor_mock, IsRunning()) |
| .WillRepeatedly(Return(false)); |
| |
| const ModelTypeSet types(AUTOFILL); |
| EXPECT_TRUE( |
| registrar_->ConfigureDataTypes(types, ModelTypeSet()).Equals(types)); |
| |
| base::WaitableEvent done(false, false); |
| db_task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind(&SyncBackendRegistrarTest::TestNonUIDataTypeActivationAsync, |
| base::Unretained(this), &change_processor_mock, &done)); |
| done.Wait(); |
| |
| registrar_->DeactivateDataType(AUTOFILL); |
| ExpectRoutingInfo(registrar_.get(), syncer::ModelSafeRoutingInfo()); |
| ExpectHasProcessorsForTypes(*registrar_, ModelTypeSet()); |
| |
| // Should do nothing. |
| TriggerChanges(registrar_.get(), AUTOFILL); |
| } |
| |
| class SyncBackendRegistrarShutdownTest : public testing::Test { |
| public: |
| void BlockDBThread() { |
| EXPECT_FALSE(db_thread_lock_.Try()); |
| |
| db_thread_blocked_.Signal(); |
| base::AutoLock l(db_thread_lock_); |
| } |
| |
| protected: |
| friend class TestRegistrar; |
| |
| SyncBackendRegistrarShutdownTest() |
| : db_thread_("DBThreadForTest"), |
| file_thread_("FileThreadForTest"), |
| db_thread_blocked_(false, false) { |
| quit_closure_ = run_loop_.QuitClosure(); |
| } |
| |
| ~SyncBackendRegistrarShutdownTest() override {} |
| |
| void SetUp() override { |
| db_thread_.StartAndWaitForTesting(); |
| file_thread_.StartAndWaitForTesting(); |
| sync_client_.reset(new RegistrarSyncClient( |
| ui_task_runner(), db_task_runner(), file_task_runner())); |
| } |
| |
| void PostQuitOnUIMessageLoop() { |
| ui_task_runner()->PostTask(FROM_HERE, quit_closure_); |
| } |
| |
| const scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner() { |
| return message_loop_.task_runner(); |
| } |
| |
| const scoped_refptr<base::SingleThreadTaskRunner> db_task_runner() { |
| return db_thread_.task_runner(); |
| } |
| |
| const scoped_refptr<base::SingleThreadTaskRunner> file_task_runner() { |
| return file_thread_.task_runner(); |
| } |
| |
| base::MessageLoop message_loop_; |
| base::Thread db_thread_; |
| base::Thread file_thread_; |
| |
| scoped_ptr<RegistrarSyncClient> sync_client_; |
| base::WaitableEvent db_thread_blocked_; |
| |
| base::Lock db_thread_lock_; |
| base::RunLoop run_loop_; |
| base::Closure quit_closure_; |
| }; |
| |
| // Wrap SyncBackendRegistrar so that we can monitor its lifetime. |
| class TestRegistrar : public SyncBackendRegistrar { |
| public: |
| explicit TestRegistrar( |
| sync_driver::SyncClient* sync_client, |
| const scoped_refptr<base::SingleThreadTaskRunner>& ui_thread, |
| const scoped_refptr<base::SingleThreadTaskRunner>& db_thread, |
| const scoped_refptr<base::SingleThreadTaskRunner>& file_thread, |
| SyncBackendRegistrarShutdownTest* test) |
| : SyncBackendRegistrar("test", |
| sync_client, |
| scoped_ptr<base::Thread>(), |
| ui_thread, |
| db_thread, |
| file_thread), |
| test_(test) {} |
| |
| ~TestRegistrar() override { test_->PostQuitOnUIMessageLoop(); } |
| |
| private: |
| SyncBackendRegistrarShutdownTest* test_; |
| }; |
| |
| TEST_F(SyncBackendRegistrarShutdownTest, BlockingShutdown) { |
| // Take ownership of |db_thread_lock_| so that the DB thread can't acquire it. |
| db_thread_lock_.Acquire(); |
| |
| // This will block the DB thread by waiting on |db_thread_lock_|. |
| db_task_runner()->PostTask( |
| FROM_HERE, base::Bind(&SyncBackendRegistrarShutdownTest::BlockDBThread, |
| base::Unretained(this))); |
| |
| scoped_ptr<TestRegistrar> registrar( |
| new TestRegistrar(sync_client_.get(), ui_task_runner(), db_task_runner(), |
| file_task_runner(), this)); |
| base::Thread* sync_thread = registrar->sync_thread(); |
| |
| // Stop here until the DB thread gets a chance to run and block on the lock. |
| // Please note that since the task above didn't finish, the task to |
| // initialize the worker on the DB thread hasn't had a chance to run yet too. |
| // Which means ModelSafeWorker::SetWorkingLoopToCurrent hasn't been called |
| // for the DB worker. |
| db_thread_blocked_.Wait(); |
| |
| registrar->SetInitialTypes(ModelTypeSet()); |
| |
| // Start the shutdown. |
| registrar->RequestWorkerStopOnUIThread(); |
| |
| sync_thread->task_runner()->PostTask( |
| FROM_HERE, base::Bind(&SyncBackendRegistrar::Shutdown, |
| base::Unretained(registrar.release()))); |
| |
| // Make sure the thread starts running. |
| sync_thread->WaitUntilThreadStarted(); |
| |
| // The test verifies that the sync thread doesn't block because |
| // of the blocked DB thread and can finish the shutdown. |
| sync_thread->message_loop()->RunUntilIdle(); |
| |
| db_thread_lock_.Release(); |
| |
| // Run the main thread loop until all workers have been removed and the |
| // registrar destroyed. |
| run_loop_.Run(); |
| } |
| |
| } // namespace |
| |
| } // namespace browser_sync |