// 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 "sync/internal_api/sync_rollback_manager.h" #include "base/files/scoped_temp_dir.h" #include "base/run_loop.h" #include "sync/internal_api/public/read_node.h" #include "sync/internal_api/public/read_transaction.h" #include "sync/internal_api/public/sessions/sync_session_snapshot.h" #include "sync/internal_api/public/test/test_internal_components_factory.h" #include "sync/internal_api/public/write_node.h" #include "sync/internal_api/public/write_transaction.h" #include "sync/internal_api/sync_backup_manager.h" #include "sync/syncable/entry.h" #include "sync/test/engine/fake_model_worker.h" #include "sync/test/test_directory_backing_store.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" using ::testing::_; using ::testing::DoDefault; using ::testing::Invoke; using ::testing::Truly; using ::testing::WithArgs; namespace syncer { namespace { class TestChangeDelegate : public SyncManager::ChangeDelegate { public: TestChangeDelegate() { ON_CALL(*this, OnChangesApplied(_, _, _, _)) .WillByDefault( WithArgs<3>(Invoke(this, &TestChangeDelegate::VerifyDeletes))); } void add_expected_delete(int64 v) { expected_deletes_.insert(v); } MOCK_METHOD4(OnChangesApplied, void(ModelType model_type, int64 model_version, const BaseTransaction* trans, const ImmutableChangeRecordList& changes)); MOCK_METHOD1(OnChangesComplete, void(ModelType model_type)); private: void VerifyDeletes(const ImmutableChangeRecordList& changes) { std::set deleted; for (size_t i = 0; i < changes.Get().size(); ++i) { const ChangeRecord& change = (changes.Get())[i]; EXPECT_EQ(ChangeRecord::ACTION_DELETE, change.action); EXPECT_TRUE(deleted.find(change.id) == deleted.end()); deleted.insert(change.id); } EXPECT_TRUE(expected_deletes_ == deleted); } std::set expected_deletes_; }; class SyncRollbackManagerTest : public testing::Test, public SyncManager::Observer { protected: virtual void SetUp() override { CHECK(temp_dir_.CreateUniqueTempDir()); worker_ = new FakeModelWorker(GROUP_UI); } MOCK_METHOD1(OnSyncCycleCompleted, void(const sessions::SyncSessionSnapshot&)); MOCK_METHOD1(OnConnectionStatusChange, void(ConnectionStatus)); MOCK_METHOD4(OnInitializationComplete, void(const WeakHandle&, const WeakHandle&, bool, ModelTypeSet)); MOCK_METHOD1(OnActionableError, void(const SyncProtocolError&)); MOCK_METHOD1(OnMigrationRequested, void(ModelTypeSet));; MOCK_METHOD1(OnProtocolEvent, void(const ProtocolEvent&)); void OnConfigDone(bool success) { EXPECT_TRUE(success); } int64 CreateEntry(UserShare* user_share, ModelType type, const std::string& client_tag) { WriteTransaction trans(FROM_HERE, user_share); ReadNode type_root(&trans); EXPECT_EQ(BaseNode::INIT_OK, type_root.InitTypeRoot(type)); WriteNode node(&trans); EXPECT_EQ(WriteNode::INIT_SUCCESS, node.InitUniqueByCreation(type, type_root, client_tag)); return node.GetEntry()->GetMetahandle(); } void InitManager(SyncManager* manager, ModelTypeSet types, TestChangeDelegate* delegate, InternalComponentsFactory::StorageOption storage_option) { manager_ = manager; types_ = types; EXPECT_CALL(*this, OnInitializationComplete(_, _, _, _)) .WillOnce(WithArgs<2>(Invoke(this, &SyncRollbackManagerTest::HandleInit))); manager->AddObserver(this); base::RunLoop run_loop; SyncManager::InitArgs args; args.database_location = temp_dir_.path(); args.service_url = GURL("https://example.com/"); args.workers.push_back(worker_); args.change_delegate = delegate; InternalComponentsFactory::StorageOption storage_used; args.internal_components_factory.reset(new TestInternalComponentsFactory( InternalComponentsFactory::Switches(), storage_option, &storage_used)); manager->Init(&args); EXPECT_EQ(storage_option, storage_used); loop_.PostTask(FROM_HERE, run_loop.QuitClosure()); run_loop.Run(); } // Create and persist an entry by unique tag in DB. void PrepopulateDb(ModelType type, const std::string& client_tag) { SyncBackupManager backup_manager; TestChangeDelegate delegate; InitManager(&backup_manager, ModelTypeSet(type), &delegate, InternalComponentsFactory::STORAGE_ON_DISK_DEFERRED); CreateEntry(backup_manager.GetUserShare(), type, client_tag); backup_manager.ShutdownOnSyncThread(SWITCH_MODE_SYNC); } // Verify entry with |client_tag| exists in sync directory. bool VerifyEntry(UserShare* user_share, ModelType type, const std::string& client_tag) { ReadTransaction trans(FROM_HERE, user_share); ReadNode node(&trans); return BaseNode::INIT_OK == node.InitByClientTagLookup(type, client_tag); } private: void ConfigureSyncer() { manager_->ConfigureSyncer( CONFIGURE_REASON_NEW_CLIENT, types_, ModelTypeSet(), ModelTypeSet(), ModelTypeSet(), ModelSafeRoutingInfo(), base::Bind(&SyncRollbackManagerTest::OnConfigDone, base::Unretained(this), true), base::Bind(&SyncRollbackManagerTest::OnConfigDone, base::Unretained(this), false)); } void HandleInit(bool success) { if (success) { loop_.PostTask(FROM_HERE, base::Bind(&SyncRollbackManagerTest::ConfigureSyncer, base::Unretained(this))); } else { manager_->ShutdownOnSyncThread(STOP_SYNC); } } base::ScopedTempDir temp_dir_; scoped_refptr worker_; base::MessageLoop loop_; // Needed for WeakHandle SyncManager* manager_; ModelTypeSet types_; }; bool IsRollbackDoneAction(SyncProtocolError e) { return e.action == syncer::ROLLBACK_DONE; } TEST_F(SyncRollbackManagerTest, RollbackBasic) { PrepopulateDb(PREFERENCES, "pref1"); TestChangeDelegate delegate; SyncRollbackManager rollback_manager; InitManager(&rollback_manager, ModelTypeSet(PREFERENCES), &delegate, InternalComponentsFactory::STORAGE_ON_DISK); // Simulate a new entry added during type initialization. int64 new_pref_id = CreateEntry(rollback_manager.GetUserShare(), PREFERENCES, "pref2"); delegate.add_expected_delete(new_pref_id); EXPECT_CALL(delegate, OnChangesApplied(_, _, _, _)) .Times(1) .WillOnce(DoDefault()); EXPECT_CALL(delegate, OnChangesComplete(_)).Times(1); EXPECT_CALL(*this, OnActionableError(Truly(IsRollbackDoneAction))).Times(1); ModelSafeRoutingInfo routing_info; routing_info[PREFERENCES] = GROUP_UI; rollback_manager.StartSyncingNormally(routing_info); } TEST_F(SyncRollbackManagerTest, NoRollbackOfTypesNotBackedUp) { PrepopulateDb(PREFERENCES, "pref1"); TestChangeDelegate delegate; SyncRollbackManager rollback_manager; InitManager(&rollback_manager, ModelTypeSet(PREFERENCES, APPS), &delegate, InternalComponentsFactory::STORAGE_ON_DISK); // Simulate new entry added during type initialization. int64 new_pref_id = CreateEntry(rollback_manager.GetUserShare(), PREFERENCES, "pref2"); CreateEntry(rollback_manager.GetUserShare(), APPS, "app1"); delegate.add_expected_delete(new_pref_id); EXPECT_CALL(delegate, OnChangesApplied(_, _, _, _)) .Times(1) .WillOnce(DoDefault()); EXPECT_CALL(delegate, OnChangesComplete(_)).Times(1); ModelSafeRoutingInfo routing_info; routing_info[PREFERENCES] = GROUP_UI; rollback_manager.StartSyncingNormally(routing_info); // APP entry is still valid. EXPECT_TRUE(VerifyEntry(rollback_manager.GetUserShare(), APPS, "app1")); } TEST_F(SyncRollbackManagerTest, BackupDbNotChangedOnAbort) { PrepopulateDb(PREFERENCES, "pref1"); TestChangeDelegate delegate; scoped_ptr rollback_manager( new SyncRollbackManager); InitManager(rollback_manager.get(), ModelTypeSet(PREFERENCES), &delegate, InternalComponentsFactory::STORAGE_ON_DISK); // Simulate a new entry added during type initialization. CreateEntry(rollback_manager->GetUserShare(), PREFERENCES, "pref2"); // Manager was shut down before sync starts. rollback_manager->ShutdownOnSyncThread(STOP_SYNC); // Verify new entry was not persisted. rollback_manager.reset(new SyncRollbackManager); InitManager(rollback_manager.get(), ModelTypeSet(PREFERENCES), &delegate, InternalComponentsFactory::STORAGE_ON_DISK); EXPECT_FALSE(VerifyEntry(rollback_manager->GetUserShare(), PREFERENCES, "pref2")); } TEST_F(SyncRollbackManagerTest, OnInitializationFailure) { // Test graceful shutdown on initialization failure. scoped_ptr rollback_manager( new SyncRollbackManager); InitManager(rollback_manager.get(), ModelTypeSet(PREFERENCES), NULL, InternalComponentsFactory::STORAGE_ON_DISK); } } // anonymous namespace } // namespace syncer