| // Copyright (c) 2011 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 "chrome/browser/sync/sessions/sync_session.h" |
| |
| #include "base/memory/ref_counted.h" |
| #include "chrome/browser/sync/engine/conflict_resolver.h" |
| #include "chrome/browser/sync/engine/mock_model_safe_workers.h" |
| #include "chrome/browser/sync/engine/syncer_types.h" |
| #include "chrome/browser/sync/engine/syncer_util.h" |
| #include "chrome/browser/sync/syncable/directory_manager.h" |
| #include "chrome/browser/sync/syncable/model_type.h" |
| #include "chrome/browser/sync/syncable/syncable.h" |
| #include "chrome/test/sync/engine/test_directory_setter_upper.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using syncable::WriteTransaction; |
| |
| namespace browser_sync { |
| namespace sessions { |
| namespace { |
| |
| class SyncSessionTest : public testing::Test, |
| public SyncSession::Delegate, |
| public ModelSafeWorkerRegistrar { |
| public: |
| SyncSessionTest() : controller_invocations_allowed_(false) { |
| GetModelSafeRoutingInfo(&routes_); |
| } |
| |
| SyncSession* MakeSession() { |
| return new SyncSession(context_.get(), this, SyncSourceInfo(), routes_, |
| std::vector<ModelSafeWorker*>()); |
| } |
| |
| virtual void SetUp() { |
| context_.reset(new SyncSessionContext(NULL, NULL, this, |
| std::vector<SyncEngineEventListener*>())); |
| routes_.clear(); |
| routes_[syncable::BOOKMARKS] = GROUP_UI; |
| routes_[syncable::AUTOFILL] = GROUP_UI; |
| session_.reset(MakeSession()); |
| } |
| virtual void TearDown() { |
| session_.reset(); |
| context_.reset(); |
| } |
| |
| virtual void OnSilencedUntil(const base::TimeTicks& silenced_until) { |
| FailControllerInvocationIfDisabled("OnSilencedUntil"); |
| } |
| virtual bool IsSyncingCurrentlySilenced() { |
| FailControllerInvocationIfDisabled("IsSyncingCurrentlySilenced"); |
| return false; |
| } |
| virtual void OnReceivedLongPollIntervalUpdate( |
| const base::TimeDelta& new_interval) { |
| FailControllerInvocationIfDisabled("OnReceivedLongPollIntervalUpdate"); |
| } |
| virtual void OnReceivedShortPollIntervalUpdate( |
| const base::TimeDelta& new_interval) { |
| FailControllerInvocationIfDisabled("OnReceivedShortPollIntervalUpdate"); |
| } |
| virtual void OnShouldStopSyncingPermanently() { |
| FailControllerInvocationIfDisabled("OnShouldStopSyncingPermanently"); |
| } |
| |
| // ModelSafeWorkerRegistrar implementation. |
| virtual void GetWorkers(std::vector<ModelSafeWorker*>* out) {} |
| virtual void GetModelSafeRoutingInfo(ModelSafeRoutingInfo* out) { |
| out->swap(routes_); |
| } |
| |
| StatusController* status() { return session_->status_controller(); } |
| protected: |
| void FailControllerInvocationIfDisabled(const std::string& msg) { |
| if (!controller_invocations_allowed_) |
| FAIL() << msg; |
| } |
| |
| syncable::ModelTypeBitSet ParamsMeaningAllEnabledTypes() { |
| syncable::ModelTypeBitSet request_params; |
| request_params[syncable::BOOKMARKS] = true; |
| request_params[syncable::AUTOFILL] = true; |
| return request_params; |
| } |
| |
| syncable::ModelTypeBitSet ParamsMeaningJustOneEnabledType() { |
| syncable::ModelTypeBitSet request_params; |
| request_params[syncable::AUTOFILL] = true; |
| return request_params; |
| } |
| |
| bool controller_invocations_allowed_; |
| scoped_ptr<SyncSession> session_; |
| scoped_ptr<SyncSessionContext> context_; |
| ModelSafeRoutingInfo routes_; |
| }; |
| |
| TEST_F(SyncSessionTest, ScopedContextHelpers) { |
| ConflictResolver resolver; |
| EXPECT_FALSE(context_->resolver()); |
| { |
| ScopedSessionContextConflictResolver s_resolver(context_.get(), &resolver); |
| EXPECT_EQ(&resolver, context_->resolver()); |
| } |
| EXPECT_FALSE(context_->resolver()); |
| } |
| |
| TEST_F(SyncSessionTest, SetWriteTransaction) { |
| TestDirectorySetterUpper db; |
| db.SetUp(); |
| session_.reset(); |
| context_.reset(new SyncSessionContext(NULL, db.manager(), this, |
| std::vector<SyncEngineEventListener*>())); |
| session_.reset(MakeSession()); |
| context_->set_account_name(db.name()); |
| syncable::ScopedDirLookup dir(context_->directory_manager(), |
| context_->account_name()); |
| ASSERT_TRUE(dir.good()); |
| |
| scoped_ptr<SyncSession> session(MakeSession()); |
| EXPECT_TRUE(NULL == session->write_transaction()); |
| { |
| WriteTransaction trans(dir, syncable::UNITTEST, __FILE__, __LINE__); |
| sessions::ScopedSetSessionWriteTransaction set_trans(session.get(), &trans); |
| EXPECT_TRUE(&trans == session->write_transaction()); |
| } |
| db.TearDown(); |
| } |
| |
| TEST_F(SyncSessionTest, MoreToSyncIfUnsyncedGreaterThanCommitted) { |
| // If any forward progress was made during the session, and the number of |
| // unsynced handles still exceeds the number of commit ids we added, there is |
| // more to sync. For example, this occurs if we had more commit ids |
| // than could fit in a single commit batch. |
| EXPECT_FALSE(session_->HasMoreToSync()); |
| OrderedCommitSet commit_set(routes_); |
| commit_set.AddCommitItem(0, syncable::Id(), syncable::BOOKMARKS); |
| status()->set_commit_set(commit_set); |
| EXPECT_FALSE(session_->HasMoreToSync()); |
| |
| std::vector<int64> unsynced_handles; |
| unsynced_handles.push_back(1); |
| unsynced_handles.push_back(2); |
| status()->set_unsynced_handles(unsynced_handles); |
| EXPECT_FALSE(session_->HasMoreToSync()); |
| status()->increment_num_successful_commits(); |
| EXPECT_TRUE(session_->HasMoreToSync()); |
| } |
| |
| TEST_F(SyncSessionTest, MoreToSyncIfConflictSetsBuilt) { |
| // If we built conflict sets, then we need to loop back and try |
| // to get updates & commit again. |
| status()->update_conflict_sets_built(true); |
| EXPECT_TRUE(session_->HasMoreToSync()); |
| } |
| |
| TEST_F(SyncSessionTest, MoreToDownloadIfDownloadFailed) { |
| status()->set_updates_request_types(ParamsMeaningAllEnabledTypes()); |
| |
| // When DownloadUpdatesCommand fails, these should be false. |
| EXPECT_FALSE(status()->ServerSaysNothingMoreToDownload()); |
| EXPECT_FALSE(status()->download_updates_succeeded()); |
| |
| // Download updates has its own loop in the syncer; it shouldn't factor |
| // into HasMoreToSync. |
| EXPECT_FALSE(session_->HasMoreToSync()); |
| } |
| |
| TEST_F(SyncSessionTest, MoreToDownloadIfGotChangesRemaining) { |
| status()->set_updates_request_types(ParamsMeaningAllEnabledTypes()); |
| |
| // When the server returns changes_remaining, that means there's |
| // more to download. |
| status()->mutable_updates_response()->mutable_get_updates() |
| ->set_changes_remaining(1000L); |
| EXPECT_FALSE(status()->ServerSaysNothingMoreToDownload()); |
| EXPECT_TRUE(status()->download_updates_succeeded()); |
| |
| // Download updates has its own loop in the syncer; it shouldn't factor |
| // into HasMoreToSync. |
| EXPECT_FALSE(session_->HasMoreToSync()); |
| } |
| |
| TEST_F(SyncSessionTest, MoreToDownloadIfGotNoChangesRemaining) { |
| status()->set_updates_request_types(ParamsMeaningAllEnabledTypes()); |
| |
| // When the server returns a timestamp, that means we're up to date. |
| status()->mutable_updates_response()->mutable_get_updates() |
| ->set_changes_remaining(0); |
| EXPECT_TRUE(status()->ServerSaysNothingMoreToDownload()); |
| EXPECT_TRUE(status()->download_updates_succeeded()); |
| |
| // Download updates has its own loop in the syncer; it shouldn't factor |
| // into HasMoreToSync. |
| EXPECT_FALSE(session_->HasMoreToSync()); |
| } |
| |
| TEST_F(SyncSessionTest, MoreToDownloadIfGotNoChangesRemainingForSubset) { |
| status()->set_updates_request_types(ParamsMeaningJustOneEnabledType()); |
| |
| // When the server returns a timestamp, that means we're up to date for that |
| // type. But there may still be more to download if there are other |
| // datatypes that we didn't request on this go-round. |
| status()->mutable_updates_response()->mutable_get_updates() |
| ->set_changes_remaining(0); |
| |
| EXPECT_TRUE(status()->ServerSaysNothingMoreToDownload()); |
| EXPECT_TRUE(status()->download_updates_succeeded()); |
| |
| // Download updates has its own loop in the syncer; it shouldn't factor |
| // into HasMoreToSync. |
| EXPECT_FALSE(session_->HasMoreToSync()); |
| } |
| |
| TEST_F(SyncSessionTest, MoreToDownloadIfGotChangesRemainingAndEntries) { |
| status()->set_updates_request_types(ParamsMeaningAllEnabledTypes()); |
| // The actual entry count should not factor into the HasMoreToSync |
| // determination. |
| status()->mutable_updates_response()->mutable_get_updates()->add_entries(); |
| status()->mutable_updates_response()->mutable_get_updates() |
| ->set_changes_remaining(1000000L);; |
| EXPECT_FALSE(status()->ServerSaysNothingMoreToDownload()); |
| EXPECT_TRUE(status()->download_updates_succeeded()); |
| |
| // Download updates has its own loop in the syncer; it shouldn't factor |
| // into HasMoreToSync. |
| EXPECT_FALSE(session_->HasMoreToSync()); |
| } |
| |
| TEST_F(SyncSessionTest, MoreToDownloadIfGotNoChangesRemainingAndEntries) { |
| status()->set_updates_request_types(ParamsMeaningAllEnabledTypes()); |
| // The actual entry count should not factor into the HasMoreToSync |
| // determination. |
| status()->mutable_updates_response()->mutable_get_updates()->add_entries(); |
| status()->mutable_updates_response()->mutable_get_updates() |
| ->set_changes_remaining(0); |
| EXPECT_TRUE(status()->ServerSaysNothingMoreToDownload()); |
| EXPECT_TRUE(status()->download_updates_succeeded()); |
| |
| // Download updates has its own loop in the syncer; it shouldn't factor |
| // into HasMoreToSync. |
| EXPECT_FALSE(session_->HasMoreToSync()); |
| } |
| |
| TEST_F(SyncSessionTest, MoreToSyncIfConflictsResolved) { |
| // Conflict resolution happens after get updates and commit, |
| // so we need to loop back and get updates / commit again now |
| // that we have made forward progress. |
| status()->update_conflicts_resolved(true); |
| EXPECT_TRUE(session_->HasMoreToSync()); |
| } |
| |
| TEST_F(SyncSessionTest, ResetTransientState) { |
| status()->update_conflicts_resolved(true); |
| status()->increment_num_successful_commits(); |
| EXPECT_TRUE(session_->HasMoreToSync()); |
| session_->ResetTransientState(); |
| EXPECT_FALSE(status()->conflicts_resolved()); |
| EXPECT_FALSE(session_->HasMoreToSync()); |
| EXPECT_FALSE(status()->TestAndClearIsDirty()); |
| } |
| |
| TEST_F(SyncSessionTest, Coalesce) { |
| std::vector<ModelSafeWorker*> workers_one, workers_two; |
| ModelSafeRoutingInfo routes_one, routes_two; |
| syncable::ModelTypePayloadMap one_type = |
| syncable::ModelTypePayloadMapFromBitSet( |
| ParamsMeaningJustOneEnabledType(), |
| std::string()); |
| syncable::ModelTypePayloadMap all_types = |
| syncable::ModelTypePayloadMapFromBitSet( |
| ParamsMeaningAllEnabledTypes(), |
| std::string()); |
| SyncSourceInfo source_one(sync_pb::GetUpdatesCallerInfo::PERIODIC, one_type); |
| SyncSourceInfo source_two(sync_pb::GetUpdatesCallerInfo::LOCAL, all_types); |
| |
| scoped_refptr<MockDBModelWorker> db_worker(new MockDBModelWorker()); |
| scoped_refptr<MockUIModelWorker> ui_worker(new MockUIModelWorker()); |
| workers_one.push_back(db_worker); |
| workers_two.push_back(db_worker); |
| workers_two.push_back(ui_worker); |
| routes_one[syncable::AUTOFILL] = GROUP_DB; |
| routes_two[syncable::AUTOFILL] = GROUP_DB; |
| routes_two[syncable::BOOKMARKS] = GROUP_UI; |
| SyncSession one(context_.get(), this, source_one, routes_one, workers_one); |
| SyncSession two(context_.get(), this, source_two, routes_two, workers_two); |
| |
| one.Coalesce(two); |
| |
| EXPECT_EQ(two.source().updates_source, one.source().updates_source); |
| EXPECT_EQ(all_types, one.source().types); |
| std::vector<ModelSafeWorker*>::const_iterator it_db = |
| std::find(one.workers().begin(), one.workers().end(), db_worker); |
| std::vector<ModelSafeWorker*>::const_iterator it_ui = |
| std::find(one.workers().begin(), one.workers().end(), ui_worker); |
| EXPECT_NE(it_db, one.workers().end()); |
| EXPECT_NE(it_ui, one.workers().end()); |
| EXPECT_EQ(routes_two, one.routing_info()); |
| } |
| |
| TEST_F(SyncSessionTest, MakeTypePayloadMapFromBitSet) { |
| syncable::ModelTypeBitSet types; |
| std::string payload = "test"; |
| syncable::ModelTypePayloadMap types_with_payloads = |
| syncable::ModelTypePayloadMapFromBitSet(types, |
| payload); |
| EXPECT_TRUE(types_with_payloads.empty()); |
| |
| types[syncable::BOOKMARKS] = true; |
| types[syncable::PASSWORDS] = true; |
| types[syncable::AUTOFILL] = true; |
| payload = "test2"; |
| types_with_payloads = syncable::ModelTypePayloadMapFromBitSet(types, payload); |
| |
| ASSERT_EQ(3U, types_with_payloads.size()); |
| EXPECT_EQ(types_with_payloads[syncable::BOOKMARKS], payload); |
| EXPECT_EQ(types_with_payloads[syncable::PASSWORDS], payload); |
| EXPECT_EQ(types_with_payloads[syncable::AUTOFILL], payload); |
| } |
| |
| TEST_F(SyncSessionTest, MakeTypePayloadMapFromRoutingInfo) { |
| std::string payload = "test"; |
| syncable::ModelTypePayloadMap types_with_payloads |
| = syncable::ModelTypePayloadMapFromRoutingInfo(routes_, payload); |
| ASSERT_EQ(routes_.size(), types_with_payloads.size()); |
| for (ModelSafeRoutingInfo::iterator iter = routes_.begin(); |
| iter != routes_.end(); |
| ++iter) { |
| EXPECT_EQ(payload, types_with_payloads[iter->first]); |
| } |
| } |
| |
| TEST_F(SyncSessionTest, CoalescePayloads) { |
| syncable::ModelTypePayloadMap original; |
| std::string empty_payload; |
| std::string payload1 = "payload1"; |
| std::string payload2 = "payload2"; |
| std::string payload3 = "payload3"; |
| original[syncable::BOOKMARKS] = empty_payload; |
| original[syncable::PASSWORDS] = payload1; |
| original[syncable::AUTOFILL] = payload2; |
| original[syncable::THEMES] = payload3; |
| |
| syncable::ModelTypePayloadMap update; |
| update[syncable::BOOKMARKS] = empty_payload; // Same. |
| update[syncable::PASSWORDS] = empty_payload; // Overwrite with empty. |
| update[syncable::AUTOFILL] = payload1; // Overwrite with non-empty. |
| update[syncable::SESSIONS] = payload2; // New. |
| // Themes untouched. |
| |
| CoalescePayloads(&original, update); |
| ASSERT_EQ(5U, original.size()); |
| EXPECT_EQ(empty_payload, original[syncable::BOOKMARKS]); |
| EXPECT_EQ(payload1, original[syncable::PASSWORDS]); |
| EXPECT_EQ(payload1, original[syncable::AUTOFILL]); |
| EXPECT_EQ(payload2, original[syncable::SESSIONS]); |
| EXPECT_EQ(payload3, original[syncable::THEMES]); |
| } |
| |
| } // namespace |
| } // namespace sessions |
| } // namespace browser_sync |