|  | // Copyright 2012 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "components/sync/engine/sync_scheduler_impl.h" | 
|  |  | 
|  | #include <stddef.h> | 
|  | #include <stdint.h> | 
|  |  | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/functional/bind.h" | 
|  | #include "base/location.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/memory/raw_ptr.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/task/sequenced_task_runner.h" | 
|  | #include "base/test/mock_callback.h" | 
|  | #include "base/test/scoped_feature_list.h" | 
|  | #include "base/test/task_environment.h" | 
|  | #include "base/test/test_timeouts.h" | 
|  | #include "base/time/time.h" | 
|  | #include "components/sync/base/extensions_activity.h" | 
|  | #include "components/sync/base/features.h" | 
|  | #include "components/sync/engine/backoff_delay_provider.h" | 
|  | #include "components/sync/engine/cancelation_signal.h" | 
|  | #include "components/sync/engine/data_type_activation_response.h" | 
|  | #include "components/sync/test/fake_model_type_processor.h" | 
|  | #include "components/sync/test/fake_sync_encryption_handler.h" | 
|  | #include "components/sync/test/mock_connection_manager.h" | 
|  | #include "components/sync/test/mock_invalidation.h" | 
|  | #include "components/sync/test/mock_nudge_handler.h" | 
|  | #include "components/sync/test/model_type_test_util.h" | 
|  | #include "net/base/net_errors.h" | 
|  | #include "net/http/http_status_code.h" | 
|  | #include "testing/gmock/include/gmock/gmock.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | using base::TimeTicks; | 
|  | using testing::_; | 
|  | using testing::AtLeast; | 
|  | using testing::DoAll; | 
|  | using testing::Eq; | 
|  | using testing::Ge; | 
|  | using testing::Gt; | 
|  | using testing::Invoke; | 
|  | using testing::Lt; | 
|  | using testing::Mock; | 
|  | using testing::Return; | 
|  | using testing::SaveArg; | 
|  | using testing::WithArg; | 
|  | using testing::WithArgs; | 
|  | using testing::WithoutArgs; | 
|  |  | 
|  | namespace syncer { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | void SimulatePollSuccess(ModelTypeSet requested_types, SyncCycle* cycle) { | 
|  | cycle->mutable_status_controller()->set_last_download_updates_result( | 
|  | SyncerError(SyncerError::SYNCER_OK)); | 
|  | } | 
|  |  | 
|  | void SimulatePollFailed(ModelTypeSet requested_types, SyncCycle* cycle) { | 
|  | cycle->mutable_status_controller()->set_last_download_updates_result( | 
|  | SyncerError(SyncerError::SERVER_RETURN_TRANSIENT_ERROR)); | 
|  | } | 
|  |  | 
|  | ACTION_P(SimulateThrottled, throttle) { | 
|  | SyncCycle* cycle = arg0; | 
|  | cycle->mutable_status_controller()->set_last_download_updates_result( | 
|  | SyncerError(SyncerError::SERVER_RETURN_THROTTLED)); | 
|  | cycle->delegate()->OnThrottled(throttle); | 
|  | } | 
|  |  | 
|  | ACTION_P2(SimulateTypeThrottled, type, throttle) { | 
|  | SyncCycle* cycle = arg0; | 
|  | cycle->mutable_status_controller()->set_commit_result( | 
|  | SyncerError(SyncerError::SYNCER_OK)); | 
|  | cycle->delegate()->OnTypesThrottled(ModelTypeSet(type), throttle); | 
|  | } | 
|  |  | 
|  | ACTION_P(SimulatePartialFailure, type) { | 
|  | SyncCycle* cycle = arg0; | 
|  | cycle->mutable_status_controller()->set_commit_result( | 
|  | SyncerError(SyncerError::SYNCER_OK)); | 
|  | cycle->delegate()->OnTypesBackedOff(ModelTypeSet(type)); | 
|  | } | 
|  |  | 
|  | ACTION_P(SimulatePollIntervalUpdate, new_poll) { | 
|  | const ModelTypeSet requested_types = arg0; | 
|  | SyncCycle* cycle = arg1; | 
|  | SimulatePollSuccess(requested_types, cycle); | 
|  | cycle->delegate()->OnReceivedPollIntervalUpdate(new_poll); | 
|  | } | 
|  |  | 
|  | ACTION_P(SimulateGuRetryDelayCommand, delay) { | 
|  | SyncCycle* cycle = arg0; | 
|  | cycle->mutable_status_controller()->set_last_download_updates_result( | 
|  | SyncerError(SyncerError::SYNCER_OK)); | 
|  | cycle->delegate()->OnReceivedGuRetryDelay(delay); | 
|  | } | 
|  |  | 
|  | void SimulateGetEncryptionKeyFailed(ModelTypeSet requsted_types, | 
|  | sync_pb::SyncEnums::GetUpdatesOrigin origin, | 
|  | SyncCycle* cycle) { | 
|  | cycle->mutable_status_controller()->set_last_get_key_result( | 
|  | SyncerError(SyncerError::SERVER_RESPONSE_VALIDATION_FAILED)); | 
|  | cycle->mutable_status_controller()->set_last_download_updates_result( | 
|  | SyncerError(SyncerError::SYNCER_OK)); | 
|  | } | 
|  |  | 
|  | void SimulateConfigureSuccess(ModelTypeSet requsted_types, | 
|  | sync_pb::SyncEnums::GetUpdatesOrigin origin, | 
|  | SyncCycle* cycle) { | 
|  | cycle->mutable_status_controller()->set_last_get_key_result( | 
|  | SyncerError(SyncerError::SYNCER_OK)); | 
|  | cycle->mutable_status_controller()->set_last_download_updates_result( | 
|  | SyncerError(SyncerError::SYNCER_OK)); | 
|  | } | 
|  |  | 
|  | void SimulateConfigureFailed(ModelTypeSet requsted_types, | 
|  | sync_pb::SyncEnums::GetUpdatesOrigin origin, | 
|  | SyncCycle* cycle) { | 
|  | cycle->mutable_status_controller()->set_last_get_key_result( | 
|  | SyncerError(SyncerError::SYNCER_OK)); | 
|  | cycle->mutable_status_controller()->set_last_download_updates_result( | 
|  | SyncerError(SyncerError::SERVER_RETURN_TRANSIENT_ERROR)); | 
|  | } | 
|  |  | 
|  | void SimulateConfigureConnectionFailure( | 
|  | ModelTypeSet requsted_types, | 
|  | sync_pb::SyncEnums::GetUpdatesOrigin origin, | 
|  | SyncCycle* cycle) { | 
|  | cycle->mutable_status_controller()->set_last_get_key_result( | 
|  | SyncerError(SyncerError::SYNCER_OK)); | 
|  | cycle->mutable_status_controller()->set_last_download_updates_result( | 
|  | SyncerError::NetworkConnectionUnavailable(net::ERR_FAILED)); | 
|  | } | 
|  |  | 
|  | void SimulateNormalSuccess(ModelTypeSet requested_types, | 
|  | NudgeTracker* nudge_tracker, | 
|  | SyncCycle* cycle) { | 
|  | cycle->mutable_status_controller()->set_commit_result( | 
|  | SyncerError(SyncerError::SYNCER_OK)); | 
|  | cycle->mutable_status_controller()->set_last_download_updates_result( | 
|  | SyncerError(SyncerError::SYNCER_OK)); | 
|  | } | 
|  |  | 
|  | void SimulateDownloadUpdatesFailed(ModelTypeSet requested_types, | 
|  | NudgeTracker* nudge_tracker, | 
|  | SyncCycle* cycle) { | 
|  | cycle->mutable_status_controller()->set_last_download_updates_result( | 
|  | SyncerError(SyncerError::SERVER_RETURN_TRANSIENT_ERROR)); | 
|  | } | 
|  |  | 
|  | void SimulateCommitFailed(ModelTypeSet requested_types, | 
|  | NudgeTracker* nudge_tracker, | 
|  | SyncCycle* cycle) { | 
|  | cycle->mutable_status_controller()->set_last_get_key_result( | 
|  | SyncerError(SyncerError::SYNCER_OK)); | 
|  | cycle->mutable_status_controller()->set_last_download_updates_result( | 
|  | SyncerError(SyncerError::SYNCER_OK)); | 
|  | cycle->mutable_status_controller()->set_commit_result( | 
|  | SyncerError(SyncerError::SERVER_RETURN_TRANSIENT_ERROR)); | 
|  | } | 
|  |  | 
|  | void SimulateConnectionFailure(ModelTypeSet requested_types, | 
|  | NudgeTracker* nudge_tracker, | 
|  | SyncCycle* cycle) { | 
|  | cycle->mutable_status_controller()->set_last_download_updates_result( | 
|  | SyncerError::NetworkConnectionUnavailable(net::ERR_FAILED)); | 
|  | } | 
|  |  | 
|  | class MockSyncer : public Syncer { | 
|  | public: | 
|  | MockSyncer(); | 
|  | MOCK_METHOD(bool, | 
|  | NormalSyncShare, | 
|  | (ModelTypeSet, NudgeTracker*, SyncCycle*), | 
|  | (override)); | 
|  | MOCK_METHOD(bool, | 
|  | ConfigureSyncShare, | 
|  | (const ModelTypeSet&, | 
|  | sync_pb::SyncEnums::GetUpdatesOrigin, | 
|  | SyncCycle*), | 
|  | (override)); | 
|  | MOCK_METHOD(bool, PollSyncShare, (ModelTypeSet, SyncCycle*), (override)); | 
|  | }; | 
|  |  | 
|  | std::unique_ptr<DataTypeActivationResponse> MakeFakeActivationResponse( | 
|  | ModelType model_type) { | 
|  | auto response = std::make_unique<DataTypeActivationResponse>(); | 
|  | response->type_processor = std::make_unique<FakeModelTypeProcessor>(); | 
|  | response->model_type_state.mutable_progress_marker()->set_data_type_id( | 
|  | GetSpecificsFieldNumberFromModelType(model_type)); | 
|  | return response; | 
|  | } | 
|  |  | 
|  | MockSyncer::MockSyncer() : Syncer(nullptr) {} | 
|  |  | 
|  | using SyncShareTimes = std::vector<TimeTicks>; | 
|  |  | 
|  | 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::RunLoop::QuitCurrentDeprecated(); | 
|  | } | 
|  |  | 
|  | void RunLoop() { | 
|  | base::RunLoop().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::SequencedTaskRunner::GetCurrentDefault()->PostTask( | 
|  | FROM_HERE, base::BindOnce(&QuitLoopNow)); | 
|  | RunLoop(); | 
|  | } | 
|  |  | 
|  | static const size_t kMinNumSamples = 5; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // Test harness for the SyncScheduler.  Test the delays and backoff timers used | 
|  | // in response to various events.  Mock time is used to avoid flakes. | 
|  | class SyncSchedulerImplTest : public testing::Test { | 
|  | public: | 
|  | SyncSchedulerImplTest() | 
|  | : task_environment_( | 
|  | base::test::SingleThreadTaskEnvironment::ThreadPoolExecutionMode:: | 
|  | ASYNC, | 
|  | base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME) {} | 
|  |  | 
|  | class MockDelayProvider : public BackoffDelayProvider { | 
|  | public: | 
|  | MockDelayProvider() | 
|  | : BackoffDelayProvider(kInitialBackoffRetryTime, | 
|  | kInitialBackoffImmediateRetryTime) {} | 
|  | MOCK_METHOD(base::TimeDelta, | 
|  | GetDelay, | 
|  | (const base::TimeDelta&), | 
|  | (override)); | 
|  | }; | 
|  |  | 
|  | void SetUp() override { | 
|  | delay_ = nullptr; | 
|  | extensions_activity_ = new ExtensionsActivity(); | 
|  |  | 
|  | connection_ = std::make_unique<MockConnectionManager>(); | 
|  | connection_->SetServerReachable(); | 
|  |  | 
|  | model_type_registry_ = std::make_unique<ModelTypeRegistry>( | 
|  | &mock_nudge_handler_, &cancelation_signal_, &encryption_handler_); | 
|  | model_type_registry_->ConnectDataType( | 
|  | HISTORY_DELETE_DIRECTIVES, | 
|  | MakeFakeActivationResponse(HISTORY_DELETE_DIRECTIVES)); | 
|  | model_type_registry_->ConnectDataType(NIGORI, | 
|  | MakeFakeActivationResponse(NIGORI)); | 
|  | model_type_registry_->ConnectDataType(THEMES, | 
|  | MakeFakeActivationResponse(THEMES)); | 
|  | model_type_registry_->ConnectDataType( | 
|  | TYPED_URLS, MakeFakeActivationResponse(TYPED_URLS)); | 
|  |  | 
|  | context_ = std::make_unique<SyncCycleContext>( | 
|  | connection_.get(), extensions_activity_.get(), | 
|  | std::vector<SyncEngineEventListener*>(), nullptr, | 
|  | model_type_registry_.get(), "fake_invalidator_client_id", | 
|  | "fake_cache_guid", "fake_birthday", "fake_bag_of_chips", | 
|  | /*poll_interval=*/base::Minutes(30)); | 
|  | context_->set_notifications_enabled(true); | 
|  | context_->set_account_name("Test"); | 
|  | RebuildScheduler(); | 
|  | } | 
|  |  | 
|  | void DisconnectDataType(ModelType type) { | 
|  | model_type_registry_->DisconnectDataType(type); | 
|  | } | 
|  |  | 
|  | void RebuildScheduler() { | 
|  | auto syncer = std::make_unique<testing::StrictMock<MockSyncer>>(); | 
|  | // The syncer is destroyed with the scheduler that owns it. | 
|  | syncer_ = syncer.get(); | 
|  | scheduler_ = std::make_unique<SyncSchedulerImpl>( | 
|  | "TestSyncScheduler", BackoffDelayProvider::FromDefaults(), context(), | 
|  | std::move(syncer), false); | 
|  | SetDefaultLocalChangeNudgeDelays(); | 
|  | } | 
|  |  | 
|  | SyncSchedulerImpl* scheduler() { return scheduler_.get(); } | 
|  | MockSyncer* syncer() { return syncer_; } | 
|  | MockDelayProvider* delay() { return delay_; } | 
|  | MockConnectionManager* connection() { return connection_.get(); } | 
|  | ModelTypeRegistry* model_type_registry() { | 
|  | return model_type_registry_.get(); | 
|  | } | 
|  | base::TimeDelta default_delay() { return base::Seconds(0); } | 
|  | base::TimeDelta long_delay() { return base::Seconds(60); } | 
|  | base::TimeDelta timeout() { return TestTimeouts::action_timeout(); } | 
|  |  | 
|  | void TearDown() override { | 
|  | PumpLoop(); | 
|  | scheduler_.reset(); | 
|  | PumpLoop(); | 
|  | } | 
|  |  | 
|  | void SetDefaultLocalChangeNudgeDelays() { | 
|  | for (ModelType type : ModelTypeSet::All()) { | 
|  | scheduler_->nudge_tracker_.SetLocalChangeDelayIgnoringMinForTest( | 
|  | type, default_delay()); | 
|  | } | 
|  | } | 
|  |  | 
|  | void AnalyzePollRun(const SyncShareTimes& times, | 
|  | size_t min_num_samples, | 
|  | const TimeTicks& optimal_start, | 
|  | const base::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 StartSyncConfiguration() { | 
|  | scheduler()->Start(SyncScheduler::CONFIGURATION_MODE, base::Time()); | 
|  | } | 
|  |  | 
|  | void StartSyncScheduler(base::Time last_poll_time) { | 
|  | scheduler()->Start(SyncScheduler::NORMAL_MODE, last_poll_time); | 
|  | } | 
|  |  | 
|  | // This stops the scheduler synchronously. | 
|  | void StopSyncScheduler() { | 
|  | base::SequencedTaskRunner::GetCurrentDefault()->PostTask( | 
|  | FROM_HERE, base::BindOnce(&SyncSchedulerImplTest::DoQuitLoopNow, | 
|  | weak_ptr_factory_.GetWeakPtr())); | 
|  | RunLoop(); | 
|  | } | 
|  |  | 
|  | bool RunAndGetBackoff() { | 
|  | StartSyncScheduler(base::Time()); | 
|  |  | 
|  | scheduler()->ScheduleLocalNudge(THEMES); | 
|  | RunLoop(); | 
|  |  | 
|  | return scheduler()->IsGlobalBackoff(); | 
|  | } | 
|  |  | 
|  | void UseMockDelayProvider() { | 
|  | delay_ = new MockDelayProvider(); | 
|  | scheduler_->delay_provider_.reset(delay_); | 
|  | } | 
|  |  | 
|  | SyncCycleContext* context() { return context_.get(); } | 
|  |  | 
|  | ModelTypeSet GetThrottledTypes() { | 
|  | ModelTypeSet throttled_types; | 
|  | ModelTypeSet blocked_types = scheduler_->nudge_tracker_.GetBlockedTypes(); | 
|  | for (ModelType type : blocked_types) { | 
|  | if (scheduler_->nudge_tracker_.GetTypeBlockingMode(type) == | 
|  | WaitInterval::BlockingMode::kThrottled) { | 
|  | throttled_types.Put(type); | 
|  | } | 
|  | } | 
|  | return throttled_types; | 
|  | } | 
|  |  | 
|  | ModelTypeSet GetBackedOffTypes() { | 
|  | ModelTypeSet backed_off_types; | 
|  | ModelTypeSet blocked_types = scheduler_->nudge_tracker_.GetBlockedTypes(); | 
|  | for (ModelType type : blocked_types) { | 
|  | if (scheduler_->nudge_tracker_.GetTypeBlockingMode(type) == | 
|  | WaitInterval::BlockingMode::kExponentialBackoff) { | 
|  | backed_off_types.Put(type); | 
|  | } | 
|  | } | 
|  | return backed_off_types; | 
|  | } | 
|  |  | 
|  | bool IsAnyTypeBlocked() { | 
|  | return scheduler_->nudge_tracker_.IsAnyTypeBlocked(); | 
|  | } | 
|  |  | 
|  | base::TimeDelta GetRetryTimerDelay() { | 
|  | EXPECT_TRUE(scheduler_->retry_timer_.IsRunning()); | 
|  | return scheduler_->retry_timer_.GetCurrentDelay(); | 
|  | } | 
|  |  | 
|  | static std::unique_ptr<SyncInvalidation> BuildInvalidation( | 
|  | int64_t version, | 
|  | const std::string& payload) { | 
|  | return MockInvalidation::Build(version, payload); | 
|  | } | 
|  |  | 
|  | base::TimeDelta GetTypeBlockingTime(ModelType type) { | 
|  | NudgeTracker::TypeTrackerMap::const_iterator tracker_it = | 
|  | scheduler_->nudge_tracker_.type_trackers_.find(type); | 
|  | DCHECK(tracker_it != scheduler_->nudge_tracker_.type_trackers_.end()); | 
|  | DCHECK(tracker_it->second->wait_interval_); | 
|  | return tracker_it->second->wait_interval_->length; | 
|  | } | 
|  |  | 
|  | void SetTypeBlockingMode(ModelType type, WaitInterval::BlockingMode mode) { | 
|  | NudgeTracker::TypeTrackerMap::const_iterator tracker_it = | 
|  | scheduler_->nudge_tracker_.type_trackers_.find(type); | 
|  | DCHECK(tracker_it != scheduler_->nudge_tracker_.type_trackers_.end()); | 
|  | DCHECK(tracker_it->second->wait_interval_); | 
|  | tracker_it->second->wait_interval_->mode = mode; | 
|  | } | 
|  |  | 
|  | void NewSchedulerForLocalBackend() { | 
|  | auto syncer = std::make_unique<testing::StrictMock<MockSyncer>>(); | 
|  | // The syncer is destroyed with the scheduler that owns it. | 
|  | syncer_ = syncer.get(); | 
|  | scheduler_ = std::make_unique<SyncSchedulerImpl>( | 
|  | "TestSyncScheduler", BackoffDelayProvider::FromDefaults(), context(), | 
|  | std::move(syncer), true); | 
|  | SetDefaultLocalChangeNudgeDelays(); | 
|  | } | 
|  |  | 
|  | bool BlockTimerIsRunning() const { | 
|  | return scheduler_->pending_wakeup_timer_.IsRunning(); | 
|  | } | 
|  |  | 
|  | base::TimeDelta GetPendingWakeupTimerDelay() { | 
|  | EXPECT_TRUE(scheduler_->pending_wakeup_timer_.IsRunning()); | 
|  | return scheduler_->pending_wakeup_timer_.GetCurrentDelay(); | 
|  | } | 
|  |  | 
|  | // Provide access for tests to private method. | 
|  | base::Time ComputeLastPollOnStart(base::Time last_poll, | 
|  | base::TimeDelta poll_interval, | 
|  | base::Time now) { | 
|  | return SyncSchedulerImpl::ComputeLastPollOnStart(last_poll, poll_interval, | 
|  | now); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | base::test::SingleThreadTaskEnvironment task_environment_; | 
|  |  | 
|  | private: | 
|  | static const base::TickClock* tick_clock_; | 
|  | static base::TimeTicks GetMockTimeTicks() { | 
|  | if (!tick_clock_) | 
|  | return base::TimeTicks(); | 
|  | return tick_clock_->NowTicks(); | 
|  | } | 
|  |  | 
|  | FakeSyncEncryptionHandler encryption_handler_; | 
|  | CancelationSignal cancelation_signal_; | 
|  | std::unique_ptr<MockConnectionManager> connection_; | 
|  | std::unique_ptr<ModelTypeRegistry> model_type_registry_; | 
|  | std::unique_ptr<SyncCycleContext> context_; | 
|  | std::unique_ptr<SyncSchedulerImpl> scheduler_; | 
|  | MockNudgeHandler mock_nudge_handler_; | 
|  | raw_ptr<MockSyncer> syncer_ = nullptr; | 
|  | raw_ptr<MockDelayProvider> delay_ = nullptr; | 
|  | scoped_refptr<ExtensionsActivity> extensions_activity_; | 
|  | base::WeakPtrFactory<SyncSchedulerImplTest> weak_ptr_factory_{this}; | 
|  | }; | 
|  |  | 
|  | const base::TickClock* SyncSchedulerImplTest::tick_clock_ = nullptr; | 
|  |  | 
|  | void RecordSyncShareImpl(SyncShareTimes* times) { | 
|  | times->push_back(TimeTicks::Now()); | 
|  | } | 
|  |  | 
|  | ACTION_P2(RecordSyncShare, times, success) { | 
|  | RecordSyncShareImpl(times); | 
|  | if (base::RunLoop::IsRunningOnCurrentThread()) | 
|  | QuitLoopNow(); | 
|  | return success; | 
|  | } | 
|  |  | 
|  | ACTION_P3(RecordSyncShareMultiple, times, quit_after, success) { | 
|  | RecordSyncShareImpl(times); | 
|  | EXPECT_LE(times->size(), quit_after); | 
|  | if (times->size() >= quit_after && | 
|  | base::RunLoop::IsRunningOnCurrentThread()) { | 
|  | QuitLoopNow(); | 
|  | } | 
|  | return success; | 
|  | } | 
|  |  | 
|  | ACTION_P(StopScheduler, scheduler) { | 
|  | scheduler->Stop(); | 
|  | } | 
|  |  | 
|  | ACTION(AddFailureAndQuitLoopNow) { | 
|  | ADD_FAILURE(); | 
|  | QuitLoopNow(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | ACTION_P(QuitLoopNowAction, success) { | 
|  | QuitLoopNow(); | 
|  | return success; | 
|  | } | 
|  |  | 
|  | // Test nudge scheduling. | 
|  | TEST_F(SyncSchedulerImplTest, Nudge) { | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce( | 
|  | DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×, true))) | 
|  | .RetiresOnSaturation(); | 
|  |  | 
|  | StartSyncScheduler(base::Time()); | 
|  |  | 
|  | scheduler()->ScheduleLocalNudge(THEMES); | 
|  | RunLoop(); | 
|  |  | 
|  | Mock::VerifyAndClearExpectations(syncer()); | 
|  |  | 
|  | // Make sure a second, later, nudge is unaffected by first (no coalescing). | 
|  | SyncShareTimes times2; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce( | 
|  | DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×2, true))); | 
|  | scheduler()->ScheduleLocalNudge(TYPED_URLS); | 
|  | RunLoop(); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncSchedulerImplTest, NudgeForDisabledType) { | 
|  | StartSyncScheduler(base::Time()); | 
|  | scheduler()->ScheduleLocalNudge(HISTORY_DELETE_DIRECTIVES); | 
|  |  | 
|  | // The user enables a custom passphrase at this point, so | 
|  | // HISTORY_DELETE_DIRECTIVES gets disabled. | 
|  | DisconnectDataType(HISTORY_DELETE_DIRECTIVES); | 
|  | ASSERT_FALSE(context()->GetConnectedTypes().Has(HISTORY_DELETE_DIRECTIVES)); | 
|  |  | 
|  | // There should be no sync cycle. | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare).Times(0); | 
|  | PumpLoop(); | 
|  | } | 
|  |  | 
|  | // Make sure a regular config command is scheduled fine in the absence of any | 
|  | // errors. | 
|  | TEST_F(SyncSchedulerImplTest, Config) { | 
|  | SyncShareTimes times; | 
|  |  | 
|  | EXPECT_CALL(*syncer(), ConfigureSyncShare) | 
|  | .WillOnce(DoAll(Invoke(SimulateConfigureSuccess), | 
|  | RecordSyncShare(×, true))); | 
|  |  | 
|  | StartSyncConfiguration(); | 
|  |  | 
|  | base::MockOnceClosure ready_task; | 
|  | EXPECT_CALL(ready_task, Run).Times(1); | 
|  | scheduler()->ScheduleConfiguration(sync_pb::SyncEnums::RECONFIGURATION, | 
|  | ModelTypeSet(THEMES), ready_task.Get()); | 
|  | PumpLoop(); | 
|  | } | 
|  |  | 
|  | // Simulate a failure and make sure the config request is retried. | 
|  | TEST_F(SyncSchedulerImplTest, ConfigWithBackingOff) { | 
|  | UseMockDelayProvider(); | 
|  | EXPECT_CALL(*delay(), GetDelay) | 
|  | .WillRepeatedly(Return(base::Milliseconds(20))); | 
|  |  | 
|  | StartSyncConfiguration(); | 
|  |  | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), ConfigureSyncShare) | 
|  | .WillOnce(DoAll(Invoke(SimulateConfigureFailed), | 
|  | RecordSyncShare(×, false))) | 
|  | .WillOnce(DoAll(Invoke(SimulateConfigureFailed), | 
|  | RecordSyncShare(×, false))); | 
|  |  | 
|  | base::MockOnceClosure ready_task; | 
|  | EXPECT_CALL(ready_task, Run).Times(1); | 
|  | scheduler()->ScheduleConfiguration(sync_pb::SyncEnums::RECONFIGURATION, | 
|  | ModelTypeSet(THEMES), ready_task.Get()); | 
|  | RunLoop(); | 
|  |  | 
|  | // RunLoop() will trigger TryCanaryJob which will retry configuration. | 
|  | // Since retry_task was already called it shouldn't be called again. | 
|  | RunLoop(); | 
|  |  | 
|  | Mock::VerifyAndClearExpectations(syncer()); | 
|  |  | 
|  | EXPECT_CALL(*syncer(), ConfigureSyncShare) | 
|  | .WillOnce(DoAll(Invoke(SimulateConfigureSuccess), | 
|  | RecordSyncShare(×, true))); | 
|  | RunLoop(); | 
|  | } | 
|  |  | 
|  | // Simuilate SyncSchedulerImpl::Stop being called in the middle of Configure. | 
|  | // This can happen if server returns NOT_MY_BIRTHDAY. | 
|  | TEST_F(SyncSchedulerImplTest, ConfigWithStop) { | 
|  | UseMockDelayProvider(); | 
|  | EXPECT_CALL(*delay(), GetDelay) | 
|  | .WillRepeatedly(Return(base::Milliseconds(20))); | 
|  |  | 
|  | StartSyncConfiguration(); | 
|  |  | 
|  | // Make ConfigureSyncShare call scheduler->Stop(). It is not supposed to call | 
|  | // retry_task or dereference configuration params. | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), ConfigureSyncShare) | 
|  | .WillOnce(DoAll(Invoke(SimulateConfigureFailed), | 
|  | StopScheduler(scheduler()), | 
|  | RecordSyncShare(×, false))); | 
|  |  | 
|  | base::MockOnceClosure ready_task; | 
|  | EXPECT_CALL(ready_task, Run).Times(0); | 
|  | scheduler()->ScheduleConfiguration(sync_pb::SyncEnums::RECONFIGURATION, | 
|  | ModelTypeSet(THEMES), ready_task.Get()); | 
|  | PumpLoop(); | 
|  | } | 
|  |  | 
|  | // Verify that in the absence of valid access token the command will fail. | 
|  | TEST_F(SyncSchedulerImplTest, ConfigNoAccessToken) { | 
|  | connection()->ResetAccessToken(); | 
|  |  | 
|  | StartSyncConfiguration(); | 
|  |  | 
|  | base::MockOnceClosure ready_task; | 
|  | EXPECT_CALL(ready_task, Run).Times(0); | 
|  | scheduler()->ScheduleConfiguration(sync_pb::SyncEnums::RECONFIGURATION, | 
|  | ModelTypeSet(THEMES), ready_task.Get()); | 
|  | PumpLoop(); | 
|  | } | 
|  |  | 
|  | // Verify that in the absence of valid access token the command will pass if | 
|  | // local sync backend is used. | 
|  | TEST_F(SyncSchedulerImplTest, ConfigNoAccessTokenLocalSync) { | 
|  | NewSchedulerForLocalBackend(); | 
|  | connection()->ResetAccessToken(); | 
|  |  | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), ConfigureSyncShare) | 
|  | .WillOnce(DoAll(Invoke(SimulateConfigureSuccess), | 
|  | RecordSyncShare(×, true))); | 
|  |  | 
|  | StartSyncConfiguration(); | 
|  |  | 
|  | base::MockOnceClosure ready_task; | 
|  | EXPECT_CALL(ready_task, Run).Times(1); | 
|  | scheduler()->ScheduleConfiguration(sync_pb::SyncEnums::RECONFIGURATION, | 
|  | ModelTypeSet(THEMES), ready_task.Get()); | 
|  | PumpLoop(); | 
|  | } | 
|  |  | 
|  | // Issue a nudge when the config has failed. Make sure both the config and | 
|  | // nudge are executed. | 
|  | TEST_F(SyncSchedulerImplTest, NudgeWithConfigWithBackingOff) { | 
|  | UseMockDelayProvider(); | 
|  | EXPECT_CALL(*delay(), GetDelay) | 
|  | .WillRepeatedly(Return(base::Milliseconds(50))); | 
|  |  | 
|  | StartSyncConfiguration(); | 
|  |  | 
|  | // Request a configure and make sure it fails. | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), ConfigureSyncShare) | 
|  | .WillOnce(DoAll(Invoke(SimulateConfigureFailed), | 
|  | RecordSyncShare(×, false))); | 
|  | base::MockOnceClosure ready_task; | 
|  | EXPECT_CALL(ready_task, Run).Times(0); | 
|  | const ModelType model_type = THEMES; | 
|  | scheduler()->ScheduleConfiguration(sync_pb::SyncEnums::RECONFIGURATION, | 
|  | ModelTypeSet(model_type), | 
|  | ready_task.Get()); | 
|  | RunLoop(); | 
|  | Mock::VerifyAndClearExpectations(syncer()); | 
|  | Mock::VerifyAndClearExpectations(&ready_task); | 
|  |  | 
|  | // Ask for a nudge while dealing with repeated configure failure. | 
|  | EXPECT_CALL(*syncer(), ConfigureSyncShare) | 
|  | .WillOnce(DoAll(Invoke(SimulateConfigureFailed), | 
|  | RecordSyncShare(×, false))); | 
|  | scheduler()->ScheduleLocalNudge(model_type); | 
|  | 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()); | 
|  |  | 
|  | // Let the next configure retry succeed. | 
|  | EXPECT_CALL(*syncer(), ConfigureSyncShare) | 
|  | .WillOnce(DoAll(Invoke(SimulateConfigureSuccess), | 
|  | RecordSyncShare(×, true))); | 
|  | RunLoop(); | 
|  |  | 
|  | // Now change the mode so nudge can execute. | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce( | 
|  | DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×, true))); | 
|  | StartSyncScheduler(base::Time()); | 
|  | PumpLoop(); | 
|  | } | 
|  |  | 
|  | // Test that nudges are coalesced. | 
|  | TEST_F(SyncSchedulerImplTest, NudgeCoalescing) { | 
|  | StartSyncScheduler(base::Time()); | 
|  |  | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce( | 
|  | DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×, true))); | 
|  | TimeTicks optimal_time = TimeTicks::Now() + default_delay(); | 
|  | scheduler()->ScheduleLocalNudge(THEMES); | 
|  | scheduler()->ScheduleLocalNudge(TYPED_URLS); | 
|  | RunLoop(); | 
|  |  | 
|  | ASSERT_EQ(1U, times.size()); | 
|  | EXPECT_GE(times[0], optimal_time); | 
|  |  | 
|  | Mock::VerifyAndClearExpectations(syncer()); | 
|  |  | 
|  | SyncShareTimes times2; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce( | 
|  | DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×2, true))); | 
|  | scheduler()->ScheduleLocalNudge(THEMES); | 
|  | RunLoop(); | 
|  | } | 
|  |  | 
|  | // Test that nudges are coalesced. | 
|  | TEST_F(SyncSchedulerImplTest, NudgeCoalescingWithDifferentTimings) { | 
|  | StartSyncScheduler(base::Time()); | 
|  |  | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce( | 
|  | DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×, true))); | 
|  |  | 
|  | // Create a huge time delay. | 
|  | base::TimeDelta delay = base::Days(1); | 
|  |  | 
|  | std::map<ModelType, base::TimeDelta> delay_map; | 
|  | delay_map[THEMES] = delay; | 
|  | scheduler()->OnReceivedCustomNudgeDelays(delay_map); | 
|  | scheduler()->ScheduleLocalNudge(THEMES); | 
|  | scheduler()->ScheduleLocalNudge(TYPED_URLS); | 
|  |  | 
|  | 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(SyncSchedulerImplTest, NudgeWithStates) { | 
|  | StartSyncScheduler(base::Time()); | 
|  |  | 
|  | SyncShareTimes times1; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce( | 
|  | DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×1, true))) | 
|  | .RetiresOnSaturation(); | 
|  | scheduler()->SetHasPendingInvalidations(THEMES, true); | 
|  | scheduler()->ScheduleInvalidationNudge(THEMES); | 
|  | RunLoop(); | 
|  |  | 
|  | Mock::VerifyAndClearExpectations(syncer()); | 
|  |  | 
|  | // Make sure a second, later, nudge is unaffected by first (no coalescing). | 
|  | SyncShareTimes times2; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce( | 
|  | DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×2, true))); | 
|  | scheduler()->SetHasPendingInvalidations(TYPED_URLS, true); | 
|  | scheduler()->ScheduleInvalidationNudge(TYPED_URLS); | 
|  | RunLoop(); | 
|  | } | 
|  |  | 
|  | // Test that polling works as expected. | 
|  | TEST_F(SyncSchedulerImplTest, Polling) { | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), PollSyncShare) | 
|  | .Times(AtLeast(kMinNumSamples)) | 
|  | .WillRepeatedly( | 
|  | DoAll(Invoke(SimulatePollSuccess), | 
|  | RecordSyncShareMultiple(×, kMinNumSamples, true))); | 
|  |  | 
|  | base::TimeDelta poll_interval(base::Milliseconds(30)); | 
|  | scheduler()->OnReceivedPollIntervalUpdate(poll_interval); | 
|  |  | 
|  | TimeTicks optimal_start = TimeTicks::Now() + poll_interval; | 
|  | StartSyncScheduler(base::Time()); | 
|  |  | 
|  | // Run again to wait for polling. | 
|  | RunLoop(); | 
|  |  | 
|  | StopSyncScheduler(); | 
|  | AnalyzePollRun(times, kMinNumSamples, optimal_start, poll_interval); | 
|  | } | 
|  |  | 
|  | // Test that polling gets the intervals from the provided context. | 
|  | TEST_F(SyncSchedulerImplTest, ShouldUseInitialPollIntervalFromContext) { | 
|  | base::TimeDelta poll_interval(base::Milliseconds(30)); | 
|  | context()->set_poll_interval(poll_interval); | 
|  | RebuildScheduler(); | 
|  |  | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), PollSyncShare) | 
|  | .Times(AtLeast(kMinNumSamples)) | 
|  | .WillRepeatedly( | 
|  | DoAll(Invoke(SimulatePollSuccess), | 
|  | RecordSyncShareMultiple(×, kMinNumSamples, true))); | 
|  |  | 
|  | TimeTicks optimal_start = TimeTicks::Now() + poll_interval; | 
|  | StartSyncScheduler(base::Time()); | 
|  |  | 
|  | // Run again to wait for polling. | 
|  | RunLoop(); | 
|  |  | 
|  | StopSyncScheduler(); | 
|  | AnalyzePollRun(times, kMinNumSamples, optimal_start, poll_interval); | 
|  | } | 
|  |  | 
|  | // Test that we reuse the previous poll time on startup, triggering the first | 
|  | // poll based on when the last one happened. Subsequent polls should have the | 
|  | // normal delay. | 
|  | TEST_F(SyncSchedulerImplTest, PollingPersistence) { | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), PollSyncShare) | 
|  | .Times(AtLeast(kMinNumSamples)) | 
|  | .WillRepeatedly( | 
|  | DoAll(Invoke(SimulatePollSuccess), | 
|  | RecordSyncShareMultiple(×, kMinNumSamples, true))); | 
|  |  | 
|  | // Use a large poll interval that wouldn't normally get hit on its own for | 
|  | // some time yet. | 
|  | base::TimeDelta poll_interval(base::Milliseconds(500)); | 
|  | scheduler()->OnReceivedPollIntervalUpdate(poll_interval); | 
|  |  | 
|  | // Set the start time to now, as the poll was overdue. | 
|  | TimeTicks optimal_start = TimeTicks::Now(); | 
|  | StartSyncScheduler(base::Time::Now() - poll_interval); | 
|  |  | 
|  | // Run again to wait for polling. | 
|  | RunLoop(); | 
|  |  | 
|  | StopSyncScheduler(); | 
|  | AnalyzePollRun(times, kMinNumSamples, optimal_start, poll_interval); | 
|  | } | 
|  |  | 
|  | // Test that if the persisted poll is in the future, it's ignored (the case | 
|  | // where the local time has been modified). | 
|  | TEST_F(SyncSchedulerImplTest, PollingPersistenceBadClock) { | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), PollSyncShare) | 
|  | .Times(AtLeast(kMinNumSamples)) | 
|  | .WillRepeatedly( | 
|  | DoAll(Invoke(SimulatePollSuccess), | 
|  | RecordSyncShareMultiple(×, kMinNumSamples, true))); | 
|  |  | 
|  | base::TimeDelta poll_interval(base::Milliseconds(30)); | 
|  | scheduler()->OnReceivedPollIntervalUpdate(poll_interval); | 
|  |  | 
|  | // Set the start time to |poll_interval| in the future. | 
|  | TimeTicks optimal_start = TimeTicks::Now() + poll_interval; | 
|  | StartSyncScheduler(base::Time::Now() + base::Minutes(10)); | 
|  |  | 
|  | // 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(SyncSchedulerImplTest, PollIntervalUpdate) { | 
|  | SyncShareTimes times; | 
|  | base::TimeDelta poll1(base::Milliseconds(120)); | 
|  | base::TimeDelta poll2(base::Milliseconds(30)); | 
|  | scheduler()->OnReceivedPollIntervalUpdate(poll1); | 
|  | EXPECT_CALL(*syncer(), PollSyncShare) | 
|  | .Times(AtLeast(kMinNumSamples)) | 
|  | .WillOnce(DoAll(WithArgs<0, 1>(SimulatePollIntervalUpdate(poll2)), | 
|  | Return(true))) | 
|  | .WillRepeatedly(DoAll( | 
|  | Invoke(SimulatePollSuccess), | 
|  | WithArg<1>(RecordSyncShareMultiple(×, kMinNumSamples, true)))); | 
|  |  | 
|  | TimeTicks optimal_start = TimeTicks::Now() + poll1 + poll2; | 
|  | StartSyncScheduler(base::Time()); | 
|  |  | 
|  | // Run again to wait for polling. | 
|  | RunLoop(); | 
|  |  | 
|  | StopSyncScheduler(); | 
|  | AnalyzePollRun(times, kMinNumSamples, optimal_start, poll2); | 
|  | } | 
|  |  | 
|  | // Test that no syncing occurs when throttled. | 
|  | TEST_F(SyncSchedulerImplTest, ThrottlingDoesThrottle) { | 
|  | base::TimeDelta poll(base::Milliseconds(20)); | 
|  | base::TimeDelta throttle(base::Minutes(10)); | 
|  | scheduler()->OnReceivedPollIntervalUpdate(poll); | 
|  |  | 
|  | EXPECT_CALL(*syncer(), ConfigureSyncShare) | 
|  | .WillOnce(DoAll(WithArg<2>(SimulateThrottled(throttle)), Return(false))) | 
|  | .WillRepeatedly(AddFailureAndQuitLoopNow()); | 
|  |  | 
|  | StartSyncScheduler(base::Time()); | 
|  |  | 
|  | const ModelType type = THEMES; | 
|  | scheduler()->ScheduleLocalNudge(type); | 
|  | PumpLoop(); | 
|  |  | 
|  | StartSyncConfiguration(); | 
|  |  | 
|  | base::MockOnceClosure ready_task; | 
|  | EXPECT_CALL(ready_task, Run).Times(0); | 
|  | scheduler()->ScheduleConfiguration(sync_pb::SyncEnums::RECONFIGURATION, | 
|  | ModelTypeSet(type), ready_task.Get()); | 
|  | PumpLoop(); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncSchedulerImplTest, ThrottlingExpiresFromPoll) { | 
|  | base::TimeDelta poll(base::Milliseconds(15)); | 
|  | base::TimeDelta throttle1(base::Milliseconds(150)); | 
|  | scheduler()->OnReceivedPollIntervalUpdate(poll); | 
|  |  | 
|  | ::testing::InSequence seq; | 
|  | EXPECT_CALL(*syncer(), PollSyncShare) | 
|  | .WillOnce(DoAll(WithArg<1>(SimulateThrottled(throttle1)), Return(false))) | 
|  | .RetiresOnSaturation(); | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), PollSyncShare) | 
|  | .WillRepeatedly( | 
|  | DoAll(Invoke(SimulatePollSuccess), | 
|  | RecordSyncShareMultiple(×, kMinNumSamples, true))); | 
|  |  | 
|  | TimeTicks optimal_start = TimeTicks::Now() + poll + throttle1; | 
|  | StartSyncScheduler(base::Time()); | 
|  |  | 
|  | // Run again to wait for polling. | 
|  | RunLoop(); | 
|  |  | 
|  | StopSyncScheduler(); | 
|  | AnalyzePollRun(times, kMinNumSamples, optimal_start, poll); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncSchedulerImplTest, ThrottlingExpiresFromNudge) { | 
|  | base::TimeDelta poll(base::Days(1)); | 
|  | base::TimeDelta throttle1(base::Milliseconds(150)); | 
|  | scheduler()->OnReceivedPollIntervalUpdate(poll); | 
|  |  | 
|  | ::testing::InSequence seq; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(WithArg<2>(SimulateThrottled(throttle1)), Return(false))) | 
|  | .RetiresOnSaturation(); | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(Invoke(SimulateNormalSuccess), QuitLoopNowAction(true))); | 
|  |  | 
|  | StartSyncScheduler(base::Time()); | 
|  | scheduler()->ScheduleLocalNudge(THEMES); | 
|  |  | 
|  | PumpLoop();  // To get PerformDelayedNudge called. | 
|  | PumpLoop();  // To get TrySyncCycleJob called | 
|  | EXPECT_TRUE(scheduler()->IsGlobalThrottle()); | 
|  | RunLoop(); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalThrottle()); | 
|  |  | 
|  | StopSyncScheduler(); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncSchedulerImplTest, ThrottlingExpiresFromConfigure) { | 
|  | scheduler()->OnReceivedPollIntervalUpdate(base::Days(1)); | 
|  |  | 
|  | ::testing::InSequence seq; | 
|  | EXPECT_CALL(*syncer(), ConfigureSyncShare) | 
|  | .WillOnce(DoAll(WithArg<2>(SimulateThrottled(base::Milliseconds(150))), | 
|  | Return(false))) | 
|  | .RetiresOnSaturation(); | 
|  | EXPECT_CALL(*syncer(), ConfigureSyncShare) | 
|  | .WillOnce( | 
|  | DoAll(Invoke(SimulateConfigureSuccess), QuitLoopNowAction(true))); | 
|  |  | 
|  | StartSyncConfiguration(); | 
|  |  | 
|  | base::MockOnceClosure ready_task; | 
|  | EXPECT_CALL(ready_task, Run).Times(0); | 
|  | scheduler()->ScheduleConfiguration(sync_pb::SyncEnums::RECONFIGURATION, | 
|  | ModelTypeSet(THEMES), ready_task.Get()); | 
|  | PumpLoop(); | 
|  | Mock::VerifyAndClearExpectations(&ready_task); | 
|  | EXPECT_TRUE(scheduler()->IsGlobalThrottle()); | 
|  |  | 
|  | RunLoop(); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalThrottle()); | 
|  |  | 
|  | StopSyncScheduler(); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncSchedulerImplTest, TypeThrottlingBlocksNudge) { | 
|  | base::TimeDelta poll(base::Days(1)); | 
|  | base::TimeDelta throttle1(base::Seconds(60)); | 
|  | scheduler()->OnReceivedPollIntervalUpdate(poll); | 
|  |  | 
|  | const ModelType type = THEMES; | 
|  |  | 
|  | ::testing::InSequence seq; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(WithArg<2>(SimulateTypeThrottled(type, throttle1)), | 
|  | Return(true))) | 
|  | .RetiresOnSaturation(); | 
|  |  | 
|  | StartSyncScheduler(base::Time()); | 
|  | scheduler()->ScheduleLocalNudge(type); | 
|  | PumpLoop();  // To get PerformDelayedNudge called. | 
|  | PumpLoop();  // To get TrySyncCycleJob called | 
|  | EXPECT_TRUE(GetThrottledTypes().Has(type)); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalBackoff()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalThrottle()); | 
|  |  | 
|  | // This won't cause a sync cycle because the types are throttled. | 
|  | scheduler()->ScheduleLocalNudge(type); | 
|  | PumpLoop(); | 
|  |  | 
|  | StopSyncScheduler(); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncSchedulerImplTest, TypeBackingOffBlocksNudge) { | 
|  | UseMockDelayProvider(); | 
|  | EXPECT_CALL(*delay(), GetDelay).WillRepeatedly(Return(long_delay())); | 
|  |  | 
|  | base::TimeDelta poll(base::Days(1)); | 
|  | scheduler()->OnReceivedPollIntervalUpdate(poll); | 
|  |  | 
|  | const ModelType type = THEMES; | 
|  |  | 
|  | ::testing::InSequence seq; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(WithArg<2>(SimulatePartialFailure(type)), Return(true))) | 
|  | .RetiresOnSaturation(); | 
|  |  | 
|  | StartSyncScheduler(base::Time()); | 
|  | scheduler()->ScheduleLocalNudge(type); | 
|  | PumpLoop();  // To get PerformDelayedNudge called. | 
|  | PumpLoop();  // To get TrySyncCycleJob called | 
|  | EXPECT_TRUE(GetBackedOffTypes().Has(type)); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalBackoff()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalThrottle()); | 
|  |  | 
|  | // This won't cause a sync cycle because the types are backed off. | 
|  | scheduler()->ScheduleLocalNudge(type); | 
|  | PumpLoop(); | 
|  |  | 
|  | StopSyncScheduler(); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncSchedulerImplTest, TypeBackingOffWillExpire) { | 
|  | UseMockDelayProvider(); | 
|  | EXPECT_CALL(*delay(), GetDelay).WillRepeatedly(Return(default_delay())); | 
|  |  | 
|  | base::TimeDelta poll(base::Days(1)); | 
|  | scheduler()->OnReceivedPollIntervalUpdate(poll); | 
|  |  | 
|  | const ModelType type = THEMES; | 
|  |  | 
|  | ::testing::InSequence seq; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(WithArg<2>(SimulatePartialFailure(type)), Return(true))) | 
|  | .RetiresOnSaturation(); | 
|  |  | 
|  | StartSyncScheduler(base::Time()); | 
|  | scheduler()->ScheduleLocalNudge(type); | 
|  | PumpLoop();  // To get PerformDelayedNudge called. | 
|  | PumpLoop();  // To get TrySyncCycleJob called | 
|  | EXPECT_TRUE(GetBackedOffTypes().Has(type)); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalBackoff()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalThrottle()); | 
|  |  | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillRepeatedly( | 
|  | DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×, true))); | 
|  | PumpLoop();  // To get PerformDelayedNudge called. | 
|  | PumpLoop();  // To get TrySyncCycleJob called | 
|  | EXPECT_FALSE(IsAnyTypeBlocked()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalBackoff()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalThrottle()); | 
|  |  | 
|  | StopSyncScheduler(); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncSchedulerImplTest, TypeBackingOffAndThrottling) { | 
|  | UseMockDelayProvider(); | 
|  | EXPECT_CALL(*delay(), GetDelay).WillRepeatedly(Return(long_delay())); | 
|  |  | 
|  | base::TimeDelta poll(base::Days(1)); | 
|  | scheduler()->OnReceivedPollIntervalUpdate(poll); | 
|  |  | 
|  | const ModelType type = THEMES; | 
|  |  | 
|  | ::testing::InSequence seq; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(WithArg<2>(SimulatePartialFailure(type)), Return(true))) | 
|  | .RetiresOnSaturation(); | 
|  |  | 
|  | StartSyncScheduler(base::Time()); | 
|  | scheduler()->ScheduleLocalNudge(type); | 
|  | PumpLoop();  // To get PerformDelayedNudge called. | 
|  | PumpLoop();  // To get TrySyncCycleJob called | 
|  | EXPECT_TRUE(GetBackedOffTypes().Has(type)); | 
|  | EXPECT_TRUE(BlockTimerIsRunning()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalBackoff()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalThrottle()); | 
|  |  | 
|  | base::TimeDelta throttle1(base::Milliseconds(150)); | 
|  |  | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(WithArg<2>(SimulateThrottled(throttle1)), Return(false))) | 
|  | .RetiresOnSaturation(); | 
|  |  | 
|  | // Sync still can throttle. | 
|  | scheduler()->ScheduleLocalNudge(TYPED_URLS); | 
|  | PumpLoop();  // TO get TypesUnblock called. | 
|  | PumpLoop();  // To get TrySyncCycleJob called. | 
|  |  | 
|  | EXPECT_TRUE(GetBackedOffTypes().Has(type)); | 
|  | EXPECT_TRUE(BlockTimerIsRunning()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalBackoff()); | 
|  | EXPECT_TRUE(scheduler()->IsGlobalThrottle()); | 
|  |  | 
|  | // Unthrottled client, but the backingoff datatype is still in backoff and | 
|  | // scheduled. | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(Invoke(SimulateNormalSuccess), QuitLoopNowAction(true))); | 
|  | RunLoop(); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalThrottle()); | 
|  | EXPECT_TRUE(GetBackedOffTypes().Has(type)); | 
|  | EXPECT_TRUE(BlockTimerIsRunning()); | 
|  |  | 
|  | StopSyncScheduler(); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncSchedulerImplTest, TypeThrottlingBackingOffBlocksNudge) { | 
|  | UseMockDelayProvider(); | 
|  | EXPECT_CALL(*delay(), GetDelay).WillRepeatedly(Return(long_delay())); | 
|  |  | 
|  | base::TimeDelta poll(base::Days(1)); | 
|  | base::TimeDelta throttle(base::Seconds(60)); | 
|  | scheduler()->OnReceivedPollIntervalUpdate(poll); | 
|  |  | 
|  | const ModelType throttled_type = THEMES; | 
|  |  | 
|  | ::testing::InSequence seq; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce( | 
|  | DoAll(WithArg<2>(SimulateTypeThrottled(throttled_type, throttle)), | 
|  | Return(true))) | 
|  | .RetiresOnSaturation(); | 
|  |  | 
|  | StartSyncScheduler(base::Time()); | 
|  | scheduler()->ScheduleLocalNudge(throttled_type); | 
|  | PumpLoop();  // To get PerformDelayedNudge called. | 
|  | PumpLoop();  // To get TrySyncCycleJob called | 
|  |  | 
|  | const ModelType backed_off_type = TYPED_URLS; | 
|  |  | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(WithArg<2>(SimulatePartialFailure(backed_off_type)), | 
|  | Return(true))) | 
|  | .RetiresOnSaturation(); | 
|  |  | 
|  | scheduler()->ScheduleLocalNudge(backed_off_type); | 
|  |  | 
|  | PumpLoop();  // To get PerformDelayedNudge called. | 
|  | PumpLoop();  // To get TrySyncCycleJob called | 
|  |  | 
|  | EXPECT_TRUE(GetThrottledTypes().Has(throttled_type)); | 
|  | EXPECT_TRUE(GetBackedOffTypes().Has(backed_off_type)); | 
|  | EXPECT_TRUE(BlockTimerIsRunning()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalBackoff()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalThrottle()); | 
|  |  | 
|  | // Neither of these will cause a sync cycle because the types are throttled or | 
|  | // backed off. | 
|  | scheduler()->ScheduleLocalNudge(throttled_type); | 
|  | PumpLoop(); | 
|  | scheduler()->ScheduleLocalNudge(backed_off_type); | 
|  | PumpLoop(); | 
|  |  | 
|  | StopSyncScheduler(); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncSchedulerImplTest, TypeThrottlingDoesBlockOtherSources) { | 
|  | UseMockDelayProvider(); | 
|  | EXPECT_CALL(*delay(), GetDelay).WillRepeatedly(Return(default_delay())); | 
|  |  | 
|  | base::TimeDelta poll(base::Days(1)); | 
|  | base::TimeDelta throttle1(base::Seconds(60)); | 
|  | scheduler()->OnReceivedPollIntervalUpdate(poll); | 
|  |  | 
|  | const ModelType throttled_type = THEMES; | 
|  |  | 
|  | ::testing::InSequence seq; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce( | 
|  | DoAll(WithArg<2>(SimulateTypeThrottled(throttled_type, throttle1)), | 
|  | Return(true))) | 
|  | .RetiresOnSaturation(); | 
|  |  | 
|  | StartSyncScheduler(base::Time()); | 
|  | scheduler()->ScheduleLocalNudge(throttled_type); | 
|  | PumpLoop();  // To get PerformDelayedNudge called. | 
|  | PumpLoop();  // To get TrySyncCycleJob called | 
|  | EXPECT_TRUE(GetThrottledTypes().Has(throttled_type)); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalBackoff()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalThrottle()); | 
|  |  | 
|  | // Ignore invalidations for throttled types. | 
|  | scheduler()->ScheduleInvalidationNudge(throttled_type); | 
|  | PumpLoop(); | 
|  |  | 
|  | // Ignore refresh requests for throttled types. | 
|  | scheduler()->ScheduleLocalRefreshRequest(ModelTypeSet(throttled_type)); | 
|  | PumpLoop(); | 
|  |  | 
|  | Mock::VerifyAndClearExpectations(syncer()); | 
|  |  | 
|  | // Local nudges for non-throttled types will trigger a sync. | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillRepeatedly( | 
|  | DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×, true))); | 
|  | scheduler()->ScheduleLocalNudge(PREFERENCES); | 
|  | RunLoop(); | 
|  | Mock::VerifyAndClearExpectations(syncer()); | 
|  |  | 
|  | StopSyncScheduler(); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncSchedulerImplTest, TypeBackingOffDoesBlockOtherSources) { | 
|  | UseMockDelayProvider(); | 
|  | EXPECT_CALL(*delay(), GetDelay).WillRepeatedly(Return(long_delay())); | 
|  |  | 
|  | base::TimeDelta poll(base::Days(1)); | 
|  | scheduler()->OnReceivedPollIntervalUpdate(poll); | 
|  |  | 
|  | const ModelType backed_off_type = THEMES; | 
|  |  | 
|  | ::testing::InSequence seq; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(WithArg<2>(SimulatePartialFailure(backed_off_type)), | 
|  | Return(true))) | 
|  | .RetiresOnSaturation(); | 
|  |  | 
|  | StartSyncScheduler(base::Time()); | 
|  | scheduler()->ScheduleLocalNudge(backed_off_type); | 
|  | PumpLoop();  // To get PerformDelayedNudge called. | 
|  | PumpLoop();  // To get TrySyncCycleJob called | 
|  | EXPECT_TRUE(GetBackedOffTypes().Has(backed_off_type)); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalBackoff()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalThrottle()); | 
|  |  | 
|  | // Ignore invalidations for backed off types. | 
|  | scheduler()->ScheduleInvalidationNudge(backed_off_type); | 
|  | PumpLoop(); | 
|  |  | 
|  | // Ignore refresh requests for backed off types. | 
|  | scheduler()->ScheduleLocalRefreshRequest(ModelTypeSet(backed_off_type)); | 
|  | PumpLoop(); | 
|  |  | 
|  | Mock::VerifyAndClearExpectations(syncer()); | 
|  |  | 
|  | // Local nudges for non-backed off types will trigger a sync. | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillRepeatedly( | 
|  | DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×, true))); | 
|  | scheduler()->ScheduleLocalNudge(PREFERENCES); | 
|  | RunLoop(); | 
|  | Mock::VerifyAndClearExpectations(syncer()); | 
|  |  | 
|  | StopSyncScheduler(); | 
|  | } | 
|  |  | 
|  | // Test nudges / polls don't run in config mode and config tasks do. | 
|  | TEST_F(SyncSchedulerImplTest, ConfigurationMode) { | 
|  | scheduler()->OnReceivedPollIntervalUpdate(base::Milliseconds(15)); | 
|  |  | 
|  | StartSyncConfiguration(); | 
|  |  | 
|  | scheduler()->ScheduleLocalNudge(TYPED_URLS); | 
|  | scheduler()->ScheduleLocalNudge(TYPED_URLS); | 
|  |  | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), ConfigureSyncShare) | 
|  | .WillOnce(DoAll(Invoke(SimulateConfigureSuccess), | 
|  | RecordSyncShare(×, true))) | 
|  | .RetiresOnSaturation(); | 
|  | base::MockOnceClosure ready_task; | 
|  | EXPECT_CALL(ready_task, Run).Times(1); | 
|  | scheduler()->ScheduleConfiguration(sync_pb::SyncEnums::RECONFIGURATION, | 
|  | ModelTypeSet(THEMES), ready_task.Get()); | 
|  | RunLoop(); | 
|  |  | 
|  | Mock::VerifyAndClearExpectations(syncer()); | 
|  |  | 
|  | // Switch to NORMAL_MODE to ensure NUDGES were properly saved and run. | 
|  | scheduler()->OnReceivedPollIntervalUpdate(base::Days(1)); | 
|  | SyncShareTimes times2; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce( | 
|  | DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×2, true))); | 
|  |  | 
|  | StartSyncScheduler(base::Time()); | 
|  |  | 
|  | RunLoop(); | 
|  | Mock::VerifyAndClearExpectations(syncer()); | 
|  | } | 
|  |  | 
|  | class BackoffTriggersSyncSchedulerImplTest : public SyncSchedulerImplTest { | 
|  | void SetUp() override { | 
|  | SyncSchedulerImplTest::SetUp(); | 
|  | UseMockDelayProvider(); | 
|  | EXPECT_CALL(*delay(), GetDelay) | 
|  | .WillRepeatedly(Return(base::Milliseconds(10))); | 
|  | } | 
|  |  | 
|  | void TearDown() override { | 
|  | StopSyncScheduler(); | 
|  | SyncSchedulerImplTest::TearDown(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | // Have the syncer fail during commit.  Expect that the scheduler enters | 
|  | // backoff. | 
|  | TEST_F(BackoffTriggersSyncSchedulerImplTest, FailCommitOnce) { | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(Invoke(SimulateCommitFailed), QuitLoopNowAction(false))); | 
|  | 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(BackoffTriggersSyncSchedulerImplTest, FailDownloadOnceThenSucceed) { | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(Invoke(SimulateDownloadUpdatesFailed), Return(false))) | 
|  | .WillOnce(DoAll(Invoke(SimulateNormalSuccess), QuitLoopNowAction(true))); | 
|  | EXPECT_FALSE(RunAndGetBackoff()); | 
|  | } | 
|  |  | 
|  | // Have the syncer fail during commit and succeed on the first retry.  Expect | 
|  | // that this clears the backoff state. | 
|  | TEST_F(BackoffTriggersSyncSchedulerImplTest, FailCommitOnceThenSucceed) { | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(Invoke(SimulateCommitFailed), Return(false))) | 
|  | .WillOnce(DoAll(Invoke(SimulateNormalSuccess), QuitLoopNowAction(true))); | 
|  | 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(BackoffTriggersSyncSchedulerImplTest, FailDownloadTwice) { | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(Invoke(SimulateDownloadUpdatesFailed), Return(false))) | 
|  | .WillRepeatedly(DoAll(Invoke(SimulateDownloadUpdatesFailed), | 
|  | QuitLoopNowAction(false))); | 
|  | 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(BackoffTriggersSyncSchedulerImplTest, FailGetEncryptionKey) { | 
|  | EXPECT_CALL(*syncer(), ConfigureSyncShare) | 
|  | .WillOnce(DoAll(Invoke(SimulateGetEncryptionKeyFailed), Return(false))) | 
|  | .WillRepeatedly(DoAll(Invoke(SimulateGetEncryptionKeyFailed), | 
|  | QuitLoopNowAction(false))); | 
|  | StartSyncConfiguration(); | 
|  |  | 
|  | base::MockOnceClosure ready_task; | 
|  | EXPECT_CALL(ready_task, Run).Times(0); | 
|  | scheduler()->ScheduleConfiguration(sync_pb::SyncEnums::RECONFIGURATION, | 
|  | ModelTypeSet(THEMES), ready_task.Get()); | 
|  | RunLoop(); | 
|  |  | 
|  | EXPECT_TRUE(scheduler()->IsGlobalBackoff()); | 
|  | } | 
|  |  | 
|  | // Test that no polls or extraneous nudges occur when in backoff. | 
|  | TEST_F(SyncSchedulerImplTest, BackoffDropsJobs) { | 
|  | base::TimeDelta poll(base::Milliseconds(10)); | 
|  | scheduler()->OnReceivedPollIntervalUpdate(poll); | 
|  | UseMockDelayProvider(); | 
|  |  | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(Invoke(SimulateCommitFailed), | 
|  | RecordSyncShareMultiple(×, 1U, false))); | 
|  | EXPECT_CALL(*delay(), GetDelay).WillRepeatedly(Return(base::Days(1))); | 
|  |  | 
|  | StartSyncScheduler(base::Time()); | 
|  |  | 
|  | // This nudge should fail and put us into backoff.  Thanks to our mock | 
|  | // GetDelay() setup above, this will be a long backoff. | 
|  | const ModelType type = THEMES; | 
|  | scheduler()->ScheduleLocalNudge(type); | 
|  | 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. | 
|  | task_environment_.FastForwardBy(poll * 10); | 
|  |  | 
|  | // Try (and fail) to schedule a nudge. | 
|  | scheduler()->ScheduleLocalNudge(type); | 
|  |  | 
|  | Mock::VerifyAndClearExpectations(syncer()); | 
|  | Mock::VerifyAndClearExpectations(delay()); | 
|  |  | 
|  | EXPECT_CALL(*delay(), GetDelay).Times(0); | 
|  |  | 
|  | StartSyncConfiguration(); | 
|  |  | 
|  | base::MockOnceClosure ready_task; | 
|  | EXPECT_CALL(ready_task, Run).Times(0); | 
|  | scheduler()->ScheduleConfiguration(sync_pb::SyncEnums::RECONFIGURATION, | 
|  | ModelTypeSet(type), ready_task.Get()); | 
|  | PumpLoop(); | 
|  | } | 
|  |  | 
|  | // Test that backoff is shaping traffic properly with consecutive errors. | 
|  | TEST_F(SyncSchedulerImplTest, BackoffElevation) { | 
|  | UseMockDelayProvider(); | 
|  |  | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .Times(kMinNumSamples) | 
|  | .WillRepeatedly( | 
|  | DoAll(Invoke(SimulateCommitFailed), | 
|  | RecordSyncShareMultiple(×, kMinNumSamples, false))); | 
|  |  | 
|  | const base::TimeDelta first = kInitialBackoffRetryTime; | 
|  | const base::TimeDelta second = base::Milliseconds(20); | 
|  | const base::TimeDelta third = base::Milliseconds(30); | 
|  | const base::TimeDelta fourth = base::Milliseconds(40); | 
|  | const base::TimeDelta fifth = base::Milliseconds(50); | 
|  | const base::TimeDelta sixth = base::Days(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(base::Time()); | 
|  |  | 
|  | // Run again with a nudge. | 
|  | scheduler()->ScheduleLocalNudge(THEMES); | 
|  | 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(SyncSchedulerImplTest, BackoffRelief) { | 
|  | UseMockDelayProvider(); | 
|  |  | 
|  | const base::TimeDelta backoff = base::Milliseconds(10); | 
|  | EXPECT_CALL(*delay(), GetDelay).WillOnce(Return(backoff)); | 
|  |  | 
|  | // Optimal start for the post-backoff poll party. | 
|  | TimeTicks optimal_start = TimeTicks::Now(); | 
|  | StartSyncScheduler(base::Time()); | 
|  |  | 
|  | // Kick off the test with a failed nudge. | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce( | 
|  | DoAll(Invoke(SimulateCommitFailed), RecordSyncShare(×, false))); | 
|  | scheduler()->ScheduleLocalNudge(THEMES); | 
|  | 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(SimulateNormalSuccess), RecordSyncShare(×, true))); | 
|  | 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(SimulatePollSuccess), | 
|  | RecordSyncShareMultiple(×, kMinNumSamples, true))); | 
|  | const base::TimeDelta poll(base::Milliseconds(10)); | 
|  | scheduler()->OnReceivedPollIntervalUpdate(poll); | 
|  |  | 
|  | // The new optimal time is now, since the desired poll should have happened | 
|  | // in the past. | 
|  | optimal_job_time = TimeTicks::Now(); | 
|  | RunLoop(); | 
|  | Mock::VerifyAndClearExpectations(syncer()); | 
|  | ASSERT_EQ(kMinNumSamples, times.size()); | 
|  | for (size_t i = 2; i < times.size(); i++) { | 
|  | SCOPED_TRACE(testing::Message() << "SyncShare # (" << i << ")"); | 
|  | EXPECT_GE(times[i], optimal_job_time); | 
|  | optimal_job_time = optimal_job_time + poll; | 
|  | } | 
|  |  | 
|  | StopSyncScheduler(); | 
|  | } | 
|  |  | 
|  | // Test that poll failures are treated like any other failure. They should | 
|  | // result in retry with backoff. | 
|  | TEST_F(SyncSchedulerImplTest, TransientPollFailure) { | 
|  | scheduler()->OnReceivedPollIntervalUpdate(base::Milliseconds(10)); | 
|  | UseMockDelayProvider();  // Will cause test failure if backoff is initiated. | 
|  | EXPECT_CALL(*delay(), GetDelay).WillRepeatedly(Return(base::Milliseconds(0))); | 
|  |  | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), PollSyncShare) | 
|  | .WillOnce( | 
|  | DoAll(Invoke(SimulatePollFailed), RecordSyncShare(×, false))) | 
|  | .WillOnce( | 
|  | DoAll(Invoke(SimulatePollSuccess), RecordSyncShare(×, true))); | 
|  |  | 
|  | StartSyncScheduler(base::Time()); | 
|  |  | 
|  | // Run the unsuccessful poll. The failed poll should not trigger backoff. | 
|  | RunLoop(); | 
|  | EXPECT_TRUE(scheduler()->IsGlobalBackoff()); | 
|  |  | 
|  | // Run the successful poll. | 
|  | RunLoop(); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalBackoff()); | 
|  | } | 
|  |  | 
|  | // Test that starting the syncer thread without a valid connection doesn't | 
|  | // break things when a connection is detected. | 
|  | TEST_F(SyncSchedulerImplTest, StartWhenNotConnected) { | 
|  | connection()->SetServerNotReachable(); | 
|  | connection()->UpdateConnectionStatus(); | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(Invoke(SimulateConnectionFailure), Return(false))) | 
|  | .WillOnce(DoAll(Invoke(SimulateNormalSuccess), Return(true))); | 
|  | StartSyncScheduler(base::Time()); | 
|  |  | 
|  | scheduler()->ScheduleLocalNudge(THEMES); | 
|  | // Should save the nudge for until after the server is reachable. | 
|  | base::RunLoop().RunUntilIdle(); | 
|  |  | 
|  | scheduler()->OnConnectionStatusChange( | 
|  | network::mojom::ConnectionType::CONNECTION_WIFI); | 
|  | connection()->SetServerReachable(); | 
|  | connection()->UpdateConnectionStatus(); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | } | 
|  |  | 
|  | // Test that when disconnect signal (CONNECTION_NONE) is received, normal sync | 
|  | // share is not called. | 
|  | TEST_F(SyncSchedulerImplTest, SyncShareNotCalledWhenDisconnected) { | 
|  | // Set server unavailable, so SyncSchedulerImpl will try to fix connection | 
|  | // error upon OnConnectionStatusChange(). | 
|  | connection()->SetServerNotReachable(); | 
|  | connection()->UpdateConnectionStatus(); | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .Times(1) | 
|  | .WillOnce(DoAll(Invoke(SimulateConnectionFailure), Return(false))); | 
|  | StartSyncScheduler(base::Time()); | 
|  |  | 
|  | scheduler()->ScheduleLocalNudge(THEMES); | 
|  | // The nudge fails because of the connection failure. | 
|  | base::RunLoop().RunUntilIdle(); | 
|  |  | 
|  | // Simulate a disconnect signal. The scheduler should not retry the previously | 
|  | // failed nudge. | 
|  | scheduler()->OnConnectionStatusChange( | 
|  | network::mojom::ConnectionType::CONNECTION_NONE); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncSchedulerImplTest, ServerConnectionChangeDuringBackoff) { | 
|  | UseMockDelayProvider(); | 
|  | EXPECT_CALL(*delay(), GetDelay).WillRepeatedly(Return(base::Milliseconds(0))); | 
|  |  | 
|  | StartSyncScheduler(base::Time()); | 
|  | connection()->SetServerNotReachable(); | 
|  | connection()->UpdateConnectionStatus(); | 
|  |  | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(Invoke(SimulateConnectionFailure), Return(false))) | 
|  | .WillOnce(DoAll(Invoke(SimulateNormalSuccess), Return(true))); | 
|  |  | 
|  | scheduler()->ScheduleLocalNudge(THEMES); | 
|  | PumpLoop();  // To get PerformDelayedNudge called. | 
|  | PumpLoop();  // Run the nudge, that will fail and schedule a quick retry. | 
|  | ASSERT_TRUE(scheduler()->IsGlobalBackoff()); | 
|  |  | 
|  | // Before we run the scheduled canary, trigger a server connection change. | 
|  | scheduler()->OnConnectionStatusChange( | 
|  | network::mojom::ConnectionType::CONNECTION_WIFI); | 
|  | connection()->SetServerReachable(); | 
|  | connection()->UpdateConnectionStatus(); | 
|  | base::RunLoop().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(SyncSchedulerImplTest, ConnectionChangeCanaryPreemptedByNudge) { | 
|  | UseMockDelayProvider(); | 
|  | EXPECT_CALL(*delay(), GetDelay).WillRepeatedly(Return(base::Milliseconds(0))); | 
|  |  | 
|  | StartSyncScheduler(base::Time()); | 
|  | connection()->SetServerNotReachable(); | 
|  | connection()->UpdateConnectionStatus(); | 
|  |  | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(Invoke(SimulateConnectionFailure), Return(false))) | 
|  | .WillOnce(DoAll(Invoke(SimulateNormalSuccess), Return(true))) | 
|  | .WillOnce(DoAll(Invoke(SimulateNormalSuccess), QuitLoopNowAction(true))); | 
|  |  | 
|  | scheduler()->ScheduleLocalNudge(THEMES); | 
|  |  | 
|  | PumpLoop();  // To get PerformDelayedNudge called. | 
|  | PumpLoop();  // Run the nudge, that will fail and schedule a quick retry. | 
|  | ASSERT_TRUE(scheduler()->IsGlobalBackoff()); | 
|  |  | 
|  | // Before we run the scheduled canary, trigger a server connection change. | 
|  | scheduler()->OnConnectionStatusChange( | 
|  | network::mojom::ConnectionType::CONNECTION_WIFI); | 
|  | PumpLoop(); | 
|  | connection()->SetServerReachable(); | 
|  | connection()->UpdateConnectionStatus(); | 
|  | scheduler()->ScheduleLocalNudge(THEMES); | 
|  | base::RunLoop().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(SyncSchedulerImplTest, DoubleCanaryInConfigure) { | 
|  | EXPECT_CALL(*syncer(), ConfigureSyncShare) | 
|  | .WillRepeatedly( | 
|  | DoAll(Invoke(SimulateConfigureConnectionFailure), Return(true))); | 
|  | StartSyncConfiguration(); | 
|  | connection()->SetServerNotReachable(); | 
|  | connection()->UpdateConnectionStatus(); | 
|  |  | 
|  | scheduler()->ScheduleConfiguration(sync_pb::SyncEnums::RECONFIGURATION, | 
|  | ModelTypeSet(THEMES), base::DoNothing()); | 
|  |  | 
|  | scheduler()->OnConnectionStatusChange( | 
|  | network::mojom::ConnectionType::CONNECTION_WIFI); | 
|  | scheduler()->OnConnectionStatusChange( | 
|  | network::mojom::ConnectionType::CONNECTION_WIFI); | 
|  |  | 
|  | PumpLoop();  // Run the nudge, that will fail and schedule a quick retry. | 
|  | } | 
|  |  | 
|  | TEST_F(SyncSchedulerImplTest, PollFromCanaryAfterAuthError) { | 
|  | scheduler()->OnReceivedPollIntervalUpdate(base::Milliseconds(15)); | 
|  |  | 
|  | SyncShareTimes times; | 
|  | ::testing::InSequence seq; | 
|  | EXPECT_CALL(*syncer(), PollSyncShare) | 
|  | .WillRepeatedly( | 
|  | DoAll(Invoke(SimulatePollSuccess), | 
|  | RecordSyncShareMultiple(×, kMinNumSamples, true))); | 
|  |  | 
|  | connection()->SetServerResponse( | 
|  | HttpResponse::ForHttpStatusCode(net::HTTP_UNAUTHORIZED)); | 
|  | StartSyncScheduler(base::Time()); | 
|  |  | 
|  | // 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(SimulatePollSuccess), RecordSyncShare(×, true))); | 
|  | scheduler()->OnCredentialsUpdated(); | 
|  | connection()->SetServerResponse(HttpResponse::ForSuccessForTest()); | 
|  | RunLoop(); | 
|  | StopSyncScheduler(); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncSchedulerImplTest, SuccessfulRetry) { | 
|  | StartSyncScheduler(base::Time()); | 
|  |  | 
|  | base::TimeDelta delay = base::Milliseconds(10); | 
|  | scheduler()->OnReceivedGuRetryDelay(delay); | 
|  | EXPECT_EQ(delay, GetRetryTimerDelay()); | 
|  |  | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce( | 
|  | DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×, true))); | 
|  |  | 
|  | // Run to wait for retrying. | 
|  | RunLoop(); | 
|  |  | 
|  | StopSyncScheduler(); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncSchedulerImplTest, FailedRetry) { | 
|  | UseMockDelayProvider(); | 
|  | EXPECT_CALL(*delay(), GetDelay) | 
|  | .WillRepeatedly(Return(base::Milliseconds(10))); | 
|  |  | 
|  | StartSyncScheduler(base::Time()); | 
|  |  | 
|  | base::TimeDelta delay = base::Milliseconds(10); | 
|  | scheduler()->OnReceivedGuRetryDelay(delay); | 
|  |  | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(Invoke(SimulateDownloadUpdatesFailed), | 
|  | RecordSyncShare(×, false))); | 
|  |  | 
|  | // Run to wait for retrying. | 
|  | RunLoop(); | 
|  |  | 
|  | EXPECT_TRUE(scheduler()->IsGlobalBackoff()); | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce( | 
|  | DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×, true))); | 
|  |  | 
|  | // Run to wait for second retrying. | 
|  | RunLoop(); | 
|  |  | 
|  | StopSyncScheduler(); | 
|  | } | 
|  |  | 
|  | ACTION_P2(VerifyRetryTimerDelay, scheduler_test, expected_delay) { | 
|  | EXPECT_EQ(expected_delay, scheduler_test->GetRetryTimerDelay()); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncSchedulerImplTest, ReceiveNewRetryDelay) { | 
|  | StartSyncScheduler(base::Time()); | 
|  |  | 
|  | base::TimeDelta delay1 = base::Milliseconds(100); | 
|  | base::TimeDelta delay2 = base::Milliseconds(200); | 
|  |  | 
|  | scheduler()->ScheduleLocalNudge(THEMES); | 
|  | scheduler()->OnReceivedGuRetryDelay(delay1); | 
|  | EXPECT_EQ(delay1, GetRetryTimerDelay()); | 
|  |  | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(WithoutArgs(VerifyRetryTimerDelay(this, delay1)), | 
|  | WithArg<2>(SimulateGuRetryDelayCommand(delay2)), | 
|  | RecordSyncShare(×, true))); | 
|  |  | 
|  | // Run nudge GU. | 
|  | RunLoop(); | 
|  | EXPECT_EQ(delay2, GetRetryTimerDelay()); | 
|  |  | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce( | 
|  | DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×, true))); | 
|  |  | 
|  | // Run to wait for retrying. | 
|  | RunLoop(); | 
|  |  | 
|  | StopSyncScheduler(); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncSchedulerImplTest, PartialFailureWillExponentialBackoff) { | 
|  | scheduler()->OnReceivedPollIntervalUpdate(base::Days(1)); | 
|  |  | 
|  | const ModelType type = THEMES; | 
|  |  | 
|  | ::testing::InSequence seq; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillRepeatedly( | 
|  | DoAll(WithArg<2>(SimulatePartialFailure(type)), Return(true))) | 
|  | .RetiresOnSaturation(); | 
|  |  | 
|  | StartSyncScheduler(base::Time()); | 
|  | scheduler()->ScheduleLocalNudge(type); | 
|  | PumpLoop();  // To get PerformDelayedNudge called. | 
|  | PumpLoop();  // To get TrySyncCycleJob called | 
|  | EXPECT_TRUE(GetBackedOffTypes().Has(type)); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalBackoff()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalThrottle()); | 
|  | base::TimeDelta first_blocking_time = GetTypeBlockingTime(THEMES); | 
|  |  | 
|  | SetTypeBlockingMode(THEMES, | 
|  | WaitInterval::BlockingMode::kExponentialBackoffRetrying); | 
|  | // This won't cause a sync cycle because the types are backed off. | 
|  | scheduler()->ScheduleLocalNudge(type); | 
|  | PumpLoop(); | 
|  | PumpLoop(); | 
|  | base::TimeDelta second_blocking_time = GetTypeBlockingTime(THEMES); | 
|  |  | 
|  | // The Exponential backoff should be between previous backoff 1.5 and 2.5 | 
|  | // times. | 
|  | EXPECT_LE(first_blocking_time * 1.5, second_blocking_time); | 
|  | EXPECT_GE(first_blocking_time * 2.5, second_blocking_time); | 
|  |  | 
|  | StopSyncScheduler(); | 
|  | } | 
|  |  | 
|  | // If a datatype is in backoff or throttling, pending_wakeup_timer_ should | 
|  | // schedule a delay job for OnTypesUnblocked. SyncScheduler sometimes use | 
|  | // pending_wakeup_timer_ to schdule PerformDelayedNudge job before | 
|  | // OnTypesUnblocked got run. This test will verify after ran | 
|  | // PerformDelayedNudge, OnTypesUnblocked will be rescheduled if any datatype is | 
|  | // in backoff or throttling. | 
|  | TEST_F(SyncSchedulerImplTest, TypeBackoffAndSuccessfulSync) { | 
|  | UseMockDelayProvider(); | 
|  | EXPECT_CALL(*delay(), GetDelay).WillRepeatedly(Return(long_delay())); | 
|  |  | 
|  | scheduler()->OnReceivedPollIntervalUpdate(base::Days(1)); | 
|  |  | 
|  | const ModelType type = THEMES; | 
|  |  | 
|  | // Set backoff datatype. | 
|  | ::testing::InSequence seq; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(WithArg<2>(SimulatePartialFailure(type)), Return(true))) | 
|  | .RetiresOnSaturation(); | 
|  |  | 
|  | StartSyncScheduler(base::Time()); | 
|  | scheduler()->ScheduleLocalNudge(type); | 
|  | PumpLoop();  // To get PerformDelayedNudge called. | 
|  | PumpLoop();  // To get TrySyncCycleJob called | 
|  | EXPECT_TRUE(GetBackedOffTypes().Has(type)); | 
|  | EXPECT_TRUE(BlockTimerIsRunning()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalBackoff()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalThrottle()); | 
|  |  | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce( | 
|  | DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×, true))) | 
|  | .RetiresOnSaturation(); | 
|  |  | 
|  | // Do a successful Sync. | 
|  | scheduler()->ScheduleLocalNudge(TYPED_URLS); | 
|  | PumpLoop();  // TO get PerformDelayedNudge called. | 
|  | PumpLoop();  // To get TrySyncCycleJob called. | 
|  |  | 
|  | // Timer is still running for backoff datatype after Sync success. | 
|  | EXPECT_TRUE(GetBackedOffTypes().Has(type)); | 
|  | EXPECT_TRUE(BlockTimerIsRunning()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalBackoff()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalThrottle()); | 
|  |  | 
|  | StopSyncScheduler(); | 
|  | } | 
|  |  | 
|  | // Verify that the timer is scheduled for an unblock job after one datatype is | 
|  | // unblocked, and there is another one still blocked. | 
|  | TEST_F(SyncSchedulerImplTest, TypeBackingOffAndFailureSync) { | 
|  | UseMockDelayProvider(); | 
|  | EXPECT_CALL(*delay(), GetDelay) | 
|  | .WillOnce(Return(long_delay())) | 
|  | .RetiresOnSaturation(); | 
|  |  | 
|  | scheduler()->OnReceivedPollIntervalUpdate(base::Days(1)); | 
|  |  | 
|  | // Set a backoff datatype. | 
|  | const ModelType backed_off_type = THEMES; | 
|  | ::testing::InSequence seq; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(WithArg<2>(SimulatePartialFailure(backed_off_type)), | 
|  | Return(true))) | 
|  | .RetiresOnSaturation(); | 
|  |  | 
|  | StartSyncScheduler(base::Time()); | 
|  | scheduler()->ScheduleLocalNudge(backed_off_type); | 
|  | PumpLoop();  // To get PerformDelayedNudge called. | 
|  | PumpLoop();  // To get TrySyncCycleJob called | 
|  | EXPECT_TRUE(GetBackedOffTypes().Has(backed_off_type)); | 
|  | EXPECT_TRUE(BlockTimerIsRunning()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalBackoff()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalThrottle()); | 
|  |  | 
|  | // Set anther backoff datatype. | 
|  | const ModelType backed_off_type2 = TYPED_URLS; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce(DoAll(WithArg<2>(SimulatePartialFailure(backed_off_type2)), | 
|  | Return(true))) | 
|  | .RetiresOnSaturation(); | 
|  | EXPECT_CALL(*delay(), GetDelay) | 
|  | .WillOnce(Return(default_delay())) | 
|  | .RetiresOnSaturation(); | 
|  |  | 
|  | scheduler()->ScheduleLocalNudge(backed_off_type2); | 
|  | PumpLoop();  // TO get PerformDelayedNudge called. | 
|  | PumpLoop();  // To get TrySyncCycleJob called. | 
|  |  | 
|  | EXPECT_TRUE(GetBackedOffTypes().Has(backed_off_type)); | 
|  | EXPECT_TRUE(GetBackedOffTypes().Has(backed_off_type2)); | 
|  | EXPECT_TRUE(BlockTimerIsRunning()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalBackoff()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalThrottle()); | 
|  |  | 
|  | // Unblock one datatype. | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillRepeatedly( | 
|  | DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×, true))); | 
|  | EXPECT_CALL(*delay(), GetDelay).WillRepeatedly(Return(long_delay())); | 
|  |  | 
|  | PumpLoop();  // TO get OnTypesUnblocked called. | 
|  | PumpLoop();  // To get TrySyncCycleJob called. | 
|  |  | 
|  | // Timer is still scheduled for another backoff datatype. | 
|  | EXPECT_TRUE(GetBackedOffTypes().Has(backed_off_type)); | 
|  | EXPECT_FALSE(GetBackedOffTypes().Has(backed_off_type2)); | 
|  | EXPECT_TRUE(BlockTimerIsRunning()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalBackoff()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalThrottle()); | 
|  |  | 
|  | StopSyncScheduler(); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncSchedulerImplTest, InterleavedNudgesStillRestart) { | 
|  | UseMockDelayProvider(); | 
|  | EXPECT_CALL(*delay(), GetDelay) | 
|  | .WillOnce(Return(long_delay())) | 
|  | .RetiresOnSaturation(); | 
|  | scheduler()->OnReceivedPollIntervalUpdate(base::Days(1)); | 
|  |  | 
|  | StartSyncScheduler(base::Time()); | 
|  | scheduler()->ScheduleLocalNudge(THEMES); | 
|  | PumpLoop();  // To get PerformDelayedNudge called. | 
|  | EXPECT_FALSE(BlockTimerIsRunning()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalBackoff()); | 
|  |  | 
|  | // This is the tricky piece. We have a gap while the sync job is bouncing to | 
|  | // get onto the |pending_wakeup_timer_|, should be scheduled with no delay. | 
|  | scheduler()->ScheduleLocalNudge(TYPED_URLS); | 
|  | EXPECT_TRUE(BlockTimerIsRunning()); | 
|  | EXPECT_EQ(base::TimeDelta(), GetPendingWakeupTimerDelay()); | 
|  | EXPECT_FALSE(scheduler()->IsGlobalBackoff()); | 
|  |  | 
|  | // Setup mock as we're about to attempt to sync. | 
|  | SyncShareTimes times; | 
|  | EXPECT_CALL(*syncer(), NormalSyncShare) | 
|  | .WillOnce( | 
|  | DoAll(Invoke(SimulateCommitFailed), RecordSyncShare(×, false))); | 
|  | // Triggers the THEMES TrySyncCycleJobImpl(), which we've setup to fail. Its | 
|  | // RestartWaiting won't schedule a delayed retry, as the TYPED_URLS nudge has | 
|  | // a smaller delay. We verify this by making sure the delay is still zero. | 
|  | PumpLoop(); | 
|  | EXPECT_TRUE(BlockTimerIsRunning()); | 
|  | EXPECT_EQ(base::TimeDelta(), GetPendingWakeupTimerDelay()); | 
|  | EXPECT_TRUE(scheduler()->IsGlobalBackoff()); | 
|  |  | 
|  | // Triggers TYPED_URLS PerformDelayedNudge(), which should no-op, because | 
|  | // we're no long healthy, and normal priorities shouldn't go through, but it | 
|  | // does need to setup the |pending_wakeup_timer_|. The delay should be ~60 | 
|  | // seconds, so verifying it's greater than 50 should be safe. | 
|  | PumpLoop(); | 
|  | EXPECT_TRUE(BlockTimerIsRunning()); | 
|  | EXPECT_LT(base::Seconds(50), GetPendingWakeupTimerDelay()); | 
|  | EXPECT_TRUE(scheduler()->IsGlobalBackoff()); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncSchedulerImplTest, PollOnStartUpAfterLongPause) { | 
|  | base::Time now = base::Time::Now(); | 
|  | base::TimeDelta poll_interval = base::Hours(4); | 
|  | base::Time last_reset = ComputeLastPollOnStart( | 
|  | /*last_poll=*/now - base::Days(1), poll_interval, now); | 
|  | EXPECT_THAT(last_reset, Gt(now - poll_interval)); | 
|  | // The max poll delay is 1% of the poll_interval. | 
|  | EXPECT_THAT(last_reset, Lt(now - 0.99 * poll_interval)); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncSchedulerImplTest, PollOnStartUpAfterShortPause) { | 
|  | base::Time now = base::Time::Now(); | 
|  | base::TimeDelta poll_interval = base::Hours(4); | 
|  | base::Time last_poll = now - base::Hours(2); | 
|  | EXPECT_THAT(ComputeLastPollOnStart(last_poll, poll_interval, now), | 
|  | Eq(last_poll)); | 
|  | } | 
|  |  | 
|  | // Verifies that the delay is in [0, 0.01*poll_interval) and spot checks the | 
|  | // random number generation. | 
|  | TEST_F(SyncSchedulerImplTest, PollOnStartUpWithinBoundsAfterLongPause) { | 
|  | base::Time now = base::Time::Now(); | 
|  | base::TimeDelta poll_interval = base::Hours(4); | 
|  | base::Time last_poll = now - base::Days(2); | 
|  | bool found_delay_greater_than_5_permille = false; | 
|  | bool found_delay_less_or_equal_5_permille = false; | 
|  | for (int i = 0; i < 10000; ++i) { | 
|  | const base::Time result = | 
|  | ComputeLastPollOnStart(last_poll, poll_interval, now); | 
|  | const base::TimeDelta delay = result + poll_interval - now; | 
|  | const double fraction = delay / poll_interval; | 
|  | if (fraction > 0.005) { | 
|  | found_delay_greater_than_5_permille = true; | 
|  | } else { | 
|  | found_delay_less_or_equal_5_permille = true; | 
|  | } | 
|  | EXPECT_THAT(fraction, Ge(0)); | 
|  | EXPECT_THAT(fraction, Lt(0.01)); | 
|  | } | 
|  | EXPECT_TRUE(found_delay_greater_than_5_permille); | 
|  | EXPECT_TRUE(found_delay_less_or_equal_5_permille); | 
|  | } | 
|  |  | 
|  | TEST_F(SyncSchedulerImplTest, TestResetPollIntervalOnStartFeatureFlag) { | 
|  | base::test::ScopedFeatureList feature_list; | 
|  | feature_list.InitAndEnableFeature(kSyncResetPollIntervalOnStart); | 
|  | base::Time now = base::Time::Now(); | 
|  | EXPECT_THAT(ComputeLastPollOnStart( | 
|  | /*last_poll=*/now - base::Days(1), | 
|  | /*poll_interval=*/base::Hours(4), now), | 
|  | Eq(now)); | 
|  | } | 
|  |  | 
|  | }  // namespace syncer |