Commit aadda591 authored by Mikel Astiz's avatar Mikel Astiz Committed by Commit Bot

Introduce SessionStore for migrating sessions to USS

The class is a key piece in the design doc ([1], accessible for Google
employees only) and will be used by the future USS bridge, to be
introduced in follow-up patches.

Ideally, SessionStore is an abstraction on top of SyncedSessionTracker
that in addition supports [de]serialization to/from disk. In the current
form, in diverges slightly from the design doc because direct mutable
access to the underlying SyncedSessionTracker is exposed. TODOs have
been added to address this in the future.

[1] https://docs.google.com/document/d/1UGM1yhHznmXvfeGvckdzBcrE6GLpgk7LsLmgPhFcIdk/edit#

Bug: 681921
Change-Id: Ia483faf7256b5c6296f33236b42974697f4e6f4e
Reviewed-on: https://chromium-review.googlesource.com/973222
Commit-Queue: Mikel Astiz <mastiz@chromium.org>
Reviewed-by: default avatarMarc Treib <treib@chromium.org>
Cr-Commit-Position: refs/heads/master@{#550587}
parent 303956b6
......@@ -16,6 +16,8 @@
namespace syncer {
SessionSyncPrefs::~SessionSyncPrefs() {}
SyncPrefObserver::~SyncPrefObserver() {}
SyncPrefs::SyncPrefs(PrefService* pref_service) : pref_service_(pref_service) {
......
......@@ -40,6 +40,14 @@ class SyncPrefObserver {
virtual ~SyncPrefObserver();
};
// Use this for the unique machine tag used for session sync.
class SessionSyncPrefs {
public:
virtual ~SessionSyncPrefs();
virtual std::string GetSyncSessionsGUID() const = 0;
virtual void SetSyncSessionsGUID(const std::string& guid) = 0;
};
// SyncPrefs is a helper class that manages getting, setting, and
// persisting global sync preferences. It is not thread-safe, and
// lives on the UI thread.
......@@ -53,7 +61,8 @@ class SyncPrefObserver {
// sync_setup_wizard.cc
// sync_setup_wizard_unittest.cc
// two_client_preferences_sync_test.cc
class SyncPrefs : public base::SupportsWeakPtr<SyncPrefs> {
class SyncPrefs : public SessionSyncPrefs,
public base::SupportsWeakPtr<SyncPrefs> {
public:
// |pref_service| may not be null.
// Does not take ownership of |pref_service|.
......@@ -62,7 +71,7 @@ class SyncPrefs : public base::SupportsWeakPtr<SyncPrefs> {
// For testing.
SyncPrefs();
virtual ~SyncPrefs();
~SyncPrefs() override;
static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
......@@ -118,8 +127,8 @@ class SyncPrefs : public base::SupportsWeakPtr<SyncPrefs> {
void SetKeystoreEncryptionBootstrapToken(const std::string& token);
// Use this for the unique machine tag used for session sync.
std::string GetSyncSessionsGUID() const;
void SetSyncSessionsGUID(const std::string& guid);
std::string GetSyncSessionsGUID() const override;
void SetSyncSessionsGUID(const std::string& guid) override;
// Maps |type| to its corresponding preference name.
static const char* GetPrefNameForDataType(ModelType type);
......
......@@ -5,8 +5,10 @@
#include "components/sync/model/entity_data.h"
#include <algorithm>
#include <ostream>
#include <utility>
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
......@@ -28,22 +30,34 @@ std::string UniquePositionToString(
} // namespace
EntityData::EntityData() = default;
EntityData::~EntityData() = default;
EntityData::EntityData(const EntityData& src) = default;
void EntityData::Swap(EntityData* other) {
id.swap(other->id);
client_tag_hash.swap(other->client_tag_hash);
non_unique_name.swap(other->non_unique_name);
EntityData::EntityData(EntityData&& other)
: id(std::move(other.id)),
client_tag_hash(std::move(other.client_tag_hash)),
non_unique_name(std::move(other.non_unique_name)),
creation_time(other.creation_time),
modification_time(other.modification_time),
parent_id(std::move(other.parent_id)),
is_folder(other.is_folder) {
specifics.Swap(&other.specifics);
unique_position.Swap(&other.unique_position);
}
specifics.Swap(&other->specifics);
EntityData::EntityData(const EntityData& src) = default;
std::swap(creation_time, other->creation_time);
std::swap(modification_time, other->modification_time);
EntityData::~EntityData() = default;
parent_id.swap(other->parent_id);
std::swap(is_folder, other->is_folder);
unique_position.Swap(&other->unique_position);
EntityData& EntityData::operator=(EntityData&& other) {
id = std::move(other.id);
client_tag_hash = std::move(other.client_tag_hash);
non_unique_name = std::move(other.non_unique_name);
creation_time = other.creation_time;
modification_time = other.modification_time;
parent_id = other.parent_id;
is_folder = other.is_folder;
specifics.Swap(&other.specifics);
unique_position.Swap(&other.unique_position);
return *this;
}
EntityDataPtr EntityData::PassToPtr() {
......@@ -106,7 +120,7 @@ size_t EntityData::EstimateMemoryUsage() const {
}
void EntityDataTraits::SwapValue(EntityData* dest, EntityData* src) {
dest->Swap(src);
std::swap(*dest, *src);
}
bool EntityDataTraits::HasValue(const EntityData& value) {
......@@ -118,4 +132,13 @@ const EntityData& EntityDataTraits::DefaultValue() {
return default_instance;
}
void PrintTo(const EntityData& entity_data, std::ostream* os) {
std::string specifics;
base::JSONWriter::WriteWithOptions(
*syncer::EntitySpecificsToValue(entity_data.specifics),
base::JSONWriter::OPTIONS_PRETTY_PRINT, &specifics);
*os << "{ id: '" << entity_data.id << "', client_tag_hash: '"
<< entity_data.client_tag_hash << "', specifics: " << specifics << "}";
}
} // namespace syncer
......@@ -5,6 +5,7 @@
#ifndef COMPONENTS_SYNC_MODEL_ENTITY_DATA_H_
#define COMPONENTS_SYNC_MODEL_ENTITY_DATA_H_
#include <iosfwd>
#include <map>
#include <memory>
#include <string>
......@@ -36,8 +37,11 @@ using EntityDataList = std::vector<EntityDataPtr>;
struct EntityData {
public:
EntityData();
EntityData(EntityData&&);
~EntityData();
EntityData& operator=(EntityData&&);
// Typically this is a server assigned sync ID, although for a local change
// that represents a new entity this field might be either empty or contain
// a temporary client sync ID.
......@@ -96,10 +100,6 @@ struct EntityData {
size_t EstimateMemoryUsage() const;
private:
friend struct EntityDataTraits;
// Used to transfer the data without copying.
void Swap(EntityData* other);
// Allow copy ctor so that UpdateId and UpdateSpecifics can make a copy of
// this EntityData.
EntityData(const EntityData& src);
......@@ -107,6 +107,9 @@ struct EntityData {
DISALLOW_ASSIGN(EntityData);
};
// gMock printer helper.
void PrintTo(const EntityData& entity_data, std::ostream* os);
} // namespace syncer
#endif // COMPONENTS_SYNC_MODEL_ENTITY_DATA_H_
......@@ -11,7 +11,11 @@ namespace syncer {
MetadataBatch::MetadataBatch() {}
MetadataBatch::~MetadataBatch() {}
EntityMetadataMap&& MetadataBatch::TakeAllMetadata() {
const EntityMetadataMap& MetadataBatch::GetAllMetadata() const {
return metadata_map_;
}
EntityMetadataMap MetadataBatch::TakeAllMetadata() {
return std::move(metadata_map_);
}
......
......@@ -20,12 +20,15 @@ using EntityMetadataMap = std::map<std::string, sync_pb::EntityMetadata>;
class MetadataBatch {
public:
MetadataBatch();
virtual ~MetadataBatch();
~MetadataBatch();
// Read-only access to the entire metadata map.
const EntityMetadataMap& GetAllMetadata() const;
// Allows the caller to take ownership of the entire metadata map. This is
// done because the caller will probably swap out all the EntityMetadata
// protos from the map for performance reasons.
EntityMetadataMap&& TakeAllMetadata();
EntityMetadataMap TakeAllMetadata();
// Add |metadata| for |storage_key| to the batch.
void AddMetadata(const std::string& storage_key,
......
......@@ -39,6 +39,8 @@ class ModelError {
// Typedef for a simple error handler callback.
using ModelErrorHandler = base::RepeatingCallback<void(const ModelError&)>;
using OnceModelErrorHandler = base::OnceCallback<void(const ModelError&)>;
} // namespace syncer
#endif // COMPONENTS_SYNC_MODEL_MODEL_ERROR_H_
......@@ -12,6 +12,45 @@
#include "testing/gtest/include/gtest/gtest.h"
namespace syncer {
namespace {
// Implementation of ModelTypeStore that delegates all calls to another
// instance, as injected in the constructor, useful for APIs that take ownership
// of ModelTypeStore.
class ForwardingModelTypeStore : public ModelTypeStore {
public:
explicit ForwardingModelTypeStore(ModelTypeStore* other) : other_(other) {}
void ReadData(const IdList& id_list, ReadDataCallback callback) override {
other_->ReadData(id_list, std::move(callback));
}
void ReadAllData(ReadAllDataCallback callback) override {
other_->ReadAllData(std::move(callback));
}
void ReadAllMetadata(ReadMetadataCallback callback) override {
other_->ReadAllMetadata(std::move(callback));
}
std::unique_ptr<WriteBatch> CreateWriteBatch() override {
return other_->CreateWriteBatch();
}
void CommitWriteBatch(std::unique_ptr<WriteBatch> write_batch,
CallbackWithResult callback) override {
other_->CommitWriteBatch(std::move(write_batch), std::move(callback));
}
void DeleteAllDataAndMetadata(CallbackWithResult callback) override {
other_->DeleteAllDataAndMetadata(std::move(callback));
}
private:
ModelTypeStore* other_;
};
} // namespace
// static
std::unique_ptr<ModelTypeStore>
......@@ -57,4 +96,17 @@ void ModelTypeStoreTestUtil::MoveStoreToCallback(
std::move(callback).Run(/*error=*/base::nullopt, std::move(store));
}
// static
RepeatingModelTypeStoreFactory
ModelTypeStoreTestUtil::FactoryForForwardingStore(ModelTypeStore* target) {
return base::BindRepeating(
[](ModelTypeStore* target, ModelType,
ModelTypeStore::InitCallback callback) {
std::move(callback).Run(
/*error=*/base::nullopt,
std::make_unique<ForwardingModelTypeStore>(target));
},
base::Unretained(target));
}
} // namespace syncer
......@@ -28,6 +28,12 @@ class ModelTypeStoreTestUtil {
static void MoveStoreToCallback(std::unique_ptr<ModelTypeStore> store,
ModelType type,
ModelTypeStore::InitCallback callback);
// Returns a callback that constructs a store that forwards all calls to
// |target|. |*target| must outlive the returned factory as well any store
// created by the factory.
static RepeatingModelTypeStoreFactory FactoryForForwardingStore(
ModelTypeStore* target);
};
} // namespace syncer
......
......@@ -17,6 +17,8 @@ static_library("sync_sessions") {
"open_tabs_ui_delegate_impl.h",
"session_data_type_controller.cc",
"session_data_type_controller.h",
"session_store.cc",
"session_store.h",
"sessions_global_id_mapper.cc",
"sessions_global_id_mapper.h",
"sessions_sync_manager.cc",
......@@ -90,6 +92,7 @@ source_set("unit_tests") {
"lost_navigations_recorder_unittest.cc",
"open_tabs_ui_delegate_impl_unittest.cc",
"session_data_type_controller_unittest.cc",
"session_store_unittest.cc",
"sessions_global_id_mapper_unittest.cc",
"sessions_sync_manager_unittest.cc",
"synced_session_tracker_unittest.cc",
......
// Copyright 2018 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_sessions/session_store.h"
#include <stdint.h>
#include <algorithm>
#include <set>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/metrics/histogram_macros.h"
#include "base/pickle.h"
#include "base/strings/stringprintf.h"
#include "components/sync/base/hash_util.h"
#include "components/sync/base/sync_prefs.h"
#include "components/sync/base/time.h"
#include "components/sync/device_info/device_info.h"
#include "components/sync/device_info/device_info_util.h"
#include "components/sync/model/entity_change.h"
#include "components/sync/model/metadata_batch.h"
#include "components/sync/model/mutable_data_batch.h"
#include "components/sync/protocol/model_type_state.pb.h"
#include "components/sync/protocol/sync.pb.h"
#include "components/sync_sessions/sync_sessions_client.h"
namespace sync_sessions {
namespace {
using sync_pb::SessionSpecifics;
using syncer::MetadataChangeList;
using syncer::ModelTypeStore;
std::string TabNodeIdToClientTag(const std::string& session_tag,
int tab_node_id) {
CHECK_GT(tab_node_id, TabNodePool::kInvalidTabNodeID);
return base::StringPrintf("%s %d", session_tag.c_str(), tab_node_id);
}
std::string EncodeStorageKey(const std::string& session_tag, int tab_node_id) {
base::Pickle pickle;
pickle.WriteString(session_tag);
pickle.WriteInt(tab_node_id);
return std::string(static_cast<const char*>(pickle.data()), pickle.size());
}
bool DecodeStorageKey(const std::string& storage_key,
std::string* session_tag,
int* tab_node_id) {
base::Pickle pickle(storage_key.c_str(), storage_key.size());
base::PickleIterator iter(pickle);
if (!iter.ReadString(session_tag)) {
return false;
}
if (!iter.ReadInt(tab_node_id)) {
return false;
}
return true;
}
std::unique_ptr<syncer::EntityData> MoveToEntityData(
const std::string& client_name,
SessionSpecifics* specifics) {
auto entity_data = std::make_unique<syncer::EntityData>();
entity_data->non_unique_name = client_name;
if (specifics->has_header()) {
entity_data->non_unique_name += " (header)";
} else if (specifics->has_tab()) {
entity_data->non_unique_name +=
base::StringPrintf(" (tab node %d)", specifics->tab_node_id());
}
entity_data->specifics.mutable_session()->Swap(specifics);
return entity_data;
}
std::string GetSessionTagWithPrefs(const std::string& cache_guid,
syncer::SessionSyncPrefs* sync_prefs) {
const std::string persisted_guid = sync_prefs->GetSyncSessionsGUID();
if (!persisted_guid.empty()) {
DVLOG(1) << "Restoring persisted session sync guid: " << persisted_guid;
return persisted_guid;
}
const std::string new_guid =
base::StringPrintf("session_sync%s", cache_guid.c_str());
DVLOG(1) << "Creating session sync guid: " << new_guid;
sync_prefs->SetSyncSessionsGUID(new_guid);
return new_guid;
}
void OnLocalDeviceInfoAvailable(
syncer::SessionSyncPrefs* sync_prefs,
const syncer::LocalDeviceInfoProvider* provider,
const base::RepeatingCallback<void(const SessionStore::SessionInfo&)>&
callback) {
const syncer::DeviceInfo* device_info = provider->GetLocalDeviceInfo();
const std::string cache_guid = provider->GetLocalSyncCacheGUID();
DCHECK(device_info);
DCHECK(!cache_guid.empty());
SessionStore::SessionInfo session_info;
session_info.client_name = device_info->client_name();
session_info.device_type = device_info->device_type();
session_info.session_tag = GetSessionTagWithPrefs(cache_guid, sync_prefs);
callback.Run(session_info);
}
// Listens to local device information and triggers the provided callback
// when the object is constructed (once DeviceInfo is available). Caller is
// responsible for storing the returned subscription as a mechanism to cancel
// the creation request (the subscription must not outlive |provider|).
std::unique_ptr<syncer::LocalDeviceInfoProvider::Subscription>
SubscribeToSessionInfo(
syncer::SessionSyncPrefs* sync_prefs,
syncer::LocalDeviceInfoProvider* provider,
const base::RepeatingCallback<void(const SessionStore::SessionInfo&)>&
callback) {
if (provider->GetLocalDeviceInfo()) {
OnLocalDeviceInfoAvailable(sync_prefs, provider, callback);
return nullptr;
}
return provider->RegisterOnInitializedCallback(base::BindRepeating(
&OnLocalDeviceInfoAvailable, base::Unretained(sync_prefs),
base::Unretained(provider), callback));
}
void ForwardError(syncer::OnceModelErrorHandler error_handler,
const base::Optional<syncer::ModelError>& error) {
if (error) {
std::move(error_handler).Run(*error);
}
}
class FactoryImpl : public base::SupportsWeakPtr<FactoryImpl> {
public:
// Raw pointers must not be null and must outlive this object.
FactoryImpl(SyncSessionsClient* sessions_client,
syncer::SessionSyncPrefs* sync_prefs,
syncer::LocalDeviceInfoProvider* local_device_info_provider,
const syncer::RepeatingModelTypeStoreFactory& store_factory,
const SessionStore::RestoredForeignTabCallback&
restored_foreign_tab_callback)
: sessions_client_(sessions_client),
store_factory_(store_factory),
restored_foreign_tab_callback_(restored_foreign_tab_callback) {
DCHECK(sessions_client);
DCHECK(sync_prefs);
DCHECK(local_device_info_provider);
DCHECK(store_factory_);
local_device_info_subscription_ = SubscribeToSessionInfo(
sync_prefs, local_device_info_provider,
base::BindRepeating(&FactoryImpl::OnSessionInfoAvailable,
base::Unretained(this)));
}
~FactoryImpl() {}
void Create(SessionStore::FactoryCompletionCallback callback) {
if (!session_info_.has_value()) {
DVLOG(1) << "Deferring creation of store until session info is available";
deferred_creations_.push_back(std::move(callback));
return;
}
CreateImpl(std::move(callback));
}
private:
void OnSessionInfoAvailable(const SessionStore::SessionInfo& session_info) {
local_device_info_subscription_.reset();
session_info_ = session_info;
std::vector<SessionStore::FactoryCompletionCallback> deferred_creations;
std::swap(deferred_creations, deferred_creations_);
for (SessionStore::FactoryCompletionCallback& callback :
deferred_creations) {
CreateImpl(std::move(callback));
}
}
void CreateImpl(SessionStore::FactoryCompletionCallback callback) {
DCHECK(session_info_.has_value());
DCHECK(deferred_creations_.empty());
DVLOG(1) << "Initiating creation of session store";
store_factory_.Run(
syncer::SESSIONS,
base::BindOnce(&FactoryImpl::OnStoreCreated, base::AsWeakPtr(this),
std::move(callback)));
}
void OnStoreCreated(SessionStore::FactoryCompletionCallback callback,
const base::Optional<syncer::ModelError>& error,
std::unique_ptr<ModelTypeStore> store) {
if (error) {
std::move(callback).Run(error, /*store=*/nullptr,
/*metadata_batch=*/nullptr);
return;
}
DCHECK(store);
ModelTypeStore* store_copy = store.get();
store_copy->ReadAllData(
base::BindOnce(&FactoryImpl::OnReadAllData, base::AsWeakPtr(this),
std::move(callback), std::move(store)));
}
void OnReadAllData(SessionStore::FactoryCompletionCallback callback,
std::unique_ptr<ModelTypeStore> store,
const base::Optional<syncer::ModelError>& error,
std::unique_ptr<ModelTypeStore::RecordList> record_list) {
if (error) {
std::move(callback).Run(error, /*store=*/nullptr,
/*metadata_batch=*/nullptr);
return;
}
store->ReadAllMetadata(base::BindOnce(
&FactoryImpl::OnReadAllMetadata, base::AsWeakPtr(this),
std::move(callback), std::move(store), std::move(record_list)));
}
void OnReadAllMetadata(
SessionStore::FactoryCompletionCallback callback,
std::unique_ptr<ModelTypeStore> store,
std::unique_ptr<ModelTypeStore::RecordList> record_list,
const base::Optional<syncer::ModelError>& error,
std::unique_ptr<syncer::MetadataBatch> metadata_batch) {
if (error) {
std::move(callback).Run(error, /*store=*/nullptr,
/*metadata_batch=*/nullptr);
return;
}
std::map<std::string, sync_pb::SessionSpecifics> initial_data;
for (ModelTypeStore::Record& record : *record_list) {
const std::string& storage_key = record.id;
SessionSpecifics specifics;
if (storage_key.empty() ||
!specifics.ParseFromString(std::move(record.value))) {
DVLOG(1) << "Ignoring corrupt database entry with key: " << storage_key;
continue;
}
initial_data[storage_key].Swap(&specifics);
}
auto session_store = std::make_unique<SessionStore>(
sessions_client_, *session_info_, std::move(store),
std::move(initial_data), metadata_batch->GetAllMetadata(),
restored_foreign_tab_callback_);
std::move(callback).Run(/*error=*/base::nullopt, std::move(session_store),
std::move(metadata_batch));
}
SyncSessionsClient* const sessions_client_;
const syncer::RepeatingModelTypeStoreFactory store_factory_;
const SessionStore::RestoredForeignTabCallback restored_foreign_tab_callback_;
base::Optional<SessionStore::SessionInfo> session_info_;
std::vector<SessionStore::FactoryCompletionCallback> deferred_creations_;
std::unique_ptr<syncer::LocalDeviceInfoProvider::Subscription>
local_device_info_subscription_;
};
} // namespace
// static
SessionStore::Factory SessionStore::CreateFactory(
SyncSessionsClient* sessions_client,
syncer::SessionSyncPrefs* sync_prefs,
syncer::LocalDeviceInfoProvider* local_device_info_provider,
const syncer::RepeatingModelTypeStoreFactory& store_factory,
const RestoredForeignTabCallback& restored_foreign_tab_callback) {
auto factory = std::make_unique<FactoryImpl>(
sessions_client, sync_prefs, local_device_info_provider, store_factory,
restored_foreign_tab_callback);
return base::BindRepeating(&FactoryImpl::Create, std::move(factory));
}
SessionStore::WriteBatch::WriteBatch(
std::unique_ptr<ModelTypeStore::WriteBatch> batch,
CommitCallback commit_cb,
syncer::OnceModelErrorHandler error_handler,
SyncedSessionTracker* session_tracker)
: batch_(std::move(batch)),
commit_cb_(std::move(commit_cb)),
error_handler_(std::move(error_handler)),
session_tracker_(session_tracker) {
DCHECK(batch_);
DCHECK(commit_cb_);
DCHECK(error_handler_);
DCHECK(session_tracker_);
}
SessionStore::WriteBatch::~WriteBatch() {
DCHECK(!batch_) << "Destructed without prior commit";
}
std::string SessionStore::WriteBatch::PutAndUpdateTracker(
const sync_pb::SessionSpecifics& specifics,
base::Time modification_time) {
UpdateTrackerWithSpecifics(specifics, modification_time, session_tracker_);
return PutWithoutUpdatingTracker(specifics);
}
std::string SessionStore::WriteBatch::DeleteForeignEntityAndUpdateTracker(
const sync_pb::SessionSpecifics& specifics) {
DCHECK(AreValidSpecifics(specifics));
DCHECK_NE(specifics.session_tag(), session_tracker_->GetLocalSessionTag());
if (specifics.has_header()) {
// TODO(mastiz): This cascades with the removal of tabs too. Should we
// reflect this as batch_->DeleteData()? The old code didn't, presumably
// because we expect the rest of the removals to follow.
session_tracker_->DeleteForeignSession(specifics.session_tag());
} else {
session_tracker_->DeleteForeignTab(specifics.session_tag(),
specifics.tab_node_id());
}
const std::string storage_key = GetStorageKey(specifics);
batch_->DeleteData(storage_key);
return storage_key;
}
std::string SessionStore::WriteBatch::PutWithoutUpdatingTracker(
const sync_pb::SessionSpecifics& specifics) {
DCHECK(AreValidSpecifics(specifics));
const std::string storage_key = GetStorageKey(specifics);
batch_->WriteData(storage_key, specifics.SerializeAsString());
return storage_key;
}
std::string SessionStore::WriteBatch::DeleteLocalTabWithoutUpdatingTracker(
int tab_node_id) {
std::string storage_key =
EncodeStorageKey(session_tracker_->GetLocalSessionTag(), tab_node_id);
batch_->DeleteData(storage_key);
return storage_key;
}
MetadataChangeList* SessionStore::WriteBatch::GetMetadataChangeList() {
return batch_->GetMetadataChangeList();
}
// static
void SessionStore::WriteBatch::Commit(std::unique_ptr<WriteBatch> batch) {
DCHECK(batch);
std::move(batch->commit_cb_)
.Run(std::move(batch->batch_),
base::BindOnce(&ForwardError, std::move(batch->error_handler_)));
}
// static
bool SessionStore::AreValidSpecifics(const SessionSpecifics& specifics) {
if (specifics.session_tag().empty()) {
return false;
}
if (specifics.has_header()) {
return !specifics.has_tab() &&
specifics.tab_node_id() == TabNodePool::kInvalidTabNodeID;
}
if (specifics.has_tab()) {
return specifics.tab_node_id() >= 0 && specifics.tab().tab_id() > 0;
}
return false;
}
// static
std::string SessionStore::GetClientTag(const SessionSpecifics& specifics) {
DCHECK(AreValidSpecifics(specifics));
if (specifics.has_header()) {
return specifics.session_tag();
}
DCHECK(specifics.has_tab());
return TabNodeIdToClientTag(specifics.session_tag(), specifics.tab_node_id());
}
// static
std::string SessionStore::GetStorageKey(const SessionSpecifics& specifics) {
DCHECK(AreValidSpecifics(specifics));
return EncodeStorageKey(specifics.session_tag(), specifics.tab_node_id());
}
// static
std::string SessionStore::GetHeaderStorageKeyForTest(
const std::string& session_tag) {
return EncodeStorageKey(session_tag, TabNodePool::kInvalidTabNodeID);
}
// static
std::string SessionStore::GetTabStorageKeyForTest(
const std::string& session_tag,
int tab_node_id) {
DCHECK_GE(tab_node_id, 0);
return EncodeStorageKey(session_tag, tab_node_id);
}
SessionStore::SessionStore(
SyncSessionsClient* sessions_client,
const SessionInfo& local_session_info,
std::unique_ptr<ModelTypeStore> store,
std::map<std::string, sync_pb::SessionSpecifics> initial_data,
const syncer::EntityMetadataMap& initial_metadata,
const RestoredForeignTabCallback& restored_foreign_tab_callback)
: store_(std::move(store)),
local_session_info_(local_session_info),
session_tracker_(sessions_client),
weak_ptr_factory_(this) {
DCHECK(store_);
DVLOG(1) << "Constructed session store with " << initial_data.size()
<< " restored entities and " << initial_metadata.size()
<< " metadata entries.";
session_tracker_.InitLocalSession(local_session_info.session_tag,
local_session_info.client_name,
local_session_info.device_type);
// Map of all rewritten local ids. Because ids are reset on each restart,
// and id generation happens outside of Sync, all ids from a previous local
// session must be rewritten in order to be valid (i.e not collide with
// newly assigned IDs). Otherwise, SyncedSessionTracker could mix up IDs.
// Key: previous session id. Value: new session id.
std::map<SessionID::id_type, SessionID> session_id_map;
bool found_local_header = false;
for (auto& storage_key_and_specifics : initial_data) {
const std::string& storage_key = storage_key_and_specifics.first;
SessionSpecifics& specifics = storage_key_and_specifics.second;
// The store should not contain invalid data, but as a precaution we filter
// out anyway in case the persisted data is corrupted.
if (!AreValidSpecifics(specifics)) {
continue;
}
// Metadata should be available if data is available. If not, it means
// the local store is corrupt, because we delete all data and metadata
// at the same time (e.g. sync is disabled).
syncer::EntityMetadataMap::const_iterator metadata_it =
initial_metadata.find(storage_key);
if (metadata_it == initial_metadata.end()) {
continue;
}
const base::Time mtime =
syncer::ProtoTimeToTime(metadata_it->second.modification_time());
if (specifics.session_tag() != local_session_info.session_tag) {
UpdateTrackerWithSpecifics(specifics, mtime, &session_tracker_);
// Notify listeners. In practice, this has the goal to load the URLs and
// visit times into the in-memory favicon cache.
if (specifics.has_tab()) {
restored_foreign_tab_callback.Run(specifics.tab(), mtime);
}
} else if (specifics.has_header()) {
// This is previously stored local header information. Restoring the local
// is actually needed on Android only where we might not have a complete
// view of local window/tabs.
// Two local headers cannot coexist because they would use the very same
// storage key in ModelTypeStore/LevelDB.
DCHECK(!found_local_header);
found_local_header = true;
// Go through and generate new tab and window ids as necessary, updating
// the specifics in place.
for (auto& window : *specifics.mutable_header()->mutable_window()) {
session_id_map.emplace(window.window_id(), SessionID::NewUnique());
window.set_window_id(session_id_map.at(window.window_id()).id());
for (int& tab_id : *window.mutable_tab()) {
if (session_id_map.count(tab_id) == 0) {
session_id_map.emplace(tab_id, SessionID::NewUnique());
}
tab_id = session_id_map.at(tab_id).id();
// Note: the tab id of the SessionTab will be updated when the tab
// node itself is processed.
}
}
UpdateTrackerWithSpecifics(specifics, mtime, &session_tracker_);
DVLOG(1) << "Loaded local header and rewrote " << session_id_map.size()
<< " ids.";
} else {
DCHECK(specifics.has_tab());
// This is a valid old tab node, add it to the tracker and associate
// it (using the new tab id).
DVLOG(1) << "Associating local tab " << specifics.tab().tab_id()
<< " with node " << specifics.tab_node_id();
// Now file the tab under the new tab id.
SessionID new_tab_id = SessionID::InvalidValue();
auto iter = session_id_map.find(specifics.tab().tab_id());
if (iter != session_id_map.end()) {
new_tab_id = iter->second;
} else {
new_tab_id = SessionID::NewUnique();
session_id_map.emplace(specifics.tab().tab_id(), new_tab_id);
}
DVLOG(1) << "Remapping tab " << specifics.tab().tab_id() << " to "
<< new_tab_id;
specifics.mutable_tab()->set_tab_id(new_tab_id.id());
session_tracker_.ReassociateLocalTab(specifics.tab_node_id(), new_tab_id);
UpdateTrackerWithSpecifics(specifics, mtime, &session_tracker_);
}
}
// Cleanup all foreign sessions, since orphaned tabs may have been added after
// the header.
for (const SyncedSession* session :
session_tracker_.LookupAllForeignSessions(SyncedSessionTracker::RAW)) {
session_tracker_.CleanupSession(session->session_tag);
}
}
SessionStore::~SessionStore() {}
std::unique_ptr<syncer::DataBatch> SessionStore::GetLocalSessionDataForKeys(
const std::vector<std::string>& storage_keys) const {
auto batch = std::make_unique<syncer::MutableDataBatch>();
const SyncedSession* local_session = session_tracker_.LookupLocalSession();
DCHECK(local_session);
DCHECK_EQ(local_session->session_tag, local_session_info_.session_tag);
for (const std::string& storage_key : storage_keys) {
std::string session_tag;
int tab_node_id;
bool success = DecodeStorageKey(storage_key, &session_tag, &tab_node_id);
DCHECK(success);
DCHECK_EQ(session_tag, local_session->session_tag);
// Header entity.
if (tab_node_id == TabNodePool::kInvalidTabNodeID) {
sync_pb::SessionSpecifics header_pb;
header_pb.set_session_tag(local_session->session_tag);
local_session->ToSessionHeaderProto().Swap(header_pb.mutable_header());
DCHECK_EQ(storage_key, GetStorageKey(header_pb));
batch->Put(storage_key,
MoveToEntityData(local_session_info_.client_name, &header_pb));
continue;
}
// Tab entities.
const SessionID tab_id =
session_tracker_.LookupLocalTabIdFromTabNodeId(tab_node_id);
DCHECK(tab_id.is_valid());
const sessions::SessionTab* tab = session_tracker_.LookupSessionTab(
local_session_info_.session_tag, tab_id);
DCHECK(tab);
sync_pb::SessionSpecifics tab_pb;
tab_pb.set_session_tag(local_session->session_tag);
tab_pb.set_tab_node_id(tab_node_id);
tab->ToSyncData().Swap(tab_pb.mutable_tab());
DCHECK_EQ(storage_key, GetStorageKey(tab_pb));
batch->Put(storage_key,
MoveToEntityData(local_session_info_.client_name, &tab_pb));
}
return batch;
}
std::unique_ptr<syncer::DataBatch> SessionStore::GetAllLocalSessionData()
const {
auto batch = std::make_unique<syncer::MutableDataBatch>();
const SyncedSession* session = session_tracker_.LookupLocalSession();
DCHECK(session);
// Return header entity.
sync_pb::SessionSpecifics header_pb;
header_pb.set_session_tag(session->session_tag);
session->ToSessionHeaderProto().Swap(header_pb.mutable_header());
const std::string header_storage_key = GetStorageKey(header_pb);
batch->Put(header_storage_key,
MoveToEntityData(session->session_name, &header_pb));
// Return mapped tabs.
for (const auto& window_pair : session->windows) {
for (const std::unique_ptr<sessions::SessionTab>& tab :
window_pair.second->wrapped_window.tabs) {
int tab_node_id =
session_tracker_.LookupTabNodeFromLocalTabId(tab->tab_id);
DCHECK_NE(TabNodePool::kInvalidTabNodeID, tab_node_id);
sync_pb::SessionSpecifics tab_pb;
tab_pb.set_tab_node_id(tab_node_id);
*tab_pb.mutable_tab() = tab->ToSyncData();
tab_pb.set_session_tag(session->session_tag);
batch->Put(GetStorageKey(tab_pb),
MoveToEntityData(session->session_name, &tab_pb));
}
}
// TODO(crbug.com/681921) Return unmapped tabs.
return batch;
}
std::unique_ptr<SessionStore::WriteBatch> SessionStore::CreateWriteBatch(
syncer::OnceModelErrorHandler error_handler) {
// The store is guaranteed to outlive WriteBatch instances (as per API
// requirement).
return std::make_unique<WriteBatch>(
store_->CreateWriteBatch(),
base::BindOnce(&ModelTypeStore::CommitWriteBatch,
base::Unretained(store_.get())),
std::move(error_handler), &session_tracker_);
}
void SessionStore::DeleteAllDataAndMetadata() {
session_tracker_.Clear();
return store_->DeleteAllDataAndMetadata(base::DoNothing());
}
} // namespace sync_sessions
// Copyright 2018 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.
#ifndef COMPONENTS_SYNC_SESSIONS_SESSION_STORE_H_
#define COMPONENTS_SYNC_SESSIONS_SESSION_STORE_H_
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "base/callback_forward.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "components/sync/device_info/local_device_info_provider.h"
#include "components/sync/model/data_batch.h"
#include "components/sync/model/model_error.h"
#include "components/sync/model/model_type_store.h"
#include "components/sync_sessions/synced_session_tracker.h"
namespace syncer {
class SessionSyncPrefs;
} // namespace syncer
namespace sync_sessions {
// Class responsible for maintaining an in-memory representation of sync
// sessions (by owning a SyncedSessionTracker) with the capability to persist
// state to disk and restore (data and metadata). The API enforces a valid and
// consistent state of the model, e.g. by making sure there is at most one sync
// entity per client tag.
class SessionStore {
public:
struct SessionInfo {
std::string session_tag;
std::string client_name;
sync_pb::SyncEnums::DeviceType device_type = sync_pb::SyncEnums::TYPE_UNSET;
};
// Creation factory. The instantiation process is quite complex because it
// loads state from disk in addition to other asynchronous dependencies like
// LocalDeviceInfoProvider.
using FactoryCompletionCallback = base::OnceCallback<void(
const base::Optional<syncer::ModelError>& error,
std::unique_ptr<SessionStore> store,
std::unique_ptr<syncer::MetadataBatch> metadata_batch)>;
using Factory =
base::RepeatingCallback<void(FactoryCompletionCallback callback)>;
// Mimics signature of FaviconCache::UpdateMappingsFromForeignTab().
using RestoredForeignTabCallback =
base::RepeatingCallback<void(const sync_pb::SessionTab&, base::Time)>;
// Creates a factory object that is capable of constructing instances of type
// |SessionStore| and handling the involved IO. All pointer arguments must not
// be null and must outlive the factory as well as the instantiated stores.
static Factory CreateFactory(
SyncSessionsClient* sessions_client,
syncer::SessionSyncPrefs* sync_prefs,
syncer::LocalDeviceInfoProvider* local_device_info_provider,
const syncer::RepeatingModelTypeStoreFactory& store_factory,
const RestoredForeignTabCallback& restored_foreign_tab_callback);
// Verifies whether a proto is malformed (e.g. required fields are missing).
static bool AreValidSpecifics(const sync_pb::SessionSpecifics& specifics);
// |specifics| must be valid, see AreValidSpecifics().
static std::string GetClientTag(const sync_pb::SessionSpecifics& specifics);
// |specifics| must be valid, see AreValidSpecifics().
static std::string GetStorageKey(const sync_pb::SessionSpecifics& specifics);
// Various overloads for testing.
static std::string GetHeaderStorageKeyForTest(const std::string& session_tag);
static std::string GetTabStorageKeyForTest(const std::string& session_tag,
int tab_node_id);
// Similar to ModelTypeStore::WriteBatch but enforces a consistent state. In
// the current implementation, some functions do *NOT* update the tracker, so
// callers are responsible for doing so.
// TODO(crbug.com/681921): Enforce consistency between in-memory and persisted
// data by always updating the tracker.
class WriteBatch {
public:
// Callback that mimics the signature of ModelTypeStore::CommitWriteBatch().
using CommitCallback = base::OnceCallback<void(
std::unique_ptr<syncer::ModelTypeStore::WriteBatch>,
syncer::ModelTypeStore::CallbackWithResult)>;
// Raw pointers must not be nullptr and must outlive this object.
WriteBatch(std::unique_ptr<syncer::ModelTypeStore::WriteBatch> batch,
CommitCallback commit_cb,
syncer::OnceModelErrorHandler error_handler,
SyncedSessionTracker* session_tracker);
~WriteBatch();
// Mutations return a storage key.
std::string PutAndUpdateTracker(const sync_pb::SessionSpecifics& specifics,
base::Time modification_time);
std::string DeleteForeignEntityAndUpdateTracker(
const sync_pb::SessionSpecifics& specifics);
// The functions below do not update SyncedSessionTracker and hence it is
// the caller's responsibility to do so *before* calling these functions.
std::string PutWithoutUpdatingTracker(
const sync_pb::SessionSpecifics& specifics);
std::string DeleteLocalTabWithoutUpdatingTracker(int tab_node_id);
syncer::MetadataChangeList* GetMetadataChangeList();
static void Commit(std::unique_ptr<WriteBatch> batch);
private:
std::unique_ptr<syncer::ModelTypeStore::WriteBatch> batch_;
CommitCallback commit_cb_;
syncer::OnceModelErrorHandler error_handler_;
SyncedSessionTracker* const session_tracker_;
DISALLOW_COPY_AND_ASSIGN(WriteBatch);
};
// Construction once all data and metadata has been loaded from disk. Use
// the factory above to take care of the IO. |sessions_client| must not be
// null and must outlive this object.
SessionStore(SyncSessionsClient* sessions_client,
const SessionInfo& local_session_info,
std::unique_ptr<syncer::ModelTypeStore> store,
std::map<std::string, sync_pb::SessionSpecifics> initial_data,
const syncer::EntityMetadataMap& initial_metadata,
const RestoredForeignTabCallback& restored_foreign_tab_callback);
~SessionStore();
const SessionInfo& local_session_info() const { return local_session_info_; }
// Converts the in-memory model (SyncedSessionTracker) of the local session to
// sync protos. |storage_keys| must correspond to valid local session
// entities.
std::unique_ptr<syncer::DataBatch> GetLocalSessionDataForKeys(
const std::vector<std::string>& storage_keys) const;
// Returns all known local session entities, generated from the in-memory
// model (SyncedSessionTracker).
// TODO(crbug.com/681921): Implement the retrieval of foreign sessions too.
// Right now, the issue is that SyncedSessionTracker does *NOT* maintain a
// mapping between tab IDs and tab node IDs for foreign sessions, and hence
// it is impossible to compute storage keys.
std::unique_ptr<syncer::DataBatch> GetAllLocalSessionData() const;
// Write API. WriteBatch instances must not outlive this store and must be
// committed prior to destruction. Besides, more than one uncommitted
// instance must not exist at any time.
std::unique_ptr<WriteBatch> CreateWriteBatch(
syncer::OnceModelErrorHandler error_handler);
void DeleteAllDataAndMetadata();
// TODO(crbug.com/681921): Avoid exposing a mutable tracker, because that
// bypasses the consistency-enforcing API.
SyncedSessionTracker* mutable_tracker() { return &session_tracker_; }
const SyncedSessionTracker* tracker() const { return &session_tracker_; }
private:
// In charge of actually persisting changes to disk.
std::unique_ptr<syncer::ModelTypeStore> store_;
const SessionInfo local_session_info_;
SyncedSessionTracker session_tracker_;
base::WeakPtrFactory<SessionStore> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(SessionStore);
};
} // namespace sync_sessions
#endif // COMPONENTS_SYNC_SESSIONS_SESSION_STORE_H_
// Copyright 2018 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_sessions/session_store.h"
#include <map>
#include <utility>
#include <vector>
#include "base/bind_helpers.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/test/mock_callback.h"
#include "components/sync/base/hash_util.h"
#include "components/sync/base/sync_prefs.h"
#include "components/sync/device_info/local_device_info_provider_mock.h"
#include "components/sync/model/model_type_store_test_util.h"
#include "components/sync/protocol/session_specifics.pb.h"
#include "components/sync/test/test_matchers.h"
#include "components/sync_sessions/mock_sync_sessions_client.h"
#include "components/sync_sessions/test_matchers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace sync_sessions {
namespace {
using sync_pb::SessionSpecifics;
using syncer::DataBatch;
using syncer::EntityData;
using syncer::EntityMetadataMap;
using syncer::HasEncryptionKeyName;
using syncer::IsEmptyMetadataBatch;
using syncer::MetadataBatch;
using syncer::MetadataBatchContains;
using syncer::ModelTypeStore;
using syncer::NoModelError;
using testing::ElementsAre;
using testing::Eq;
using testing::IsEmpty;
using testing::IsNull;
using testing::Matcher;
using testing::NiceMock;
using testing::NotNull;
using testing::Pair;
using testing::Return;
using testing::UnorderedElementsAre;
using testing::_;
const char kCacheGuid[] = "SomeCacheGuid";
const char kClientName[] = "Some Client Name";
class MockSessionSyncPrefs : public syncer::SessionSyncPrefs {
public:
MockSessionSyncPrefs() = default;
~MockSessionSyncPrefs() override = default;
MOCK_CONST_METHOD0(GetSyncSessionsGUID, std::string());
MOCK_METHOD1(SetSyncSessionsGUID, void(const std::string& guid));
};
// A mock callback that a) can be used as mock to verify call expectations and
// b) conveniently exposes the last instantiated session store.
class MockFactoryCompletionCallback {
public:
MOCK_METHOD3(Run,
void(const base::Optional<syncer::ModelError>& error,
SessionStore* store,
MetadataBatch* metadata_batch));
SessionStore::FactoryCompletionCallback Get() {
return base::BindOnce(
[](MockFactoryCompletionCallback* callback,
const base::Optional<syncer::ModelError>& error,
std::unique_ptr<SessionStore> store,
std::unique_ptr<MetadataBatch> metadata_batch) {
// Store a copy of the pointer for GetResult().
callback->store_ = std::move(store);
// Call mock method.
callback->Run(error, callback->store_.get(), metadata_batch.get());
callback->loop_.Quit();
},
base::Unretained(this));
}
// Waits until the callback gets triggered.
void Wait() { loop_.Run(); }
SessionStore* GetResult() { return store_.get(); }
std::unique_ptr<SessionStore> StealResult() { return std::move(store_); }
private:
base::RunLoop loop_;
std::unique_ptr<SessionStore> store_;
};
testing::Matcher<const EntityData&> SpecificsMatchHeader(
testing::Matcher<std::string> session_tag,
const std::vector<int>& window_ids,
const std::vector<int>& tab_ids) {
return testing::Field(
&EntityData::specifics,
testing::Property(&sync_pb::EntitySpecifics::session,
MatchesHeader(session_tag, window_ids, tab_ids)));
}
std::map<std::string, EntityData> BatchToEntityDataMap(
std::unique_ptr<DataBatch> batch) {
std::map<std::string, EntityData> storage_key_to_data;
while (batch && batch->HasNext()) {
auto batch_entry = batch->Next();
const std::string& storage_key = batch_entry.first;
std::unique_ptr<EntityData> entity_data = std::move(batch_entry.second);
EXPECT_THAT(entity_data, NotNull());
if (entity_data) {
storage_key_to_data.emplace(storage_key, std::move(*entity_data));
}
}
return storage_key_to_data;
}
std::unique_ptr<MetadataBatch> ReadAllPersistedMetadataFrom(
ModelTypeStore* store) {
std::unique_ptr<MetadataBatch> batch;
base::RunLoop loop;
store->ReadAllMetadata(base::BindOnce(
[](std::unique_ptr<MetadataBatch>* output_batch, base::RunLoop* loop,
const base::Optional<syncer::ModelError>& error,
std::unique_ptr<MetadataBatch> input_batch) {
EXPECT_FALSE(error) << error->ToString();
EXPECT_THAT(input_batch, NotNull());
*output_batch = std::move(input_batch);
loop->Quit();
},
&batch, &loop));
loop.Run();
return batch;
}
std::map<std::string, SessionSpecifics> ReadAllPersistedDataFrom(
ModelTypeStore* store) {
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, SessionSpecifics> result;
if (records) {
for (const ModelTypeStore::Record& record : *records) {
SessionSpecifics specifics;
EXPECT_TRUE(specifics.ParseFromString(record.value));
result.emplace(record.id, specifics);
}
}
return result;
}
class SessionStoreFactoryTest : public ::testing::Test {
protected:
SessionStoreFactoryTest()
: underlying_store_(
syncer::ModelTypeStoreTestUtil::CreateInMemoryStoreForTest(
syncer::SESSIONS)),
factory_(SessionStore::CreateFactory(
&mock_sync_sessions_client_,
&mock_sync_prefs_,
&mock_device_info_provider_,
syncer::ModelTypeStoreTestUtil::FactoryForForwardingStore(
underlying_store_.get()),
mock_restored_foreign_tab_callback_.Get())) {}
~SessionStoreFactoryTest() override {}
void InitializeDeviceInfoProvider() {
mock_device_info_provider_.Initialize(std::make_unique<syncer::DeviceInfo>(
kCacheGuid, kClientName, "Chromium 10k", "Chrome 10k",
sync_pb::SyncEnums_DeviceType_TYPE_LINUX, "device_id"));
}
base::MessageLoop message_loop_;
testing::NiceMock<MockSyncSessionsClient> mock_sync_sessions_client_;
testing::NiceMock<MockSessionSyncPrefs> mock_sync_prefs_;
syncer::LocalDeviceInfoProviderMock mock_device_info_provider_;
testing::NiceMock<
base::MockCallback<SessionStore::RestoredForeignTabCallback>>
mock_restored_foreign_tab_callback_;
std::unique_ptr<ModelTypeStore> underlying_store_;
SessionStore::Factory factory_;
};
TEST_F(SessionStoreFactoryTest, ShouldWaitForDeviceInfo) {
MockFactoryCompletionCallback completion;
EXPECT_CALL(completion, Run(_, _, _)).Times(0);
factory_.Run(completion.Get());
EXPECT_CALL(completion, Run(NoModelError(), /*store=*/NotNull(),
MetadataBatchContains(_, IsEmpty())));
EXPECT_CALL(mock_sync_prefs_, GetSyncSessionsGUID());
EXPECT_CALL(mock_sync_prefs_,
SetSyncSessionsGUID(std::string("session_sync") + kCacheGuid));
InitializeDeviceInfoProvider();
completion.Wait();
ASSERT_THAT(completion.GetResult(), NotNull());
EXPECT_THAT(completion.GetResult()->local_session_info().client_name,
Eq(kClientName));
// Second deviceinfo should be ignored.
EXPECT_CALL(completion, Run(_, _, _)).Times(0);
EXPECT_CALL(mock_sync_prefs_, GetSyncSessionsGUID()).Times(0);
EXPECT_CALL(mock_sync_prefs_, SetSyncSessionsGUID(_)).Times(0);
InitializeDeviceInfoProvider();
}
TEST_F(SessionStoreFactoryTest,
ShouldCreateStoreIfDeviceInfoInitiallyAvailable) {
EXPECT_CALL(mock_sync_prefs_, GetSyncSessionsGUID());
EXPECT_CALL(mock_sync_prefs_,
SetSyncSessionsGUID(std::string("session_sync") + kCacheGuid));
InitializeDeviceInfoProvider();
MockFactoryCompletionCallback completion;
EXPECT_CALL(completion, Run(NoModelError(), /*store=*/NotNull(),
MetadataBatchContains(_, IsEmpty())));
factory_.Run(completion.Get());
completion.Wait();
ASSERT_THAT(completion.GetResult(), NotNull());
EXPECT_THAT(completion.GetResult()->local_session_info().client_name,
Eq(kClientName));
// Second deviceinfo should be ignored.
EXPECT_CALL(completion, Run(_, _, _)).Times(0);
EXPECT_CALL(mock_sync_prefs_, GetSyncSessionsGUID()).Times(0);
EXPECT_CALL(mock_sync_prefs_, SetSyncSessionsGUID(_)).Times(0);
InitializeDeviceInfoProvider();
}
TEST_F(SessionStoreFactoryTest, ShouldReadSessionsGuidFromPrefs) {
const std::string kCachedGuid = "cachedguid1";
EXPECT_CALL(mock_sync_prefs_, SetSyncSessionsGUID(_)).Times(0);
EXPECT_CALL(mock_sync_prefs_, GetSyncSessionsGUID())
.WillOnce(Return(kCachedGuid));
InitializeDeviceInfoProvider();
NiceMock<MockFactoryCompletionCallback> completion;
factory_.Run(completion.Get());
completion.Wait();
ASSERT_THAT(completion.GetResult(), NotNull());
EXPECT_THAT(completion.GetResult()->local_session_info().session_tag,
Eq(kCachedGuid));
}
// Test fixture that creates an initial session store.
class SessionStoreTest : public SessionStoreFactoryTest {
protected:
const std::string kLocalSessionTag = "localsessiontag";
SessionStoreTest() {
ON_CALL(mock_sync_prefs_, GetSyncSessionsGUID())
.WillByDefault(Return(kLocalSessionTag));
session_store_ = CreateSessionStore();
}
std::unique_ptr<SessionStore> CreateSessionStore() {
NiceMock<MockFactoryCompletionCallback> completion;
InitializeDeviceInfoProvider();
factory_.Run(completion.Get());
completion.Wait();
EXPECT_THAT(completion.GetResult(), NotNull());
return completion.StealResult();
}
SessionStore* session_store() { return session_store_.get(); }
private:
std::unique_ptr<SessionStore> session_store_;
};
TEST_F(SessionStoreTest, ShouldCreateLocalSession) {
const std::string header_storage_key =
SessionStore::GetHeaderStorageKeyForTest(kLocalSessionTag);
EXPECT_THAT(
BatchToEntityDataMap(session_store()->GetAllLocalSessionData()),
ElementsAre(Pair(header_storage_key,
SpecificsMatchHeader(kLocalSessionTag, /*window_ids=*/{},
/*tab_ids=*/{}))));
// Verify that GetLocalSessionDataForKeys() returns the header entity.
EXPECT_THAT(
BatchToEntityDataMap(
session_store()->GetLocalSessionDataForKeys({header_storage_key})),
ElementsAre(Pair(header_storage_key,
SpecificsMatchHeader(kLocalSessionTag, /*window_ids=*/{},
/*tab_ids=*/{}))));
// Verify the underlying storage does NOT contain the data.
EXPECT_THAT(ReadAllPersistedDataFrom(underlying_store_.get()), IsEmpty());
// Verify the underlying storage does NOT contain metadata.
EXPECT_THAT(ReadAllPersistedMetadataFrom(underlying_store_.get()),
IsEmptyMetadataBatch());
}
TEST_F(SessionStoreTest, ShouldWriteAndRestoreMetadata) {
const std::string kStorageKey1 = "TestStorageKey1";
const std::string kServerId1 = "TestServerId1";
const std::string kEncryptionKeyName1 = "TestEncryptionKey1";
// Populate with metadata.
std::unique_ptr<SessionStore::WriteBatch> batch =
session_store()->CreateWriteBatch(/*error_handler=*/base::DoNothing());
ASSERT_THAT(batch, NotNull());
sync_pb::EntityMetadata metadata1;
metadata1.set_server_id(kServerId1);
batch->GetMetadataChangeList()->UpdateMetadata(kStorageKey1, metadata1);
sync_pb::ModelTypeState model_type_state;
model_type_state.set_encryption_key_name(kEncryptionKeyName1);
batch->GetMetadataChangeList()->UpdateModelTypeState(model_type_state);
SessionStore::WriteBatch::Commit(std::move(batch));
// Verify the underlying storage contains the metadata.
EXPECT_THAT(ReadAllPersistedMetadataFrom(underlying_store_.get()),
MetadataBatchContains(HasEncryptionKeyName(kEncryptionKeyName1),
ElementsAre(Pair(kStorageKey1, _))));
// Create second session store.
NiceMock<MockFactoryCompletionCallback> completion;
EXPECT_CALL(completion, Run(NoModelError(), /*store=*/NotNull(),
MetadataBatchContains(
HasEncryptionKeyName(kEncryptionKeyName1),
ElementsAre(Pair(kStorageKey1, _)))));
factory_.Run(completion.Get());
completion.Wait();
EXPECT_THAT(completion.GetResult(), NotNull());
EXPECT_NE(session_store(), completion.GetResult());
}
TEST_F(SessionStoreTest, ShouldUpdateTrackerWithForeignData) {
const std::string kForeignSessionTag = "SomeForeignTag";
const int kWindowId = 5;
const int kTabId1 = 7;
const int kTabId2 = 8;
const int kTabNodeId1 = 2;
const int kTabNodeId2 = 3;
EXPECT_CALL(mock_restored_foreign_tab_callback_, Run(_, _)).Times(0);
ASSERT_THAT(session_store()->tracker()->LookupAllForeignSessions(
SyncedSessionTracker::RAW),
IsEmpty());
// Populate with data.
SessionSpecifics header;
header.set_session_tag(kForeignSessionTag);
header.mutable_header()->add_window()->set_window_id(kWindowId);
header.mutable_header()->mutable_window(0)->add_tab(kTabId1);
header.mutable_header()->mutable_window(0)->add_tab(kTabId2);
ASSERT_TRUE(SessionStore::AreValidSpecifics(header));
SessionSpecifics tab1;
tab1.set_session_tag(kForeignSessionTag);
tab1.set_tab_node_id(kTabNodeId1);
tab1.mutable_tab()->set_window_id(kWindowId);
tab1.mutable_tab()->set_tab_id(kTabId1);
ASSERT_TRUE(SessionStore::AreValidSpecifics(tab1));
SessionSpecifics tab2;
tab2.set_session_tag(kForeignSessionTag);
tab2.set_tab_node_id(kTabNodeId2);
tab2.mutable_tab()->set_window_id(kWindowId);
tab2.mutable_tab()->set_tab_id(kTabId2);
ASSERT_TRUE(SessionStore::AreValidSpecifics(tab2));
std::unique_ptr<SessionStore::WriteBatch> batch =
session_store()->CreateWriteBatch(/*error_handler=*/base::DoNothing());
ASSERT_THAT(batch, NotNull());
batch->PutAndUpdateTracker(header, base::Time::Now());
batch->PutAndUpdateTracker(tab1, base::Time::Now());
batch->PutAndUpdateTracker(tab2, base::Time::Now());
SessionStore::WriteBatch::Commit(std::move(batch));
EXPECT_THAT(session_store()->tracker()->LookupAllForeignSessions(
SyncedSessionTracker::RAW),
ElementsAre(MatchesSyncedSession(
kForeignSessionTag, {{kWindowId, {kTabId1, kTabId2}}})));
}
TEST_F(SessionStoreTest, ShouldWriteAndRestoreForeignData) {
const std::string kForeignSessionTag = "SomeForeignTag";
const int kWindowId = 5;
const int kTabId1 = 7;
const int kTabNodeId1 = 2;
EXPECT_CALL(mock_restored_foreign_tab_callback_, Run(_, _)).Times(0);
const std::string local_header_storage_key =
SessionStore::GetHeaderStorageKeyForTest(kLocalSessionTag);
ASSERT_THAT(session_store()->tracker()->LookupAllForeignSessions(
SyncedSessionTracker::RAW),
IsEmpty());
// Local session is automatically created.
ASSERT_THAT(BatchToEntityDataMap(session_store()->GetAllLocalSessionData()),
ElementsAre(Pair(local_header_storage_key, _)));
ASSERT_THAT(ReadAllPersistedDataFrom(underlying_store_.get()), IsEmpty());
// Populate with data.
SessionSpecifics header;
header.set_session_tag(kForeignSessionTag);
header.mutable_header()->add_window()->set_window_id(kWindowId);
header.mutable_header()->mutable_window(0)->add_tab(kTabId1);
ASSERT_TRUE(SessionStore::AreValidSpecifics(header));
SessionSpecifics tab1;
tab1.set_session_tag(kForeignSessionTag);
tab1.set_tab_node_id(kTabNodeId1);
tab1.mutable_tab()->set_window_id(kWindowId);
tab1.mutable_tab()->set_tab_id(kTabId1);
ASSERT_TRUE(SessionStore::AreValidSpecifics(tab1));
std::unique_ptr<SessionStore::WriteBatch> batch =
session_store()->CreateWriteBatch(/*error_handler=*/base::DoNothing());
ASSERT_THAT(batch, NotNull());
batch->PutAndUpdateTracker(header, base::Time::Now());
batch->PutAndUpdateTracker(tab1, base::Time::Now());
const std::string header_storage_key =
SessionStore::GetHeaderStorageKeyForTest(kForeignSessionTag);
const std::string tab_storage_key1 =
SessionStore::GetTabStorageKeyForTest(kForeignSessionTag, kTabNodeId1);
sync_pb::EntityMetadata header_metadata;
header_metadata.set_server_id("someserverid1");
batch->GetMetadataChangeList()->UpdateMetadata(header_storage_key,
header_metadata);
sync_pb::EntityMetadata tab1_metadata;
tab1_metadata.set_server_id("someserverid2");
batch->GetMetadataChangeList()->UpdateMetadata(tab_storage_key1,
tab1_metadata);
SessionStore::WriteBatch::Commit(std::move(batch));
// Verify the underlying storage contains the data.
ASSERT_THAT(
ReadAllPersistedDataFrom(underlying_store_.get()),
UnorderedElementsAre(
Pair(header_storage_key,
MatchesHeader(kForeignSessionTag, {kWindowId}, {kTabId1})),
Pair(tab_storage_key1,
MatchesTab(kForeignSessionTag, kWindowId, kTabId1, kTabNodeId1,
/*urls=*/_))));
// Verify tracker exposes the foreign tabs.
ASSERT_THAT(
session_store()->tracker()->LookupAllForeignSessions(
SyncedSessionTracker::RAW),
ElementsAre(MatchesSyncedSession(
kForeignSessionTag, {{kWindowId, std::vector<int>{kTabId1}}})));
// Creation of a second session store should trigger a callback for the
// restored tab.
EXPECT_CALL(mock_restored_foreign_tab_callback_,
Run(testing::Property(&sync_pb::SessionTab::tab_id, kTabId1), _));
// Create second session store to verify that the persisted state is restored,
// by mimicing a Chrome restart and using |underlying_store_| (in-memory) as a
// replacement for on-disk persistence.
std::unique_ptr<SessionStore> restored_store = CreateSessionStore();
ASSERT_THAT(restored_store, NotNull());
ASSERT_NE(session_store(), restored_store.get());
// Verify tracker was restored.
EXPECT_THAT(
restored_store->tracker()->LookupAllForeignSessions(
SyncedSessionTracker::RAW),
ElementsAre(MatchesSyncedSession(
kForeignSessionTag, {{kWindowId, std::vector<int>{kTabId1}}})));
}
} // namespace
} // namespace sync_sessions
......@@ -153,17 +153,14 @@ const std::string& SyncedSessionTracker::GetLocalSessionTag() const {
return local_session_tag_;
}
std::vector<const SyncedSession*> SyncedSessionTracker::LookupAllSessions(
SessionLookup lookup) const {
return LookupSessions(lookup, /*exclude_local_session=*/false);
}
std::vector<const SyncedSession*>
SyncedSessionTracker::LookupAllForeignSessions(SessionLookup lookup) const {
std::vector<const SyncedSession*> sessions;
for (const auto& session_pair : session_map_) {
const SyncedSession& foreign_session = session_pair.second.synced_session;
if (session_pair.first != local_session_tag_ &&
(lookup == RAW || IsPresentable(sessions_client_, foreign_session))) {
sessions.push_back(&foreign_session);
}
}
return sessions;
return LookupSessions(lookup, /*exclude_local_session=*/true);
}
bool SyncedSessionTracker::LookupSessionWindows(
......@@ -205,6 +202,18 @@ std::set<int> SyncedSessionTracker::LookupTabNodeIds(
return session ? session->tab_node_ids : std::set<int>();
}
std::vector<const sessions::SessionTab*>
SyncedSessionTracker::LookupUnmappedTabs(const std::string& session_tag) const {
const TrackedSession* session = LookupTrackedSession(session_tag);
std::vector<const sessions::SessionTab*> unmapped_tabs;
if (session) {
for (const auto& unmapped_tab_entry : session->unmapped_tabs) {
unmapped_tabs.push_back(unmapped_tab_entry.second.get());
}
}
return unmapped_tabs;
}
const SyncedSession* SyncedSessionTracker::LookupLocalSession() const {
return LookupSession(local_session_tag_);
}
......@@ -295,6 +304,23 @@ SyncedSessionTracker::TrackedSession* SyncedSessionTracker::GetTrackedSession(
return session;
}
std::vector<const SyncedSession*> SyncedSessionTracker::LookupSessions(
SessionLookup lookup,
bool exclude_local_session) const {
std::vector<const SyncedSession*> sessions;
for (const auto& session_pair : session_map_) {
const SyncedSession& session = session_pair.second.synced_session;
if (lookup == PRESENTABLE && !IsPresentable(sessions_client_, session)) {
continue;
}
if (exclude_local_session && session_pair.first == local_session_tag_) {
continue;
}
sessions.push_back(&session);
}
return sessions;
}
void SyncedSessionTracker::CleanupSessionImpl(const std::string& session_tag) {
TrackedSession* session = LookupTrackedSession(session_tag);
if (!session)
......@@ -471,6 +497,10 @@ void SyncedSessionTracker::CleanupLocalTabs(std::set<int>* deleted_node_ids) {
}
}
int SyncedSessionTracker::LookupTabNodeFromLocalTabId(SessionID tab_id) const {
return local_tab_pool_.GetTabNodeIdFromTabId(tab_id);
}
bool SyncedSessionTracker::GetTabNodeFromLocalTabId(SessionID tab_id,
int* tab_node_id) {
DCHECK(!local_session_tag_.empty());
......@@ -481,19 +511,33 @@ bool SyncedSessionTracker::GetTabNodeFromLocalTabId(SessionID tab_id,
// kept in sync and as consistent as possible.
GetTab(local_session_tag_, tab_id); // Ignore result.
bool reused_existing_tab =
local_tab_pool_.GetTabNodeForTab(tab_id, tab_node_id);
*tab_node_id = local_tab_pool_.GetTabNodeIdFromTabId(tab_id);
if (*tab_node_id != TabNodePool::kInvalidTabNodeID) {
DCHECK_NE(0U, GetTrackedSession(local_session_tag_)
->tab_node_ids.count(*tab_node_id));
return true; // Reused existing tab.
}
// Could not reuse an existing tab so create a new one.
*tab_node_id = local_tab_pool_.AssociateWithFreeTabNode(tab_id);
DCHECK_NE(TabNodePool::kInvalidTabNodeID, *tab_node_id);
GetTrackedSession(local_session_tag_)->tab_node_ids.insert(*tab_node_id);
// AssociateWithFreeTabNode() might have created a new tab node if none could
// be reused so make sure we register it in |tab_node_ids|.
bool reused_existing_tab = !GetTrackedSession(local_session_tag_)
->tab_node_ids.insert(*tab_node_id)
.second;
return reused_existing_tab;
}
bool SyncedSessionTracker::IsLocalTabNodeAssociated(int tab_node_id) {
if (tab_node_id == TabNodePool::kInvalidTabNodeID)
return false;
bool SyncedSessionTracker::IsLocalTabNodeAssociated(int tab_node_id) const {
return local_tab_pool_.GetTabIdFromTabNodeId(tab_node_id).is_valid();
}
SessionID SyncedSessionTracker::LookupLocalTabIdFromTabNodeId(
int tab_node_id) const {
return local_tab_pool_.GetTabIdFromTabNodeId(tab_node_id);
}
void SyncedSessionTracker::ReassociateLocalTab(int tab_node_id,
SessionID new_tab_id) {
DCHECK(!local_session_tag_.empty());
......
......@@ -46,6 +46,12 @@ class SyncedSessionTracker {
// **** Synced session/tab query methods. ****
// Returns vector with all sessions we're tracking. SyncedSession ownership
// remains within the SyncedSessionTracker. Lookup parameter is used to decide
// which tabs should be included.
std::vector<const SyncedSession*> LookupAllSessions(
SessionLookup lookup) const;
// Returns all foreign sessions we're tracking (skips the local session
// object). SyncedSession ownership remains within the SyncedSessionTracker.
// Lookup parameter is used to decide which foreign tabs should be include.
......@@ -56,6 +62,10 @@ class SyncedSessionTracker {
// session having tag |session_tag|.
std::set<int> LookupTabNodeIds(const std::string& session_tag) const;
// Returns tabs that are unmapped for session with tag |session_tag|.
std::vector<const sessions::SessionTab*> LookupUnmappedTabs(
const std::string& session_tag) const;
// Attempts to look up the session windows associatd with the session given
// by |session_tag|. Ownership of SessionWindows stays within the
// SyncedSessionTracker.
......@@ -167,6 +177,10 @@ class SyncedSessionTracker {
// free tab nodes to be deleted.
void CleanupLocalTabs(std::set<int>* deleted_node_ids);
// Returns the tab node ID for |tab_id| if an existing tab node was found, or
// kInvalidTabNodeID otherwise.
int LookupTabNodeFromLocalTabId(SessionID tab_id) const;
// Fills |tab_node_id| with a tab node for |tab_id|. Returns true if an
// existing tab node was found, false if there was none and one had to be
// created.
......@@ -174,7 +188,11 @@ class SyncedSessionTracker {
// Returns whether |tab_node_id| refers to a valid tab node that is associated
// with a tab.
bool IsLocalTabNodeAssociated(int tab_node_id);
bool IsLocalTabNodeAssociated(int tab_node_id) const;
// Returns the local tab ID associated to |tab_node_id| or
// SessionID::InvalidValue() if not associated.
SessionID LookupLocalTabIdFromTabNodeId(int tab_node_id) const;
// Reassociates the tab denoted by |tab_node_id| with a new tab id, preserving
// any previous SessionTab object the node was associated with. This is useful
......@@ -254,6 +272,10 @@ class SyncedSessionTracker {
// Creates tracked session if it wasn't known previously. Never returns null.
TrackedSession* GetTrackedSession(const std::string& session_tag);
std::vector<const SyncedSession*> LookupSessions(
SessionLookup lookup,
bool exclude_local_session) const;
// Implementation of CleanupForeignSession/CleanupLocalTabs.
void CleanupSessionImpl(const std::string& session_tag);
......
......@@ -168,6 +168,44 @@ TEST_F(SyncedSessionTrackerTest, PutTabInWindow) {
// Should clean up memory on its own.
}
TEST_F(SyncedSessionTrackerTest, LookupAllSessions) {
EXPECT_THAT(
GetTracker()->LookupAllSessions(SyncedSessionTracker::PRESENTABLE),
IsEmpty());
GetTracker()->InitLocalSession(kTag, kSessionName, kDeviceType);
GetTracker()->PutWindowInSession(kTag, kWindow1);
GetTracker()->PutTabInWindow(kTag, kWindow1, kTab1);
EXPECT_THAT(GetTracker()->LookupAllSessions(SyncedSessionTracker::RAW),
ElementsAre(HasSessionTag(kTag)));
EXPECT_THAT(
GetTracker()->LookupAllSessions(SyncedSessionTracker::PRESENTABLE),
IsEmpty());
sessions::SessionTab* tab = GetTracker()->GetTab(kTag, kTab1);
ASSERT_TRUE(tab);
tab->navigations.push_back(
sessions::SerializedNavigationEntryTestHelper::CreateNavigation(kValidUrl,
kTitle));
EXPECT_THAT(
GetTracker()->LookupAllSessions(SyncedSessionTracker::PRESENTABLE),
ElementsAre(HasSessionTag(kTag)));
GetTracker()->GetSession(kTag2);
GetTracker()->PutWindowInSession(kTag2, kWindow1);
GetTracker()->PutTabInWindow(kTag2, kWindow1, kTab2);
sessions::SessionTab* tab2 = GetTracker()->GetTab(kTag2, kTab2);
ASSERT_TRUE(tab2);
tab2->navigations.push_back(
sessions::SerializedNavigationEntryTestHelper::CreateNavigation(kValidUrl,
kTitle));
EXPECT_THAT(
GetTracker()->LookupAllSessions(SyncedSessionTracker::PRESENTABLE),
ElementsAre(HasSessionTag(kTag), HasSessionTag(kTag2)));
}
TEST_F(SyncedSessionTrackerTest, LookupAllForeignSessions) {
const char kInvalidUrl[] = "invalid.url";
ON_CALL(*GetSyncSessionsClient(), ShouldSyncURL(GURL(kInvalidUrl)))
......@@ -347,6 +385,20 @@ TEST_F(SyncedSessionTrackerTest, LookupTabNodeIds) {
EXPECT_THAT(GetTracker()->LookupTabNodeIds(kTag2), IsEmpty());
}
TEST_F(SyncedSessionTrackerTest, LookupUnmappedTabs) {
EXPECT_THAT(GetTracker()->LookupUnmappedTabs(kTag), IsEmpty());
sessions::SessionTab* tab = GetTracker()->GetTab(kTag, kTab1);
ASSERT_THAT(tab, NotNull());
EXPECT_THAT(GetTracker()->LookupUnmappedTabs(kTag), ElementsAre(tab));
EXPECT_THAT(GetTracker()->LookupUnmappedTabs(kTag2), IsEmpty());
GetTracker()->PutWindowInSession(kTag, kWindow1);
GetTracker()->PutTabInWindow(kTag, kWindow1, kTab1);
EXPECT_THAT(GetTracker()->LookupUnmappedTabs(kTag), IsEmpty());
}
TEST_F(SyncedSessionTrackerTest, SessionTracking) {
ASSERT_TRUE(GetTracker()->Empty());
......
......@@ -50,25 +50,12 @@ void TabNodePool::AssociateTabNode(int tab_node_id, SessionID tab_id) {
tabid_nodeid_map_[tab_id] = tab_node_id;
}
bool TabNodePool::GetTabNodeForTab(SessionID tab_id, int* tab_node_id) {
if (tabid_nodeid_map_.find(tab_id) != tabid_nodeid_map_.end()) {
*tab_node_id = tabid_nodeid_map_[tab_id];
return true;
}
if (free_nodes_pool_.empty()) {
// Tab pool has no free nodes, allocate new one.
*tab_node_id = ++max_used_tab_node_id_;
AddTabNode(*tab_node_id);
AssociateTabNode(*tab_node_id, tab_id);
return false;
} else {
// Return the next free node.
*tab_node_id = *free_nodes_pool_.begin();
AssociateTabNode(*tab_node_id, tab_id);
return true;
int TabNodePool::GetTabNodeIdFromTabId(SessionID tab_id) const {
TabIDToTabNodeIDMap::const_iterator it = tabid_nodeid_map_.find(tab_id);
if (it != tabid_nodeid_map_.end()) {
return it->second;
}
return kInvalidTabNodeID;
}
void TabNodePool::FreeTab(SessionID tab_id) {
......@@ -85,6 +72,21 @@ void TabNodePool::FreeTab(SessionID tab_id) {
free_nodes_pool_.insert(tab_node_id);
}
int TabNodePool::AssociateWithFreeTabNode(SessionID tab_id) {
int tab_node_id;
if (free_nodes_pool_.empty()) {
// Tab pool has no free nodes, allocate new one.
tab_node_id = ++max_used_tab_node_id_;
AddTabNode(tab_node_id);
} else {
// Return the next free node.
tab_node_id = *free_nodes_pool_.begin();
}
AssociateTabNode(tab_node_id, tab_id);
return tab_node_id;
}
void TabNodePool::ReassociateTabNode(int tab_node_id, SessionID tab_id) {
DCHECK_GT(tab_node_id, kInvalidTabNodeID);
DCHECK(tab_id.is_valid());
......
......@@ -42,19 +42,18 @@ class TabNodePool {
static const int kInvalidTabNodeID;
// Fills |tab_node_id| with a tab node associated with |tab_id|.
// If tab_id is already associated with a tab_node_id, reuses the existing
// association. Otherwise attempts to get the next free tab node and
// associate it with |tab_id|. If none are available, will create a new tab
// node.
// Returns true if a pre-existing tab node could be reused, false if a new one
// had to be created.
bool GetTabNodeForTab(SessionID tab_id, int* tab_node_id);
// Returns the tab node associated with |tab_id| or kInvalidTabNodeID if
// no association existed.
int GetTabNodeIdFromTabId(SessionID tab_id) const;
// Returns the tab_id for |tab_node_id| if it is associated else returns an
// invalid ID.
SessionID GetTabIdFromTabNodeId(int tab_node_id) const;
// Gets the next free tab node (or creates a new one if needed) and associates
// it to |tab_id|. Returns the tab node ID associated to |tab_id|.
int AssociateWithFreeTabNode(SessionID tab_id);
// Reassociates |tab_node_id| with |tab_id|. If |tab_node_id| is not already
// known, it is added to the tab node pool before being associated.
void ReassociateTabNode(int tab_node_id, SessionID tab_id);
......
......@@ -6,6 +6,7 @@
#include <vector>
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace sync_sessions {
......@@ -16,19 +17,19 @@ class SyncTabNodePoolTest : public testing::Test {
int GetMaxUsedTabNodeId() const { return pool_.max_used_tab_node_id_; }
void AddFreeTabNodes(size_t size, const int node_ids[]);
void AddFreeTabNodes(const std::vector<int>& node_ids) {
for (int node_id : node_ids) {
pool_.free_nodes_pool_.insert(node_id);
}
}
TabNodePool pool_;
};
void SyncTabNodePoolTest::AddFreeTabNodes(size_t size, const int node_ids[]) {
for (size_t i = 0; i < size; ++i) {
pool_.free_nodes_pool_.insert(node_ids[i]);
}
}
namespace {
using testing::UnorderedElementsAre;
const int kTabNodeId1 = 10;
const int kTabNodeId2 = 5;
const int kTabNodeId3 = 1000;
......@@ -57,9 +58,11 @@ TEST_F(SyncTabNodePoolTest, TabNodeIdIncreases) {
pool_.CleanupTabNodes(&deleted_node_ids);
EXPECT_TRUE(deleted_node_ids.empty());
for (int i = 0; i < 3; ++i) {
int tab_node_id = -1;
EXPECT_TRUE(pool_.GetTabNodeForTab(SessionID::FromSerializedValue(i + 1),
&tab_node_id));
const SessionID tab_id = SessionID::FromSerializedValue(i + 1);
ASSERT_EQ(TabNodePool::kInvalidTabNodeID,
pool_.GetTabNodeIdFromTabId(tab_id));
EXPECT_NE(TabNodePool::kInvalidTabNodeID,
pool_.AssociateWithFreeTabNode(tab_id));
EXPECT_EQ(kTabNodeId3, GetMaxUsedTabNodeId());
}
pool_.CleanupTabNodes(&deleted_node_ids);
......@@ -135,19 +138,19 @@ TEST_F(SyncTabNodePoolTest, ReassociateThenFree) {
pool_.CleanupTabNodes(&deleted_node_ids);
EXPECT_TRUE(deleted_node_ids.empty());
EXPECT_TRUE(pool_.Full());
std::set<int> free_sync_ids;
// Reassociate tab nodes.
std::vector<int> sync_ids;
for (int i = 1; i <= 3; ++i) {
int tab_node_id = -1;
EXPECT_TRUE(pool_.GetTabNodeForTab(SessionID::FromSerializedValue(i),
&tab_node_id));
free_sync_ids.insert(tab_node_id);
const SessionID tab_id = SessionID::FromSerializedValue(i);
EXPECT_EQ(TabNodePool::kInvalidTabNodeID,
pool_.GetTabNodeIdFromTabId(tab_id));
sync_ids.push_back(pool_.AssociateWithFreeTabNode(tab_id));
}
EXPECT_TRUE(pool_.Empty());
EXPECT_EQ(3u, free_sync_ids.size());
EXPECT_EQ(1u, free_sync_ids.count(kTabNodeId1));
EXPECT_EQ(1u, free_sync_ids.count(kTabNodeId2));
EXPECT_EQ(1u, free_sync_ids.count(kTabNodeId3));
EXPECT_THAT(sync_ids,
UnorderedElementsAre(kTabNodeId1, kTabNodeId2, kTabNodeId3));
}
TEST_F(SyncTabNodePoolTest, Init) {
......@@ -156,25 +159,32 @@ TEST_F(SyncTabNodePoolTest, Init) {
}
TEST_F(SyncTabNodePoolTest, AddGet) {
int free_nodes[] = {5, 10};
AddFreeTabNodes(2, free_nodes);
AddFreeTabNodes({5, 10});
EXPECT_EQ(2U, pool_.Capacity());
int tab_node_id = -1;
EXPECT_TRUE(pool_.GetTabNodeForTab(kTabId1, &tab_node_id));
EXPECT_EQ(5, tab_node_id);
ASSERT_EQ(TabNodePool::kInvalidTabNodeID,
pool_.GetTabNodeIdFromTabId(kTabId1));
EXPECT_EQ(5, pool_.AssociateWithFreeTabNode(kTabId1));
EXPECT_FALSE(pool_.Empty());
EXPECT_FALSE(pool_.Full());
EXPECT_EQ(2U, pool_.Capacity());
ASSERT_EQ(TabNodePool::kInvalidTabNodeID,
pool_.GetTabNodeIdFromTabId(kTabId2));
// 5 is now used, should return 10.
EXPECT_TRUE(pool_.GetTabNodeForTab(kTabId2, &tab_node_id));
EXPECT_EQ(10, tab_node_id);
EXPECT_EQ(10, pool_.AssociateWithFreeTabNode(kTabId2));
}
TEST_F(SyncTabNodePoolTest, GetTabNodeForTabCreate) {
int tab_node_id = -1;
EXPECT_FALSE(pool_.GetTabNodeForTab(kTabId1, &tab_node_id));
EXPECT_EQ(0, tab_node_id);
TEST_F(SyncTabNodePoolTest, AssociateWithFreeTabNode) {
ASSERT_EQ(TabNodePool::kInvalidTabNodeID,
pool_.GetTabNodeIdFromTabId(kTabId1));
ASSERT_EQ(TabNodePool::kInvalidTabNodeID,
pool_.GetTabNodeIdFromTabId(kTabId2));
EXPECT_EQ(0, pool_.AssociateWithFreeTabNode(kTabId1));
EXPECT_EQ(0, pool_.GetTabNodeIdFromTabId(kTabId1));
ASSERT_EQ(TabNodePool::kInvalidTabNodeID,
pool_.GetTabNodeIdFromTabId(kTabId2));
EXPECT_EQ(1, pool_.AssociateWithFreeTabNode(kTabId2));
EXPECT_EQ(1, pool_.GetTabNodeIdFromTabId(kTabId2));
}
TEST_F(SyncTabNodePoolTest, TabPoolFreeNodeLimits) {
......@@ -185,10 +195,8 @@ TEST_F(SyncTabNodePoolTest, TabPoolFreeNodeLimits) {
// kFreeNodesLowWatermark.
std::vector<int> used_sync_ids;
for (size_t i = 1; i <= TabNodePool::kFreeNodesHighWatermark + 1; ++i) {
int sync_id = -1;
EXPECT_FALSE(
pool_.GetTabNodeForTab(SessionID::FromSerializedValue(i), &sync_id));
used_sync_ids.push_back(sync_id);
used_sync_ids.push_back(
pool_.AssociateWithFreeTabNode(SessionID::FromSerializedValue(i)));
}
// Free all except one node.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment