#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() {
base::RunLoop::QuitCurrentDeprecated();
}
void RunLoop() {
base::RunLoop().Run();
}
void PumpLoop() {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&QuitLoopNow));
RunLoop();
}
static const size_t kMinNumSamples = 5;
}
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",
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>>();
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);
}
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>>();
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();
}
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_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());
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);
DisconnectDataType(HISTORY_DELETE_DIRECTIVES);
ASSERT_FALSE(context()->GetConnectedTypes().Has(HISTORY_DELETE_DIRECTIVES));
EXPECT_CALL(*syncer(), NormalSyncShare).Times(0);
PumpLoop();
}
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();
}
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();
Mock::VerifyAndClearExpectations(syncer());
EXPECT_CALL(*syncer(), ConfigureSyncShare)
.WillOnce(DoAll(Invoke(SimulateConfigureSuccess),
RecordSyncShare(×, true)));
RunLoop();
}
TEST_F(SyncSchedulerImplTest, ConfigWithStop) {
UseMockDelayProvider();
EXPECT_CALL(*delay(), GetDelay)
.WillRepeatedly(Return(base::Milliseconds(20)));
StartSyncConfiguration();
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();
}
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();
}
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();
}
TEST_F(SyncSchedulerImplTest, NudgeWithConfigWithBackingOff) {
UseMockDelayProvider();
EXPECT_CALL(*delay(), GetDelay)
.WillRepeatedly(Return(base::Milliseconds(50)));
StartSyncConfiguration();
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);
EXPECT_CALL(*syncer(), ConfigureSyncShare)
.WillOnce(DoAll(Invoke(SimulateConfigureFailed),
RecordSyncShare(×, false)));
scheduler()->ScheduleLocalNudge(model_type);
RunLoop();
Mock::VerifyAndClearExpectations(syncer());
EXPECT_CALL(*syncer(), ConfigureSyncShare)
.WillOnce(DoAll(Invoke(SimulateConfigureSuccess),
RecordSyncShare(×, true)));
RunLoop();
EXPECT_CALL(*syncer(), NormalSyncShare)
.WillOnce(
DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×, true)));
StartSyncScheduler(base::Time());
PumpLoop();
}
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_F(SyncSchedulerImplTest, NudgeCoalescingWithDifferentTimings) {
StartSyncScheduler(base::Time());
SyncShareTimes times;
EXPECT_CALL(*syncer(), NormalSyncShare)
.WillOnce(
DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×, true)));
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());
ASSERT_EQ(1U, times.size());
EXPECT_GE(times[0], min_time);
EXPECT_LE(times[0], max_time);
}
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());
SyncShareTimes times2;
EXPECT_CALL(*syncer(), NormalSyncShare)
.WillOnce(
DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×2, true)));
scheduler()->SetHasPendingInvalidations(TYPED_URLS, true);
scheduler()->ScheduleInvalidationNudge(TYPED_URLS);
RunLoop();
}
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());
RunLoop();
StopSyncScheduler();
AnalyzePollRun(times, kMinNumSamples, optimal_start, poll_interval);
}
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());
RunLoop();
StopSyncScheduler();
AnalyzePollRun(times, kMinNumSamples, optimal_start, poll_interval);
}
TEST_F(SyncSchedulerImplTest, PollingPersistence) {
SyncShareTimes times;
EXPECT_CALL(*syncer(), PollSyncShare)
.Times(AtLeast(kMinNumSamples))
.WillRepeatedly(
DoAll(Invoke(SimulatePollSuccess),
RecordSyncShareMultiple(×, kMinNumSamples, true)));
base::TimeDelta poll_interval(base::Milliseconds(500));
scheduler()->OnReceivedPollIntervalUpdate(poll_interval);
TimeTicks optimal_start = TimeTicks::Now();
StartSyncScheduler(base::Time::Now() - poll_interval);
RunLoop();
StopSyncScheduler();
AnalyzePollRun(times, kMinNumSamples, optimal_start, poll_interval);
}
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);
TimeTicks optimal_start = TimeTicks::Now() + poll_interval;
StartSyncScheduler(base::Time::Now() + base::Minutes(10));
RunLoop();
StopSyncScheduler();
AnalyzePollRun(times, kMinNumSamples, optimal_start, poll_interval);
}
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());
RunLoop();
StopSyncScheduler();
AnalyzePollRun(times, kMinNumSamples, optimal_start, poll2);
}
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());
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();
PumpLoop();
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();
PumpLoop();
EXPECT_TRUE(GetThrottledTypes().Has(type));
EXPECT_FALSE(scheduler()->IsGlobalBackoff());
EXPECT_FALSE(scheduler()->IsGlobalThrottle());
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();
PumpLoop();
EXPECT_TRUE(GetBackedOffTypes().Has(type));
EXPECT_FALSE(scheduler()->IsGlobalBackoff());
EXPECT_FALSE(scheduler()->IsGlobalThrottle());
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();
PumpLoop();
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();
PumpLoop();
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();
PumpLoop();
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();
scheduler()->ScheduleLocalNudge(TYPED_URLS);
PumpLoop();
PumpLoop();
EXPECT_TRUE(GetBackedOffTypes().Has(type));
EXPECT_TRUE(BlockTimerIsRunning());
EXPECT_FALSE(scheduler()->IsGlobalBackoff());
EXPECT_TRUE(scheduler()->IsGlobalThrottle());
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();
PumpLoop();
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();
PumpLoop();
EXPECT_TRUE(GetThrottledTypes().Has(throttled_type));
EXPECT_TRUE(GetBackedOffTypes().Has(backed_off_type));
EXPECT_TRUE(BlockTimerIsRunning());
EXPECT_FALSE(scheduler()->IsGlobalBackoff());
EXPECT_FALSE(scheduler()->IsGlobalThrottle());
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();
PumpLoop();
EXPECT_TRUE(GetThrottledTypes().Has(throttled_type));
EXPECT_FALSE(scheduler()->IsGlobalBackoff());
EXPECT_FALSE(scheduler()->IsGlobalThrottle());
scheduler()->ScheduleInvalidationNudge(throttled_type);
PumpLoop();
scheduler()->ScheduleLocalRefreshRequest(ModelTypeSet(throttled_type));
PumpLoop();
Mock::VerifyAndClearExpectations(syncer());
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();
PumpLoop();
EXPECT_TRUE(GetBackedOffTypes().Has(backed_off_type));
EXPECT_FALSE(scheduler()->IsGlobalBackoff());
EXPECT_FALSE(scheduler()->IsGlobalThrottle());
scheduler()->ScheduleInvalidationNudge(backed_off_type);
PumpLoop();
scheduler()->ScheduleLocalRefreshRequest(ModelTypeSet(backed_off_type));
PumpLoop();
Mock::VerifyAndClearExpectations(syncer());
SyncShareTimes times;
EXPECT_CALL(*syncer(), NormalSyncShare)
.WillRepeatedly(
DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×, true)));
scheduler()->ScheduleLocalNudge(PREFERENCES);
RunLoop();
Mock::VerifyAndClearExpectations(syncer());
StopSyncScheduler();
}
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());
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();
}
};
TEST_F(BackoffTriggersSyncSchedulerImplTest, FailCommitOnce) {
EXPECT_CALL(*syncer(), NormalSyncShare)
.WillOnce(DoAll(Invoke(SimulateCommitFailed), QuitLoopNowAction(false)));
EXPECT_TRUE(RunAndGetBackoff());
}
TEST_F(BackoffTriggersSyncSchedulerImplTest, FailDownloadOnceThenSucceed) {
EXPECT_CALL(*syncer(), NormalSyncShare)
.WillOnce(DoAll(Invoke(SimulateDownloadUpdatesFailed), Return(false)))
.WillOnce(DoAll(Invoke(SimulateNormalSuccess), QuitLoopNowAction(true)));
EXPECT_FALSE(RunAndGetBackoff());
}
TEST_F(BackoffTriggersSyncSchedulerImplTest, FailCommitOnceThenSucceed) {
EXPECT_CALL(*syncer(), NormalSyncShare)
.WillOnce(DoAll(Invoke(SimulateCommitFailed), Return(false)))
.WillOnce(DoAll(Invoke(SimulateNormalSuccess), QuitLoopNowAction(true)));
EXPECT_FALSE(RunAndGetBackoff());
}
TEST_F(BackoffTriggersSyncSchedulerImplTest, FailDownloadTwice) {
EXPECT_CALL(*syncer(), NormalSyncShare)
.WillOnce(DoAll(Invoke(SimulateDownloadUpdatesFailed), Return(false)))
.WillRepeatedly(DoAll(Invoke(SimulateDownloadUpdatesFailed),
QuitLoopNowAction(false)));
EXPECT_TRUE(RunAndGetBackoff());
}
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_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());
const ModelType type = THEMES;
scheduler()->ScheduleLocalNudge(type);
RunLoop();
Mock::VerifyAndClearExpectations(syncer());
task_environment_.FastForwardBy(poll * 10);
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_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());
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_F(SyncSchedulerImplTest, BackoffRelief) {
UseMockDelayProvider();
const base::TimeDelta backoff = base::Milliseconds(10);
EXPECT_CALL(*delay(), GetDelay).WillOnce(Return(backoff));
TimeTicks optimal_start = TimeTicks::Now();
StartSyncScheduler(base::Time());
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);
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);
EXPECT_CALL(*syncer(), PollSyncShare)
.WillRepeatedly(
DoAll(Invoke(SimulatePollSuccess),
RecordSyncShareMultiple(×, kMinNumSamples, true)));
const base::TimeDelta poll(base::Milliseconds(10));
scheduler()->OnReceivedPollIntervalUpdate(poll);
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_F(SyncSchedulerImplTest, TransientPollFailure) {
scheduler()->OnReceivedPollIntervalUpdate(base::Milliseconds(10));
UseMockDelayProvider();
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());
RunLoop();
EXPECT_TRUE(scheduler()->IsGlobalBackoff());
RunLoop();
EXPECT_FALSE(scheduler()->IsGlobalBackoff());
}
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);
base::RunLoop().RunUntilIdle();
scheduler()->OnConnectionStatusChange(
network::mojom::ConnectionType::CONNECTION_WIFI);
connection()->SetServerReachable();
connection()->UpdateConnectionStatus();
base::RunLoop().RunUntilIdle();
}
TEST_F(SyncSchedulerImplTest, SyncShareNotCalledWhenDisconnected) {
connection()->SetServerNotReachable();
connection()->UpdateConnectionStatus();
EXPECT_CALL(*syncer(), NormalSyncShare)
.Times(1)
.WillOnce(DoAll(Invoke(SimulateConnectionFailure), Return(false)));
StartSyncScheduler(base::Time());
scheduler()->ScheduleLocalNudge(THEMES);
base::RunLoop().RunUntilIdle();
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();
PumpLoop();
ASSERT_TRUE(scheduler()->IsGlobalBackoff());
scheduler()->OnConnectionStatusChange(
network::mojom::ConnectionType::CONNECTION_WIFI);
connection()->SetServerReachable();
connection()->UpdateConnectionStatus();
base::RunLoop().RunUntilIdle();
}
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();
PumpLoop();
ASSERT_TRUE(scheduler()->IsGlobalBackoff());
scheduler()->OnConnectionStatusChange(
network::mojom::ConnectionType::CONNECTION_WIFI);
PumpLoop();
connection()->SetServerReachable();
connection()->UpdateConnectionStatus();
scheduler()->ScheduleLocalNudge(THEMES);
base::RunLoop().RunUntilIdle();
}
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();
}
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());
RunLoop();
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)));
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)));
RunLoop();
EXPECT_TRUE(scheduler()->IsGlobalBackoff());
EXPECT_CALL(*syncer(), NormalSyncShare)
.WillOnce(
DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×, true)));
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)));
RunLoop();
EXPECT_EQ(delay2, GetRetryTimerDelay());
EXPECT_CALL(*syncer(), NormalSyncShare)
.WillOnce(
DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×, true)));
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();
PumpLoop();
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);
scheduler()->ScheduleLocalNudge(type);
PumpLoop();
PumpLoop();
base::TimeDelta second_blocking_time = GetTypeBlockingTime(THEMES);
EXPECT_LE(first_blocking_time * 1.5, second_blocking_time);
EXPECT_GE(first_blocking_time * 2.5, second_blocking_time);
StopSyncScheduler();
}
TEST_F(SyncSchedulerImplTest, TypeBackoffAndSuccessfulSync) {
UseMockDelayProvider();
EXPECT_CALL(*delay(), GetDelay).WillRepeatedly(Return(long_delay()));
scheduler()->OnReceivedPollIntervalUpdate(base::Days(1));
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();
PumpLoop();
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();
scheduler()->ScheduleLocalNudge(TYPED_URLS);
PumpLoop();
PumpLoop();
EXPECT_TRUE(GetBackedOffTypes().Has(type));
EXPECT_TRUE(BlockTimerIsRunning());
EXPECT_FALSE(scheduler()->IsGlobalBackoff());
EXPECT_FALSE(scheduler()->IsGlobalThrottle());
StopSyncScheduler();
}
TEST_F(SyncSchedulerImplTest, TypeBackingOffAndFailureSync) {
UseMockDelayProvider();
EXPECT_CALL(*delay(), GetDelay)
.WillOnce(Return(long_delay()))
.RetiresOnSaturation();
scheduler()->OnReceivedPollIntervalUpdate(base::Days(1));
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();
PumpLoop();
EXPECT_TRUE(GetBackedOffTypes().Has(backed_off_type));
EXPECT_TRUE(BlockTimerIsRunning());
EXPECT_FALSE(scheduler()->IsGlobalBackoff());
EXPECT_FALSE(scheduler()->IsGlobalThrottle());
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();
PumpLoop();
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());
SyncShareTimes times;
EXPECT_CALL(*syncer(), NormalSyncShare)
.WillRepeatedly(
DoAll(Invoke(SimulateNormalSuccess), RecordSyncShare(×, true)));
EXPECT_CALL(*delay(), GetDelay).WillRepeatedly(Return(long_delay()));
PumpLoop();
PumpLoop();
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();
EXPECT_FALSE(BlockTimerIsRunning());
EXPECT_FALSE(scheduler()->IsGlobalBackoff());
scheduler()->ScheduleLocalNudge(TYPED_URLS);
EXPECT_TRUE(BlockTimerIsRunning());
EXPECT_EQ(base::TimeDelta(), GetPendingWakeupTimerDelay());
EXPECT_FALSE(scheduler()->IsGlobalBackoff());
SyncShareTimes times;
EXPECT_CALL(*syncer(), NormalSyncShare)
.WillOnce(
DoAll(Invoke(SimulateCommitFailed), RecordSyncShare(×, false)));
PumpLoop();
EXPECT_TRUE(BlockTimerIsRunning());
EXPECT_EQ(base::TimeDelta(), GetPendingWakeupTimerDelay());
EXPECT_TRUE(scheduler()->IsGlobalBackoff());
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(
now - base::Days(1), poll_interval, now);
EXPECT_THAT(last_reset, Gt(now - 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));
}
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(
now - base::Days(1),
base::Hours(4), now),
Eq(now));
}
}