blob: db3ea8c5083e2fd585c0f21d3d8b6f2547374ea3 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sync/engine/get_updates_processor.h"
#include <stdint.h>
#include <string>
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/stl_util.h"
#include "sync/engine/get_updates_delegate.h"
#include "sync/engine/update_handler.h"
#include "sync/internal_api/public/base/model_type_test_util.h"
#include "sync/protocol/sync.pb.h"
#include "sync/sessions/debug_info_getter.h"
#include "sync/sessions/nudge_tracker.h"
#include "sync/sessions/status_controller.h"
#include "sync/test/engine/fake_model_worker.h"
#include "sync/test/engine/mock_update_handler.h"
#include "sync/test/mock_invalidation.h"
#include "sync/test/sessions/mock_debug_info_getter.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace syncer {
namespace {
scoped_ptr<InvalidationInterface> BuildInvalidation(
int64_t version,
const std::string& payload) {
return MockInvalidation::Build(version, payload);
}
} // namespace
using sessions::MockDebugInfoGetter;
// A test fixture for tests exercising download updates functions.
class GetUpdatesProcessorTest : public ::testing::Test {
protected:
GetUpdatesProcessorTest() :
kTestStartTime(base::TimeTicks::Now()),
update_handler_deleter_(&update_handler_map_) {}
void SetUp() override {
AddUpdateHandler(AUTOFILL);
AddUpdateHandler(BOOKMARKS);
AddUpdateHandler(PREFERENCES);
}
ModelTypeSet enabled_types() {
return enabled_types_;
}
scoped_ptr<GetUpdatesProcessor> BuildGetUpdatesProcessor(
const GetUpdatesDelegate& delegate) {
return scoped_ptr<GetUpdatesProcessor>(
new GetUpdatesProcessor(&update_handler_map_, delegate));
}
void InitFakeUpdateResponse(sync_pb::GetUpdatesResponse* response) {
ModelTypeSet types = enabled_types();
for (ModelTypeSet::Iterator it = types.First(); it.Good(); it.Inc()) {
sync_pb::DataTypeProgressMarker* marker =
response->add_new_progress_marker();
marker->set_data_type_id(GetSpecificsFieldNumberFromModelType(it.Get()));
marker->set_token("foobarbaz");
sync_pb::DataTypeContext* context = response->add_context_mutations();
context->set_data_type_id(GetSpecificsFieldNumberFromModelType(it.Get()));
context->set_version(1);
context->set_context("context");
}
response->set_changes_remaining(0);
}
const UpdateHandler* GetHandler(ModelType type) {
UpdateHandlerMap::iterator it = update_handler_map_.find(type);
if (it == update_handler_map_.end())
return NULL;
return it->second;
}
const base::TimeTicks kTestStartTime;
protected:
MockUpdateHandler* AddUpdateHandler(ModelType type) {
enabled_types_.Put(type);
MockUpdateHandler* handler = new MockUpdateHandler(type);
update_handler_map_.insert(std::make_pair(type, handler));
return handler;
}
private:
ModelTypeSet enabled_types_;
UpdateHandlerMap update_handler_map_;
STLValueDeleter<UpdateHandlerMap> update_handler_deleter_;
scoped_ptr<GetUpdatesProcessor> get_updates_processor_;
DISALLOW_COPY_AND_ASSIGN(GetUpdatesProcessorTest);
};
// Basic test to make sure nudges are expressed properly in the request.
TEST_F(GetUpdatesProcessorTest, BookmarkNudge) {
sessions::NudgeTracker nudge_tracker;
nudge_tracker.RecordLocalChange(ModelTypeSet(BOOKMARKS));
sync_pb::ClientToServerMessage message;
NormalGetUpdatesDelegate normal_delegate(nudge_tracker);
scoped_ptr<GetUpdatesProcessor> processor(
BuildGetUpdatesProcessor(normal_delegate));
processor->PrepareGetUpdates(enabled_types(), &message);
const sync_pb::GetUpdatesMessage& gu_msg = message.get_updates();
EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::LOCAL,
gu_msg.caller_info().source());
EXPECT_EQ(sync_pb::SyncEnums::GU_TRIGGER, gu_msg.get_updates_origin());
for (int i = 0; i < gu_msg.from_progress_marker_size(); ++i) {
syncer::ModelType type = GetModelTypeFromSpecificsFieldNumber(
gu_msg.from_progress_marker(i).data_type_id());
const sync_pb::DataTypeProgressMarker& progress_marker =
gu_msg.from_progress_marker(i);
const sync_pb::GetUpdateTriggers& gu_trigger =
progress_marker.get_update_triggers();
// We perform some basic tests of GU trigger and source fields here. The
// more complicated scenarios are tested by the NudgeTracker tests.
if (type == BOOKMARKS) {
EXPECT_TRUE(progress_marker.has_notification_hint());
EXPECT_EQ("", progress_marker.notification_hint());
EXPECT_EQ(1, gu_trigger.local_modification_nudges());
EXPECT_EQ(0, gu_trigger.datatype_refresh_nudges());
} else {
EXPECT_FALSE(progress_marker.has_notification_hint());
EXPECT_EQ(0, gu_trigger.local_modification_nudges());
EXPECT_EQ(0, gu_trigger.datatype_refresh_nudges());
}
}
}
// Basic test to ensure invalidation payloads are expressed in the request.
TEST_F(GetUpdatesProcessorTest, NotifyMany) {
sessions::NudgeTracker nudge_tracker;
nudge_tracker.RecordRemoteInvalidation(
AUTOFILL, BuildInvalidation(1, "autofill_payload"));
nudge_tracker.RecordRemoteInvalidation(
BOOKMARKS, BuildInvalidation(1, "bookmark_payload"));
nudge_tracker.RecordRemoteInvalidation(
PREFERENCES, BuildInvalidation(1, "preferences_payload"));
ModelTypeSet notified_types;
notified_types.Put(AUTOFILL);
notified_types.Put(BOOKMARKS);
notified_types.Put(PREFERENCES);
sync_pb::ClientToServerMessage message;
NormalGetUpdatesDelegate normal_delegate(nudge_tracker);
scoped_ptr<GetUpdatesProcessor> processor(
BuildGetUpdatesProcessor(normal_delegate));
processor->PrepareGetUpdates(enabled_types(), &message);
const sync_pb::GetUpdatesMessage& gu_msg = message.get_updates();
EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::NOTIFICATION,
gu_msg.caller_info().source());
EXPECT_EQ(sync_pb::SyncEnums::GU_TRIGGER, gu_msg.get_updates_origin());
for (int i = 0; i < gu_msg.from_progress_marker_size(); ++i) {
syncer::ModelType type = GetModelTypeFromSpecificsFieldNumber(
gu_msg.from_progress_marker(i).data_type_id());
const sync_pb::DataTypeProgressMarker& progress_marker =
gu_msg.from_progress_marker(i);
const sync_pb::GetUpdateTriggers& gu_trigger =
progress_marker.get_update_triggers();
// We perform some basic tests of GU trigger and source fields here. The
// more complicated scenarios are tested by the NudgeTracker tests.
if (notified_types.Has(type)) {
EXPECT_TRUE(progress_marker.has_notification_hint());
EXPECT_FALSE(progress_marker.notification_hint().empty());
EXPECT_EQ(1, gu_trigger.notification_hint_size());
} else {
EXPECT_FALSE(progress_marker.has_notification_hint());
EXPECT_EQ(0, gu_trigger.notification_hint_size());
}
}
}
// Basic test to ensure initial sync requests are expressed in the request.
TEST_F(GetUpdatesProcessorTest, InitialSyncRequest) {
sessions::NudgeTracker nudge_tracker;
nudge_tracker.RecordInitialSyncRequired(AUTOFILL);
nudge_tracker.RecordInitialSyncRequired(PREFERENCES);
ModelTypeSet initial_sync_types = ModelTypeSet(AUTOFILL, PREFERENCES);
sync_pb::ClientToServerMessage message;
NormalGetUpdatesDelegate normal_delegate(nudge_tracker);
scoped_ptr<GetUpdatesProcessor> processor(
BuildGetUpdatesProcessor(normal_delegate));
processor->PrepareGetUpdates(enabled_types(), &message);
const sync_pb::GetUpdatesMessage& gu_msg = message.get_updates();
EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::DATATYPE_REFRESH,
gu_msg.caller_info().source());
EXPECT_EQ(sync_pb::SyncEnums::GU_TRIGGER, gu_msg.get_updates_origin());
for (int i = 0; i < gu_msg.from_progress_marker_size(); ++i) {
syncer::ModelType type = GetModelTypeFromSpecificsFieldNumber(
gu_msg.from_progress_marker(i).data_type_id());
const sync_pb::DataTypeProgressMarker& progress_marker =
gu_msg.from_progress_marker(i);
const sync_pb::GetUpdateTriggers& gu_trigger =
progress_marker.get_update_triggers();
// We perform some basic tests of GU trigger and source fields here. The
// more complicated scenarios are tested by the NudgeTracker tests.
if (initial_sync_types.Has(type)) {
EXPECT_TRUE(gu_trigger.initial_sync_in_progress());
} else {
EXPECT_TRUE(gu_trigger.has_initial_sync_in_progress());
EXPECT_FALSE(gu_trigger.initial_sync_in_progress());
}
}
}
TEST_F(GetUpdatesProcessorTest, ConfigureTest) {
sync_pb::ClientToServerMessage message;
ConfigureGetUpdatesDelegate configure_delegate(
sync_pb::GetUpdatesCallerInfo::RECONFIGURATION);
scoped_ptr<GetUpdatesProcessor> processor(
BuildGetUpdatesProcessor(configure_delegate));
processor->PrepareGetUpdates(enabled_types(), &message);
const sync_pb::GetUpdatesMessage& gu_msg = message.get_updates();
EXPECT_EQ(sync_pb::SyncEnums::RECONFIGURATION, gu_msg.get_updates_origin());
EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::RECONFIGURATION,
gu_msg.caller_info().source());
ModelTypeSet progress_types;
for (int i = 0; i < gu_msg.from_progress_marker_size(); ++i) {
syncer::ModelType type = GetModelTypeFromSpecificsFieldNumber(
gu_msg.from_progress_marker(i).data_type_id());
progress_types.Put(type);
}
EXPECT_TRUE(enabled_types().Equals(progress_types));
}
TEST_F(GetUpdatesProcessorTest, PollTest) {
sync_pb::ClientToServerMessage message;
PollGetUpdatesDelegate poll_delegate;
scoped_ptr<GetUpdatesProcessor> processor(
BuildGetUpdatesProcessor(poll_delegate));
processor->PrepareGetUpdates(enabled_types(), &message);
const sync_pb::GetUpdatesMessage& gu_msg = message.get_updates();
EXPECT_EQ(sync_pb::SyncEnums::PERIODIC, gu_msg.get_updates_origin());
EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::PERIODIC,
gu_msg.caller_info().source());
ModelTypeSet progress_types;
for (int i = 0; i < gu_msg.from_progress_marker_size(); ++i) {
syncer::ModelType type = GetModelTypeFromSpecificsFieldNumber(
gu_msg.from_progress_marker(i).data_type_id());
progress_types.Put(type);
}
EXPECT_TRUE(enabled_types().Equals(progress_types));
}
TEST_F(GetUpdatesProcessorTest, RetryTest) {
sessions::NudgeTracker nudge_tracker;
// Schedule a retry.
base::TimeTicks t1 = kTestStartTime;
nudge_tracker.SetNextRetryTime(t1);
// Get the nudge tracker to think the retry is due.
nudge_tracker.SetSyncCycleStartTime(t1 + base::TimeDelta::FromSeconds(1));
sync_pb::ClientToServerMessage message;
NormalGetUpdatesDelegate normal_delegate(nudge_tracker);
scoped_ptr<GetUpdatesProcessor> processor(
BuildGetUpdatesProcessor(normal_delegate));
processor->PrepareGetUpdates(enabled_types(), &message);
const sync_pb::GetUpdatesMessage& gu_msg = message.get_updates();
EXPECT_EQ(sync_pb::SyncEnums::RETRY, gu_msg.get_updates_origin());
EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::RETRY,
gu_msg.caller_info().source());
EXPECT_TRUE(gu_msg.is_retry());
ModelTypeSet progress_types;
for (int i = 0; i < gu_msg.from_progress_marker_size(); ++i) {
syncer::ModelType type = GetModelTypeFromSpecificsFieldNumber(
gu_msg.from_progress_marker(i).data_type_id());
progress_types.Put(type);
}
EXPECT_TRUE(enabled_types().Equals(progress_types));
}
TEST_F(GetUpdatesProcessorTest, NudgeWithRetryTest) {
sessions::NudgeTracker nudge_tracker;
// Schedule a retry.
base::TimeTicks t1 = kTestStartTime;
nudge_tracker.SetNextRetryTime(t1);
// Get the nudge tracker to think the retry is due.
nudge_tracker.SetSyncCycleStartTime(t1 + base::TimeDelta::FromSeconds(1));
// Record a local change, too.
nudge_tracker.RecordLocalChange(ModelTypeSet(BOOKMARKS));
sync_pb::ClientToServerMessage message;
NormalGetUpdatesDelegate normal_delegate(nudge_tracker);
scoped_ptr<GetUpdatesProcessor> processor(
BuildGetUpdatesProcessor(normal_delegate));
processor->PrepareGetUpdates(enabled_types(), &message);
const sync_pb::GetUpdatesMessage& gu_msg = message.get_updates();
EXPECT_NE(sync_pb::SyncEnums::RETRY, gu_msg.get_updates_origin());
EXPECT_NE(sync_pb::GetUpdatesCallerInfo::RETRY,
gu_msg.caller_info().source());
EXPECT_TRUE(gu_msg.is_retry());
}
// Verify that a bogus response message is detected.
TEST_F(GetUpdatesProcessorTest, InvalidResponse) {
sync_pb::GetUpdatesResponse gu_response;
InitFakeUpdateResponse(&gu_response);
// This field is essential for making the client stop looping. If it's unset
// then something is very wrong. The client should detect this.
gu_response.clear_changes_remaining();
sessions::NudgeTracker nudge_tracker;
NormalGetUpdatesDelegate normal_delegate(nudge_tracker);
sessions::StatusController status;
scoped_ptr<GetUpdatesProcessor> processor(
BuildGetUpdatesProcessor(normal_delegate));
SyncerError error = processor->ProcessResponse(gu_response,
enabled_types(),
&status);
EXPECT_EQ(error, SERVER_RESPONSE_VALIDATION_FAILED);
}
// Verify that we correctly detect when there's more work to be done.
TEST_F(GetUpdatesProcessorTest, MoreToDownloadResponse) {
sync_pb::GetUpdatesResponse gu_response;
InitFakeUpdateResponse(&gu_response);
gu_response.set_changes_remaining(1);
sessions::NudgeTracker nudge_tracker;
NormalGetUpdatesDelegate normal_delegate(nudge_tracker);
sessions::StatusController status;
scoped_ptr<GetUpdatesProcessor> processor(
BuildGetUpdatesProcessor(normal_delegate));
SyncerError error = processor->ProcessResponse(gu_response,
enabled_types(),
&status);
EXPECT_EQ(error, SERVER_MORE_TO_DOWNLOAD);
}
// A simple scenario: No updates returned and nothing more to download.
TEST_F(GetUpdatesProcessorTest, NormalResponseTest) {
sync_pb::GetUpdatesResponse gu_response;
InitFakeUpdateResponse(&gu_response);
gu_response.set_changes_remaining(0);
sessions::NudgeTracker nudge_tracker;
NormalGetUpdatesDelegate normal_delegate(nudge_tracker);
sessions::StatusController status;
scoped_ptr<GetUpdatesProcessor> processor(
BuildGetUpdatesProcessor(normal_delegate));
SyncerError error = processor->ProcessResponse(gu_response,
enabled_types(),
&status);
EXPECT_EQ(error, SYNCER_OK);
}
// Variant of GetUpdatesProcessor test designed to test update application.
//
// Maintains two enabled types, but requests that updates be applied for only
// one of them.
class GetUpdatesProcessorApplyUpdatesTest : public GetUpdatesProcessorTest {
public:
GetUpdatesProcessorApplyUpdatesTest() {}
~GetUpdatesProcessorApplyUpdatesTest() override {}
void SetUp() override {
bookmarks_handler_ = AddUpdateHandler(BOOKMARKS);
autofill_handler_ = AddUpdateHandler(AUTOFILL);
}
ModelTypeSet GetGuTypes() {
return ModelTypeSet(AUTOFILL);
}
MockUpdateHandler* GetNonAppliedHandler() {
return bookmarks_handler_;
}
MockUpdateHandler* GetAppliedHandler() {
return autofill_handler_;
}
private:
MockUpdateHandler* bookmarks_handler_;
MockUpdateHandler* autofill_handler_;
};
// Verify that a normal cycle applies updates non-passively to the specified
// types.
TEST_F(GetUpdatesProcessorApplyUpdatesTest, Normal) {
sessions::NudgeTracker nudge_tracker;
NormalGetUpdatesDelegate normal_delegate(nudge_tracker);
scoped_ptr<GetUpdatesProcessor> processor(
BuildGetUpdatesProcessor(normal_delegate));
EXPECT_EQ(0, GetNonAppliedHandler()->GetApplyUpdatesCount());
EXPECT_EQ(0, GetAppliedHandler()->GetApplyUpdatesCount());
sessions::StatusController status;
processor->ApplyUpdates(GetGuTypes(), &status);
EXPECT_EQ(0, GetNonAppliedHandler()->GetApplyUpdatesCount());
EXPECT_EQ(1, GetAppliedHandler()->GetApplyUpdatesCount());
EXPECT_EQ(0, GetNonAppliedHandler()->GetPassiveApplyUpdatesCount());
EXPECT_EQ(0, GetAppliedHandler()->GetPassiveApplyUpdatesCount());
EXPECT_TRUE(status.get_updates_request_types().Equals(GetGuTypes()));
}
// Verify that a configure cycle applies updates passively to the specified
// types.
TEST_F(GetUpdatesProcessorApplyUpdatesTest, Configure) {
ConfigureGetUpdatesDelegate configure_delegate(
sync_pb::GetUpdatesCallerInfo::RECONFIGURATION);
scoped_ptr<GetUpdatesProcessor> processor(
BuildGetUpdatesProcessor(configure_delegate));
EXPECT_EQ(0, GetNonAppliedHandler()->GetPassiveApplyUpdatesCount());
EXPECT_EQ(0, GetAppliedHandler()->GetPassiveApplyUpdatesCount());
sessions::StatusController status;
processor->ApplyUpdates(GetGuTypes(), &status);
EXPECT_EQ(0, GetNonAppliedHandler()->GetPassiveApplyUpdatesCount());
EXPECT_EQ(1, GetAppliedHandler()->GetPassiveApplyUpdatesCount());
EXPECT_EQ(0, GetNonAppliedHandler()->GetApplyUpdatesCount());
EXPECT_EQ(0, GetAppliedHandler()->GetApplyUpdatesCount());
EXPECT_TRUE(status.get_updates_request_types().Equals(GetGuTypes()));
}
// Verify that a poll cycle applies updates non-passively to the specified
// types.
TEST_F(GetUpdatesProcessorApplyUpdatesTest, Poll) {
PollGetUpdatesDelegate poll_delegate;
scoped_ptr<GetUpdatesProcessor> processor(
BuildGetUpdatesProcessor(poll_delegate));
EXPECT_EQ(0, GetNonAppliedHandler()->GetApplyUpdatesCount());
EXPECT_EQ(0, GetAppliedHandler()->GetApplyUpdatesCount());
sessions::StatusController status;
processor->ApplyUpdates(GetGuTypes(), &status);
EXPECT_EQ(0, GetNonAppliedHandler()->GetApplyUpdatesCount());
EXPECT_EQ(1, GetAppliedHandler()->GetApplyUpdatesCount());
EXPECT_EQ(0, GetNonAppliedHandler()->GetPassiveApplyUpdatesCount());
EXPECT_EQ(0, GetAppliedHandler()->GetPassiveApplyUpdatesCount());
EXPECT_TRUE(status.get_updates_request_types().Equals(GetGuTypes()));
}
class DownloadUpdatesDebugInfoTest : public ::testing::Test {
public:
DownloadUpdatesDebugInfoTest() {}
~DownloadUpdatesDebugInfoTest() override {}
sessions::StatusController* status() {
return &status_;
}
sessions::DebugInfoGetter* debug_info_getter() {
return &debug_info_getter_;
}
void AddDebugEvent() {
debug_info_getter_.AddDebugEvent();
}
private:
sessions::StatusController status_;
MockDebugInfoGetter debug_info_getter_;
};
// Verify CopyClientDebugInfo when there are no events to upload.
TEST_F(DownloadUpdatesDebugInfoTest, VerifyCopyClientDebugInfo_Empty) {
sync_pb::DebugInfo debug_info;
GetUpdatesProcessor::CopyClientDebugInfo(debug_info_getter(), &debug_info);
EXPECT_EQ(0, debug_info.events_size());
}
TEST_F(DownloadUpdatesDebugInfoTest, VerifyCopyOverwrites) {
sync_pb::DebugInfo debug_info;
AddDebugEvent();
GetUpdatesProcessor::CopyClientDebugInfo(debug_info_getter(), &debug_info);
EXPECT_EQ(1, debug_info.events_size());
GetUpdatesProcessor::CopyClientDebugInfo(debug_info_getter(), &debug_info);
EXPECT_EQ(1, debug_info.events_size());
}
} // namespace syncer