blob: 8a4357dc3975a4f541c22eaa40a60e0f77e7ac65 [file] [log] [blame]
// Copyright 2015 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 "components/sync_device_info/device_info_sync_bridge.h"
#include <algorithm>
#include <set>
#include <utility>
#include "base/bind.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_task_environment.h"
#include "components/sync/base/time.h"
#include "components/sync/model/data_batch.h"
#include "components/sync/model/data_type_activation_request.h"
#include "components/sync/model/data_type_error_handler_mock.h"
#include "components/sync/model/entity_data.h"
#include "components/sync/model/metadata_batch.h"
#include "components/sync/model/mock_model_type_change_processor.h"
#include "components/sync/model/model_type_store_test_util.h"
#include "components/sync/protocol/model_type_state.pb.h"
#include "components/sync/test/test_matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace syncer {
namespace {
using base::OneShotTimer;
using sync_pb::DeviceInfoSpecifics;
using sync_pb::EntitySpecifics;
using sync_pb::ModelTypeState;
using testing::_;
using testing::IsEmpty;
using testing::Matcher;
using testing::NotNull;
using testing::Pair;
using testing::Return;
using testing::SizeIs;
using testing::UnorderedElementsAre;
using DeviceInfoList = std::vector<std::unique_ptr<DeviceInfo>>;
using StorageKeyList = ModelTypeSyncBridge::StorageKeyList;
using RecordList = ModelTypeStore::RecordList;
using StartCallback = ModelTypeControllerDelegate::StartCallback;
using WriteBatch = ModelTypeStore::WriteBatch;
const int kLocalSuffix = 0;
MATCHER_P(HasDeviceInfo, expected, "") {
return arg.device_info().SerializeAsString() == expected.SerializeAsString();
}
MATCHER_P(EqualsProto, expected, "") {
return arg.SerializeAsString() == expected.SerializeAsString();
}
MATCHER_P(ModelEqualsSpecifics, expected_specifics, "") {
// Note that we ignore the device name here to avoid having to inject the
// local device's.
return expected_specifics.cache_guid() == arg.guid() &&
expected_specifics.device_type() == arg.device_type() &&
expected_specifics.sync_user_agent() == arg.sync_user_agent() &&
expected_specifics.chrome_version() == arg.chrome_version() &&
expected_specifics.signin_scoped_device_id() ==
arg.signin_scoped_device_id() &&
expected_specifics.feature_fields()
.send_tab_to_self_receiving_enabled() ==
arg.send_tab_to_self_receiving_enabled();
}
Matcher<std::unique_ptr<EntityData>> HasSpecifics(
const Matcher<sync_pb::EntitySpecifics>& m) {
return testing::Pointee(testing::Field(&EntityData::specifics, m));
}
MATCHER(HasLastUpdatedAboutNow, "") {
const sync_pb::DeviceInfoSpecifics& specifics = arg.device_info();
const base::Time now = base::Time::Now();
const base::TimeDelta tolerance = base::TimeDelta::FromMinutes(1);
const base::Time actual_last_updated =
ProtoTimeToTime(specifics.last_updated_timestamp());
if (actual_last_updated < now - tolerance) {
*result_listener << "which is too far in the past";
return false;
}
if (actual_last_updated > now + tolerance) {
*result_listener << "which is too far in the future";
return false;
}
return true;
}
std::string CacheGuidForSuffix(int suffix) {
return base::StringPrintf("cache guid %d", suffix);
}
std::string ClientNameForSuffix(int suffix) {
return base::StringPrintf("client name %d", suffix);
}
std::string SyncUserAgentForSuffix(int suffix) {
return base::StringPrintf("sync user agent %d", suffix);
}
std::string ChromeVersionForSuffix(int suffix) {
return base::StringPrintf("chrome version %d", suffix);
}
std::string SigninScopedDeviceIdForSuffix(int suffix) {
return base::StringPrintf("signin scoped device id %d", suffix);
}
DataTypeActivationRequest TestDataTypeActivationRequest() {
DataTypeActivationRequest request;
request.cache_guid = CacheGuidForSuffix(kLocalSuffix);
return request;
}
DeviceInfoSpecifics CreateSpecifics(
int suffix,
base::Time last_updated = base::Time::Now()) {
DeviceInfoSpecifics specifics;
specifics.set_cache_guid(CacheGuidForSuffix(suffix));
specifics.set_client_name(ClientNameForSuffix(suffix));
specifics.set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_LINUX);
specifics.set_sync_user_agent(SyncUserAgentForSuffix(suffix));
specifics.set_chrome_version(ChromeVersionForSuffix(suffix));
specifics.set_signin_scoped_device_id(SigninScopedDeviceIdForSuffix(suffix));
specifics.set_last_updated_timestamp(TimeToProtoTime(last_updated));
specifics.mutable_feature_fields()->set_send_tab_to_self_receiving_enabled(
true);
return specifics;
}
ModelTypeState StateWithEncryption(const std::string& encryption_key_name) {
ModelTypeState state;
state.set_initial_sync_done(true);
state.set_cache_guid(CacheGuidForSuffix(kLocalSuffix));
state.set_encryption_key_name(encryption_key_name);
return state;
}
// Creates an EntityData around a copy of the given specifics.
std::unique_ptr<EntityData> SpecificsToEntity(
const DeviceInfoSpecifics& specifics) {
auto data = std::make_unique<EntityData>();
*data->specifics.mutable_device_info() = specifics;
return data;
}
std::string CacheGuidToTag(const std::string& guid) {
return "DeviceInfo_" + guid;
}
// Helper method to reduce duplicated code between tests. Wraps the given
// specifics objects in an EntityData and EntityChange of type ACTION_ADD, and
// returns an EntityChangeList containing them all. Order is maintained.
EntityChangeList EntityAddList(
const std::vector<DeviceInfoSpecifics>& specifics_list) {
EntityChangeList changes;
for (const auto& specifics : specifics_list) {
changes.push_back(EntityChange::CreateAdd(specifics.cache_guid(),
SpecificsToEntity(specifics)));
}
return changes;
}
std::map<std::string, sync_pb::EntitySpecifics> DataBatchToSpecificsMap(
std::unique_ptr<DataBatch> batch) {
std::map<std::string, sync_pb::EntitySpecifics> storage_key_to_specifics;
while (batch && batch->HasNext()) {
const syncer::KeyAndData& pair = batch->Next();
storage_key_to_specifics[pair.first] = pair.second->specifics;
}
return storage_key_to_specifics;
}
class TestLocalDeviceInfoProvider : public MutableLocalDeviceInfoProvider {
public:
TestLocalDeviceInfoProvider() = default;
~TestLocalDeviceInfoProvider() override = default;
// MutableLocalDeviceInfoProvider implementation.
void Initialize(const std::string& cache_guid,
const std::string& session_name) override {
local_device_info_ = std::make_unique<DeviceInfo>(
cache_guid, session_name, ChromeVersionForSuffix(kLocalSuffix),
SyncUserAgentForSuffix(kLocalSuffix),
sync_pb::SyncEnums_DeviceType_TYPE_LINUX,
SigninScopedDeviceIdForSuffix(kLocalSuffix), base::Time(), true);
}
void Clear() override { local_device_info_.reset(); }
version_info::Channel GetChannel() const override {
return version_info::Channel::UNKNOWN;
}
const DeviceInfo* GetLocalDeviceInfo() const override {
return local_device_info_.get();
}
std::unique_ptr<Subscription> RegisterOnInitializedCallback(
const base::RepeatingClosure& callback) override {
NOTIMPLEMENTED();
return nullptr;
}
private:
std::unique_ptr<DeviceInfo> local_device_info_;
DISALLOW_COPY_AND_ASSIGN(TestLocalDeviceInfoProvider);
};
class DeviceInfoSyncBridgeTest : public testing::Test,
public DeviceInfoTracker::Observer {
protected:
DeviceInfoSyncBridgeTest()
: store_(ModelTypeStoreTestUtil::CreateInMemoryStoreForTest()) {
ON_CALL(*processor(), IsTrackingMetadata()).WillByDefault(Return(true));
}
~DeviceInfoSyncBridgeTest() override {
// Some tests may never initialize the bridge.
if (bridge_)
bridge_->RemoveObserver(this);
// Force all remaining (store) tasks to execute so we don't leak memory.
base::RunLoop().RunUntilIdle();
}
void OnDeviceInfoChange() override { change_count_++; }
// Initialized the bridge based on the current local device and store.
void InitializeBridge() {
bridge_ = std::make_unique<DeviceInfoSyncBridge>(
std::make_unique<TestLocalDeviceInfoProvider>(),
ModelTypeStoreTestUtil::FactoryForForwardingStore(store_.get()),
mock_processor_.CreateForwardingProcessor());
bridge_->AddObserver(this);
}
// Creates the bridge and runs any outstanding tasks. This will typically
// cause all initialization callbacks between the sevice and store to fire.
void InitializeAndPump() {
InitializeBridge();
base::RunLoop().RunUntilIdle();
}
// Creates the bridge with no prior data on the store, and mimics sync being
// enabled by the user with no remote data.
void InitializeAndMergeInitialData() {
InitializeAndPump();
bridge()->OnSyncStarting(TestDataTypeActivationRequest());
std::unique_ptr<MetadataChangeList> metadata_change_list =
bridge()->CreateMetadataChangeList();
metadata_change_list->UpdateModelTypeState(StateWithEncryption(""));
bridge()->MergeSyncData(std::move(metadata_change_list),
EntityChangeList());
}
// Allows access to the store before that will ultimately be used to
// initialize the bridge.
ModelTypeStore* store() {
EXPECT_TRUE(store_);
return store_.get();
}
// Get the number of times the bridge notifies observers of changes.
int change_count() { return change_count_; }
LocalDeviceInfoProvider* local_device() {
return bridge_->GetLocalDeviceInfoProvider();
}
// Allows access to the bridge after InitializeBridge() is called.
DeviceInfoSyncBridge* bridge() {
EXPECT_TRUE(bridge_);
return bridge_.get();
}
MockModelTypeChangeProcessor* processor() { return &mock_processor_; }
// Should only be called after the bridge has been initialized. Will first
// recover the bridge's store, so another can be initialized later, and then
// deletes the bridge.
void PumpAndShutdown() {
ASSERT_TRUE(bridge_);
base::RunLoop().RunUntilIdle();
bridge_->RemoveObserver(this);
}
void RestartBridge() {
PumpAndShutdown();
InitializeAndPump();
}
void ForcePulse() { bridge()->ForcePulseForTest(); }
void CommitToStoreAndWait(std::unique_ptr<WriteBatch> batch) {
base::RunLoop loop;
store()->CommitWriteBatch(
std::move(batch),
base::BindOnce(
[](base::RunLoop* loop, const base::Optional<ModelError>& result) {
EXPECT_FALSE(result.has_value()) << result->ToString();
loop->Quit();
},
&loop));
loop.Run();
}
void WriteDataToStore(
const std::vector<DeviceInfoSpecifics>& specifics_list) {
std::unique_ptr<WriteBatch> batch = store()->CreateWriteBatch();
for (auto& specifics : specifics_list) {
batch->WriteData(specifics.cache_guid(), specifics.SerializeAsString());
}
CommitToStoreAndWait(std::move(batch));
}
void WriteToStoreWithMetadata(
const std::vector<DeviceInfoSpecifics>& specifics_list,
ModelTypeState state) {
std::unique_ptr<WriteBatch> batch = store()->CreateWriteBatch();
for (auto& specifics : specifics_list) {
batch->WriteData(specifics.cache_guid(), specifics.SerializeAsString());
}
batch->GetMetadataChangeList()->UpdateModelTypeState(state);
CommitToStoreAndWait(std::move(batch));
}
std::map<std::string, DeviceInfoSpecifics> ReadAllFromStore() {
std::unique_ptr<ModelTypeStore::RecordList> records;
base::RunLoop loop;
store()->ReadAllData(base::BindOnce(
[](std::unique_ptr<ModelTypeStore::RecordList>* output_records,
base::RunLoop* loop, const base::Optional<syncer::ModelError>& error,
std::unique_ptr<ModelTypeStore::RecordList> input_records) {
EXPECT_FALSE(error) << error->ToString();
EXPECT_THAT(input_records, NotNull());
*output_records = std::move(input_records);
loop->Quit();
},
&records, &loop));
loop.Run();
std::map<std::string, DeviceInfoSpecifics> result;
if (records) {
for (const ModelTypeStore::Record& record : *records) {
DeviceInfoSpecifics specifics;
EXPECT_TRUE(specifics.ParseFromString(record.value));
result.emplace(record.id, specifics);
}
}
return result;
}
std::map<std::string, sync_pb::EntitySpecifics> GetAllData() {
base::RunLoop loop;
std::unique_ptr<DataBatch> batch;
bridge_->GetAllDataForDebugging(base::BindOnce(
[](base::RunLoop* loop, std::unique_ptr<DataBatch>* out_batch,
std::unique_ptr<DataBatch> batch) {
*out_batch = std::move(batch);
loop->Quit();
},
&loop, &batch));
loop.Run();
EXPECT_NE(nullptr, batch);
return DataBatchToSpecificsMap(std::move(batch));
}
std::map<std::string, sync_pb::EntitySpecifics> GetData(
const std::vector<std::string>& storage_keys) {
base::RunLoop loop;
std::unique_ptr<DataBatch> batch;
bridge_->GetData(storage_keys, base::BindOnce(
[](base::RunLoop* loop,
std::unique_ptr<DataBatch>* out_batch,
std::unique_ptr<DataBatch> batch) {
*out_batch = std::move(batch);
loop->Quit();
},
&loop, &batch));
loop.Run();
EXPECT_NE(nullptr, batch);
return DataBatchToSpecificsMap(std::move(batch));
}
private:
int change_count_ = 0;
// In memory model type store needs to be able to post tasks.
base::test::ScopedTaskEnvironment task_environment_;
testing::NiceMock<MockModelTypeChangeProcessor> mock_processor_;
// Holds the store.
const std::unique_ptr<ModelTypeStore> store_;
// Not initialized immediately (upon test's constructor). This allows each
// test case to modify the dependencies the bridge will be constructed with.
std::unique_ptr<DeviceInfoSyncBridge> bridge_;
};
TEST_F(DeviceInfoSyncBridgeTest, BeforeSyncEnabled) {
InitializeBridge();
EXPECT_THAT(bridge()->GetAllDeviceInfo(), IsEmpty());
base::RunLoop().RunUntilIdle();
EXPECT_THAT(bridge()->GetAllDeviceInfo(), IsEmpty());
}
TEST_F(DeviceInfoSyncBridgeTest, GetClientTagNormal) {
InitializeBridge();
const std::string guid = "abc";
EntitySpecifics entity_specifics;
entity_specifics.mutable_device_info()->set_cache_guid(guid);
EntityData entity_data;
entity_data.specifics = entity_specifics;
EXPECT_EQ(CacheGuidToTag(guid), bridge()->GetClientTag(entity_data));
}
TEST_F(DeviceInfoSyncBridgeTest, GetClientTagEmpty) {
InitializeBridge();
EntitySpecifics entity_specifics;
entity_specifics.mutable_device_info();
EntityData entity_data;
entity_data.specifics = entity_specifics;
EXPECT_EQ(CacheGuidToTag(""), bridge()->GetClientTag(entity_data));
}
TEST_F(DeviceInfoSyncBridgeTest, TestWithLocalDataWithoutMetadata) {
const DeviceInfoSpecifics specifics = CreateSpecifics(1);
WriteDataToStore({specifics});
InitializeAndPump();
// Local data without sync metadata should be thrown away.
EXPECT_TRUE(ReadAllFromStore().empty());
EXPECT_EQ(0u, bridge()->GetAllDeviceInfo().size());
}
TEST_F(DeviceInfoSyncBridgeTest, TestWithLocalMetadata) {
WriteToStoreWithMetadata(std::vector<DeviceInfoSpecifics>(),
StateWithEncryption("ekn"));
InitializeAndPump();
// Local metadata without data about the local device is corrupt.
EXPECT_TRUE(ReadAllFromStore().empty());
EXPECT_EQ(0u, bridge()->GetAllDeviceInfo().size());
}
TEST_F(DeviceInfoSyncBridgeTest, TestWithLocalDataAndMetadata) {
const DeviceInfoSpecifics local_specifics = CreateSpecifics(kLocalSuffix);
ModelTypeState state = StateWithEncryption("ekn");
WriteToStoreWithMetadata({local_specifics}, state);
EXPECT_CALL(*processor(), ModelReadyToSync(MetadataBatchContains(
HasEncryptionKeyName("ekn"),
/*entities=*/_)));
InitializeAndPump();
EXPECT_EQ(1u, bridge()->GetAllDeviceInfo().size());
EXPECT_THAT(*bridge()->GetDeviceInfo(local_specifics.cache_guid()),
ModelEqualsSpecifics(local_specifics));
EXPECT_TRUE(bridge()->IsPulseTimerRunningForTest());
}
TEST_F(DeviceInfoSyncBridgeTest, TestWithMultipleLocalDataAndMetadata) {
const DeviceInfoSpecifics local_specifics = CreateSpecifics(kLocalSuffix);
const DeviceInfoSpecifics remote_specifics = CreateSpecifics(1);
ModelTypeState state = StateWithEncryption("ekn");
WriteToStoreWithMetadata({local_specifics, remote_specifics}, state);
EXPECT_CALL(*processor(), ModelReadyToSync(MetadataBatchContains(
HasEncryptionKeyName("ekn"),
/*entities=*/_)));
InitializeAndPump();
EXPECT_EQ(2u, bridge()->GetAllDeviceInfo().size());
EXPECT_THAT(*bridge()->GetDeviceInfo(local_specifics.cache_guid()),
ModelEqualsSpecifics(local_specifics));
EXPECT_THAT(*bridge()->GetDeviceInfo(remote_specifics.cache_guid()),
ModelEqualsSpecifics(remote_specifics));
}
TEST_F(DeviceInfoSyncBridgeTest, GetData) {
const DeviceInfoSpecifics local_specifics = CreateSpecifics(kLocalSuffix);
const DeviceInfoSpecifics specifics1 = CreateSpecifics(1);
const DeviceInfoSpecifics specifics2 = CreateSpecifics(2);
const DeviceInfoSpecifics specifics3 = CreateSpecifics(3);
WriteToStoreWithMetadata(
{local_specifics, specifics1, specifics2, specifics3},
StateWithEncryption("ekn"));
InitializeAndPump();
EXPECT_THAT(GetData({specifics1.cache_guid()}),
UnorderedElementsAre(
Pair(specifics1.cache_guid(), HasDeviceInfo(specifics1))));
EXPECT_THAT(GetData({specifics1.cache_guid(), specifics3.cache_guid()}),
UnorderedElementsAre(
Pair(specifics1.cache_guid(), HasDeviceInfo(specifics1)),
Pair(specifics3.cache_guid(), HasDeviceInfo(specifics3))));
EXPECT_THAT(GetData({specifics1.cache_guid(), specifics2.cache_guid(),
specifics3.cache_guid()}),
UnorderedElementsAre(
Pair(specifics1.cache_guid(), HasDeviceInfo(specifics1)),
Pair(specifics2.cache_guid(), HasDeviceInfo(specifics2)),
Pair(specifics3.cache_guid(), HasDeviceInfo(specifics3))));
}
TEST_F(DeviceInfoSyncBridgeTest, GetDataMissing) {
InitializeAndPump();
EXPECT_THAT(GetData({"does_not_exist"}), IsEmpty());
}
TEST_F(DeviceInfoSyncBridgeTest, GetAllData) {
const DeviceInfoSpecifics local_specifics = CreateSpecifics(kLocalSuffix);
const DeviceInfoSpecifics specifics1 = CreateSpecifics(1);
const DeviceInfoSpecifics specifics2 = CreateSpecifics(2);
WriteToStoreWithMetadata({local_specifics, specifics1, specifics2},
StateWithEncryption("ekn"));
InitializeAndPump();
EXPECT_THAT(GetAllData(),
UnorderedElementsAre(
Pair(local_device()->GetLocalDeviceInfo()->guid(), _),
Pair(specifics1.cache_guid(), HasDeviceInfo(specifics1)),
Pair(specifics2.cache_guid(), HasDeviceInfo(specifics2))));
}
TEST_F(DeviceInfoSyncBridgeTest, ApplySyncChangesEmpty) {
InitializeAndMergeInitialData();
ASSERT_EQ(1, change_count());
auto error = bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
EntityChangeList());
EXPECT_FALSE(error);
EXPECT_EQ(1, change_count());
}
TEST_F(DeviceInfoSyncBridgeTest, ApplySyncChangesInMemory) {
InitializeAndMergeInitialData();
ASSERT_EQ(1, change_count());
const DeviceInfoSpecifics specifics = CreateSpecifics(1);
auto error_on_add = bridge()->ApplySyncChanges(
bridge()->CreateMetadataChangeList(), EntityAddList({specifics}));
EXPECT_FALSE(error_on_add);
std::unique_ptr<DeviceInfo> info =
bridge()->GetDeviceInfo(specifics.cache_guid());
ASSERT_TRUE(info);
EXPECT_THAT(*info, ModelEqualsSpecifics(specifics));
EXPECT_EQ(2, change_count());
syncer::EntityChangeList entity_change_list;
entity_change_list.push_back(
EntityChange::CreateDelete(specifics.cache_guid()));
auto error_on_delete = bridge()->ApplySyncChanges(
bridge()->CreateMetadataChangeList(), std::move(entity_change_list));
EXPECT_FALSE(error_on_delete);
EXPECT_FALSE(bridge()->GetDeviceInfo(specifics.cache_guid()));
EXPECT_EQ(3, change_count());
}
TEST_F(DeviceInfoSyncBridgeTest, ApplySyncChangesStore) {
InitializeAndMergeInitialData();
ASSERT_EQ(1, change_count());
const DeviceInfoSpecifics specifics = CreateSpecifics(1);
ModelTypeState state = StateWithEncryption("ekn");
std::unique_ptr<MetadataChangeList> metadata_changes =
bridge()->CreateMetadataChangeList();
metadata_changes->UpdateModelTypeState(state);
auto error = bridge()->ApplySyncChanges(std::move(metadata_changes),
EntityAddList({specifics}));
EXPECT_FALSE(error);
EXPECT_EQ(2, change_count());
EXPECT_CALL(*processor(), ModelReadyToSync(MetadataBatchContains(
HasEncryptionKeyName("ekn"),
/*entities=*/IsEmpty())));
RestartBridge();
std::unique_ptr<DeviceInfo> info =
bridge()->GetDeviceInfo(specifics.cache_guid());
ASSERT_TRUE(info);
EXPECT_THAT(*info, ModelEqualsSpecifics(specifics));
}
TEST_F(DeviceInfoSyncBridgeTest, ApplySyncChangesWithLocalGuid) {
InitializeAndMergeInitialData();
ASSERT_EQ(1, change_count());
ASSERT_TRUE(
bridge()->GetDeviceInfo(local_device()->GetLocalDeviceInfo()->guid()));
ASSERT_EQ(1, change_count());
// The bridge should ignore these changes using this specifics because its
// guid will match the local device.
EXPECT_CALL(*processor(), Put(_, _, _)).Times(0);
const DeviceInfoSpecifics specifics = CreateSpecifics(kLocalSuffix);
auto error_on_add = bridge()->ApplySyncChanges(
bridge()->CreateMetadataChangeList(), EntityAddList({specifics}));
EXPECT_FALSE(error_on_add);
EXPECT_EQ(1, change_count());
syncer::EntityChangeList entity_change_list;
entity_change_list.push_back(
EntityChange::CreateDelete(specifics.cache_guid()));
auto error_on_delete = bridge()->ApplySyncChanges(
bridge()->CreateMetadataChangeList(), std::move(entity_change_list));
EXPECT_FALSE(error_on_delete);
EXPECT_EQ(1, change_count());
}
TEST_F(DeviceInfoSyncBridgeTest, ApplyDeleteNonexistent) {
InitializeAndMergeInitialData();
ASSERT_EQ(1, change_count());
syncer::EntityChangeList entity_change_list;
entity_change_list.push_back(EntityChange::CreateDelete("guid"));
EXPECT_CALL(*processor(), Delete(_, _)).Times(0);
auto error = bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
std::move(entity_change_list));
EXPECT_FALSE(error);
EXPECT_EQ(1, change_count());
}
TEST_F(DeviceInfoSyncBridgeTest, MergeEmpty) {
const std::string kLocalGuid = CacheGuidForSuffix(kLocalSuffix);
InitializeAndPump();
ASSERT_FALSE(local_device()->GetLocalDeviceInfo());
ASSERT_FALSE(bridge()->IsPulseTimerRunningForTest());
EXPECT_CALL(*processor(), Put(kLocalGuid, _, _));
EXPECT_CALL(*processor(), Delete(_, _)).Times(0);
bridge()->OnSyncStarting(TestDataTypeActivationRequest());
auto error = bridge()->MergeSyncData(bridge()->CreateMetadataChangeList(),
EntityChangeList());
EXPECT_FALSE(error);
EXPECT_EQ(1, change_count());
ASSERT_TRUE(local_device()->GetLocalDeviceInfo());
EXPECT_EQ(kLocalGuid, local_device()->GetLocalDeviceInfo()->guid());
EXPECT_TRUE(bridge()->IsPulseTimerRunningForTest());
}
TEST_F(DeviceInfoSyncBridgeTest, MergeLocalGuid) {
const std::string kLocalGuid = CacheGuidForSuffix(kLocalSuffix);
InitializeAndPump();
ASSERT_FALSE(local_device()->GetLocalDeviceInfo());
EXPECT_CALL(*processor(), Put(kLocalGuid, _, _));
EXPECT_CALL(*processor(), Delete(_, _)).Times(0);
bridge()->OnSyncStarting(TestDataTypeActivationRequest());
auto error =
bridge()->MergeSyncData(bridge()->CreateMetadataChangeList(),
EntityAddList({CreateSpecifics(kLocalSuffix)}));
EXPECT_FALSE(error);
EXPECT_EQ(1, change_count());
ASSERT_TRUE(local_device()->GetLocalDeviceInfo());
EXPECT_EQ(kLocalGuid, local_device()->GetLocalDeviceInfo()->guid());
}
TEST_F(DeviceInfoSyncBridgeTest, CountActiveDevices) {
InitializeAndMergeInitialData();
// Local device.
EXPECT_EQ(1, bridge()->CountActiveDevices());
ON_CALL(*processor(), GetEntityCreationTime(_))
.WillByDefault(Return(base::Time::Now()));
ON_CALL(*processor(), GetEntityModificationTime(_))
.WillByDefault(Return(base::Time::Now()));
// Regardless of the time, these following two ApplySyncChanges(...) calls
// have the same guid as the local device.
bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
EntityAddList({CreateSpecifics(kLocalSuffix)}));
EXPECT_EQ(1, bridge()->CountActiveDevices());
bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
EntityAddList({CreateSpecifics(kLocalSuffix)}));
EXPECT_EQ(1, bridge()->CountActiveDevices());
// A different guid will actually contribute to the count.
bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
EntityAddList({CreateSpecifics(1)}));
EXPECT_EQ(2, bridge()->CountActiveDevices());
// Now set time to long ago in the past, it should not be active anymore.
bridge()->ApplySyncChanges(
bridge()->CreateMetadataChangeList(),
EntityAddList({CreateSpecifics(
1, base::Time::Now() - base::TimeDelta::FromDays(365))}));
EXPECT_EQ(1, bridge()->CountActiveDevices());
}
TEST_F(DeviceInfoSyncBridgeTest, CountActiveDevicesWithOverlappingTime) {
InitializeAndMergeInitialData();
// Local device.
ASSERT_EQ(1, bridge()->CountActiveDevices());
const DeviceInfoSpecifics specifics1 = CreateSpecifics(1);
const DeviceInfoSpecifics specifics2 = CreateSpecifics(2);
const DeviceInfoSpecifics specifics3 = CreateSpecifics(3);
// Time ranges are overlapping.
ON_CALL(*processor(), GetEntityCreationTime(specifics1.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(1)));
ON_CALL(*processor(), GetEntityModificationTime(specifics1.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(3)));
ON_CALL(*processor(), GetEntityCreationTime(specifics2.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(2)));
ON_CALL(*processor(), GetEntityModificationTime(specifics2.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(4)));
ON_CALL(*processor(), GetEntityCreationTime(specifics3.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(2)));
ON_CALL(*processor(), GetEntityModificationTime(specifics3.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(5)));
// With two devices, the local device gets ignored because it doesn't overlap.
bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
EntityAddList({specifics1, specifics2}));
ASSERT_EQ(3u, bridge()->GetAllDeviceInfo().size());
EXPECT_EQ(2, bridge()->CountActiveDevices());
// The third device is also overlapping with the first two (and the local one
// is still excluded).
bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
EntityAddList({specifics3}));
ASSERT_EQ(4u, bridge()->GetAllDeviceInfo().size());
EXPECT_EQ(3, bridge()->CountActiveDevices());
}
TEST_F(DeviceInfoSyncBridgeTest, CountActiveDevicesWithNonOverlappingTime) {
InitializeAndMergeInitialData();
// Local device.
ASSERT_EQ(1, bridge()->CountActiveDevices());
const DeviceInfoSpecifics specifics1 = CreateSpecifics(1);
const DeviceInfoSpecifics specifics2 = CreateSpecifics(2);
const DeviceInfoSpecifics specifics3 = CreateSpecifics(3);
// Time ranges are non-overlapping.
ON_CALL(*processor(), GetEntityCreationTime(specifics1.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(1)));
ON_CALL(*processor(), GetEntityModificationTime(specifics1.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(2)));
ON_CALL(*processor(), GetEntityCreationTime(specifics2.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(3)));
ON_CALL(*processor(), GetEntityModificationTime(specifics2.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(4)));
ON_CALL(*processor(), GetEntityCreationTime(specifics3.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(5)));
ON_CALL(*processor(), GetEntityModificationTime(specifics3.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(6)));
bridge()->ApplySyncChanges(
bridge()->CreateMetadataChangeList(),
EntityAddList({specifics1, specifics2, specifics3}));
ASSERT_EQ(4u, bridge()->GetAllDeviceInfo().size());
EXPECT_EQ(1, bridge()->CountActiveDevices());
}
TEST_F(DeviceInfoSyncBridgeTest,
CountActiveDevicesWithNonOverlappingTimeAndDistictType) {
InitializeAndMergeInitialData();
// Local device.
ASSERT_EQ(1, bridge()->CountActiveDevices());
DeviceInfoSpecifics specifics1 = CreateSpecifics(1);
DeviceInfoSpecifics specifics2 = CreateSpecifics(2);
DeviceInfoSpecifics specifics3 = CreateSpecifics(3);
// We avoid TYPE_LINUX below to prevent collisions with the local device,
// exposed as Linux by LocalDeviceInfoProviderMock.
specifics1.set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_PHONE);
specifics2.set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_CROS);
specifics3.set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_WIN);
// Time ranges are non-overlapping.
ON_CALL(*processor(), GetEntityCreationTime(specifics1.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(1)));
ON_CALL(*processor(), GetEntityModificationTime(specifics1.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(2)));
ON_CALL(*processor(), GetEntityCreationTime(specifics2.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(3)));
ON_CALL(*processor(), GetEntityModificationTime(specifics2.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(4)));
ON_CALL(*processor(), GetEntityCreationTime(specifics3.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(5)));
ON_CALL(*processor(), GetEntityModificationTime(specifics3.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(6)));
bridge()->ApplySyncChanges(
bridge()->CreateMetadataChangeList(),
EntityAddList({specifics1, specifics2, specifics3}));
ASSERT_EQ(4u, bridge()->GetAllDeviceInfo().size());
EXPECT_EQ(4, bridge()->CountActiveDevices());
}
TEST_F(DeviceInfoSyncBridgeTest, CountActiveDevicesWithMalformedTimestamps) {
InitializeAndMergeInitialData();
// Local device.
ASSERT_EQ(1, bridge()->CountActiveDevices());
const DeviceInfoSpecifics specifics1 = CreateSpecifics(1);
const DeviceInfoSpecifics specifics2 = CreateSpecifics(2);
// Time ranges are overlapping.
ON_CALL(*processor(), GetEntityCreationTime(specifics1.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(1)));
ON_CALL(*processor(), GetEntityModificationTime(specifics1.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(4)));
ON_CALL(*processor(), GetEntityCreationTime(specifics2.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(3)));
ON_CALL(*processor(), GetEntityModificationTime(specifics2.cache_guid()))
.WillByDefault(
Return(base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(2)));
// With two devices, the local device gets ignored because it doesn't overlap.
bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
EntityAddList({specifics1, specifics2}));
ASSERT_EQ(3u, bridge()->GetAllDeviceInfo().size());
EXPECT_EQ(1, bridge()->CountActiveDevices());
}
TEST_F(DeviceInfoSyncBridgeTest, SendLocalData) {
// Ensure |last_updated| is about now, plus or minus a little bit.
EXPECT_CALL(*processor(), Put(_, HasSpecifics(HasLastUpdatedAboutNow()), _));
InitializeAndMergeInitialData();
EXPECT_EQ(1, change_count());
testing::Mock::VerifyAndClearExpectations(processor());
// Ensure |last_updated| is about now, plus or minus a little bit.
EXPECT_CALL(*processor(), Put(_, HasSpecifics(HasLastUpdatedAboutNow()), _));
ForcePulse();
EXPECT_EQ(2, change_count());
}
TEST_F(DeviceInfoSyncBridgeTest, ApplyStopSyncChangesWithClearData) {
InitializeAndMergeInitialData();
ASSERT_EQ(1u, bridge()->GetAllDeviceInfo().size());
ASSERT_EQ(1, change_count());
ASSERT_FALSE(ReadAllFromStore().empty());
ASSERT_TRUE(bridge()->IsPulseTimerRunningForTest());
const DeviceInfoSpecifics specifics = CreateSpecifics(1);
auto error = bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
EntityAddList({specifics}));
ASSERT_FALSE(error);
ASSERT_EQ(2u, bridge()->GetAllDeviceInfo().size());
ASSERT_EQ(2, change_count());
// Should clear out all local data and notify observers.
bridge()->ApplyStopSyncChanges(bridge()->CreateMetadataChangeList());
EXPECT_EQ(0u, bridge()->GetAllDeviceInfo().size());
EXPECT_EQ(3, change_count());
EXPECT_TRUE(ReadAllFromStore().empty());
EXPECT_FALSE(bridge()->IsPulseTimerRunningForTest());
// Reloading from storage shouldn't contain remote data.
RestartBridge();
EXPECT_EQ(0u, bridge()->GetAllDeviceInfo().size());
// If sync is re-enabled and the remote data is now empty, we shouldn't
// contain remote data.
bridge()->OnSyncStarting(TestDataTypeActivationRequest());
bridge()->MergeSyncData(bridge()->CreateMetadataChangeList(),
EntityChangeList());
// Local device.
EXPECT_EQ(1u, bridge()->GetAllDeviceInfo().size());
EXPECT_TRUE(bridge()->IsPulseTimerRunningForTest());
}
TEST_F(DeviceInfoSyncBridgeTest, ApplyStopSyncChangesWithKeepData) {
InitializeAndMergeInitialData();
ASSERT_EQ(1u, bridge()->GetAllDeviceInfo().size());
ASSERT_EQ(1, change_count());
ASSERT_FALSE(ReadAllFromStore().empty());
ASSERT_TRUE(bridge()->IsPulseTimerRunningForTest());
const DeviceInfoSpecifics specifics = CreateSpecifics(1);
auto error = bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
EntityAddList({specifics}));
ASSERT_FALSE(error);
ASSERT_EQ(2u, bridge()->GetAllDeviceInfo().size());
ASSERT_EQ(2, change_count());
// Should clear out all local data and notify observers.
bridge()->ApplyStopSyncChanges(/*delete_metadata_change_list=*/nullptr);
EXPECT_EQ(2u, bridge()->GetAllDeviceInfo().size());
EXPECT_EQ(2, change_count());
EXPECT_FALSE(ReadAllFromStore().empty());
EXPECT_TRUE(bridge()->IsPulseTimerRunningForTest());
// Reloading from storage should still contain remote data.
RestartBridge();
EXPECT_EQ(2u, bridge()->GetAllDeviceInfo().size());
EXPECT_TRUE(bridge()->IsPulseTimerRunningForTest());
}
} // namespace
} // namespace syncer