diff options
author | stanisc <stanisc@chromium.org> | 2016-01-12 14:20:16 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-01-12 22:21:41 +0000 |
commit | 227d3214f2b02d201d695faa2f8ea2223b6b62bf (patch) | |
tree | 4fd36b43416197d59220bb3648bf159836008b17 /sync | |
parent | a9c0cb389711e755c41bd20c9335b5dee7b1d097 (diff) | |
download | chromium_src-227d3214f2b02d201d695faa2f8ea2223b6b62bf.zip chromium_src-227d3214f2b02d201d695faa2f8ea2223b6b62bf.tar.gz chromium_src-227d3214f2b02d201d695faa2f8ea2223b6b62bf.tar.bz2 |
Use MetadataChangeList and EntityChangeList in SharedModelTypeProcessor
BUG=569678,569636
Review URL: https://codereview.chromium.org/1565503003
Cr-Commit-Position: refs/heads/master@{#369009}
Diffstat (limited to 'sync')
-rw-r--r-- | sync/BUILD.gn | 2 | ||||
-rw-r--r-- | sync/api/entity_change.h | 6 | ||||
-rw-r--r-- | sync/api/metadata_change_list.h | 30 | ||||
-rw-r--r-- | sync/api/model_type_service.h | 4 | ||||
-rw-r--r-- | sync/internal_api/public/shared_model_type_processor.h | 1 | ||||
-rw-r--r-- | sync/internal_api/public/simple_metadata_change_list.cc | 19 | ||||
-rw-r--r-- | sync/internal_api/public/simple_metadata_change_list.h | 6 | ||||
-rw-r--r-- | sync/internal_api/public/test/fake_metadata_change_list.h | 57 | ||||
-rw-r--r-- | sync/internal_api/public/test/fake_model_type_service.h | 1 | ||||
-rw-r--r-- | sync/internal_api/shared_model_type_processor.cc | 128 | ||||
-rw-r--r-- | sync/internal_api/shared_model_type_processor_unittest.cc | 282 | ||||
-rw-r--r-- | sync/internal_api/test/fake_metadata_change_list.cc | 59 | ||||
-rw-r--r-- | sync/sync_tests.gypi | 2 |
13 files changed, 521 insertions, 76 deletions
diff --git a/sync/BUILD.gn b/sync/BUILD.gn index f38a415..04065d6 100644 --- a/sync/BUILD.gn +++ b/sync/BUILD.gn @@ -555,6 +555,7 @@ static_library("test_support_sync_core") { static_library("test_support_sync_internal_api") { testonly = true sources = [ + "internal_api/public/test/fake_metadata_change_list.h", "internal_api/public/test/fake_model_type_service.h", "internal_api/public/test/fake_sync_manager.h", "internal_api/public/test/null_sync_context_proxy.h", @@ -562,6 +563,7 @@ static_library("test_support_sync_internal_api") { "internal_api/public/test/test_entry_factory.h", "internal_api/public/test/test_internal_components_factory.h", "internal_api/public/test/test_user_share.h", + "internal_api/test/fake_metadata_change_list.cc", "internal_api/test/fake_model_type_service.cc", "internal_api/test/fake_sync_manager.cc", "internal_api/test/null_sync_context_proxy.cc", diff --git a/sync/api/entity_change.h b/sync/api/entity_change.h index 88e3812..866d38c 100644 --- a/sync/api/entity_change.h +++ b/sync/api/entity_change.h @@ -34,9 +34,9 @@ class SYNC_EXPORT EntityChange { private: EntityChange(std::string client_key, ChangeType type, EntityDataPtr data); - const std::string client_key_; - const ChangeType type_; - const EntityDataPtr data_; + std::string client_key_; + ChangeType type_; + EntityDataPtr data_; }; typedef std::vector<EntityChange> EntityChangeList; diff --git a/sync/api/metadata_change_list.h b/sync/api/metadata_change_list.h index e4435a8..a8c0d95 100644 --- a/sync/api/metadata_change_list.h +++ b/sync/api/metadata_change_list.h @@ -5,15 +5,45 @@ #ifndef SYNC_API_METADATA_CHANGE_LIST_H_ #define SYNC_API_METADATA_CHANGE_LIST_H_ +#include <string> + #include "sync/base/sync_export.h" +namespace sync_pb { +class EntityMetadata; +} // namespace sync_pb + namespace syncer_v2 { +struct DataTypeState; // Interface used by the processor and service to communicate about metadata. +// The purpose of the interface is to record changes to data type global and +// per entity metadata for the purpose of propagating changes to the datatype +// specific storage implementation. +// The implementation of the interface is supposed to keep the record of all +// updated / deleted metadata records and provide a mechanism to enumerate +// them. If there are multiple UpdateMetadata / ClearMetadata calls made for the +// same metadata record the last one is supposed to win. class SYNC_EXPORT MetadataChangeList { public: MetadataChangeList() {} virtual ~MetadataChangeList() {} + + // Requests DataTypeState to be updated in the storage. + virtual void UpdateDataTypeState(const DataTypeState& data_type_state) = 0; + + // Requests DataTypeState to be cleared from the storage. + virtual void ClearDataTypeState() = 0; + + // Requests metadata entry to be updated in the storage. + // Please note that the update might contain a deleted entry if + // metadata.is_deleted() is true (as opposed to clearing the entry from the + // storage completely by calling the Clear method). + virtual void UpdateMetadata(const std::string& client_tag, + const sync_pb::EntityMetadata& metadata) = 0; + + // Requests metadata entry to be cleared from the storage. + virtual void ClearMetadata(const std::string& client_tag) = 0; }; } // namespace syncer_v2 diff --git a/sync/api/model_type_service.h b/sync/api/model_type_service.h index f7ad9cd..48acdbe 100644 --- a/sync/api/model_type_service.h +++ b/sync/api/model_type_service.h @@ -48,6 +48,10 @@ class SYNC_EXPORT ModelTypeService { EntityDataList entity_data_list) = 0; // Apply changes from the sync server locally. + // Please note that |entity_changes| might have fewer entries than + // |metadata_change_list| in case when some of the data changes are filtered + // out, or even be empty in case when a commit confirmation is processed and + // only the metadata needs to persisted. virtual syncer::SyncError ApplySyncChanges( scoped_ptr<MetadataChangeList> metadata_change_list, EntityChangeList entity_changes) = 0; diff --git a/sync/internal_api/public/shared_model_type_processor.h b/sync/internal_api/public/shared_model_type_processor.h index eb47d93..6c9382aa 100644 --- a/sync/internal_api/public/shared_model_type_processor.h +++ b/sync/internal_api/public/shared_model_type_processor.h @@ -11,6 +11,7 @@ #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/threading/non_thread_safe.h" +#include "sync/api/metadata_change_list.h" #include "sync/api/model_type_change_processor.h" #include "sync/api/model_type_service.h" #include "sync/api/sync_error.h" diff --git a/sync/internal_api/public/simple_metadata_change_list.cc b/sync/internal_api/public/simple_metadata_change_list.cc index 27d2b41..c1bb25f 100644 --- a/sync/internal_api/public/simple_metadata_change_list.cc +++ b/sync/internal_api/public/simple_metadata_change_list.cc @@ -10,6 +10,25 @@ SimpleMetadataChangeList::SimpleMetadataChangeList() {} SimpleMetadataChangeList::~SimpleMetadataChangeList() {} +void SimpleMetadataChangeList::UpdateDataTypeState( + const DataTypeState& data_type_state) { + // TODO(skym): Implementation. +} + +void SimpleMetadataChangeList::UpdateMetadata( + const std::string& client_tag, + const sync_pb::EntityMetadata& metadata) { + // TODO(skym): Implementation. +} + +void SimpleMetadataChangeList::ClearDataTypeState() { + // TODO(skym): Implementation. +} + +void SimpleMetadataChangeList::ClearMetadata(const std::string& client_tag) { + // TODO(skym): Implementation. +} + void SimpleMetadataChangeList::TranfserChanges( ModelTypeStore* store, ModelTypeStore::WriteBatch* write_batch) { diff --git a/sync/internal_api/public/simple_metadata_change_list.h b/sync/internal_api/public/simple_metadata_change_list.h index 12bbe59..aca0e69 100644 --- a/sync/internal_api/public/simple_metadata_change_list.h +++ b/sync/internal_api/public/simple_metadata_change_list.h @@ -19,6 +19,12 @@ class SYNC_EXPORT SimpleMetadataChangeList : public MetadataChangeList { SimpleMetadataChangeList(); ~SimpleMetadataChangeList() override; + void UpdateDataTypeState(const DataTypeState& data_type_state) override; + void ClearDataTypeState() override; + void UpdateMetadata(const std::string& client_tag, + const sync_pb::EntityMetadata& metadata) override; + void ClearMetadata(const std::string& client_tag) override; + // Moves all currently accumulated changes into the write batch, clear out // local copies. Calling this multiple times will work, but should not be // necessary. diff --git a/sync/internal_api/public/test/fake_metadata_change_list.h b/sync/internal_api/public/test/fake_metadata_change_list.h new file mode 100644 index 0000000..6359602 --- /dev/null +++ b/sync/internal_api/public/test/fake_metadata_change_list.h @@ -0,0 +1,57 @@ +// Copyright 2016 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 SYNC_INTERNAL_API_PUBLIC_TEST_FAKE_METADATA_CHANGE_LIST_H_ +#define SYNC_INTERNAL_API_PUBLIC_TEST_FAKE_METADATA_CHANGE_LIST_H_ + +#include <vector> + +#include "sync/api/metadata_change_list.h" +#include "sync/internal_api/public/non_blocking_sync_common.h" +#include "sync/protocol/entity_metadata.pb.h" + +namespace syncer_v2 { + +// A non-functional implementation of MetadataChangeList for +// testing purposes. +// This class simply records all calls with all arguments for further +// analysis by the test code. +class FakeMetadataChangeList : public MetadataChangeList { + public: + FakeMetadataChangeList(); + ~FakeMetadataChangeList() override; + + void UpdateDataTypeState(const DataTypeState& data_type_state) override; + void ClearDataTypeState() override; + void UpdateMetadata(const std::string& client_tag, + const sync_pb::EntityMetadata& metadata) override; + void ClearMetadata(const std::string& client_tag) override; + + enum Action { + UPDATE_DATA_TYPE_STATE, + CLEAR_DATA_TYPE_STATE, + UPDATE_METADATA, + CLEAR_METADATA + }; + + struct Record { + Record(); + virtual ~Record(); + + Action action; + std::string tag; + DataTypeState data_type_state; + sync_pb::EntityMetadata metadata; + }; + + size_t GetNumRecords() const; + const Record& GetNthRecord(size_t n) const; + + private: + std::vector<Record> records_; +}; + +} // namespace syncer_v2 + +#endif // SYNC_INTERNAL_API_PUBLIC_TEST_FAKE_METADATA_CHANGE_LIST_H_ diff --git a/sync/internal_api/public/test/fake_model_type_service.h b/sync/internal_api/public/test/fake_model_type_service.h index 9361483..a5ba17e 100644 --- a/sync/internal_api/public/test/fake_model_type_service.h +++ b/sync/internal_api/public/test/fake_model_type_service.h @@ -8,6 +8,7 @@ #include <string> #include "sync/api/data_batch.h" +#include "sync/api/entity_change.h" #include "sync/api/metadata_batch.h" #include "sync/api/metadata_change_list.h" #include "sync/api/model_type_service.h" diff --git a/sync/internal_api/shared_model_type_processor.cc b/sync/internal_api/shared_model_type_processor.cc index a6ed683..f6573be 100644 --- a/sync/internal_api/shared_model_type_processor.cc +++ b/sync/internal_api/shared_model_type_processor.cc @@ -151,16 +151,15 @@ void SharedModelTypeProcessor::OnConnect(scoped_ptr<CommitQueue> worker) { void SharedModelTypeProcessor::Put(const std::string& client_tag, scoped_ptr<EntityData> entity_data, MetadataChangeList* metadata_change_list) { - // TODO(skym): Add metadata to persist to MetadataChangeList, crbug/569636. - DCHECK(entity_data.get()); DCHECK(!entity_data->is_deleted()); DCHECK(!entity_data->non_unique_name.empty()); DCHECK_EQ(type_, syncer::GetModelTypeFromSpecifics(entity_data->specifics)); // If the service specified an overriding hash, use that, otherwise generate - // one from the tag. TODO(skym): This behavior should be delayed, once - // crbug/561818 is fixed we will only perform this logic in the create case. + // one from the tag. + // TODO(skym): This behavior should be delayed, once crbug.com/561818 is fixed + // we will only perform this logic in the create case. const std::string client_tag_hash( entity_data->client_tag_hash.empty() ? syncer::syncable::GenerateSyncableHash(type_, client_tag) @@ -169,9 +168,9 @@ void SharedModelTypeProcessor::Put(const std::string& client_tag, base::Time now = base::Time::Now(); ModelTypeEntity* entity = nullptr; - // TODO(stanisc): crbug/561818: Search by client_tag rather than + // TODO(stanisc): crbug.com/561818: Search by client_tag rather than // client_tag_hash. - EntityMap::const_iterator it = entities_.find(client_tag_hash); + auto it = entities_.find(client_tag_hash); if (it == entities_.end()) { // The service is creating a new entity. scoped_ptr<ModelTypeEntity> scoped_entity = ModelTypeEntity::CreateNew( @@ -182,32 +181,39 @@ void SharedModelTypeProcessor::Put(const std::string& client_tag, } else { // The service is updating an existing entity. entity = it->second.get(); + DCHECK_EQ(client_tag, entity->client_key()); } + // TODO(stanisc): crbug.com/561829: Avoid committing a change if there is no + // actual change. entity->MakeLocalChange(std::move(entity_data), now); + metadata_change_list->UpdateMetadata(client_tag, entity->metadata()); + FlushPendingCommitRequests(); } void SharedModelTypeProcessor::Delete( const std::string& client_key, MetadataChangeList* metadata_change_list) { - // TODO(skym): Add metadata to persist to MetadataChangeList, crbug/569636. - const std::string client_tag_hash( syncer::syncable::GenerateSyncableHash(type_, client_key)); - // TODO(skym): crbug/561818: Search by client_tag rather than client_tag_hash. - EntityMap::const_iterator it = entities_.find(client_tag_hash); + // TODO(skym): crbug.com/561818: Search by client_tag rather than + // client_tag_hash. + auto it = entities_.find(client_tag_hash); if (it == entities_.end()) { // That's unusual, but not necessarily a bad thing. // Missing is as good as deleted as far as the model is concerned. DLOG(WARNING) << "Attempted to delete missing item." << " client tag: " << client_key; - } else { - ModelTypeEntity* entity = it->second.get(); - entity->Delete(); + return; } + ModelTypeEntity* entity = it->second.get(); + entity->Delete(); + + metadata_change_list->UpdateMetadata(client_key, entity->metadata()); + FlushPendingCommitRequests(); } @@ -223,8 +229,7 @@ void SharedModelTypeProcessor::FlushPendingCommitRequests() { return; // TODO(rlarocque): Do something smarter than iterate here. - for (EntityMap::const_iterator it = entities_.begin(); it != entities_.end(); - ++it) { + for (auto it = entities_.begin(); it != entities_.end(); ++it) { if (it->second->RequiresCommitRequest()) { CommitRequestData request; it->second->InitializeCommitRequestData(&request); @@ -240,14 +245,18 @@ void SharedModelTypeProcessor::FlushPendingCommitRequests() { void SharedModelTypeProcessor::OnCommitCompleted( const DataTypeState& type_state, const CommitResponseDataList& response_list) { + scoped_ptr<MetadataChangeList> change_list = + service_->CreateMetadataChangeList(); + data_type_state_ = type_state; + change_list->UpdateDataTypeState(data_type_state_); - for (CommitResponseDataList::const_iterator list_it = response_list.begin(); - list_it != response_list.end(); ++list_it) { + for (auto list_it = response_list.begin(); list_it != response_list.end(); + ++list_it) { const CommitResponseData& response_data = *list_it; const std::string& client_tag_hash = response_data.client_tag_hash; - EntityMap::const_iterator it = entities_.find(client_tag_hash); + auto it = entities_.find(client_tag_hash); if (it == entities_.end()) { NOTREACHED() << "Received commit response for missing item." << " type: " << type_ << " client_tag: " << client_tag_hash; @@ -256,21 +265,35 @@ void SharedModelTypeProcessor::OnCommitCompleted( it->second->ReceiveCommitResponse( response_data.id, response_data.sequence_number, response_data.response_version, data_type_state_.encryption_key_name); + // TODO(stanisc): crbug.com/573333: Delete case. + // This might be the right place to clear a metadata entry that has + // been deleted locally and confirmed deleted by the server. + change_list->UpdateMetadata(it->second->client_key(), + it->second->metadata()); } } + + // TODO(stanisc): What is the right method to submit metadata changes to the + // service? Is using empty EntityChangeList OK? + // TODO(stanisc): crbug.com/570085: Error handling. + service_->ApplySyncChanges(std::move(change_list), EntityChangeList()); } void SharedModelTypeProcessor::OnUpdateReceived( const DataTypeState& data_type_state, const UpdateResponseDataList& response_list, const UpdateResponseDataList& pending_updates) { + scoped_ptr<MetadataChangeList> metadata_changes = + service_->CreateMetadataChangeList(); + EntityChangeList entity_changes; + + metadata_changes->UpdateDataTypeState(data_type_state); bool got_new_encryption_requirements = data_type_state_.encryption_key_name != data_type_state.encryption_key_name; - data_type_state_ = data_type_state; - for (UpdateResponseDataList::const_iterator list_it = response_list.begin(); - list_it != response_list.end(); ++list_it) { + for (auto list_it = response_list.begin(); list_it != response_list.end(); + ++list_it) { const UpdateResponseData& response_data = *list_it; const EntityData& data = response_data.entity.value(); const std::string& client_tag_hash = data.client_tag_hash; @@ -280,24 +303,47 @@ void SharedModelTypeProcessor::OnUpdateReceived( pending_updates_map_.erase(client_tag_hash); ModelTypeEntity* entity = nullptr; - EntityMap::const_iterator it = entities_.find(client_tag_hash); + auto it = entities_.find(client_tag_hash); if (it == entities_.end()) { + if (data.is_deleted()) { + DLOG(WARNING) << "Received remote delete for a non-existing item." + << " client_tag_hash: " << client_tag_hash; + continue; + } + // Let the service define |client_tag| based on the entity data. - std::string client_tag = service_->GetClientTag(data); + std::string client_key = service_->GetClientTag(data); scoped_ptr<ModelTypeEntity> scoped_entity = ModelTypeEntity::CreateNew( - client_tag, client_tag_hash, data.id, data.creation_time); + client_key, client_tag_hash, data.id, data.creation_time); entity = scoped_entity.get(); entities_.insert( std::make_pair(client_tag_hash, std::move(scoped_entity))); + entity_changes.push_back( + EntityChange::CreateAdd(client_key, response_data.entity)); + } else { entity = it->second.get(); + if (data.is_deleted()) { + entity_changes.push_back( + EntityChange::CreateDelete(entity->client_key())); + } else { + // TODO(stanisc): crbug.com/561829: Avoid sending an update to the + // service if there is no actual change. + entity_changes.push_back(EntityChange::CreateUpdate( + entity->client_key(), response_data.entity)); + } } entity->ApplyUpdateFromServer(response_data); + // TODO(stanisc): crbug.com/573333: Delete case. + // This might be the right place to clear metadata entry instead of + // updating it. + metadata_changes->UpdateMetadata(entity->client_key(), entity->metadata()); - // TODO(stanisc): Do something special when conflicts are detected. + // TODO(stanisc): crbug.com/521867: Do something special when conflicts are + // detected. // If the received entity has out of date encryption, we schedule another // commit to fix it. @@ -306,20 +352,20 @@ void SharedModelTypeProcessor::OnUpdateReceived( DVLOG(2) << ModelTypeToString(type_) << ": Requesting re-encrypt commit " << response_data.encryption_key_name << " -> " << data_type_state_.encryption_key_name; - EntityMap::const_iterator it2 = entities_.find(client_tag_hash); + auto it2 = entities_.find(client_tag_hash); it2->second->UpdateDesiredEncryptionKey( data_type_state_.encryption_key_name); } } + // TODO: crbug.com/529498: stop saving pending updates. // Save pending updates in the appropriate data structure. - for (UpdateResponseDataList::const_iterator list_it = pending_updates.begin(); - list_it != pending_updates.end(); ++list_it) { + for (auto list_it = pending_updates.begin(); list_it != pending_updates.end(); + ++list_it) { const UpdateResponseData& update = *list_it; const std::string& client_tag_hash = update.entity->client_tag_hash; - UpdateMap::const_iterator lookup_it = - pending_updates_map_.find(client_tag_hash); + auto lookup_it = pending_updates_map_.find(client_tag_hash); if (lookup_it == pending_updates_map_.end()) { pending_updates_map_.insert(std::make_pair( client_tag_hash, make_scoped_ptr(new UpdateResponseData(update)))); @@ -333,43 +379,43 @@ void SharedModelTypeProcessor::OnUpdateReceived( } if (got_new_encryption_requirements) { - for (EntityMap::const_iterator it = entities_.begin(); - it != entities_.end(); ++it) { + for (auto it = entities_.begin(); it != entities_.end(); ++it) { it->second->UpdateDesiredEncryptionKey( data_type_state_.encryption_key_name); } } + // Inform the service of the new or updated data. + // TODO(stanisc): crbug.com/570085: Error handling. + service_->ApplySyncChanges(std::move(metadata_changes), entity_changes); + // We may have new reasons to commit by the time this function is done. FlushPendingCommitRequests(); - - // TODO(rlarocque): Inform the model of the new or updated data. - // TODO(rlarocque): Persist the new data on disk. } UpdateResponseDataList SharedModelTypeProcessor::GetPendingUpdates() { UpdateResponseDataList pending_updates_list; - for (UpdateMap::const_iterator it = pending_updates_map_.begin(); - it != pending_updates_map_.end(); ++it) { + for (auto it = pending_updates_map_.begin(); it != pending_updates_map_.end(); + ++it) { pending_updates_list.push_back(*it->second); } return pending_updates_list; } void SharedModelTypeProcessor::ClearTransientSyncState() { - for (EntityMap::const_iterator it = entities_.begin(); it != entities_.end(); - ++it) { + for (auto it = entities_.begin(); it != entities_.end(); ++it) { it->second->ClearTransientSyncState(); } } void SharedModelTypeProcessor::ClearSyncState() { - for (EntityMap::const_iterator it = entities_.begin(); it != entities_.end(); - ++it) { + for (auto it = entities_.begin(); it != entities_.end(); ++it) { it->second->ClearSyncState(); } pending_updates_map_.clear(); data_type_state_ = DataTypeState(); + // TODO(stanisc): crbug.com/561830, crbug.com/573333: Update the service to + // let it know that all metadata need to be cleared from the storage. } } // namespace syncer_v2 diff --git a/sync/internal_api/shared_model_type_processor_unittest.cc b/sync/internal_api/shared_model_type_processor_unittest.cc index 411e8b1..eb23793 100644 --- a/sync/internal_api/shared_model_type_processor_unittest.cc +++ b/sync/internal_api/shared_model_type_processor_unittest.cc @@ -14,6 +14,7 @@ #include "sync/internal_api/public/activation_context.h" #include "sync/internal_api/public/base/model_type.h" #include "sync/internal_api/public/non_blocking_sync_common.h" +#include "sync/internal_api/public/test/fake_metadata_change_list.h" #include "sync/internal_api/public/test/fake_model_type_service.h" #include "sync/protocol/sync.pb.h" #include "sync/syncable/syncable_util.h" @@ -32,7 +33,7 @@ static const syncer::ModelType kModelType = syncer::PREFERENCES; // to be re-tested here. // // These tests skip past initialization and focus on steady state sync engine -// behvior. This is where we test how the type sync proxy responds to the +// behavior. This is where we test how the processor responds to the // model's requests to make changes to its data, the messages incoming from the // sync server, and what happens when the two conflict. // @@ -69,8 +70,10 @@ class SharedModelTypeProcessorTest : public ::testing::Test, void Restart(); // Local data modification. Emulates signals from the model thread. - void WriteItem(const std::string& tag, const std::string& value); - void DeleteItem(const std::string& tag); + void WriteItem(const std::string& tag, + const std::string& value, + MetadataChangeList* change_list); + void DeleteItem(const std::string& tag, MetadataChangeList* change_list); // Emulates an "initial sync done" message from the // CommitQueue. @@ -122,6 +125,9 @@ class SharedModelTypeProcessorTest : public ::testing::Test, MockCommitQueue* mock_queue(); SharedModelTypeProcessor* type_processor(); + const EntityChangeList* entity_change_list() const; + const FakeMetadataChangeList* metadata_change_list() const; + private: static std::string GenerateTagHash(const std::string& tag); static sync_pb::EntitySpecifics GenerateSpecifics(const std::string& tag, @@ -139,6 +145,10 @@ class SharedModelTypeProcessorTest : public ::testing::Test, // FakeModelTypeService overrides. std::string GetClientTag(const EntityData& entity_data) override; + scoped_ptr<MetadataChangeList> CreateMetadataChangeList() override; + syncer::SyncError ApplySyncChanges( + scoped_ptr<MetadataChangeList> metadata_change_list, + EntityChangeList entity_changes) override; // This sets ThreadTaskRunnerHandle on the current thread, which the type // processor will pick up as the sync task runner. @@ -151,6 +161,11 @@ class SharedModelTypeProcessorTest : public ::testing::Test, scoped_ptr<SharedModelTypeProcessor> type_processor_; DataTypeState data_type_state_; + + // The last received EntityChangeList. + scoped_ptr<EntityChangeList> entity_change_list_; + // The last received MetadataChangeList. + scoped_ptr<FakeMetadataChangeList> metadata_change_list_; }; SharedModelTypeProcessorTest::SharedModelTypeProcessorTest() @@ -209,21 +224,25 @@ void SharedModelTypeProcessorTest::StartDone( } void SharedModelTypeProcessorTest::WriteItem(const std::string& tag, - const std::string& value) { + const std::string& value, + MetadataChangeList* change_list) { scoped_ptr<EntityData> entity_data = make_scoped_ptr(new EntityData()); entity_data->specifics = GenerateSpecifics(tag, value); entity_data->non_unique_name = tag; - type_processor_->Put(tag, std::move(entity_data), nullptr); + type_processor_->Put(tag, std::move(entity_data), change_list); } -void SharedModelTypeProcessorTest::DeleteItem(const std::string& tag) { - type_processor_->Delete(tag, nullptr); +void SharedModelTypeProcessorTest::DeleteItem(const std::string& tag, + MetadataChangeList* change_list) { + type_processor_->Delete(tag, change_list); } void SharedModelTypeProcessorTest::OnInitialSyncDone() { data_type_state_.initial_sync_done = true; UpdateResponseDataList empty_update_list; + // TODO(stanisc): crbug/569645: replace this with loading the initial state + // via LoadMetadata callback. type_processor_->OnUpdateReceived(data_type_state_, empty_update_list, empty_update_list); } @@ -328,6 +347,16 @@ SharedModelTypeProcessor* SharedModelTypeProcessorTest::type_processor() { return type_processor_.get(); } +const EntityChangeList* SharedModelTypeProcessorTest::entity_change_list() + const { + return entity_change_list_.get(); +} + +const FakeMetadataChangeList* +SharedModelTypeProcessorTest::metadata_change_list() const { + return metadata_change_list_.get(); +} + std::string SharedModelTypeProcessorTest::GenerateTagHash( const std::string& tag) { return syncer::syncable::GenerateSyncableHash(kModelType, tag); @@ -362,6 +391,26 @@ std::string SharedModelTypeProcessorTest::GetClientTag( return entity_data.specifics.preference().name(); } +scoped_ptr<MetadataChangeList> +SharedModelTypeProcessorTest::CreateMetadataChangeList() { + // Reset the current first and return a new one. + metadata_change_list_.reset(); + return scoped_ptr<MetadataChangeList>(new FakeMetadataChangeList()); +} + +syncer::SyncError SharedModelTypeProcessorTest::ApplySyncChanges( + scoped_ptr<MetadataChangeList> metadata_change_list, + EntityChangeList entity_changes) { + EXPECT_FALSE(metadata_change_list_); + // |metadata_change_list| is expected to be an instance of + // FakeMetadataChangeList - see above. + metadata_change_list_.reset( + static_cast<FakeMetadataChangeList*>(metadata_change_list.release())); + EXPECT_TRUE(metadata_change_list_); + entity_change_list_.reset(new EntityChangeList(entity_changes)); + return syncer::SyncError(); +} + size_t SharedModelTypeProcessorTest::GetNumCommitRequestLists() { return mock_queue_->GetNumCommitRequestLists(); } @@ -389,7 +438,8 @@ TEST_F(SharedModelTypeProcessorTest, CreateLocalItem) { InitializeToReadyState(); EXPECT_EQ(0U, GetNumCommitRequestLists()); - WriteItem("tag1", "value1"); + FakeMetadataChangeList change_list; + WriteItem("tag1", "value1", &change_list); // Verify the commit request this operation has triggered. EXPECT_EQ(1U, GetNumCommitRequestLists()); @@ -406,6 +456,21 @@ TEST_F(SharedModelTypeProcessorTest, CreateLocalItem) { EXPECT_FALSE(tag1_data.is_deleted()); EXPECT_EQ("tag1", tag1_data.specifics.preference().name()); EXPECT_EQ("value1", tag1_data.specifics.preference().value()); + + EXPECT_EQ(1U, change_list.GetNumRecords()); + + const FakeMetadataChangeList::Record& record = change_list.GetNthRecord(0); + EXPECT_EQ(FakeMetadataChangeList::UPDATE_METADATA, record.action); + EXPECT_EQ("tag1", record.tag); + EXPECT_TRUE(record.metadata.has_client_tag_hash()); + EXPECT_FALSE(record.metadata.has_server_id()); + EXPECT_FALSE(record.metadata.is_deleted()); + EXPECT_EQ(1, record.metadata.sequence_number()); + EXPECT_EQ(0, record.metadata.acked_sequence_number()); + EXPECT_EQ(kUncommittedVersion, record.metadata.server_version()); + EXPECT_TRUE(record.metadata.has_creation_time()); + EXPECT_TRUE(record.metadata.has_modification_time()); + EXPECT_TRUE(record.metadata.has_specifics_hash()); } // The purpose of this test case is to test setting |client_tag_hash| and |id| @@ -416,14 +481,16 @@ TEST_F(SharedModelTypeProcessorTest, CreateAndModifyWithOverrides) { InitializeToReadyState(); EXPECT_EQ(0U, GetNumCommitRequestLists()); + FakeMetadataChangeList change_list; + scoped_ptr<EntityData> entity_data = make_scoped_ptr(new EntityData()); - entity_data->specifics.mutable_preference()->set_name("tag1"); + entity_data->specifics.mutable_preference()->set_name("name1"); entity_data->specifics.mutable_preference()->set_value("value1"); - entity_data->non_unique_name = "tag1"; + entity_data->non_unique_name = "name1"; entity_data->client_tag_hash = "hash"; entity_data->id = "cid1"; - type_processor()->Put("tag1", std::move(entity_data), nullptr); + type_processor()->Put("tag1", std::move(entity_data), &change_list); // Don't access through tag because we forced a specific hash. EXPECT_EQ(1U, GetNumCommitRequestLists()); @@ -435,13 +502,18 @@ TEST_F(SharedModelTypeProcessorTest, CreateAndModifyWithOverrides) { EXPECT_EQ("cid1", out_entity1.id); EXPECT_EQ("value1", out_entity1.specifics.preference().value()); + EXPECT_EQ(1U, change_list.GetNumRecords()); + entity_data.reset(new EntityData()); - entity_data->specifics.mutable_preference()->set_name("tag2"); + entity_data->specifics.mutable_preference()->set_name("name2"); entity_data->specifics.mutable_preference()->set_value("value2"); - entity_data->non_unique_name = "tag2"; + entity_data->non_unique_name = "name2"; entity_data->client_tag_hash = "hash"; + // TODO (skym): Consider removing this. The ID should never be changed by the + // client once established. entity_data->id = "cid2"; - type_processor()->Put("tag2", std::move(entity_data), nullptr); + + type_processor()->Put("tag1", std::move(entity_data), &change_list); EXPECT_EQ(2U, GetNumCommitRequestLists()); ASSERT_TRUE(mock_queue()->HasCommitRequestForTagHash("hash")); @@ -452,6 +524,24 @@ TEST_F(SharedModelTypeProcessorTest, CreateAndModifyWithOverrides) { EXPECT_EQ("hash", out_entity2.client_tag_hash); EXPECT_EQ("cid1", out_entity2.id); EXPECT_EQ("value2", out_entity2.specifics.preference().value()); + + EXPECT_EQ(2U, change_list.GetNumRecords()); + + const FakeMetadataChangeList::Record& record1 = change_list.GetNthRecord(0); + EXPECT_EQ(FakeMetadataChangeList::UPDATE_METADATA, record1.action); + EXPECT_EQ("tag1", record1.tag); + EXPECT_EQ("cid1", record1.metadata.server_id()); + EXPECT_EQ("hash", record1.metadata.client_tag_hash()); + + const FakeMetadataChangeList::Record& record2 = change_list.GetNthRecord(1); + EXPECT_EQ(FakeMetadataChangeList::UPDATE_METADATA, record2.action); + EXPECT_EQ("tag1", record2.tag); + // TODO (skym): Is this correct? + EXPECT_EQ("cid1", record2.metadata.server_id()); + EXPECT_EQ("hash", record2.metadata.client_tag_hash()); + + EXPECT_NE(record1.metadata.specifics_hash(), + record2.metadata.specifics_hash()); } // Creates a new local item then modifies it. @@ -460,14 +550,16 @@ TEST_F(SharedModelTypeProcessorTest, CreateAndModifyLocalItem) { InitializeToReadyState(); EXPECT_EQ(0U, GetNumCommitRequestLists()); - WriteItem("tag1", "value1"); + FakeMetadataChangeList change_list; + + WriteItem("tag1", "value1", &change_list); EXPECT_EQ(1U, GetNumCommitRequestLists()); ASSERT_TRUE(HasCommitRequestForTag("tag1")); const CommitRequestData& tag1_v1_request_data = GetLatestCommitRequestForTag("tag1"); const EntityData& tag1_v1_data = tag1_v1_request_data.entity.value(); - WriteItem("tag1", "value2"); + WriteItem("tag1", "value2", &change_list); EXPECT_EQ(2U, GetNumCommitRequestLists()); ASSERT_TRUE(HasCommitRequestForTag("tag1")); @@ -489,6 +581,31 @@ TEST_F(SharedModelTypeProcessorTest, CreateAndModifyLocalItem) { EXPECT_FALSE(tag1_v2_data.is_deleted()); EXPECT_EQ("tag1", tag1_v2_data.specifics.preference().name()); EXPECT_EQ("value2", tag1_v2_data.specifics.preference().value()); + + EXPECT_EQ(2U, change_list.GetNumRecords()); + + const FakeMetadataChangeList::Record& record1 = change_list.GetNthRecord(0); + EXPECT_EQ(FakeMetadataChangeList::UPDATE_METADATA, record1.action); + EXPECT_EQ("tag1", record1.tag); + EXPECT_FALSE(record1.metadata.has_server_id()); + EXPECT_FALSE(record1.metadata.is_deleted()); + EXPECT_EQ(1, record1.metadata.sequence_number()); + EXPECT_EQ(0, record1.metadata.acked_sequence_number()); + EXPECT_EQ(kUncommittedVersion, record1.metadata.server_version()); + + const FakeMetadataChangeList::Record& record2 = change_list.GetNthRecord(1); + EXPECT_EQ(FakeMetadataChangeList::UPDATE_METADATA, record1.action); + EXPECT_EQ("tag1", record2.tag); + EXPECT_FALSE(record2.metadata.has_server_id()); + EXPECT_FALSE(record2.metadata.is_deleted()); + EXPECT_EQ(2, record2.metadata.sequence_number()); + EXPECT_EQ(0, record2.metadata.acked_sequence_number()); + EXPECT_EQ(kUncommittedVersion, record2.metadata.server_version()); + + EXPECT_EQ(record1.metadata.client_tag_hash(), + record2.metadata.client_tag_hash()); + EXPECT_NE(record1.metadata.specifics_hash(), + record2.metadata.specifics_hash()); } // Deletes an item we've never seen before. @@ -496,8 +613,12 @@ TEST_F(SharedModelTypeProcessorTest, CreateAndModifyLocalItem) { TEST_F(SharedModelTypeProcessorTest, DeleteUnknown) { InitializeToReadyState(); - DeleteItem("tag1"); + FakeMetadataChangeList change_list; + + DeleteItem("tag1", &change_list); EXPECT_EQ(0U, GetNumCommitRequestLists()); + + EXPECT_EQ(0U, change_list.GetNumRecords()); } // Creates an item locally then deletes it. @@ -508,12 +629,19 @@ TEST_F(SharedModelTypeProcessorTest, DeleteUnknown) { TEST_F(SharedModelTypeProcessorTest, DeleteServerUnknown) { InitializeToReadyState(); - WriteItem("tag1", "value1"); + FakeMetadataChangeList change_list; + + // TODO(stanisc): crbug.com/573333: Review this case. If the flush of + // all locally modified items was scheduled to run on a separate task, than + // the correct behavior would be to commit just the detele, or perhaps no + // commit at all. + + WriteItem("tag1", "value1", &change_list); EXPECT_EQ(1U, GetNumCommitRequestLists()); ASSERT_TRUE(HasCommitRequestForTag("tag1")); const CommitRequestData& tag1_v1_data = GetLatestCommitRequestForTag("tag1"); - DeleteItem("tag1"); + DeleteItem("tag1", &change_list); EXPECT_EQ(2U, GetNumCommitRequestLists()); ASSERT_TRUE(HasCommitRequestForTag("tag1")); const CommitRequestData& tag1_v2_data = GetLatestCommitRequestForTag("tag1"); @@ -523,6 +651,31 @@ TEST_F(SharedModelTypeProcessorTest, DeleteServerUnknown) { EXPECT_TRUE(tag1_v2_data.entity->id.empty()); EXPECT_EQ(kUncommittedVersion, tag1_v2_data.base_version); EXPECT_TRUE(tag1_v2_data.entity->is_deleted()); + + EXPECT_EQ(2U, change_list.GetNumRecords()); + + const FakeMetadataChangeList::Record& record1 = change_list.GetNthRecord(0); + EXPECT_EQ(FakeMetadataChangeList::UPDATE_METADATA, record1.action); + EXPECT_EQ("tag1", record1.tag); + EXPECT_FALSE(record1.metadata.is_deleted()); + EXPECT_EQ(1, record1.metadata.sequence_number()); + EXPECT_EQ(0, record1.metadata.acked_sequence_number()); + EXPECT_EQ(kUncommittedVersion, record1.metadata.server_version()); + + // TODO(stanisc): crbug.com/573333: Review this case. Depending on the + // implementation the second action performed on metadata change list might + // be CLEAR_METADATA. For a real implementation of MetadataChangeList this + // might also mean that the change list wouldn't contain any metadata + // records at all - the first call would create an entry and the second would + // remove it. + + const FakeMetadataChangeList::Record& record2 = change_list.GetNthRecord(1); + EXPECT_EQ(FakeMetadataChangeList::UPDATE_METADATA, record1.action); + EXPECT_EQ("tag1", record2.tag); + EXPECT_TRUE(record2.metadata.is_deleted()); + EXPECT_EQ(2, record2.metadata.sequence_number()); + EXPECT_EQ(0, record2.metadata.acked_sequence_number()); + EXPECT_EQ(kUncommittedVersion, record2.metadata.server_version()); } // Creates an item locally then deletes it. @@ -533,12 +686,14 @@ TEST_F(SharedModelTypeProcessorTest, DeleteServerUnknown) { TEST_F(SharedModelTypeProcessorTest, DeleteServerUnknown_RacyCommitResponse) { InitializeToReadyState(); - WriteItem("tag1", "value1"); + FakeMetadataChangeList change_list; + + WriteItem("tag1", "value1", &change_list); EXPECT_EQ(1U, GetNumCommitRequestLists()); ASSERT_TRUE(HasCommitRequestForTag("tag1")); const CommitRequestData& tag1_v1_data = GetLatestCommitRequestForTag("tag1"); - DeleteItem("tag1"); + DeleteItem("tag1", &change_list); EXPECT_EQ(2U, GetNumCommitRequestLists()); ASSERT_TRUE(HasCommitRequestForTag("tag1")); @@ -547,6 +702,33 @@ TEST_F(SharedModelTypeProcessorTest, DeleteServerUnknown_RacyCommitResponse) { // the sync thread. It will update some metadata, but won't do much else. SuccessfulCommitResponse(tag1_v1_data); + // In reality the change list used to commit local changes should never + // overlap with the changelist used to deliver commit confirmation. In this + // test setup the two change lists are isolated - one is on the stack and + // another is the class member. + + // Local metadata changes. + EXPECT_EQ(2U, change_list.GetNumRecords()); + + // Metadata changes from commit response. + EXPECT_TRUE(metadata_change_list()); + EXPECT_EQ(2U, metadata_change_list()->GetNumRecords()); + + const FakeMetadataChangeList::Record& record1 = + metadata_change_list()->GetNthRecord(0); + EXPECT_EQ(FakeMetadataChangeList::UPDATE_DATA_TYPE_STATE, record1.action); + + const FakeMetadataChangeList::Record& record2 = + metadata_change_list()->GetNthRecord(1); + EXPECT_EQ(FakeMetadataChangeList::UPDATE_METADATA, record2.action); + EXPECT_EQ("tag1", record2.tag); + // Deleted from the second local modification. + EXPECT_TRUE(record2.metadata.is_deleted()); + // sequence_number = 2 from the second local modification. + EXPECT_EQ(2, record2.metadata.sequence_number()); + // acked_sequence_number = 1 from the first commit response. + EXPECT_EQ(1, record2.metadata.acked_sequence_number()); + // TODO(rlarocque): Verify the state of the item is correct once we get // storage hooked up in these tests. For example, verify the item is still // marked as deleted. @@ -558,19 +740,39 @@ TEST_F(SharedModelTypeProcessorTest, TwoIndependentItems) { InitializeToReadyState(); EXPECT_EQ(0U, GetNumCommitRequestLists()); - WriteItem("tag1", "value1"); + FakeMetadataChangeList change_list; + + WriteItem("tag1", "value1", &change_list); // There should be one commit request for this item only. ASSERT_EQ(1U, GetNumCommitRequestLists()); EXPECT_EQ(1U, GetNthCommitRequestList(0).size()); ASSERT_TRUE(HasCommitRequestForTag("tag1")); - WriteItem("tag2", "value2"); + WriteItem("tag2", "value2", &change_list); // The second write should trigger another single-item commit request. ASSERT_EQ(2U, GetNumCommitRequestLists()); EXPECT_EQ(1U, GetNthCommitRequestList(1).size()); ASSERT_TRUE(HasCommitRequestForTag("tag2")); + + EXPECT_EQ(2U, change_list.GetNumRecords()); + + const FakeMetadataChangeList::Record& record1 = change_list.GetNthRecord(0); + EXPECT_EQ(FakeMetadataChangeList::UPDATE_METADATA, record1.action); + EXPECT_EQ("tag1", record1.tag); + EXPECT_FALSE(record1.metadata.is_deleted()); + EXPECT_EQ(1, record1.metadata.sequence_number()); + EXPECT_EQ(0, record1.metadata.acked_sequence_number()); + EXPECT_EQ(kUncommittedVersion, record1.metadata.server_version()); + + const FakeMetadataChangeList::Record& record2 = change_list.GetNthRecord(1); + EXPECT_EQ(FakeMetadataChangeList::UPDATE_METADATA, record2.action); + EXPECT_EQ("tag2", record2.tag); + EXPECT_FALSE(record2.metadata.is_deleted()); + EXPECT_EQ(1, record2.metadata.sequence_number()); + EXPECT_EQ(0, record2.metadata.acked_sequence_number()); + EXPECT_EQ(kUncommittedVersion, record2.metadata.server_version()); } // Starts the type sync proxy with no local state. @@ -579,9 +781,19 @@ TEST_F(SharedModelTypeProcessorTest, TwoIndependentItems) { TEST_F(SharedModelTypeProcessorTest, NoCommitsUntilInitialSyncDone) { Start(); - WriteItem("tag1", "value1"); + FakeMetadataChangeList change_list; + + WriteItem("tag1", "value1", &change_list); EXPECT_EQ(0U, GetNumCommitRequestLists()); + // Even though there the item hasn't been committed its metadata should have + // already been updated and the sequence number changed. + EXPECT_EQ(1U, change_list.GetNumRecords()); + const FakeMetadataChangeList::Record& record1 = change_list.GetNthRecord(0); + EXPECT_EQ(FakeMetadataChangeList::UPDATE_METADATA, record1.action); + EXPECT_EQ("tag1", record1.tag); + EXPECT_EQ(1, record1.metadata.sequence_number()); + OnInitialSyncDone(); EXPECT_EQ(1U, GetNumCommitRequestLists()); EXPECT_TRUE(HasCommitRequestForTag("tag1")); @@ -594,19 +806,21 @@ TEST_F(SharedModelTypeProcessorTest, NoCommitsUntilInitialSyncDone) { TEST_F(SharedModelTypeProcessorTest, Stop) { InitializeToReadyState(); + FakeMetadataChangeList change_list; + // The first item is fully committed. - WriteItem("tag1", "value1"); + WriteItem("tag1", "value1", &change_list); ASSERT_TRUE(HasCommitRequestForTag("tag1")); SuccessfulCommitResponse(GetLatestCommitRequestForTag("tag1")); // The second item has a commit request in progress. - WriteItem("tag2", "value2"); + WriteItem("tag2", "value2", &change_list); EXPECT_TRUE(HasCommitRequestForTag("tag2")); Stop(); // The third item is added after stopping. - WriteItem("tag3", "value3"); + WriteItem("tag3", "value3", &change_list); Restart(); @@ -630,19 +844,21 @@ TEST_F(SharedModelTypeProcessorTest, Stop) { TEST_F(SharedModelTypeProcessorTest, Disable) { InitializeToReadyState(); + FakeMetadataChangeList change_list; + // The first item is fully committed. - WriteItem("tag1", "value1"); + WriteItem("tag1", "value1", &change_list); ASSERT_TRUE(HasCommitRequestForTag("tag1")); SuccessfulCommitResponse(GetLatestCommitRequestForTag("tag1")); // The second item has a commit request in progress. - WriteItem("tag2", "value2"); + WriteItem("tag2", "value2", &change_list); EXPECT_TRUE(HasCommitRequestForTag("tag2")); Disable(); // The third item is added after disable. - WriteItem("tag3", "value3"); + WriteItem("tag3", "value3", &change_list); // Now we re-enable. Restart(); @@ -728,14 +944,16 @@ TEST_F(SharedModelTypeProcessorTest, StopWithPendingUpdates) { TEST_F(SharedModelTypeProcessorTest, DISABLED_ReEncryptCommitsWithNewKey) { InitializeToReadyState(); + FakeMetadataChangeList change_list; + // Commit an item. - WriteItem("tag1", "value1"); + WriteItem("tag1", "value1", &change_list); ASSERT_TRUE(HasCommitRequestForTag("tag1")); const CommitRequestData& tag1_v1_data = GetLatestCommitRequestForTag("tag1"); SuccessfulCommitResponse(tag1_v1_data); // Create another item and don't wait for its commit response. - WriteItem("tag2", "value2"); + WriteItem("tag2", "value2", &change_list); ASSERT_EQ(2U, GetNumCommitRequestLists()); diff --git a/sync/internal_api/test/fake_metadata_change_list.cc b/sync/internal_api/test/fake_metadata_change_list.cc new file mode 100644 index 0000000..407066d --- /dev/null +++ b/sync/internal_api/test/fake_metadata_change_list.cc @@ -0,0 +1,59 @@ +// Copyright 2016 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 <string> + +#include "sync/internal_api/public/test/fake_metadata_change_list.h" + +namespace syncer_v2 { + +FakeMetadataChangeList::FakeMetadataChangeList() {} + +FakeMetadataChangeList::~FakeMetadataChangeList() {} + +FakeMetadataChangeList::Record::Record() {} + +FakeMetadataChangeList::Record::~Record() {} + +void FakeMetadataChangeList::UpdateDataTypeState( + const DataTypeState& data_type_state) { + Record record; + record.action = UPDATE_DATA_TYPE_STATE; + record.data_type_state = data_type_state; + records_.push_back(record); +} + +void FakeMetadataChangeList::ClearDataTypeState() { + Record record; + record.action = CLEAR_DATA_TYPE_STATE; + records_.push_back(record); +} + +void FakeMetadataChangeList::UpdateMetadata( + const std::string& client_tag, + const sync_pb::EntityMetadata& metadata) { + Record record; + record.action = UPDATE_METADATA; + record.tag = client_tag; + record.metadata.CopyFrom(metadata); + records_.push_back(record); +} + +void FakeMetadataChangeList::ClearMetadata(const std::string& client_tag) { + Record record; + record.action = CLEAR_METADATA; + record.tag = client_tag; + records_.push_back(record); +} + +size_t FakeMetadataChangeList::GetNumRecords() const { + return records_.size(); +} + +const FakeMetadataChangeList::Record& FakeMetadataChangeList::GetNthRecord( + size_t n) const { + return records_[n]; +} + +} // namespace syncer_v2 diff --git a/sync/sync_tests.gypi b/sync/sync_tests.gypi index 079812c..c9ec6ed 100644 --- a/sync/sync_tests.gypi +++ b/sync/sync_tests.gypi @@ -183,6 +183,7 @@ 'test_support_sync_core', ], 'sources': [ + 'internal_api/public/test/fake_metadata_change_list.h', 'internal_api/public/test/fake_model_type_service.h', 'internal_api/public/test/fake_sync_manager.h', 'internal_api/public/test/null_sync_context_proxy.h', @@ -190,6 +191,7 @@ 'internal_api/public/test/test_entry_factory.h', 'internal_api/public/test/test_internal_components_factory.h', 'internal_api/public/test/test_user_share.h', + 'internal_api/test/fake_metadata_change_list.cc', 'internal_api/test/fake_model_type_service.cc', 'internal_api/test/fake_sync_manager.cc', 'internal_api/test/null_sync_context_proxy.cc', |