// 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 "base/synchronization/waitable_event.h" #include "base/test/test_timeouts.h" #include "chrome/browser/sync/engine/mock_model_safe_workers.h" #include "chrome/browser/sync/engine/syncer.h" #include "chrome/browser/sync/engine/syncer_thread.h" #include "chrome/browser/sync/sessions/test_util.h" #include "chrome/test/sync/engine/mock_connection_manager.h" #include "chrome/test/sync/engine/test_directory_setter_upper.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/gmock/include/gmock/gmock.h" using base::TimeDelta; using base::TimeTicks; using testing::_; using testing::AtLeast; using testing::DoAll; using testing::Eq; using testing::Invoke; using testing::Mock; using testing::Return; using testing::WithArg; namespace browser_sync { using sessions::SyncSession; using sessions::SyncSessionContext; using sessions::SyncSessionSnapshot; using syncable::ModelTypeBitSet; using sync_pb::GetUpdatesCallerInfo; class MockSyncer : public Syncer { public: MOCK_METHOD3(SyncShare, void(sessions::SyncSession*, SyncerStep, SyncerStep)); }; // Used when tests want to record syncing activity to examine later. struct SyncShareRecords { std::vector<TimeTicks> times; std::vector<linked_ptr<SyncSessionSnapshot> > snapshots; }; // Convenient to use in tests wishing to analyze SyncShare calls over time. static const size_t kMinNumSamples = 5; class SyncerThread2Test : public testing::Test { public: class MockDelayProvider : public SyncerThread::DelayProvider { public: MOCK_METHOD1(GetDelay, TimeDelta(const TimeDelta&)); }; virtual void SetUp() { syncdb_.SetUp(); syncer_ = new MockSyncer(); delay_ = NULL; registrar_.reset(MockModelSafeWorkerRegistrar::PassiveBookmarks()); connection_.reset(new MockConnectionManager(syncdb_.manager(), "Test")); connection_->SetServerReachable(); context_ = new SyncSessionContext(connection_.get(), syncdb_.manager(), registrar_.get(), std::vector<SyncEngineEventListener*>()); context_->set_notifications_enabled(true); context_->set_account_name("Test"); syncer_thread_.reset(new SyncerThread(context_, syncer_)); } virtual void SetUpWithTypes(syncable::ModelTypeBitSet types) { syncdb_.SetUp(); syncer_ = new MockSyncer(); delay_ = NULL; registrar_.reset(MockModelSafeWorkerRegistrar::PassiveForTypes(types)); connection_.reset(new MockConnectionManager(syncdb_.manager(), "Test")); connection_->SetServerReachable(); context_ = new SyncSessionContext(connection_.get(), syncdb_.manager(), registrar_.get(), std::vector<SyncEngineEventListener*>()); context_->set_notifications_enabled(true); context_->set_account_name("Test"); syncer_thread_.reset(new SyncerThread(context_, syncer_)); } SyncerThread* syncer_thread() { return syncer_thread_.get(); } MockSyncer* syncer() { return syncer_; } MockDelayProvider* delay() { return delay_; } MockConnectionManager* connection() { return connection_.get(); } TimeDelta zero() { return TimeDelta::FromSeconds(0); } TimeDelta timeout() { return TimeDelta::FromMilliseconds(TestTimeouts::action_timeout_ms()); } virtual void TearDown() { syncer_thread()->Stop(); syncdb_.TearDown(); } void AnalyzePollRun(const SyncShareRecords& records, size_t min_num_samples, const TimeTicks& optimal_start, const TimeDelta& poll_interval) { const std::vector<TimeTicks>& data(records.times); EXPECT_GE(data.size(), min_num_samples); for (size_t i = 0; i < data.size(); i++) { SCOPED_TRACE(testing::Message() << "SyncShare # (" << i << ")"); TimeTicks optimal_next_sync = optimal_start + poll_interval * i; EXPECT_GE(data[i], optimal_next_sync); EXPECT_EQ(GetUpdatesCallerInfo::PERIODIC, records.snapshots[i]->source.updates_source); } } bool GetBackoffAndResetTest(base::WaitableEvent* done) { syncable::ModelTypeBitSet nudge_types; syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, nudge_types, FROM_HERE); done->TimedWait(timeout()); TearDown(); done->Reset(); Mock::VerifyAndClearExpectations(syncer()); bool backing_off = syncer_thread()->IsBackingOff(); SetUp(); UseMockDelayProvider(); EXPECT_CALL(*delay(), GetDelay(_)) .WillRepeatedly(Return(TimeDelta::FromMilliseconds(1))); return backing_off; } void UseMockDelayProvider() { delay_ = new MockDelayProvider(); syncer_thread_->delay_provider_.reset(delay_); } void PostSignalTask(base::WaitableEvent* done) { syncer_thread_->thread_.message_loop()->PostTask(FROM_HERE, NewRunnableFunction(&SyncerThread2Test::SignalWaitableEvent, done)); } void FlushLastTask(base::WaitableEvent* done) { PostSignalTask(done); done->TimedWait(timeout()); done->Reset(); } static void SignalWaitableEvent(base::WaitableEvent* event) { event->Signal(); } static void QuitMessageLoop() { MessageLoop::current()->Quit(); } // Compare a ModelTypeBitSet to a ModelTypePayloadMap, ignoring // payload values. bool CompareModelTypeBitSetToModelTypePayloadMap( const syncable::ModelTypeBitSet& lhs, const syncable::ModelTypePayloadMap& rhs) { size_t count = 0; for (syncable::ModelTypePayloadMap::const_iterator i = rhs.begin(); i != rhs.end(); ++i, ++count) { if (!lhs.test(i->first)) return false; } if (lhs.count() != count) return false; return true; } SyncSessionContext* context() { return context_; } private: scoped_ptr<SyncerThread> syncer_thread_; scoped_ptr<MockConnectionManager> connection_; SyncSessionContext* context_; MockSyncer* syncer_; MockDelayProvider* delay_; scoped_ptr<MockModelSafeWorkerRegistrar> registrar_; MockDirectorySetterUpper syncdb_; }; bool RecordSyncShareImpl(SyncSession* s, SyncShareRecords* record, size_t signal_after) { record->times.push_back(TimeTicks::Now()); record->snapshots.push_back(make_linked_ptr(new SyncSessionSnapshot( s->TakeSnapshot()))); return record->times.size() >= signal_after; } ACTION_P4(RecordSyncShareAndPostSignal, record, signal_after, test, event) { if (RecordSyncShareImpl(arg0, record, signal_after) && event) test->PostSignalTask(event); } ACTION_P3(RecordSyncShare, record, signal_after, event) { if (RecordSyncShareImpl(arg0, record, signal_after) && event) event->Signal(); } ACTION_P(SignalEvent, event) { SyncerThread2Test::SignalWaitableEvent(event); } // Test nudge scheduling. TEST_F(SyncerThread2Test, Nudge) { syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); base::WaitableEvent done(false, false); SyncShareRecords records; syncable::ModelTypeBitSet model_types; model_types[syncable::BOOKMARKS] = true; EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&records, 1U, &done)))) .RetiresOnSaturation(); syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, model_types, FROM_HERE); done.TimedWait(timeout()); EXPECT_EQ(1U, records.snapshots.size()); EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(model_types, records.snapshots[0]->source.types)); EXPECT_EQ(GetUpdatesCallerInfo::LOCAL, records.snapshots[0]->source.updates_source); // Make sure a second, later, nudge is unaffected by first (no coalescing). SyncShareRecords records2; model_types[syncable::BOOKMARKS] = false; model_types[syncable::AUTOFILL] = true; EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&records2, 1U, &done)))); syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, model_types, FROM_HERE); done.TimedWait(timeout()); EXPECT_EQ(1U, records2.snapshots.size()); EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(model_types, records2.snapshots[0]->source.types)); EXPECT_EQ(GetUpdatesCallerInfo::LOCAL, records2.snapshots[0]->source.updates_source); } // Make sure a regular config command is scheduled fine in the absence of any // errors. TEST_F(SyncerThread2Test, Config) { base::WaitableEvent done(false, false); SyncShareRecords records; syncable::ModelTypeBitSet model_types; model_types[syncable::BOOKMARKS] = true; EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&records, 1U, &done)))); syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL); syncer_thread()->ScheduleConfig(model_types); done.TimedWait(timeout()); EXPECT_EQ(1U, records.snapshots.size()); EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(model_types, records.snapshots[0]->source.types)); EXPECT_EQ(GetUpdatesCallerInfo::FIRST_UPDATE, records.snapshots[0]->source.updates_source); } // Simulate a failure and make sure the config request is retried. TEST_F(SyncerThread2Test, ConfigWithBackingOff) { base::WaitableEvent done(false, false); base::WaitableEvent* dummy = NULL; UseMockDelayProvider(); EXPECT_CALL(*delay(), GetDelay(_)) .WillRepeatedly(Return(TimeDelta::FromMilliseconds(1))); SyncShareRecords records; syncable::ModelTypeBitSet model_types; model_types[syncable::BOOKMARKS] = true; EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateCommitFailed), WithArg<0>(RecordSyncShare(&records, 1U, dummy)))) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&records, 1U, &done)))); syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL); syncer_thread()->ScheduleConfig(model_types); done.TimedWait(timeout()); EXPECT_EQ(2U, records.snapshots.size()); EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(model_types, records.snapshots[1]->source.types)); EXPECT_EQ(GetUpdatesCallerInfo::SYNC_CYCLE_CONTINUATION, records.snapshots[1]->source.updates_source); } // Issue 2 config commands. Second one right after the first has failed // and make sure LATEST is executed. TEST_F(SyncerThread2Test, MultipleConfigWithBackingOff) { syncable::ModelTypeBitSet model_types1, model_types2; model_types1[syncable::BOOKMARKS] = true; model_types2[syncable::AUTOFILL] = true; SetUpWithTypes(model_types1 | model_types2); base::WaitableEvent done(false, false); base::WaitableEvent done1(false, false); base::WaitableEvent* dummy = NULL; UseMockDelayProvider(); EXPECT_CALL(*delay(), GetDelay(_)) .WillRepeatedly(Return(TimeDelta::FromMilliseconds(30))); SyncShareRecords records; EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateCommitFailed), WithArg<0>(RecordSyncShare(&records, 1U, dummy)))) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateCommitFailed), WithArg<0>(RecordSyncShare(&records, 1U, &done1)))) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&records, 1U, &done)))); syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL); syncer_thread()->ScheduleConfig(model_types1); // done1 indicates the first config failed. done1.TimedWait(timeout()); syncer_thread()->ScheduleConfig(model_types2); done.TimedWait(timeout()); EXPECT_EQ(3U, records.snapshots.size()); EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(model_types2, records.snapshots[2]->source.types)); EXPECT_EQ(GetUpdatesCallerInfo::FIRST_UPDATE, records.snapshots[2]->source.updates_source); } // Issue a nudge when the config has failed. Make sure both the config and // nudge are executed. TEST_F(SyncerThread2Test, NudgeWithConfigWithBackingOff) { syncable::ModelTypeBitSet model_types; model_types[syncable::BOOKMARKS] = true; base::WaitableEvent done(false, false); base::WaitableEvent done1(false, false); base::WaitableEvent done2(false, false); base::WaitableEvent* dummy = NULL; UseMockDelayProvider(); EXPECT_CALL(*delay(), GetDelay(_)) .WillRepeatedly(Return(TimeDelta::FromMilliseconds(50))); SyncShareRecords records; EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateCommitFailed), WithArg<0>(RecordSyncShare(&records, 1U, dummy)))) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateCommitFailed), WithArg<0>(RecordSyncShare(&records, 1U, &done1)))) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&records, 1U, &done2)))) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&records, 1U, &done)))); syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL); syncer_thread()->ScheduleConfig(model_types); done1.TimedWait(timeout()); syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, model_types, FROM_HERE); // done2 indicates config suceeded. Now change the mode so nudge can execute. done2.TimedWait(timeout()); syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); done.TimedWait(timeout()); EXPECT_EQ(4U, records.snapshots.size()); EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(model_types, records.snapshots[2]->source.types)); EXPECT_EQ(GetUpdatesCallerInfo::SYNC_CYCLE_CONTINUATION, records.snapshots[2]->source.updates_source); EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(model_types, records.snapshots[3]->source.types)); EXPECT_EQ(GetUpdatesCallerInfo::LOCAL, records.snapshots[3]->source.updates_source); } // Test that nudges are coalesced. TEST_F(SyncerThread2Test, NudgeCoalescing) { syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); base::WaitableEvent done(false, false); SyncShareRecords r; EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&r, 1U, &done)))); syncable::ModelTypeBitSet types1, types2, types3; types1[syncable::BOOKMARKS] = true; types2[syncable::AUTOFILL] = true; types3[syncable::THEMES] = true; TimeDelta delay = TimeDelta::FromMilliseconds( TestTimeouts::tiny_timeout_ms()); TimeTicks optimal_time = TimeTicks::Now() + delay; syncer_thread()->ScheduleNudge(delay, NUDGE_SOURCE_UNKNOWN, types1, FROM_HERE); syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, types2, FROM_HERE); done.TimedWait(timeout()); EXPECT_EQ(1U, r.snapshots.size()); EXPECT_GE(r.times[0], optimal_time); EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap( types1 | types2, r.snapshots[0]->source.types)); EXPECT_EQ(GetUpdatesCallerInfo::LOCAL, r.snapshots[0]->source.updates_source); SyncShareRecords r2; EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&r2, 1U, &done)))); syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_NOTIFICATION, types3, FROM_HERE); done.TimedWait(timeout()); EXPECT_EQ(1U, r2.snapshots.size()); EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(types3, r2.snapshots[0]->source.types)); EXPECT_EQ(GetUpdatesCallerInfo::NOTIFICATION, r2.snapshots[0]->source.updates_source); } // Test nudge scheduling. TEST_F(SyncerThread2Test, NudgeWithPayloads) { syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); base::WaitableEvent done(false, false); SyncShareRecords records; syncable::ModelTypePayloadMap model_types_with_payloads; model_types_with_payloads[syncable::BOOKMARKS] = "test"; EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&records, 1U, &done)))) .RetiresOnSaturation(); syncer_thread()->ScheduleNudgeWithPayloads(zero(), NUDGE_SOURCE_LOCAL, model_types_with_payloads, FROM_HERE); done.TimedWait(timeout()); EXPECT_EQ(1U, records.snapshots.size()); EXPECT_EQ(model_types_with_payloads, records.snapshots[0]->source.types); EXPECT_EQ(GetUpdatesCallerInfo::LOCAL, records.snapshots[0]->source.updates_source); // Make sure a second, later, nudge is unaffected by first (no coalescing). SyncShareRecords records2; model_types_with_payloads.erase(syncable::BOOKMARKS); model_types_with_payloads[syncable::AUTOFILL] = "test2"; EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&records2, 1U, &done)))); syncer_thread()->ScheduleNudgeWithPayloads(zero(), NUDGE_SOURCE_LOCAL, model_types_with_payloads, FROM_HERE); done.TimedWait(timeout()); EXPECT_EQ(1U, records2.snapshots.size()); EXPECT_EQ(model_types_with_payloads, records2.snapshots[0]->source.types); EXPECT_EQ(GetUpdatesCallerInfo::LOCAL, records2.snapshots[0]->source.updates_source); } // Test that nudges are coalesced. TEST_F(SyncerThread2Test, NudgeWithPayloadsCoalescing) { syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); base::WaitableEvent done(false, false); SyncShareRecords r; EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&r, 1U, &done)))); syncable::ModelTypePayloadMap types1, types2, types3; types1[syncable::BOOKMARKS] = "test1"; types2[syncable::AUTOFILL] = "test2"; types3[syncable::THEMES] = "test3"; TimeDelta delay = TimeDelta::FromMilliseconds( TestTimeouts::tiny_timeout_ms()); TimeTicks optimal_time = TimeTicks::Now() + delay; syncer_thread()->ScheduleNudgeWithPayloads(delay, NUDGE_SOURCE_UNKNOWN, types1, FROM_HERE); syncer_thread()->ScheduleNudgeWithPayloads(zero(), NUDGE_SOURCE_LOCAL, types2, FROM_HERE); done.TimedWait(timeout()); EXPECT_EQ(1U, r.snapshots.size()); EXPECT_GE(r.times[0], optimal_time); syncable::ModelTypePayloadMap coalesced_types; syncable::CoalescePayloads(&coalesced_types, types1); syncable::CoalescePayloads(&coalesced_types, types2); EXPECT_EQ(coalesced_types, r.snapshots[0]->source.types); EXPECT_EQ(GetUpdatesCallerInfo::LOCAL, r.snapshots[0]->source.updates_source); SyncShareRecords r2; EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&r2, 1U, &done)))); syncer_thread()->ScheduleNudgeWithPayloads(zero(), NUDGE_SOURCE_NOTIFICATION, types3, FROM_HERE); done.TimedWait(timeout()); EXPECT_EQ(1U, r2.snapshots.size()); EXPECT_EQ(types3, r2.snapshots[0]->source.types); EXPECT_EQ(GetUpdatesCallerInfo::NOTIFICATION, r2.snapshots[0]->source.updates_source); } // Test that polling works as expected. TEST_F(SyncerThread2Test, Polling) { SyncShareRecords records; base::WaitableEvent done(false, false); TimeDelta poll_interval(TimeDelta::FromMilliseconds(30)); syncer_thread()->OnReceivedLongPollIntervalUpdate(poll_interval); EXPECT_CALL(*syncer(), SyncShare(_,_,_)).Times(AtLeast(kMinNumSamples)) .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&records, kMinNumSamples, &done)))); TimeTicks optimal_start = TimeTicks::Now() + poll_interval; syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); done.TimedWait(timeout()); syncer_thread()->Stop(); AnalyzePollRun(records, kMinNumSamples, optimal_start, poll_interval); } // Test that the short poll interval is used. TEST_F(SyncerThread2Test, PollNotificationsDisabled) { SyncShareRecords records; base::WaitableEvent done(false, false); TimeDelta poll_interval(TimeDelta::FromMilliseconds(30)); syncer_thread()->OnReceivedShortPollIntervalUpdate(poll_interval); syncer_thread()->set_notifications_enabled(false); EXPECT_CALL(*syncer(), SyncShare(_,_,_)).Times(AtLeast(kMinNumSamples)) .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&records, kMinNumSamples, &done)))); TimeTicks optimal_start = TimeTicks::Now() + poll_interval; syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); done.TimedWait(timeout()); syncer_thread()->Stop(); AnalyzePollRun(records, kMinNumSamples, optimal_start, poll_interval); } // Test that polling intervals are updated when needed. TEST_F(SyncerThread2Test, PollIntervalUpdate) { SyncShareRecords records; base::WaitableEvent done(false, false); TimeDelta poll1(TimeDelta::FromMilliseconds(120)); TimeDelta poll2(TimeDelta::FromMilliseconds(30)); syncer_thread()->OnReceivedLongPollIntervalUpdate(poll1); EXPECT_CALL(*syncer(), SyncShare(_,_,_)).Times(AtLeast(kMinNumSamples)) .WillOnce(WithArg<0>( sessions::test_util::SimulatePollIntervalUpdate(poll2))) .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&records, kMinNumSamples, &done)))); TimeTicks optimal_start = TimeTicks::Now() + poll1 + poll2; syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); done.TimedWait(timeout()); syncer_thread()->Stop(); AnalyzePollRun(records, kMinNumSamples, optimal_start, poll2); } // Test that a sync session is run through to completion. TEST_F(SyncerThread2Test, HasMoreToSync) { syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); base::WaitableEvent done(false, false); EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(Invoke(sessions::test_util::SimulateHasMoreToSync)) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), SignalEvent(&done))); syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, ModelTypeBitSet(), FROM_HERE); done.TimedWait(timeout()); // If more nudges are scheduled, they'll be waited on by TearDown, and would // cause our expectation to break. } // Test that no syncing occurs when throttled. TEST_F(SyncerThread2Test, ThrottlingDoesThrottle) { syncable::ModelTypeBitSet types; types[syncable::BOOKMARKS] = true; base::WaitableEvent done(false, false); TimeDelta poll(TimeDelta::FromMilliseconds(5)); TimeDelta throttle(TimeDelta::FromMinutes(10)); syncer_thread()->OnReceivedLongPollIntervalUpdate(poll); EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(WithArg<0>(sessions::test_util::SimulateThrottled(throttle))); syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, types, FROM_HERE); FlushLastTask(&done); syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL); syncer_thread()->ScheduleConfig(types); FlushLastTask(&done); } TEST_F(SyncerThread2Test, ThrottlingExpires) { SyncShareRecords records; base::WaitableEvent done(false, false); TimeDelta poll(TimeDelta::FromMilliseconds(15)); TimeDelta throttle1(TimeDelta::FromMilliseconds(150)); TimeDelta throttle2(TimeDelta::FromMinutes(10)); syncer_thread()->OnReceivedLongPollIntervalUpdate(poll); ::testing::InSequence seq; EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(WithArg<0>(sessions::test_util::SimulateThrottled(throttle1))) .RetiresOnSaturation(); EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&records, kMinNumSamples, &done)))); TimeTicks optimal_start = TimeTicks::Now() + poll + throttle1; syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); done.TimedWait(timeout()); syncer_thread()->Stop(); AnalyzePollRun(records, kMinNumSamples, optimal_start, poll); } // Test nudges / polls don't run in config mode and config tasks do. TEST_F(SyncerThread2Test, ConfigurationMode) { TimeDelta poll(TimeDelta::FromMilliseconds(15)); SyncShareRecords records; base::WaitableEvent done(false, false); base::WaitableEvent* dummy = NULL; syncer_thread()->OnReceivedLongPollIntervalUpdate(poll); EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce((Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&records, 1U, dummy)))); syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL); syncable::ModelTypeBitSet nudge_types; nudge_types[syncable::AUTOFILL] = true; syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, nudge_types, FROM_HERE); syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, nudge_types, FROM_HERE); syncable::ModelTypeBitSet config_types; config_types[syncable::BOOKMARKS] = true; // TODO(tim): This will fail once CONFIGURATION tasks are implemented. Update // the EXPECT when that happens. syncer_thread()->ScheduleConfig(config_types); FlushLastTask(&done); syncer_thread()->Stop(); EXPECT_EQ(1U, records.snapshots.size()); EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(config_types, records.snapshots[0]->source.types)); } // Test that exponential backoff is properly triggered. TEST_F(SyncerThread2Test, BackoffTriggers) { base::WaitableEvent done(false, false); UseMockDelayProvider(); EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(Invoke(sessions::test_util::SimulateDownloadUpdatesFailed)) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), SignalEvent(&done))); EXPECT_FALSE(GetBackoffAndResetTest(&done)); // Note GetBackoffAndResetTest clears mocks and re-instantiates the syncer. EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(Invoke(sessions::test_util::SimulateCommitFailed)) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), SignalEvent(&done))); EXPECT_FALSE(GetBackoffAndResetTest(&done)); EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(Invoke(sessions::test_util::SimulateDownloadUpdatesFailed)) .WillRepeatedly(DoAll(Invoke( sessions::test_util::SimulateDownloadUpdatesFailed), SignalEvent(&done))); EXPECT_TRUE(GetBackoffAndResetTest(&done)); EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(Invoke(sessions::test_util::SimulateCommitFailed)) .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateCommitFailed), SignalEvent(&done))); EXPECT_TRUE(GetBackoffAndResetTest(&done)); EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(Invoke(sessions::test_util::SimulateDownloadUpdatesFailed)) .WillOnce(Invoke(sessions::test_util::SimulateDownloadUpdatesFailed)) .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateSuccess), SignalEvent(&done))); EXPECT_FALSE(GetBackoffAndResetTest(&done)); EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(Invoke(sessions::test_util::SimulateCommitFailed)) .WillOnce(Invoke(sessions::test_util::SimulateCommitFailed)) .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateSuccess), SignalEvent(&done))); EXPECT_FALSE(GetBackoffAndResetTest(&done)); } // Test that no polls or extraneous nudges occur when in backoff. TEST_F(SyncerThread2Test, BackoffDropsJobs) { SyncShareRecords r; TimeDelta poll(TimeDelta::FromMilliseconds(5)); base::WaitableEvent done(false, false); syncable::ModelTypeBitSet types; types[syncable::BOOKMARKS] = true; syncer_thread()->OnReceivedLongPollIntervalUpdate(poll); UseMockDelayProvider(); EXPECT_CALL(*syncer(), SyncShare(_,_,_)).Times(2) .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateCommitFailed), RecordSyncShareAndPostSignal(&r, 2U, this, &done))); EXPECT_CALL(*delay(), GetDelay(_)) .WillRepeatedly(Return(TimeDelta::FromDays(1))); syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); ASSERT_TRUE(done.TimedWait(timeout())); done.Reset(); Mock::VerifyAndClearExpectations(syncer()); EXPECT_EQ(2U, r.snapshots.size()); EXPECT_EQ(GetUpdatesCallerInfo::PERIODIC, r.snapshots[0]->source.updates_source); EXPECT_EQ(GetUpdatesCallerInfo::SYNC_CYCLE_CONTINUATION, r.snapshots[1]->source.updates_source); EXPECT_CALL(*syncer(), SyncShare(_,_,_)).Times(1) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateCommitFailed), RecordSyncShareAndPostSignal(&r, 1U, this, &done))); // We schedule a nudge with enough delay (10X poll interval) that at least // one or two polls would have taken place. The nudge should succeed. syncer_thread()->ScheduleNudge(poll * 10, NUDGE_SOURCE_LOCAL, types, FROM_HERE); ASSERT_TRUE(done.TimedWait(timeout())); done.Reset(); Mock::VerifyAndClearExpectations(syncer()); Mock::VerifyAndClearExpectations(delay()); EXPECT_EQ(3U, r.snapshots.size()); EXPECT_EQ(GetUpdatesCallerInfo::LOCAL, r.snapshots[2]->source.updates_source); EXPECT_CALL(*syncer(), SyncShare(_,_,_)).Times(0); EXPECT_CALL(*delay(), GetDelay(_)).Times(0); syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL); syncer_thread()->ScheduleConfig(types); FlushLastTask(&done); syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, types, FROM_HERE); syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, types, FROM_HERE); FlushLastTask(&done); } // Test that backoff is shaping traffic properly with consecutive errors. TEST_F(SyncerThread2Test, BackoffElevation) { SyncShareRecords r; const TimeDelta poll(TimeDelta::FromMilliseconds(10)); base::WaitableEvent done(false, false); syncer_thread()->OnReceivedLongPollIntervalUpdate(poll); UseMockDelayProvider(); const TimeDelta first = TimeDelta::FromSeconds(1); const TimeDelta second = TimeDelta::FromMilliseconds(10); const TimeDelta third = TimeDelta::FromMilliseconds(20); const TimeDelta fourth = TimeDelta::FromMilliseconds(30); const TimeDelta fifth = TimeDelta::FromDays(1); EXPECT_CALL(*syncer(), SyncShare(_,_,_)).Times(kMinNumSamples) .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateCommitFailed), RecordSyncShareAndPostSignal(&r, kMinNumSamples, this, &done))); EXPECT_CALL(*delay(), GetDelay(Eq(first))).WillOnce(Return(second)) .RetiresOnSaturation(); EXPECT_CALL(*delay(), GetDelay(Eq(second))).WillOnce(Return(third)) .RetiresOnSaturation(); EXPECT_CALL(*delay(), GetDelay(Eq(third))).WillOnce(Return(fourth)) .RetiresOnSaturation(); EXPECT_CALL(*delay(), GetDelay(Eq(fourth))).WillOnce(Return(fifth)); syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); ASSERT_TRUE(done.TimedWait(timeout())); EXPECT_GE(r.times[2] - r.times[1], second); EXPECT_GE(r.times[3] - r.times[2], third); EXPECT_GE(r.times[4] - r.times[3], fourth); } // Test that things go back to normal once a canary task makes forward progress // following a succession of failures. TEST_F(SyncerThread2Test, BackoffRelief) { SyncShareRecords r; const TimeDelta poll(TimeDelta::FromMilliseconds(10)); base::WaitableEvent done(false, false); syncer_thread()->OnReceivedLongPollIntervalUpdate(poll); UseMockDelayProvider(); const TimeDelta backoff = TimeDelta::FromMilliseconds(100); EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(Invoke(sessions::test_util::SimulateCommitFailed)) .WillOnce(Invoke(sessions::test_util::SimulateCommitFailed)) .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateSuccess), RecordSyncShareAndPostSignal(&r, kMinNumSamples, this, &done))); EXPECT_CALL(*delay(), GetDelay(_)).WillOnce(Return(backoff)); // Optimal start for the post-backoff poll party. TimeTicks optimal_start = TimeTicks::Now() + poll + backoff; syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); done.TimedWait(timeout()); syncer_thread()->Stop(); // Check for healthy polling after backoff is relieved. // Can't use AnalyzePollRun because first sync is a continuation. Bleh. for (size_t i = 0; i < r.times.size(); i++) { SCOPED_TRACE(testing::Message() << "SyncShare # (" << i << ")"); TimeTicks optimal_next_sync = optimal_start + poll * i; EXPECT_GE(r.times[i], optimal_next_sync); EXPECT_EQ(i == 0 ? GetUpdatesCallerInfo::SYNC_CYCLE_CONTINUATION : GetUpdatesCallerInfo::PERIODIC, r.snapshots[i]->source.updates_source); } } TEST_F(SyncerThread2Test, GetRecommendedDelay) { EXPECT_LE(TimeDelta::FromSeconds(0), SyncerThread::GetRecommendedDelay(TimeDelta::FromSeconds(0))); EXPECT_LE(TimeDelta::FromSeconds(1), SyncerThread::GetRecommendedDelay(TimeDelta::FromSeconds(1))); EXPECT_LE(TimeDelta::FromSeconds(50), SyncerThread::GetRecommendedDelay(TimeDelta::FromSeconds(50))); EXPECT_LE(TimeDelta::FromSeconds(10), SyncerThread::GetRecommendedDelay(TimeDelta::FromSeconds(10))); EXPECT_EQ(TimeDelta::FromSeconds(kMaxBackoffSeconds), SyncerThread::GetRecommendedDelay( TimeDelta::FromSeconds(kMaxBackoffSeconds))); EXPECT_EQ(TimeDelta::FromSeconds(kMaxBackoffSeconds), SyncerThread::GetRecommendedDelay( TimeDelta::FromSeconds(kMaxBackoffSeconds + 1))); } // Test that appropriate syncer steps are requested for each job type. TEST_F(SyncerThread2Test, SyncerSteps) { // Nudges. base::WaitableEvent done(false, false); EXPECT_CALL(*syncer(), SyncShare(_, SYNCER_BEGIN, SYNCER_END)) .Times(1); syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, ModelTypeBitSet(), FROM_HERE); FlushLastTask(&done); syncer_thread()->Stop(); Mock::VerifyAndClearExpectations(syncer()); // ClearUserData. EXPECT_CALL(*syncer(), SyncShare(_, CLEAR_PRIVATE_DATA, SYNCER_END)) .Times(1); syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); syncer_thread()->ScheduleClearUserData(); FlushLastTask(&done); syncer_thread()->Stop(); Mock::VerifyAndClearExpectations(syncer()); // Configuration. EXPECT_CALL(*syncer(), SyncShare(_, DOWNLOAD_UPDATES, APPLY_UPDATES)); syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL); syncer_thread()->ScheduleConfig(ModelTypeBitSet()); FlushLastTask(&done); syncer_thread()->Stop(); Mock::VerifyAndClearExpectations(syncer()); // Poll. EXPECT_CALL(*syncer(), SyncShare(_, SYNCER_BEGIN, SYNCER_END)) .Times(AtLeast(1)) .WillRepeatedly(SignalEvent(&done)); const TimeDelta poll(TimeDelta::FromMilliseconds(10)); syncer_thread()->OnReceivedLongPollIntervalUpdate(poll); syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); done.TimedWait(timeout()); syncer_thread()->Stop(); Mock::VerifyAndClearExpectations(syncer()); done.Reset(); } // Test config tasks don't run during normal mode. // TODO(tim): Implement this test and then the functionality! TEST_F(SyncerThread2Test, DISABLED_NoConfigDuringNormal) { } // Test that starting the syncer thread without a valid connection doesn't // break things when a connection is detected. TEST_F(SyncerThread2Test, StartWhenNotConnected) { base::WaitableEvent done(false, false); MessageLoop cur; connection()->SetServerNotReachable(); EXPECT_CALL(*syncer(), SyncShare(_,_,_)).WillOnce(SignalEvent(&done)); syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, ModelTypeBitSet(), FROM_HERE); FlushLastTask(&done); connection()->SetServerReachable(); cur.PostTask(FROM_HERE, NewRunnableFunction( &SyncerThread2Test::QuitMessageLoop)); cur.Run(); // By now, the server connection event should have been posted to the // SyncerThread. FlushLastTask(&done); done.TimedWait(timeout()); } TEST_F(SyncerThread2Test, SetsPreviousRoutingInfo) { base::WaitableEvent done(false, false); ModelSafeRoutingInfo info; EXPECT_TRUE(info == context()->previous_session_routing_info()); ModelSafeRoutingInfo expected; context()->registrar()->GetModelSafeRoutingInfo(&expected); ASSERT_FALSE(expected.empty()); EXPECT_CALL(*syncer(), SyncShare(_,_,_)).Times(1); syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, ModelTypeBitSet(), FROM_HERE); FlushLastTask(&done); syncer_thread()->Stop(); EXPECT_TRUE(expected == context()->previous_session_routing_info()); } } // namespace browser_sync // SyncerThread won't outlive the test! DISABLE_RUNNABLE_METHOD_REFCOUNT(browser_sync::SyncerThread2Test);