| // Copyright (c) 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 "base/bind.h" |
| #include "base/callback.h" |
| #include "base/compiler_specific.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/test/test_timeouts.h" |
| #include "sync/engine/backoff_delay_provider.h" |
| #include "sync/engine/sync_scheduler_impl.h" |
| #include "sync/engine/syncer.h" |
| #include "sync/internal_api/public/base/cancelation_signal.h" |
| #include "sync/internal_api/public/base/model_type_test_util.h" |
| #include "sync/notifier/invalidation_util.h" |
| #include "sync/notifier/object_id_invalidation_map.h" |
| #include "sync/sessions/test_util.h" |
| #include "sync/test/callback_counter.h" |
| #include "sync/test/engine/fake_model_worker.h" |
| #include "sync/test/engine/mock_connection_manager.h" |
| #include "sync/test/engine/test_directory_setter_upper.h" |
| #include "sync/util/extensions_activity.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using base::TimeDelta; |
| using base::TimeTicks; |
| using testing::_; |
| using testing::AtLeast; |
| using testing::DoAll; |
| using testing::Invoke; |
| using testing::Mock; |
| using testing::Return; |
| using testing::WithArg; |
| using testing::WithArgs; |
| |
| namespace syncer { |
| using sessions::SyncSession; |
| using sessions::SyncSessionContext; |
| using sync_pb::GetUpdatesCallerInfo; |
| |
| class MockSyncer : public Syncer { |
| public: |
| MockSyncer(); |
| MOCK_METHOD3(NormalSyncShare, bool(ModelTypeSet, |
| const sessions::NudgeTracker&, |
| sessions::SyncSession*)); |
| MOCK_METHOD3(ConfigureSyncShare, |
| bool(ModelTypeSet, |
| sync_pb::GetUpdatesCallerInfo::GetUpdatesSource, |
| SyncSession*)); |
| MOCK_METHOD2(PollSyncShare, bool(ModelTypeSet, sessions::SyncSession*)); |
| }; |
| |
| MockSyncer::MockSyncer() |
| : Syncer(NULL) {} |
| |
| typedef std::vector<TimeTicks> SyncShareTimes; |
| |
| void QuitLoopNow() { |
| // We use QuitNow() instead of Quit() as the latter may get stalled |
| // indefinitely in the presence of repeated timers with low delays |
| // and a slow test (e.g., ThrottlingDoesThrottle [which has a poll |
| // delay of 5ms] run under TSAN on the trybots). |
| base::MessageLoop::current()->QuitNow(); |
| } |
| |
| void RunLoop() { |
| base::MessageLoop::current()->Run(); |
| } |
| |
| void PumpLoop() { |
| // Do it this way instead of RunAllPending to pump loop exactly once |
| // (necessary in the presence of timers; see comment in |
| // QuitLoopNow). |
| base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(&QuitLoopNow)); |
| RunLoop(); |
| } |
| |
| void PumpLoopFor(base::TimeDelta time) { |
| // Allow the loop to run for the specified amount of time. |
| base::MessageLoop::current()->PostDelayedTask( |
| FROM_HERE, base::Bind(&QuitLoopNow), time); |
| RunLoop(); |
| } |
| |
| ModelSafeRoutingInfo TypesToRoutingInfo(ModelTypeSet types) { |
| ModelSafeRoutingInfo routes; |
| for (ModelTypeSet::Iterator iter = types.First(); iter.Good(); iter.Inc()) { |
| routes[iter.Get()] = GROUP_PASSIVE; |
| } |
| return routes; |
| } |
| |
| // Convenient to use in tests wishing to analyze SyncShare calls over time. |
| static const size_t kMinNumSamples = 5; |
| class SyncSchedulerTest : public testing::Test { |
| public: |
| SyncSchedulerTest() : syncer_(NULL), delay_(NULL), weak_ptr_factory_(this) {} |
| |
| class MockDelayProvider : public BackoffDelayProvider { |
| public: |
| MockDelayProvider() : BackoffDelayProvider( |
| TimeDelta::FromSeconds(kInitialBackoffRetrySeconds), |
| TimeDelta::FromSeconds(kInitialBackoffImmediateRetrySeconds)) { |
| } |
| |
| MOCK_METHOD1(GetDelay, TimeDelta(const TimeDelta&)); |
| }; |
| |
| virtual void SetUp() { |
| dir_maker_.SetUp(); |
| syncer_ = new testing::StrictMock<MockSyncer>(); |
| delay_ = NULL; |
| extensions_activity_ = new ExtensionsActivity(); |
| |
| routing_info_[BOOKMARKS] = GROUP_UI; |
| routing_info_[AUTOFILL] = GROUP_DB; |
| routing_info_[THEMES] = GROUP_UI; |
| routing_info_[NIGORI] = GROUP_PASSIVE; |
| |
| workers_.clear(); |
| workers_.push_back(make_scoped_refptr(new FakeModelWorker(GROUP_UI))); |
| workers_.push_back(make_scoped_refptr(new FakeModelWorker(GROUP_DB))); |
| workers_.push_back(make_scoped_refptr(new FakeModelWorker(GROUP_PASSIVE))); |
| |
| std::vector<ModelSafeWorker*> workers; |
| for (std::vector<scoped_refptr<FakeModelWorker> >::iterator it = |
| workers_.begin(); it != workers_.end(); ++it) { |
| workers.push_back(it->get()); |
| } |
| |
| connection_.reset(new MockConnectionManager(directory(), |
| &cancelation_signal_)); |
| connection_->SetServerReachable(); |
| context_.reset(new SyncSessionContext( |
| connection_.get(), directory(), workers, |
| extensions_activity_.get(), |
| std::vector<SyncEngineEventListener*>(), NULL, NULL, |
| true, // enable keystore encryption |
| false, // force enable pre-commit GU avoidance |
| "fake_invalidator_client_id")); |
| context_->set_routing_info(routing_info_); |
| context_->set_notifications_enabled(true); |
| context_->set_account_name("Test"); |
| scheduler_.reset( |
| new SyncSchedulerImpl("TestSyncScheduler", |
| BackoffDelayProvider::FromDefaults(), |
| context(), |
| syncer_)); |
| } |
| |
| SyncSchedulerImpl* scheduler() { return scheduler_.get(); } |
| const ModelSafeRoutingInfo& routing_info() { return routing_info_; } |
| MockSyncer* syncer() { return syncer_; } |
| MockDelayProvider* delay() { return delay_; } |
| MockConnectionManager* connection() { return connection_.get(); } |
| TimeDelta zero() { return TimeDelta::FromSeconds(0); } |
| TimeDelta timeout() { |
| return TestTimeouts::action_timeout(); |
| } |
| |
| virtual void TearDown() { |
| PumpLoop(); |
| scheduler_.reset(); |
| PumpLoop(); |
| dir_maker_.TearDown(); |
| } |
| |
| void AnalyzePollRun(const SyncShareTimes& times, size_t min_num_samples, |
| const TimeTicks& optimal_start, const TimeDelta& poll_interval) { |
| EXPECT_GE(times.size(), min_num_samples); |
| for (size_t i = 0; i < times.size(); i++) { |
| SCOPED_TRACE(testing::Message() << "SyncShare # (" << i << ")"); |
| TimeTicks optimal_next_sync = optimal_start + poll_interval * i; |
| EXPECT_GE(times[i], optimal_next_sync); |
| } |
| } |
| |
| void DoQuitLoopNow() { |
| QuitLoopNow(); |
| } |
| |
| void StartSyncScheduler(SyncScheduler::Mode mode) { |
| scheduler()->Start(mode); |
| } |
| |
| // This stops the scheduler synchronously. |
| void StopSyncScheduler() { |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind(&SyncSchedulerTest::DoQuitLoopNow, |
| weak_ptr_factory_.GetWeakPtr())); |
| RunLoop(); |
| } |
| |
| bool RunAndGetBackoff() { |
| ModelTypeSet nudge_types(BOOKMARKS); |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| |
| scheduler()->ScheduleLocalNudge(zero(), nudge_types, FROM_HERE); |
| RunLoop(); |
| |
| return scheduler()->IsBackingOff(); |
| } |
| |
| void UseMockDelayProvider() { |
| delay_ = new MockDelayProvider(); |
| scheduler_->delay_provider_.reset(delay_); |
| } |
| |
| SyncSessionContext* context() { return context_.get(); } |
| |
| ModelTypeSet GetThrottledTypes() { |
| return scheduler_->nudge_tracker_.GetThrottledTypes(); |
| } |
| |
| private: |
| syncable::Directory* directory() { |
| return dir_maker_.directory(); |
| } |
| |
| base::MessageLoop loop_; |
| TestDirectorySetterUpper dir_maker_; |
| CancelationSignal cancelation_signal_; |
| scoped_ptr<MockConnectionManager> connection_; |
| scoped_ptr<SyncSessionContext> context_; |
| scoped_ptr<SyncSchedulerImpl> scheduler_; |
| MockSyncer* syncer_; |
| MockDelayProvider* delay_; |
| std::vector<scoped_refptr<FakeModelWorker> > workers_; |
| scoped_refptr<ExtensionsActivity> extensions_activity_; |
| ModelSafeRoutingInfo routing_info_; |
| base::WeakPtrFactory<SyncSchedulerTest> weak_ptr_factory_; |
| }; |
| |
| void RecordSyncShareImpl(SyncShareTimes* times) { |
| times->push_back(TimeTicks::Now()); |
| } |
| |
| ACTION_P(RecordSyncShare, times) { |
| RecordSyncShareImpl(times); |
| if (base::MessageLoop::current()->is_running()) |
| QuitLoopNow(); |
| return true; |
| } |
| |
| ACTION_P2(RecordSyncShareMultiple, times, quit_after) { |
| RecordSyncShareImpl(times); |
| EXPECT_LE(times->size(), quit_after); |
| if (times->size() >= quit_after && |
| base::MessageLoop::current()->is_running()) { |
| QuitLoopNow(); |
| } |
| return true; |
| } |
| |
| ACTION_P(StopScheduler, scheduler) { |
| scheduler->Stop(); |
| } |
| |
| ACTION(AddFailureAndQuitLoopNow) { |
| ADD_FAILURE(); |
| QuitLoopNow(); |
| return true; |
| } |
| |
| ACTION(QuitLoopNowAction) { |
| QuitLoopNow(); |
| return true; |
| } |
| |
| // Test nudge scheduling. |
| TEST_F(SyncSchedulerTest, Nudge) { |
| SyncShareTimes times; |
| ModelTypeSet model_types(BOOKMARKS); |
| |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateNormalSuccess), |
| RecordSyncShare(×))) |
| .RetiresOnSaturation(); |
| |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| |
| scheduler()->ScheduleLocalNudge(zero(), model_types, FROM_HERE); |
| RunLoop(); |
| |
| Mock::VerifyAndClearExpectations(syncer()); |
| |
| // Make sure a second, later, nudge is unaffected by first (no coalescing). |
| SyncShareTimes times2; |
| model_types.Remove(BOOKMARKS); |
| model_types.Put(AUTOFILL); |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateNormalSuccess), |
| RecordSyncShare(×2))); |
| scheduler()->ScheduleLocalNudge(zero(), model_types, FROM_HERE); |
| RunLoop(); |
| } |
| |
| // Make sure a regular config command is scheduled fine in the absence of any |
| // errors. |
| TEST_F(SyncSchedulerTest, Config) { |
| SyncShareTimes times; |
| const ModelTypeSet model_types(BOOKMARKS); |
| |
| EXPECT_CALL(*syncer(), ConfigureSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateConfigureSuccess), |
| RecordSyncShare(×))); |
| |
| StartSyncScheduler(SyncScheduler::CONFIGURATION_MODE); |
| |
| CallbackCounter ready_counter; |
| CallbackCounter retry_counter; |
| ConfigurationParams params( |
| GetUpdatesCallerInfo::RECONFIGURATION, |
| model_types, |
| TypesToRoutingInfo(model_types), |
| base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)), |
| base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter))); |
| scheduler()->ScheduleConfiguration(params); |
| PumpLoop(); |
| ASSERT_EQ(1, ready_counter.times_called()); |
| ASSERT_EQ(0, retry_counter.times_called()); |
| } |
| |
| // Simulate a failure and make sure the config request is retried. |
| TEST_F(SyncSchedulerTest, ConfigWithBackingOff) { |
| UseMockDelayProvider(); |
| EXPECT_CALL(*delay(), GetDelay(_)) |
| .WillRepeatedly(Return(TimeDelta::FromMilliseconds(1))); |
| SyncShareTimes times; |
| const ModelTypeSet model_types(BOOKMARKS); |
| |
| StartSyncScheduler(SyncScheduler::CONFIGURATION_MODE); |
| |
| EXPECT_CALL(*syncer(), ConfigureSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateConfigureFailed), |
| RecordSyncShare(×))) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateConfigureFailed), |
| RecordSyncShare(×))); |
| |
| CallbackCounter ready_counter; |
| CallbackCounter retry_counter; |
| ConfigurationParams params( |
| GetUpdatesCallerInfo::RECONFIGURATION, |
| model_types, |
| TypesToRoutingInfo(model_types), |
| base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)), |
| base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter))); |
| scheduler()->ScheduleConfiguration(params); |
| RunLoop(); |
| ASSERT_EQ(0, ready_counter.times_called()); |
| ASSERT_EQ(1, retry_counter.times_called()); |
| |
| // RunLoop() will trigger TryCanaryJob which will retry configuration. |
| // Since retry_task was already called it shouldn't be called again. |
| RunLoop(); |
| ASSERT_EQ(0, ready_counter.times_called()); |
| ASSERT_EQ(1, retry_counter.times_called()); |
| |
| Mock::VerifyAndClearExpectations(syncer()); |
| |
| EXPECT_CALL(*syncer(), ConfigureSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateConfigureSuccess), |
| RecordSyncShare(×))); |
| RunLoop(); |
| |
| ASSERT_EQ(1, ready_counter.times_called()); |
| } |
| |
| // Simuilate SyncSchedulerImpl::Stop being called in the middle of Configure. |
| // This can happen if server returns NOT_MY_BIRTHDAY. |
| TEST_F(SyncSchedulerTest, ConfigWithStop) { |
| UseMockDelayProvider(); |
| EXPECT_CALL(*delay(), GetDelay(_)) |
| .WillRepeatedly(Return(TimeDelta::FromMilliseconds(1))); |
| SyncShareTimes times; |
| const ModelTypeSet model_types(BOOKMARKS); |
| |
| StartSyncScheduler(SyncScheduler::CONFIGURATION_MODE); |
| |
| // Make ConfigureSyncShare call scheduler->Stop(). It is not supposed to call |
| // retry_task or dereference configuration params. |
| EXPECT_CALL(*syncer(), ConfigureSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateConfigureFailed), |
| StopScheduler(scheduler()), |
| RecordSyncShare(×))); |
| |
| CallbackCounter ready_counter; |
| CallbackCounter retry_counter; |
| ConfigurationParams params( |
| GetUpdatesCallerInfo::RECONFIGURATION, |
| model_types, |
| TypesToRoutingInfo(model_types), |
| base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)), |
| base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter))); |
| scheduler()->ScheduleConfiguration(params); |
| PumpLoop(); |
| ASSERT_EQ(0, ready_counter.times_called()); |
| ASSERT_EQ(0, retry_counter.times_called()); |
| } |
| |
| // Issue a nudge when the config has failed. Make sure both the config and |
| // nudge are executed. |
| TEST_F(SyncSchedulerTest, NudgeWithConfigWithBackingOff) { |
| const ModelTypeSet model_types(BOOKMARKS); |
| UseMockDelayProvider(); |
| EXPECT_CALL(*delay(), GetDelay(_)) |
| .WillRepeatedly(Return(TimeDelta::FromMilliseconds(50))); |
| SyncShareTimes times; |
| |
| StartSyncScheduler(SyncScheduler::CONFIGURATION_MODE); |
| |
| // Request a configure and make sure it fails. |
| EXPECT_CALL(*syncer(), ConfigureSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateConfigureFailed), |
| RecordSyncShare(×))); |
| CallbackCounter ready_counter; |
| CallbackCounter retry_counter; |
| ConfigurationParams params( |
| GetUpdatesCallerInfo::RECONFIGURATION, |
| model_types, |
| TypesToRoutingInfo(model_types), |
| base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)), |
| base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter))); |
| scheduler()->ScheduleConfiguration(params); |
| RunLoop(); |
| ASSERT_EQ(0, ready_counter.times_called()); |
| ASSERT_EQ(1, retry_counter.times_called()); |
| Mock::VerifyAndClearExpectations(syncer()); |
| |
| // Ask for a nudge while dealing with repeated configure failure. |
| EXPECT_CALL(*syncer(), ConfigureSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateConfigureFailed), |
| RecordSyncShare(×))); |
| scheduler()->ScheduleLocalNudge(zero(), model_types, FROM_HERE); |
| RunLoop(); |
| // Note that we're not RunLoop()ing for the NUDGE we just scheduled, but |
| // for the first retry attempt from the config job (after |
| // waiting ~+/- 50ms). |
| Mock::VerifyAndClearExpectations(syncer()); |
| ASSERT_EQ(0, ready_counter.times_called()); |
| |
| // Let the next configure retry succeed. |
| EXPECT_CALL(*syncer(), ConfigureSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateConfigureSuccess), |
| RecordSyncShare(×))); |
| RunLoop(); |
| |
| // Now change the mode so nudge can execute. |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateNormalSuccess), |
| RecordSyncShare(×))); |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| PumpLoop(); |
| } |
| |
| // Test that nudges are coalesced. |
| TEST_F(SyncSchedulerTest, NudgeCoalescing) { |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| |
| SyncShareTimes times; |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateNormalSuccess), |
| RecordSyncShare(×))); |
| const ModelTypeSet types1(BOOKMARKS), types2(AUTOFILL), types3(THEMES); |
| TimeDelta delay = zero(); |
| TimeTicks optimal_time = TimeTicks::Now() + delay; |
| scheduler()->ScheduleLocalNudge(delay, types1, FROM_HERE); |
| scheduler()->ScheduleLocalNudge(zero(), types2, FROM_HERE); |
| RunLoop(); |
| |
| ASSERT_EQ(1U, times.size()); |
| EXPECT_GE(times[0], optimal_time); |
| |
| Mock::VerifyAndClearExpectations(syncer()); |
| |
| SyncShareTimes times2; |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateNormalSuccess), |
| RecordSyncShare(×2))); |
| scheduler()->ScheduleLocalNudge(zero(), types3, FROM_HERE); |
| RunLoop(); |
| } |
| |
| // Test that nudges are coalesced. |
| TEST_F(SyncSchedulerTest, NudgeCoalescingWithDifferentTimings) { |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| |
| SyncShareTimes times; |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateNormalSuccess), |
| RecordSyncShare(×))); |
| ModelTypeSet types1(BOOKMARKS), types2(AUTOFILL), types3; |
| |
| // Create a huge time delay. |
| TimeDelta delay = TimeDelta::FromDays(1); |
| |
| scheduler()->ScheduleLocalNudge(delay, types1, FROM_HERE); |
| scheduler()->ScheduleLocalNudge(zero(), types2, FROM_HERE); |
| |
| TimeTicks min_time = TimeTicks::Now(); |
| TimeTicks max_time = TimeTicks::Now() + delay; |
| |
| RunLoop(); |
| Mock::VerifyAndClearExpectations(syncer()); |
| |
| // Make sure the sync happened at the right time. |
| ASSERT_EQ(1U, times.size()); |
| EXPECT_GE(times[0], min_time); |
| EXPECT_LE(times[0], max_time); |
| } |
| |
| // Test nudge scheduling. |
| TEST_F(SyncSchedulerTest, NudgeWithStates) { |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| |
| SyncShareTimes times1; |
| ObjectIdInvalidationMap invalidations1 = |
| BuildInvalidationMap(BOOKMARKS, 10, "test"); |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateNormalSuccess), |
| RecordSyncShare(×1))) |
| .RetiresOnSaturation(); |
| scheduler()->ScheduleInvalidationNudge(zero(), invalidations1, FROM_HERE); |
| RunLoop(); |
| |
| Mock::VerifyAndClearExpectations(syncer()); |
| |
| // Make sure a second, later, nudge is unaffected by first (no coalescing). |
| SyncShareTimes times2; |
| ObjectIdInvalidationMap invalidations2 = |
| BuildInvalidationMap(AUTOFILL, 10, "test2"); |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateNormalSuccess), |
| RecordSyncShare(×2))); |
| scheduler()->ScheduleInvalidationNudge(zero(), invalidations2, FROM_HERE); |
| RunLoop(); |
| } |
| |
| // Test that polling works as expected. |
| TEST_F(SyncSchedulerTest, Polling) { |
| SyncShareTimes times; |
| TimeDelta poll_interval(TimeDelta::FromMilliseconds(30)); |
| EXPECT_CALL(*syncer(), PollSyncShare(_,_)).Times(AtLeast(kMinNumSamples)) |
| .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulatePollSuccess), |
| RecordSyncShareMultiple(×, kMinNumSamples))); |
| |
| scheduler()->OnReceivedLongPollIntervalUpdate(poll_interval); |
| |
| TimeTicks optimal_start = TimeTicks::Now() + poll_interval; |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| |
| // Run again to wait for polling. |
| RunLoop(); |
| |
| StopSyncScheduler(); |
| AnalyzePollRun(times, kMinNumSamples, optimal_start, poll_interval); |
| } |
| |
| // Test that the short poll interval is used. |
| TEST_F(SyncSchedulerTest, PollNotificationsDisabled) { |
| SyncShareTimes times; |
| TimeDelta poll_interval(TimeDelta::FromMilliseconds(30)); |
| EXPECT_CALL(*syncer(), PollSyncShare(_,_)).Times(AtLeast(kMinNumSamples)) |
| .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulatePollSuccess), |
| RecordSyncShareMultiple(×, kMinNumSamples))); |
| |
| scheduler()->OnReceivedShortPollIntervalUpdate(poll_interval); |
| scheduler()->SetNotificationsEnabled(false); |
| |
| TimeTicks optimal_start = TimeTicks::Now() + poll_interval; |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| |
| // Run again to wait for polling. |
| RunLoop(); |
| |
| StopSyncScheduler(); |
| AnalyzePollRun(times, kMinNumSamples, optimal_start, poll_interval); |
| } |
| |
| // Test that polling intervals are updated when needed. |
| TEST_F(SyncSchedulerTest, PollIntervalUpdate) { |
| SyncShareTimes times; |
| TimeDelta poll1(TimeDelta::FromMilliseconds(120)); |
| TimeDelta poll2(TimeDelta::FromMilliseconds(30)); |
| scheduler()->OnReceivedLongPollIntervalUpdate(poll1); |
| EXPECT_CALL(*syncer(), PollSyncShare(_,_)).Times(AtLeast(kMinNumSamples)) |
| .WillOnce(DoAll( |
| WithArgs<0,1>( |
| sessions::test_util::SimulatePollIntervalUpdate(poll2)), |
| Return(true))) |
| .WillRepeatedly( |
| DoAll(Invoke(sessions::test_util::SimulatePollSuccess), |
| WithArg<1>( |
| RecordSyncShareMultiple(×, kMinNumSamples)))); |
| |
| TimeTicks optimal_start = TimeTicks::Now() + poll1 + poll2; |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| |
| // Run again to wait for polling. |
| RunLoop(); |
| |
| StopSyncScheduler(); |
| AnalyzePollRun(times, kMinNumSamples, optimal_start, poll2); |
| } |
| |
| // Test that the sessions commit delay is updated when needed. |
| TEST_F(SyncSchedulerTest, SessionsCommitDelay) { |
| SyncShareTimes times; |
| TimeDelta delay1(TimeDelta::FromMilliseconds(120)); |
| TimeDelta delay2(TimeDelta::FromMilliseconds(30)); |
| scheduler()->OnReceivedSessionsCommitDelay(delay1); |
| |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce( |
| DoAll( |
| WithArgs<0,1,2>( |
| sessions::test_util::SimulateSessionsCommitDelayUpdate( |
| delay2)), |
| Invoke(sessions::test_util::SimulateNormalSuccess), |
| QuitLoopNowAction())); |
| |
| EXPECT_EQ(delay1, scheduler()->GetSessionsCommitDelay()); |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| |
| EXPECT_EQ(delay1, scheduler()->GetSessionsCommitDelay()); |
| const ModelTypeSet model_types(BOOKMARKS); |
| scheduler()->ScheduleLocalNudge(zero(), model_types, FROM_HERE); |
| RunLoop(); |
| |
| EXPECT_EQ(delay2, scheduler()->GetSessionsCommitDelay()); |
| StopSyncScheduler(); |
| } |
| |
| // Test that no syncing occurs when throttled. |
| TEST_F(SyncSchedulerTest, ThrottlingDoesThrottle) { |
| const ModelTypeSet types(BOOKMARKS); |
| TimeDelta poll(TimeDelta::FromMilliseconds(5)); |
| TimeDelta throttle(TimeDelta::FromMinutes(10)); |
| scheduler()->OnReceivedLongPollIntervalUpdate(poll); |
| |
| EXPECT_CALL(*syncer(), ConfigureSyncShare(_,_,_)) |
| .WillOnce(DoAll( |
| WithArg<2>(sessions::test_util::SimulateThrottled(throttle)), |
| Return(true))) |
| .WillRepeatedly(AddFailureAndQuitLoopNow()); |
| |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| |
| scheduler()->ScheduleLocalNudge( |
| TimeDelta::FromMicroseconds(1), types, FROM_HERE); |
| PumpLoop(); |
| |
| StartSyncScheduler(SyncScheduler::CONFIGURATION_MODE); |
| |
| CallbackCounter ready_counter; |
| CallbackCounter retry_counter; |
| ConfigurationParams params( |
| GetUpdatesCallerInfo::RECONFIGURATION, |
| types, |
| TypesToRoutingInfo(types), |
| base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)), |
| base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter))); |
| scheduler()->ScheduleConfiguration(params); |
| PumpLoop(); |
| ASSERT_EQ(0, ready_counter.times_called()); |
| ASSERT_EQ(1, retry_counter.times_called()); |
| |
| } |
| |
| TEST_F(SyncSchedulerTest, ThrottlingExpiresFromPoll) { |
| SyncShareTimes times; |
| TimeDelta poll(TimeDelta::FromMilliseconds(15)); |
| TimeDelta throttle1(TimeDelta::FromMilliseconds(150)); |
| scheduler()->OnReceivedLongPollIntervalUpdate(poll); |
| |
| ::testing::InSequence seq; |
| EXPECT_CALL(*syncer(), PollSyncShare(_,_)) |
| .WillOnce(DoAll( |
| WithArg<1>(sessions::test_util::SimulateThrottled(throttle1)), |
| Return(true))) |
| .RetiresOnSaturation(); |
| EXPECT_CALL(*syncer(), PollSyncShare(_,_)) |
| .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulatePollSuccess), |
| RecordSyncShareMultiple(×, kMinNumSamples))); |
| |
| TimeTicks optimal_start = TimeTicks::Now() + poll + throttle1; |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| |
| // Run again to wait for polling. |
| RunLoop(); |
| |
| StopSyncScheduler(); |
| AnalyzePollRun(times, kMinNumSamples, optimal_start, poll); |
| } |
| |
| TEST_F(SyncSchedulerTest, ThrottlingExpiresFromNudge) { |
| SyncShareTimes times; |
| TimeDelta poll(TimeDelta::FromDays(1)); |
| TimeDelta throttle1(TimeDelta::FromMilliseconds(150)); |
| scheduler()->OnReceivedLongPollIntervalUpdate(poll); |
| |
| ::testing::InSequence seq; |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll( |
| WithArg<2>(sessions::test_util::SimulateThrottled(throttle1)), |
| Return(true))) |
| .RetiresOnSaturation(); |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateNormalSuccess), |
| QuitLoopNowAction())); |
| |
| const ModelTypeSet types(BOOKMARKS); |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| scheduler()->ScheduleLocalNudge(zero(), types, FROM_HERE); |
| |
| PumpLoop(); // To get PerformDelayedNudge called. |
| PumpLoop(); // To get TrySyncSessionJob called |
| EXPECT_TRUE(scheduler()->IsCurrentlyThrottled()); |
| RunLoop(); |
| EXPECT_FALSE(scheduler()->IsCurrentlyThrottled()); |
| |
| StopSyncScheduler(); |
| } |
| |
| TEST_F(SyncSchedulerTest, ThrottlingExpiresFromConfigure) { |
| SyncShareTimes times; |
| TimeDelta poll(TimeDelta::FromDays(1)); |
| TimeDelta throttle1(TimeDelta::FromMilliseconds(150)); |
| scheduler()->OnReceivedLongPollIntervalUpdate(poll); |
| |
| ::testing::InSequence seq; |
| EXPECT_CALL(*syncer(), ConfigureSyncShare(_,_,_)) |
| .WillOnce(DoAll( |
| WithArg<2>(sessions::test_util::SimulateThrottled(throttle1)), |
| Return(true))) |
| .RetiresOnSaturation(); |
| EXPECT_CALL(*syncer(), ConfigureSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateConfigureSuccess), |
| QuitLoopNowAction())); |
| |
| const ModelTypeSet types(BOOKMARKS); |
| StartSyncScheduler(SyncScheduler::CONFIGURATION_MODE); |
| |
| CallbackCounter ready_counter; |
| CallbackCounter retry_counter; |
| ConfigurationParams params( |
| GetUpdatesCallerInfo::RECONFIGURATION, |
| types, |
| TypesToRoutingInfo(types), |
| base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)), |
| base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter))); |
| scheduler()->ScheduleConfiguration(params); |
| PumpLoop(); |
| EXPECT_EQ(0, ready_counter.times_called()); |
| EXPECT_EQ(1, retry_counter.times_called()); |
| EXPECT_TRUE(scheduler()->IsCurrentlyThrottled()); |
| |
| RunLoop(); |
| EXPECT_FALSE(scheduler()->IsCurrentlyThrottled()); |
| |
| StopSyncScheduler(); |
| } |
| |
| TEST_F(SyncSchedulerTest, TypeThrottlingBlocksNudge) { |
| UseMockDelayProvider(); |
| EXPECT_CALL(*delay(), GetDelay(_)) |
| .WillRepeatedly(Return(zero())); |
| |
| TimeDelta poll(TimeDelta::FromDays(1)); |
| TimeDelta throttle1(TimeDelta::FromSeconds(60)); |
| scheduler()->OnReceivedLongPollIntervalUpdate(poll); |
| |
| const ModelTypeSet types(BOOKMARKS); |
| |
| ::testing::InSequence seq; |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll( |
| WithArg<2>( |
| sessions::test_util::SimulateTypesThrottled(types, throttle1)), |
| Return(true))) |
| .RetiresOnSaturation(); |
| |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| scheduler()->ScheduleLocalNudge(zero(), types, FROM_HERE); |
| PumpLoop(); // To get PerformDelayedNudge called. |
| PumpLoop(); // To get TrySyncSessionJob called |
| EXPECT_TRUE(GetThrottledTypes().HasAll(types)); |
| |
| // This won't cause a sync cycle because the types are throttled. |
| scheduler()->ScheduleLocalNudge(zero(), types, FROM_HERE); |
| PumpLoop(); |
| |
| StopSyncScheduler(); |
| } |
| |
| TEST_F(SyncSchedulerTest, TypeThrottlingDoesBlockOtherSources) { |
| UseMockDelayProvider(); |
| EXPECT_CALL(*delay(), GetDelay(_)) |
| .WillRepeatedly(Return(zero())); |
| |
| SyncShareTimes times; |
| TimeDelta poll(TimeDelta::FromDays(1)); |
| TimeDelta throttle1(TimeDelta::FromSeconds(60)); |
| scheduler()->OnReceivedLongPollIntervalUpdate(poll); |
| |
| const ModelTypeSet throttled_types(BOOKMARKS); |
| const ModelTypeSet unthrottled_types(PREFERENCES); |
| |
| ::testing::InSequence seq; |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll( |
| WithArg<2>( |
| sessions::test_util::SimulateTypesThrottled( |
| throttled_types, throttle1)), |
| Return(true))) |
| .RetiresOnSaturation(); |
| |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| scheduler()->ScheduleLocalNudge(zero(), throttled_types, FROM_HERE); |
| PumpLoop(); // To get PerformDelayedNudge called. |
| PumpLoop(); // To get TrySyncSessionJob called |
| EXPECT_TRUE(GetThrottledTypes().HasAll(throttled_types)); |
| |
| // Ignore invalidations for throttled types. |
| ObjectIdInvalidationMap invalidations = |
| BuildInvalidationMap(BOOKMARKS, 10, "test"); |
| scheduler()->ScheduleInvalidationNudge(zero(), invalidations, FROM_HERE); |
| PumpLoop(); |
| |
| // Ignore refresh requests for throttled types. |
| scheduler()->ScheduleLocalRefreshRequest(zero(), throttled_types, FROM_HERE); |
| PumpLoop(); |
| |
| Mock::VerifyAndClearExpectations(syncer()); |
| |
| // Local nudges for non-throttled types will trigger a sync. |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateNormalSuccess), |
| RecordSyncShare(×))); |
| scheduler()->ScheduleLocalNudge(zero(), unthrottled_types, FROM_HERE); |
| RunLoop(); |
| Mock::VerifyAndClearExpectations(syncer()); |
| |
| StopSyncScheduler(); |
| } |
| |
| // Test nudges / polls don't run in config mode and config tasks do. |
| TEST_F(SyncSchedulerTest, ConfigurationMode) { |
| TimeDelta poll(TimeDelta::FromMilliseconds(15)); |
| SyncShareTimes times; |
| scheduler()->OnReceivedLongPollIntervalUpdate(poll); |
| |
| StartSyncScheduler(SyncScheduler::CONFIGURATION_MODE); |
| |
| const ModelTypeSet nudge_types(AUTOFILL); |
| scheduler()->ScheduleLocalNudge(zero(), nudge_types, FROM_HERE); |
| scheduler()->ScheduleLocalNudge(zero(), nudge_types, FROM_HERE); |
| |
| const ModelTypeSet config_types(BOOKMARKS); |
| |
| EXPECT_CALL(*syncer(), ConfigureSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateConfigureSuccess), |
| RecordSyncShare(×))) |
| .RetiresOnSaturation(); |
| CallbackCounter ready_counter; |
| CallbackCounter retry_counter; |
| ConfigurationParams params( |
| GetUpdatesCallerInfo::RECONFIGURATION, |
| config_types, |
| TypesToRoutingInfo(config_types), |
| base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)), |
| base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter))); |
| scheduler()->ScheduleConfiguration(params); |
| RunLoop(); |
| ASSERT_EQ(1, ready_counter.times_called()); |
| ASSERT_EQ(0, retry_counter.times_called()); |
| |
| Mock::VerifyAndClearExpectations(syncer()); |
| |
| // Switch to NORMAL_MODE to ensure NUDGES were properly saved and run. |
| scheduler()->OnReceivedLongPollIntervalUpdate(TimeDelta::FromDays(1)); |
| SyncShareTimes times2; |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateNormalSuccess), |
| RecordSyncShare(×2))); |
| |
| // TODO(tim): Figure out how to remove this dangerous need to reset |
| // routing info between mode switches. |
| context()->set_routing_info(routing_info()); |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| |
| RunLoop(); |
| Mock::VerifyAndClearExpectations(syncer()); |
| } |
| |
| class BackoffTriggersSyncSchedulerTest : public SyncSchedulerTest { |
| virtual void SetUp() { |
| SyncSchedulerTest::SetUp(); |
| UseMockDelayProvider(); |
| EXPECT_CALL(*delay(), GetDelay(_)) |
| .WillRepeatedly(Return(TimeDelta::FromMilliseconds(1))); |
| } |
| |
| virtual void TearDown() { |
| StopSyncScheduler(); |
| SyncSchedulerTest::TearDown(); |
| } |
| }; |
| |
| // Have the sycner fail during commit. Expect that the scheduler enters |
| // backoff. |
| TEST_F(BackoffTriggersSyncSchedulerTest, FailCommitOnce) { |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateCommitFailed), |
| QuitLoopNowAction())); |
| EXPECT_TRUE(RunAndGetBackoff()); |
| } |
| |
| // Have the syncer fail during download updates and succeed on the first |
| // retry. Expect that this clears the backoff state. |
| TEST_F(BackoffTriggersSyncSchedulerTest, FailDownloadOnceThenSucceed) { |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll( |
| Invoke(sessions::test_util::SimulateDownloadUpdatesFailed), |
| Return(true))) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateNormalSuccess), |
| QuitLoopNowAction())); |
| EXPECT_FALSE(RunAndGetBackoff()); |
| } |
| |
| // Have the syncer fail during commit and succeed on the first retry. Expect |
| // that this clears the backoff state. |
| TEST_F(BackoffTriggersSyncSchedulerTest, FailCommitOnceThenSucceed) { |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll( |
| Invoke(sessions::test_util::SimulateCommitFailed), |
| Return(true))) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateNormalSuccess), |
| QuitLoopNowAction())); |
| EXPECT_FALSE(RunAndGetBackoff()); |
| } |
| |
| // Have the syncer fail to download updates and fail again on the retry. |
| // Expect this will leave the scheduler in backoff. |
| TEST_F(BackoffTriggersSyncSchedulerTest, FailDownloadTwice) { |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll( |
| Invoke(sessions::test_util::SimulateDownloadUpdatesFailed), |
| Return(true))) |
| .WillRepeatedly(DoAll( |
| Invoke(sessions::test_util::SimulateDownloadUpdatesFailed), |
| QuitLoopNowAction())); |
| EXPECT_TRUE(RunAndGetBackoff()); |
| } |
| |
| // Have the syncer fail to get the encryption key yet succeed in downloading |
| // updates. Expect this will leave the scheduler in backoff. |
| TEST_F(BackoffTriggersSyncSchedulerTest, FailGetEncryptionKey) { |
| EXPECT_CALL(*syncer(), ConfigureSyncShare(_,_,_)) |
| .WillOnce(DoAll( |
| Invoke(sessions::test_util::SimulateGetEncryptionKeyFailed), |
| Return(true))) |
| .WillRepeatedly(DoAll( |
| Invoke(sessions::test_util::SimulateGetEncryptionKeyFailed), |
| QuitLoopNowAction())); |
| StartSyncScheduler(SyncScheduler::CONFIGURATION_MODE); |
| |
| ModelTypeSet types(BOOKMARKS); |
| CallbackCounter ready_counter; |
| CallbackCounter retry_counter; |
| ConfigurationParams params( |
| GetUpdatesCallerInfo::RECONFIGURATION, |
| types, |
| TypesToRoutingInfo(types), |
| base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)), |
| base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter))); |
| scheduler()->ScheduleConfiguration(params); |
| RunLoop(); |
| |
| EXPECT_TRUE(scheduler()->IsBackingOff()); |
| } |
| |
| // Test that no polls or extraneous nudges occur when in backoff. |
| TEST_F(SyncSchedulerTest, BackoffDropsJobs) { |
| SyncShareTimes times; |
| TimeDelta poll(TimeDelta::FromMilliseconds(5)); |
| const ModelTypeSet types(BOOKMARKS); |
| scheduler()->OnReceivedLongPollIntervalUpdate(poll); |
| UseMockDelayProvider(); |
| |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateCommitFailed), |
| RecordSyncShareMultiple(×, 1U))); |
| EXPECT_CALL(*delay(), GetDelay(_)). |
| WillRepeatedly(Return(TimeDelta::FromDays(1))); |
| |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| |
| // This nudge should fail and put us into backoff. Thanks to our mock |
| // GetDelay() setup above, this will be a long backoff. |
| scheduler()->ScheduleLocalNudge(zero(), types, FROM_HERE); |
| RunLoop(); |
| |
| // From this point forward, no SyncShare functions should be invoked. |
| Mock::VerifyAndClearExpectations(syncer()); |
| |
| // Wait a while (10x poll interval) so a few poll jobs will be attempted. |
| PumpLoopFor(poll * 10); |
| |
| // Try (and fail) to schedule a nudge. |
| scheduler()->ScheduleLocalNudge( |
| base::TimeDelta::FromMilliseconds(1), |
| types, |
| FROM_HERE); |
| |
| Mock::VerifyAndClearExpectations(syncer()); |
| Mock::VerifyAndClearExpectations(delay()); |
| |
| EXPECT_CALL(*delay(), GetDelay(_)).Times(0); |
| |
| StartSyncScheduler(SyncScheduler::CONFIGURATION_MODE); |
| |
| CallbackCounter ready_counter; |
| CallbackCounter retry_counter; |
| ConfigurationParams params( |
| GetUpdatesCallerInfo::RECONFIGURATION, |
| types, |
| TypesToRoutingInfo(types), |
| base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)), |
| base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter))); |
| scheduler()->ScheduleConfiguration(params); |
| PumpLoop(); |
| ASSERT_EQ(0, ready_counter.times_called()); |
| ASSERT_EQ(1, retry_counter.times_called()); |
| |
| } |
| |
| // Test that backoff is shaping traffic properly with consecutive errors. |
| TEST_F(SyncSchedulerTest, BackoffElevation) { |
| SyncShareTimes times; |
| UseMockDelayProvider(); |
| |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)).Times(kMinNumSamples) |
| .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateCommitFailed), |
| RecordSyncShareMultiple(×, kMinNumSamples))); |
| |
| const TimeDelta first = TimeDelta::FromSeconds(kInitialBackoffRetrySeconds); |
| const TimeDelta second = TimeDelta::FromMilliseconds(2); |
| const TimeDelta third = TimeDelta::FromMilliseconds(3); |
| const TimeDelta fourth = TimeDelta::FromMilliseconds(4); |
| const TimeDelta fifth = TimeDelta::FromMilliseconds(5); |
| const TimeDelta sixth = TimeDelta::FromDays(1); |
| |
| EXPECT_CALL(*delay(), GetDelay(first)).WillOnce(Return(second)) |
| .RetiresOnSaturation(); |
| EXPECT_CALL(*delay(), GetDelay(second)).WillOnce(Return(third)) |
| .RetiresOnSaturation(); |
| EXPECT_CALL(*delay(), GetDelay(third)).WillOnce(Return(fourth)) |
| .RetiresOnSaturation(); |
| EXPECT_CALL(*delay(), GetDelay(fourth)).WillOnce(Return(fifth)) |
| .RetiresOnSaturation(); |
| EXPECT_CALL(*delay(), GetDelay(fifth)).WillOnce(Return(sixth)); |
| |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| |
| // Run again with a nudge. |
| scheduler()->ScheduleLocalNudge(zero(), ModelTypeSet(BOOKMARKS), FROM_HERE); |
| RunLoop(); |
| |
| ASSERT_EQ(kMinNumSamples, times.size()); |
| EXPECT_GE(times[1] - times[0], second); |
| EXPECT_GE(times[2] - times[1], third); |
| EXPECT_GE(times[3] - times[2], fourth); |
| EXPECT_GE(times[4] - times[3], fifth); |
| } |
| |
| // Test that things go back to normal once a retry makes forward progress. |
| TEST_F(SyncSchedulerTest, BackoffRelief) { |
| SyncShareTimes times; |
| const TimeDelta poll(TimeDelta::FromMilliseconds(10)); |
| scheduler()->OnReceivedLongPollIntervalUpdate(poll); |
| UseMockDelayProvider(); |
| |
| const TimeDelta backoff = TimeDelta::FromMilliseconds(5); |
| EXPECT_CALL(*delay(), GetDelay(_)).WillOnce(Return(backoff)); |
| |
| // Optimal start for the post-backoff poll party. |
| TimeTicks optimal_start = TimeTicks::Now(); |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| |
| // Kick off the test with a failed nudge. |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateCommitFailed), |
| RecordSyncShare(×))); |
| scheduler()->ScheduleLocalNudge(zero(), ModelTypeSet(BOOKMARKS), FROM_HERE); |
| RunLoop(); |
| Mock::VerifyAndClearExpectations(syncer()); |
| TimeTicks optimal_job_time = optimal_start; |
| ASSERT_EQ(1U, times.size()); |
| EXPECT_GE(times[0], optimal_job_time); |
| |
| // The retry succeeds. |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll( |
| Invoke(sessions::test_util::SimulateNormalSuccess), |
| RecordSyncShare(×))); |
| RunLoop(); |
| Mock::VerifyAndClearExpectations(syncer()); |
| optimal_job_time = optimal_job_time + backoff; |
| ASSERT_EQ(2U, times.size()); |
| EXPECT_GE(times[1], optimal_job_time); |
| |
| // Now let the Poll timer do its thing. |
| EXPECT_CALL(*syncer(), PollSyncShare(_,_)) |
| .WillRepeatedly(DoAll( |
| Invoke(sessions::test_util::SimulatePollSuccess), |
| RecordSyncShareMultiple(×, kMinNumSamples))); |
| RunLoop(); |
| Mock::VerifyAndClearExpectations(syncer()); |
| ASSERT_EQ(kMinNumSamples, times.size()); |
| for (size_t i = 2; i < times.size(); i++) { |
| optimal_job_time = optimal_job_time + poll; |
| SCOPED_TRACE(testing::Message() << "SyncShare # (" << i << ")"); |
| EXPECT_GE(times[i], optimal_job_time); |
| } |
| |
| StopSyncScheduler(); |
| } |
| |
| // Test that poll failures are ignored. They should have no effect on |
| // subsequent poll attempts, nor should they trigger a backoff/retry. |
| TEST_F(SyncSchedulerTest, TransientPollFailure) { |
| SyncShareTimes times; |
| const TimeDelta poll_interval(TimeDelta::FromMilliseconds(1)); |
| scheduler()->OnReceivedLongPollIntervalUpdate(poll_interval); |
| UseMockDelayProvider(); // Will cause test failure if backoff is initiated. |
| |
| EXPECT_CALL(*syncer(), PollSyncShare(_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulatePollFailed), |
| RecordSyncShare(×))) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulatePollSuccess), |
| RecordSyncShare(×))); |
| |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| |
| // Run the unsucessful poll. The failed poll should not trigger backoff. |
| RunLoop(); |
| EXPECT_FALSE(scheduler()->IsBackingOff()); |
| |
| // Run the successful poll. |
| RunLoop(); |
| EXPECT_FALSE(scheduler()->IsBackingOff()); |
| } |
| |
| // Test that starting the syncer thread without a valid connection doesn't |
| // break things when a connection is detected. |
| TEST_F(SyncSchedulerTest, StartWhenNotConnected) { |
| connection()->SetServerNotReachable(); |
| connection()->UpdateConnectionStatus(); |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateConnectionFailure), |
| Return(true))) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateNormalSuccess), |
| Return(true))); |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| |
| scheduler()->ScheduleLocalNudge(zero(), ModelTypeSet(BOOKMARKS), FROM_HERE); |
| // Should save the nudge for until after the server is reachable. |
| base::MessageLoop::current()->RunUntilIdle(); |
| |
| scheduler()->OnConnectionStatusChange(); |
| connection()->SetServerReachable(); |
| connection()->UpdateConnectionStatus(); |
| base::MessageLoop::current()->RunUntilIdle(); |
| } |
| |
| TEST_F(SyncSchedulerTest, ServerConnectionChangeDuringBackoff) { |
| UseMockDelayProvider(); |
| EXPECT_CALL(*delay(), GetDelay(_)) |
| .WillRepeatedly(Return(TimeDelta::FromMilliseconds(0))); |
| |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| connection()->SetServerNotReachable(); |
| connection()->UpdateConnectionStatus(); |
| |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateConnectionFailure), |
| Return(true))) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateNormalSuccess), |
| Return(true))); |
| |
| scheduler()->ScheduleLocalNudge(zero(), ModelTypeSet(BOOKMARKS), FROM_HERE); |
| PumpLoop(); // To get PerformDelayedNudge called. |
| PumpLoop(); // Run the nudge, that will fail and schedule a quick retry. |
| ASSERT_TRUE(scheduler()->IsBackingOff()); |
| |
| // Before we run the scheduled canary, trigger a server connection change. |
| scheduler()->OnConnectionStatusChange(); |
| connection()->SetServerReachable(); |
| connection()->UpdateConnectionStatus(); |
| base::MessageLoop::current()->RunUntilIdle(); |
| } |
| |
| // This was supposed to test the scenario where we receive a nudge while a |
| // connection change canary is scheduled, but has not run yet. Since we've made |
| // the connection change canary synchronous, this is no longer possible. |
| TEST_F(SyncSchedulerTest, ConnectionChangeCanaryPreemptedByNudge) { |
| UseMockDelayProvider(); |
| EXPECT_CALL(*delay(), GetDelay(_)) |
| .WillRepeatedly(Return(TimeDelta::FromMilliseconds(0))); |
| |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| connection()->SetServerNotReachable(); |
| connection()->UpdateConnectionStatus(); |
| |
| EXPECT_CALL(*syncer(), NormalSyncShare(_,_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateConnectionFailure), |
| Return(true))) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateNormalSuccess), |
| Return(true))) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulateNormalSuccess), |
| QuitLoopNowAction())); |
| |
| scheduler()->ScheduleLocalNudge(zero(), ModelTypeSet(BOOKMARKS), FROM_HERE); |
| |
| PumpLoop(); // To get PerformDelayedNudge called. |
| PumpLoop(); // Run the nudge, that will fail and schedule a quick retry. |
| ASSERT_TRUE(scheduler()->IsBackingOff()); |
| |
| // Before we run the scheduled canary, trigger a server connection change. |
| scheduler()->OnConnectionStatusChange(); |
| PumpLoop(); |
| connection()->SetServerReachable(); |
| connection()->UpdateConnectionStatus(); |
| scheduler()->ScheduleLocalNudge(zero(), ModelTypeSet(BOOKMARKS), FROM_HERE); |
| base::MessageLoop::current()->RunUntilIdle(); |
| } |
| |
| // Tests that we don't crash trying to run two canaries at once if we receive |
| // extra connection status change notifications. See crbug.com/190085. |
| TEST_F(SyncSchedulerTest, DoubleCanaryInConfigure) { |
| EXPECT_CALL(*syncer(), ConfigureSyncShare(_,_,_)) |
| .WillRepeatedly(DoAll( |
| Invoke(sessions::test_util::SimulateConfigureConnectionFailure), |
| Return(true))); |
| StartSyncScheduler(SyncScheduler::CONFIGURATION_MODE); |
| connection()->SetServerNotReachable(); |
| connection()->UpdateConnectionStatus(); |
| |
| ModelTypeSet model_types(BOOKMARKS); |
| CallbackCounter ready_counter; |
| CallbackCounter retry_counter; |
| ConfigurationParams params( |
| GetUpdatesCallerInfo::RECONFIGURATION, |
| model_types, |
| TypesToRoutingInfo(model_types), |
| base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)), |
| base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter))); |
| scheduler()->ScheduleConfiguration(params); |
| |
| scheduler()->OnConnectionStatusChange(); |
| scheduler()->OnConnectionStatusChange(); |
| |
| PumpLoop(); // Run the nudge, that will fail and schedule a quick retry. |
| } |
| |
| TEST_F(SyncSchedulerTest, PollFromCanaryAfterAuthError) { |
| SyncShareTimes times; |
| TimeDelta poll(TimeDelta::FromMilliseconds(15)); |
| scheduler()->OnReceivedLongPollIntervalUpdate(poll); |
| |
| ::testing::InSequence seq; |
| EXPECT_CALL(*syncer(), PollSyncShare(_,_)) |
| .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulatePollSuccess), |
| RecordSyncShareMultiple(×, kMinNumSamples))); |
| |
| connection()->SetServerStatus(HttpResponse::SYNC_AUTH_ERROR); |
| StartSyncScheduler(SyncScheduler::NORMAL_MODE); |
| |
| // Run to wait for polling. |
| RunLoop(); |
| |
| // Normally OnCredentialsUpdated calls TryCanaryJob that doesn't run Poll, |
| // but after poll finished with auth error from poll timer it should retry |
| // poll once more |
| EXPECT_CALL(*syncer(), PollSyncShare(_,_)) |
| .WillOnce(DoAll(Invoke(sessions::test_util::SimulatePollSuccess), |
| RecordSyncShare(×))); |
| scheduler()->OnCredentialsUpdated(); |
| connection()->SetServerStatus(HttpResponse::SERVER_CONNECTION_OK); |
| RunLoop(); |
| StopSyncScheduler(); |
| } |
| |
| } // namespace syncer |