blob: 75565190d8238996ca9a9fd4e0d4b874cce07b66 [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_driver/device_info_service.h"
#include <map>
#include <set>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "components/sync_driver/local_device_info_provider_mock.h"
#include "sync/api/data_batch.h"
#include "sync/api/metadata_batch.h"
#include "sync/api/model_type_store.h"
#include "sync/internal_api/public/test/model_type_store_test_util.h"
#include "sync/protocol/data_type_state.pb.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace sync_driver_v2 {
using syncer_v2::DataBatch;
using syncer_v2::EntityData;
using syncer_v2::MetadataBatch;
using syncer_v2::MetadataChangeList;
using syncer_v2::ModelTypeChangeProcessor;
using syncer_v2::ModelTypeService;
using syncer_v2::ModelTypeStore;
using syncer_v2::ModelTypeStoreTestUtil;
using syncer_v2::TagAndData;
using sync_driver::DeviceInfo;
using sync_driver::DeviceInfoTracker;
using sync_driver::LocalDeviceInfoProviderMock;
using sync_pb::DataTypeState;
using sync_pb::DeviceInfoSpecifics;
using sync_pb::EntitySpecifics;
using ClientTagList = ModelTypeService::ClientTagList;
using Result = ModelTypeStore::Result;
using WriteBatch = ModelTypeStore::WriteBatch;
namespace {
void AssertResultIsSuccess(Result result) {
ASSERT_EQ(Result::SUCCESS, result);
}
void AssertEqual(const DeviceInfoSpecifics& s1, const DeviceInfoSpecifics& s2) {
ASSERT_EQ(s1.cache_guid(), s2.cache_guid());
ASSERT_EQ(s1.client_name(), s2.client_name());
ASSERT_EQ(s1.device_type(), s2.device_type());
ASSERT_EQ(s1.sync_user_agent(), s2.sync_user_agent());
ASSERT_EQ(s1.chrome_version(), s2.chrome_version());
ASSERT_EQ(s1.signin_scoped_device_id(), s2.signin_scoped_device_id());
}
void AssertEqual(const DeviceInfoSpecifics& specifics,
const DeviceInfo& model) {
ASSERT_EQ(specifics.cache_guid(), model.guid());
ASSERT_EQ(specifics.client_name(), model.client_name());
ASSERT_EQ(specifics.device_type(), model.device_type());
ASSERT_EQ(specifics.sync_user_agent(), model.sync_user_agent());
ASSERT_EQ(specifics.chrome_version(), model.chrome_version());
ASSERT_EQ(specifics.signin_scoped_device_id(),
model.signin_scoped_device_id());
}
void AssertErrorFromDataBatch(syncer::SyncError error,
scoped_ptr<DataBatch> batch) {
ASSERT_TRUE(error.IsSet());
}
void AssertExpectedFromDataBatch(
std::map<std::string, DeviceInfoSpecifics> expected,
syncer::SyncError error,
scoped_ptr<DataBatch> batch) {
ASSERT_FALSE(error.IsSet());
while (batch->HasNext()) {
const TagAndData& pair = batch->Next();
std::map<std::string, DeviceInfoSpecifics>::iterator iter =
expected.find(pair.first);
ASSERT_NE(iter, expected.end());
AssertEqual(iter->second, pair.second->specifics.device_info());
// Removing allows us to verify we don't see the same item multiple times,
// and that we saw everything we expected.
expected.erase(iter);
}
ASSERT_TRUE(expected.empty());
}
// Instead of actually processing anything, simply accumulates all instructions
// in members that can then be accessed. TODO(skym): If this ends up being
// useful for other model type unittests it should be moved out to a shared
// location.
class FakeModelTypeChangeProcessor : public ModelTypeChangeProcessor {
public:
FakeModelTypeChangeProcessor() {}
~FakeModelTypeChangeProcessor() override {}
void Put(const std::string& client_tag,
scoped_ptr<EntityData> entity_data,
MetadataChangeList* metadata_change_list) override {
put_map_.insert(std::make_pair(client_tag, std::move(entity_data)));
}
void Delete(const std::string& client_tag,
MetadataChangeList* metadata_change_list) override {
delete_set_.insert(client_tag);
}
void OnMetadataLoaded(scoped_ptr<MetadataBatch> batch) override {
std::swap(metadata_, batch);
}
const std::map<std::string, scoped_ptr<EntityData>>& put_map() const {
return put_map_;
}
const std::set<std::string>& delete_set() const { return delete_set_; }
const MetadataBatch* metadata() const { return metadata_.get(); }
private:
std::map<std::string, scoped_ptr<EntityData>> put_map_;
std::set<std::string> delete_set_;
scoped_ptr<MetadataBatch> metadata_;
};
class DeviceInfoServiceTest : public testing::Test,
public DeviceInfoTracker::Observer {
protected:
~DeviceInfoServiceTest() override {
// Some tests may never initialize the service.
if (service_)
service_->RemoveObserver(this);
// Force all remaining (store) tasks to execute so we don't leak memory.
base::RunLoop().RunUntilIdle();
}
void OnDeviceInfoChange() override { num_device_info_changed_callbacks_++; }
protected:
DeviceInfoServiceTest()
: num_device_info_changed_callbacks_(0),
store_(ModelTypeStoreTestUtil::CreateInMemoryStoreForTest()),
local_device_(new LocalDeviceInfoProviderMock(
"guid_1",
"client_1",
"Chromium 10k",
"Chrome 10k",
sync_pb::SyncEnums_DeviceType_TYPE_LINUX,
"device_id")) {}
// Initialized the service based on the current local device and store. Can
// only be called once per run, as it passes |store_|.
void InitializeService() {
ASSERT_TRUE(store_);
service_.reset(new DeviceInfoService(
local_device_.get(),
base::Bind(&ModelTypeStoreTestUtil::MoveStoreToCallback,
base::Passed(&store_))));
service_->AddObserver(this);
}
// Creates the service and runs any outstanding tasks. This will typically
// cause all initialization callbacks between the sevice and store to fire.
void InitializeAndPump() {
InitializeService();
base::RunLoop().RunUntilIdle();
}
void SetProcessorAndPump() {
processor_ = new FakeModelTypeChangeProcessor();
service()->set_change_processor(make_scoped_ptr(processor_));
base::RunLoop().RunUntilIdle();
}
DeviceInfoSpecifics GenerateTestSpecifics() {
int label = ++generated_count_;
DeviceInfoSpecifics specifics;
specifics.set_cache_guid(base::StringPrintf("cache guid %d", label));
specifics.set_client_name(base::StringPrintf("client name %d", label));
specifics.set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_LINUX);
specifics.set_sync_user_agent(
base::StringPrintf("sync user agent %d", label));
specifics.set_chrome_version(
base::StringPrintf("chrome version %d", label));
specifics.set_signin_scoped_device_id(
base::StringPrintf("signin scoped device id %d", label));
return specifics;
}
// Allows access to the store before that will ultimately be used to
// initialize the service.
ModelTypeStore* store() {
EXPECT_TRUE(store_);
return store_.get();
}
// Get the number of times the service notifies observers of changes.
int num_device_info_changed_callbacks() {
return num_device_info_changed_callbacks_;
}
// Allows overriding the provider before the service is initialized.
void set_local_device(scoped_ptr<LocalDeviceInfoProviderMock> provider) {
ASSERT_FALSE(service_);
std::swap(local_device_, provider);
}
LocalDeviceInfoProviderMock* local_device() { return local_device_.get(); }
// Allows access to the service after InitializeService() is called.
DeviceInfoService* service() {
EXPECT_TRUE(service_);
return service_.get();
}
FakeModelTypeChangeProcessor* processor() {
EXPECT_TRUE(processor_);
return processor_;
}
private:
int num_device_info_changed_callbacks_;
// Although we never use this in this class, the in memory model type store
// grabs the current task runner from a static accessor which point at this
// message loop. Must be declared/initilized before we call the synchronous
// CreateInMemoryStoreForTest.
base::MessageLoop message_loop_;
// Temporarily holds the store before the service is initialized.
scoped_ptr<ModelTypeStore> store_;
scoped_ptr<LocalDeviceInfoProviderMock> local_device_;
// Not initialized immediately (upon test's constructor). This allows each
// test case to modify the dependencies the service will be constructed with.
scoped_ptr<DeviceInfoService> service_;
// A non-owning pointer to the processor given to the service. Will be nullptr
// before being given to the service, to make ownership easier.
FakeModelTypeChangeProcessor* processor_ = nullptr;
// A monotonically increasing label for generated specifics objects with data
// that is slightly different from eachother.
int generated_count_ = 0;
};
TEST_F(DeviceInfoServiceTest, EmptyDataReconciliation) {
InitializeService();
ASSERT_EQ(0u, service()->GetAllDeviceInfo().size());
base::RunLoop().RunUntilIdle();
// TODO(skym): crbug.com/582460: Verify reconciliation has happened.
}
TEST_F(DeviceInfoServiceTest, LocalProviderSubscription) {
set_local_device(make_scoped_ptr(new LocalDeviceInfoProviderMock()));
InitializeAndPump();
ASSERT_EQ(0u, service()->GetAllDeviceInfo().size());
local_device()->Initialize(make_scoped_ptr(
new DeviceInfo("guid_1", "client_1", "Chromium 10k", "Chrome 10k",
sync_pb::SyncEnums_DeviceType_TYPE_LINUX, "device_id")));
// TODO(skym): crbug.com/582460: Verify reconciliation has happened.
}
TEST_F(DeviceInfoServiceTest, NonEmptyStoreLoad) {
// Override the provider so that reconciliation never happens.
set_local_device(make_scoped_ptr(new LocalDeviceInfoProviderMock()));
scoped_ptr<WriteBatch> batch = store()->CreateWriteBatch();
DeviceInfoSpecifics specifics(GenerateTestSpecifics());
store()->WriteData(batch.get(), "tag", specifics.SerializeAsString());
store()->CommitWriteBatch(std::move(batch),
base::Bind(&AssertResultIsSuccess));
InitializeAndPump();
ScopedVector<DeviceInfo> all_device_info(service()->GetAllDeviceInfo());
ASSERT_EQ(1u, all_device_info.size());
AssertEqual(specifics, *all_device_info[0]);
AssertEqual(specifics, *service()->GetDeviceInfo("tag").get());
}
TEST_F(DeviceInfoServiceTest, GetClientTagNormal) {
InitializeAndPump();
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(guid, service()->GetClientTag(entity_data));
}
TEST_F(DeviceInfoServiceTest, GetClientTagEmpty) {
InitializeAndPump();
EntitySpecifics entity_specifics;
entity_specifics.mutable_device_info();
EntityData entity_data;
entity_data.specifics = entity_specifics;
EXPECT_EQ("", service()->GetClientTag(entity_data));
}
TEST_F(DeviceInfoServiceTest, TestInitStoreThenProc) {
scoped_ptr<WriteBatch> batch = store()->CreateWriteBatch();
DeviceInfoSpecifics specifics(GenerateTestSpecifics());
store()->WriteData(batch.get(), "tag", specifics.SerializeAsString());
DataTypeState state;
state.set_encryption_key_name("ekn");
store()->WriteGlobalMetadata(batch.get(), state.SerializeAsString());
store()->CommitWriteBatch(std::move(batch),
base::Bind(&AssertResultIsSuccess));
InitializeAndPump();
// Verify that we have data. We do this because we're testing that the service
// may sometimes come up after our store init is fully completed.
ScopedVector<DeviceInfo> all_device_info(service()->GetAllDeviceInfo());
ASSERT_EQ(1u, all_device_info.size());
AssertEqual(specifics, *all_device_info[0]);
AssertEqual(specifics, *service()->GetDeviceInfo("tag").get());
SetProcessorAndPump();
ASSERT_TRUE(processor()->metadata());
ASSERT_EQ(state.encryption_key_name(),
processor()->metadata()->GetDataTypeState().encryption_key_name());
}
TEST_F(DeviceInfoServiceTest, TestInitProcBeforeStoreFinishes) {
scoped_ptr<WriteBatch> batch = store()->CreateWriteBatch();
DeviceInfoSpecifics specifics(GenerateTestSpecifics());
store()->WriteData(batch.get(), "tag", specifics.SerializeAsString());
DataTypeState state;
state.set_encryption_key_name("ekn");
store()->WriteGlobalMetadata(batch.get(), state.SerializeAsString());
store()->CommitWriteBatch(std::move(batch),
base::Bind(&AssertResultIsSuccess));
InitializeService();
// Verify we have _NO_ data yet, to verify that we're testing when the
// processor is attached and ready before our store init is fully completed.
ASSERT_EQ(0u, service()->GetAllDeviceInfo().size());
SetProcessorAndPump();
ASSERT_TRUE(processor()->metadata());
ASSERT_EQ(state.encryption_key_name(),
processor()->metadata()->GetDataTypeState().encryption_key_name());
}
TEST_F(DeviceInfoServiceTest, GetData) {
scoped_ptr<WriteBatch> batch = store()->CreateWriteBatch();
DeviceInfoSpecifics specifics1(GenerateTestSpecifics());
DeviceInfoSpecifics specifics3(GenerateTestSpecifics());
store()->WriteData(batch.get(), "tag1", specifics1.SerializeAsString());
store()->WriteData(batch.get(), "tag2",
GenerateTestSpecifics().SerializeAsString());
store()->WriteData(batch.get(), "tag3", specifics3.SerializeAsString());
store()->CommitWriteBatch(std::move(batch),
base::Bind(&AssertResultIsSuccess));
InitializeAndPump();
std::map<std::string, DeviceInfoSpecifics> expected;
expected["tag1"] = specifics1;
expected["tag3"] = specifics3;
ClientTagList client_tags;
client_tags.push_back("tag1");
client_tags.push_back("tag3");
service()->GetData(client_tags,
base::Bind(&AssertExpectedFromDataBatch, expected));
}
TEST_F(DeviceInfoServiceTest, GetDataMissing) {
InitializeAndPump();
std::map<std::string, DeviceInfoSpecifics> expected;
ClientTagList client_tags;
client_tags.push_back("tag1");
service()->GetData(client_tags,
base::Bind(&AssertExpectedFromDataBatch, expected));
}
TEST_F(DeviceInfoServiceTest, GetDataNotInitialized) {
InitializeService();
ClientTagList client_tags;
service()->GetData(client_tags, base::Bind(&AssertErrorFromDataBatch));
}
TEST_F(DeviceInfoServiceTest, GetAllData) {
scoped_ptr<WriteBatch> batch = store()->CreateWriteBatch();
DeviceInfoSpecifics specifics1(GenerateTestSpecifics());
DeviceInfoSpecifics specifics2(GenerateTestSpecifics());
store()->WriteData(batch.get(), "tag1", specifics1.SerializeAsString());
store()->WriteData(batch.get(), "tag2", specifics2.SerializeAsString());
store()->CommitWriteBatch(std::move(batch),
base::Bind(&AssertResultIsSuccess));
InitializeAndPump();
std::map<std::string, DeviceInfoSpecifics> expected;
expected["tag1"] = specifics1;
expected["tag2"] = specifics2;
ClientTagList client_tags;
client_tags.push_back("tag1");
client_tags.push_back("tag2");
service()->GetData(client_tags,
base::Bind(&AssertExpectedFromDataBatch, expected));
}
TEST_F(DeviceInfoServiceTest, GetAllDataNotInitialized) {
InitializeService();
service()->GetAllData(base::Bind(&AssertErrorFromDataBatch));
}
} // namespace
} // namespace sync_driver_v2