| Index: sync/engine/non_blocking_type_processor_core_unittest.cc
|
| diff --git a/sync/engine/non_blocking_type_processor_core_unittest.cc b/sync/engine/non_blocking_type_processor_core_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0fdec932e3a02cf22ac8ea47dac1fc6201d1155c
|
| --- /dev/null
|
| +++ b/sync/engine/non_blocking_type_processor_core_unittest.cc
|
| @@ -0,0 +1,859 @@
|
| +// Copyright 2014 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "sync/engine/non_blocking_type_processor_core.h"
|
| +
|
| +#include "base/basictypes.h"
|
| +#include "base/bind.h"
|
| +#include "base/bind_helpers.h"
|
| +#include "base/callback.h"
|
| +#include "sync/engine/non_blocking_sync_common.h"
|
| +#include "sync/engine/non_blocking_type_commit_contribution.h"
|
| +#include "sync/protocol/sync.pb.h"
|
| +#include "sync/sessions/status_controller.h"
|
| +#include "sync/syncable/syncable_util.h"
|
| +#include "sync/util/time.h"
|
| +
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +
|
| +using google::protobuf::RepeatedPtrField;
|
| +
|
| +namespace syncer {
|
| +
|
| +class NonBlockingTypeProcessorCoreTest;
|
| +
|
| +namespace {
|
| +
|
| +class MockNonBlockingTypeProcessor : public NonBlockingTypeProcessorInterface {
|
| + public:
|
| + MockNonBlockingTypeProcessor(NonBlockingTypeProcessorCoreTest* parent);
|
| + virtual ~MockNonBlockingTypeProcessor();
|
| +
|
| + virtual void ReceiveCommitResponse(
|
| + const DataTypeState& type_state,
|
| + const CommitResponseDataList& response_list) OVERRIDE;
|
| + virtual void ReceiveUpdateResponse(
|
| + const DataTypeState& type_state,
|
| + const UpdateResponseDataList& response_list) OVERRIDE;
|
| +
|
| + private:
|
| + NonBlockingTypeProcessorCoreTest* parent_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(MockNonBlockingTypeProcessor);
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +// Tests the NonBlockingTypeProcessorCore.
|
| +//
|
| +// This class passes messages between the model thread and sync server.
|
| +// As such, its code is subject to lots of different race conditions. This
|
| +// test harness lets us exhaustively test all possibl races. We try to
|
| +// focus on just a few interesting cases, though.
|
| +//
|
| +// Inputs:
|
| +// - Initial data type state from the model thread.
|
| +// - Commit requests from the model thread.
|
| +// - Update responses from the server.
|
| +// - Commit responses from the server.
|
| +//
|
| +// Outputs:
|
| +// - Commit requests to the server.
|
| +// - Commit responses to the model thread.
|
| +// - Update responses to the model thread.
|
| +//
|
| +// We use the MockNonBlockingTypeProcessor to stub out all communication
|
| +// with the model thread. That interface is synchronous, which makes it
|
| +// much easier to test races.
|
| +//
|
| +// The interface with the server already built around "pulling" data from this
|
| +// class, so we don't have to mock out any of it. We do wrap it with some
|
| +// convenience functions to we can emulate server behavior, though.
|
| +class NonBlockingTypeProcessorCoreTest : public ::testing::Test {
|
| + public:
|
| + static const std::string kTypeParentId;
|
| +
|
| + NonBlockingTypeProcessorCoreTest();
|
| + virtual ~NonBlockingTypeProcessorCoreTest();
|
| +
|
| + // One of these Initialize functions should be called at the beginning of
|
| + // each test.
|
| +
|
| + // Initializes with no data type state. We will be unable to perform any
|
| + // significant server action until we receive an update response that
|
| + // contains the type root node for this type.
|
| + void FirstInitialize();
|
| +
|
| + // Initializes with some existing data type state. Allows us to start
|
| + // committing items right away.
|
| + void NormalInitialize();
|
| +
|
| + // Initialize with a custom initial DataTypeState.
|
| + void InitializeWithState(const DataTypeState& state);
|
| +
|
| + // Modifications on the model thread that get sent to us.
|
| + void CommitRequest(const std::string& tag, const std::string& value);
|
| + void DeleteRequest(const std::string& tag);
|
| +
|
| + // Pretends to receive update messages from the server.
|
| + void TypeRootUpdateFromServer();
|
| + void UpdateFromServer(int64 version_offset,
|
| + const std::string& tag,
|
| + const std::string& value);
|
| + void TombstoneFromServer(int64 version_offset, const std::string& tag);
|
| +
|
| + // Callbacks from the mock processor. Called when the core_ tries to send
|
| + // messages to its associated processor on the model thread.
|
| + void ModelThreadReceivesCommitResponse(
|
| + const DataTypeState& type_state,
|
| + const CommitResponseDataList& response_list);
|
| + void ModelThreadReceivesUpdateResponse(
|
| + const DataTypeState& type_state,
|
| + const UpdateResponseDataList& response_list);
|
| +
|
| + // By default, this harness behaves as if all tasks posted to the model
|
| + // thread are executed immediately. However, this is not necessarily true.
|
| + // The model's TaskRunner has a queue, and the tasks we post to it could
|
| + // linger there for a while. In the meantime, the model thread could
|
| + // continue posting tasks to us based on its stale state.
|
| + //
|
| + // If you want to test those race cases, then these functions are for you.
|
| + void SetModelThreadIsSynchronous(bool is_synchronous);
|
| + void PumpModelThread();
|
| +
|
| + // Returns true if the |core_| is ready to commit something.
|
| + bool WillCommit();
|
| +
|
| + // Pretend to successfully commit all outstanding unsynced items.
|
| + // It is safe to call this only if WillCommit() returns true.
|
| + void DoSuccessfulCommit();
|
| +
|
| + // Read commit messages the core_ sent to the emulated server.
|
| + size_t GetNumCommitMessagesOnServer() const;
|
| + sync_pb::ClientToServerMessage GetNthCommitMessageOnServer(size_t n) const;
|
| +
|
| + // Read the latest version of sync entities committed to the emulated server.
|
| + bool HasCommitEntityOnServer(const std::string& tag) const;
|
| + sync_pb::SyncEntity GetLatestCommitEntityOnServer(
|
| + const std::string& tag) const;
|
| +
|
| + // Read the latest update messages received on the model thread.
|
| + // Note that if the model thread is in non-blocking mode, this data will not
|
| + // be updated until the response is actually processed by the model thread.
|
| + size_t GetNumModelThreadUpdateResponses() const;
|
| + UpdateResponseDataList GetNthModelThreadUpdateResponse(size_t n) const;
|
| + DataTypeState GetNthModelThreadUpdateState(size_t n) const;
|
| +
|
| + // Reads the latest update response datas on the model thread.
|
| + // Note that if the model thread is in non-blocking mode, this data will not
|
| + // be updated until the response is actually processed by the model thread.
|
| + bool HasUpdateResponseOnModelThread(const std::string& tag) const;
|
| + UpdateResponseData GetUpdateResponseOnModelThread(
|
| + const std::string& tag) const;
|
| +
|
| + // Read the latest commit messages received on the model thread.
|
| + // Note that if the model thread is in non-blocking mode, this data will not
|
| + // be updated until the response is actually processed by the model thread.
|
| + size_t GetNumModelThreadCommitResponses() const;
|
| + CommitResponseDataList GetNthModelThreadCommitResponse(size_t n) const;
|
| + DataTypeState GetNthModelThreadCommitState(size_t n) const;
|
| +
|
| + // Reads the latest commit response datas on the model thread.
|
| + // Note that if the model thread is in non-blocking mode, this data will not
|
| + // be updated until the response is actually processed by the model thread.
|
| + bool HasCommitResponseOnModelThread(const std::string& tag) const;
|
| + CommitResponseData GetCommitResponseOnModelThread(
|
| + const std::string& tag) const;
|
| +
|
| + // Helpers for building various messages and structures.
|
| + std::string GenerateId(const std::string& tag_hash) const;
|
| + std::string GenerateTagHash(const std::string& tag) const;
|
| + sync_pb::EntitySpecifics GenerateSpecifics(const std::string& tag,
|
| + const std::string& value) const;
|
| +
|
| + // Get and set our emulated server state.
|
| + int64 GetServerVersion(const std::string& tag_hash);
|
| + void SetServerVersion(const std::string& tag_hash, int64 version);
|
| +
|
| + // Get and set our emulated model thread state.
|
| + int64 GetCurrentSequenceNumber(const std::string& tag_hash) const;
|
| + int64 GetNextSequenceNumber(const std::string& tag_hash);
|
| + int64 GetModelVersion(const std::string& tag_hash) const;
|
| + void SetModelVersion(const std::string& tag_hash, int64 version);
|
| +
|
| + // Receive a commit response in the emulated model thread.
|
| + void ModelThreadReceiveCommitResponseImpl(
|
| + const DataTypeState& type_state,
|
| + const CommitResponseDataList& response_list);
|
| +
|
| + // Receive an update response in the emulated model thread.
|
| + void ModelThreadReceiveUpdateResponseImpl(
|
| + const DataTypeState& type_state,
|
| + const UpdateResponseDataList& response_list);
|
| +
|
| + private:
|
| + sync_pb::DataTypeProgressMarker GenerateResponseProgressMarker() const;
|
| +
|
| + scoped_ptr<NonBlockingTypeProcessorCore> core_;
|
| + MockNonBlockingTypeProcessor* mock_processor_;
|
| +
|
| + // Model thread state maps.
|
| + std::map<const std::string, int64> model_sequence_numbers_;
|
| + std::map<const std::string, int64> model_base_versions_;
|
| +
|
| + // Server state maps.
|
| + std::map<const std::string, int64> server_versions_;
|
| +
|
| + // Logs of messages sent to the server. Used in assertions.
|
| + std::map<const std::string, sync_pb::SyncEntity> committed_items_;
|
| + std::vector<sync_pb::ClientToServerMessage> commit_messages_;
|
| +
|
| + // State related to emulation of the model thread's task queue. Used to
|
| + // defer model thread work to simulate a full model thread task runner queue.
|
| + bool model_thread_is_synchronous_;
|
| + std::vector<base::Closure> model_thread_tasks_;
|
| +
|
| + // A cache of messages sent to the model thread.
|
| + std::vector<CommitResponseDataList> commit_responses_to_model_thread_;
|
| + std::vector<UpdateResponseDataList> updates_responses_to_model_thread_;
|
| + std::vector<DataTypeState> updates_states_to_model_thread_;
|
| + std::vector<DataTypeState> commit_states_to_model_thread_;
|
| +
|
| + // A cache of the latest responses on the model thread, by client tag.
|
| + std::map<const std::string, CommitResponseData>
|
| + model_thread_commit_response_items_;
|
| + std::map<const std::string, UpdateResponseData>
|
| + model_thread_update_response_items_;
|
| +};
|
| +
|
| +// These had to wait until the class definition of
|
| +// NonBlockingTypeProcessorCoreTest
|
| +MockNonBlockingTypeProcessor::MockNonBlockingTypeProcessor(
|
| + NonBlockingTypeProcessorCoreTest* parent)
|
| + : parent_(parent) {
|
| +}
|
| +
|
| +MockNonBlockingTypeProcessor::~MockNonBlockingTypeProcessor() {
|
| +}
|
| +
|
| +void MockNonBlockingTypeProcessor::ReceiveCommitResponse(
|
| + const DataTypeState& type_state,
|
| + const CommitResponseDataList& response_list) {
|
| + parent_->ModelThreadReceivesCommitResponse(type_state, response_list);
|
| +}
|
| +
|
| +void MockNonBlockingTypeProcessor::ReceiveUpdateResponse(
|
| + const DataTypeState& type_state,
|
| + const UpdateResponseDataList& response_list) {
|
| + parent_->ModelThreadReceivesUpdateResponse(type_state, response_list);
|
| +}
|
| +
|
| +const std::string NonBlockingTypeProcessorCoreTest::kTypeParentId =
|
| + "PrefsRootNodeID";
|
| +
|
| +NonBlockingTypeProcessorCoreTest::NonBlockingTypeProcessorCoreTest()
|
| + : model_thread_is_synchronous_(true) {
|
| +}
|
| +
|
| +NonBlockingTypeProcessorCoreTest::~NonBlockingTypeProcessorCoreTest() {
|
| +}
|
| +
|
| +void NonBlockingTypeProcessorCoreTest::FirstInitialize() {
|
| + DataTypeState initial_state;
|
| + initial_state.progress_marker.set_data_type_id(
|
| + GetSpecificsFieldNumberFromModelType(PREFERENCES));
|
| + initial_state.next_client_id = 0;
|
| +
|
| + InitializeWithState(initial_state);
|
| +}
|
| +
|
| +void NonBlockingTypeProcessorCoreTest::NormalInitialize() {
|
| + DataTypeState initial_state;
|
| + initial_state.progress_marker.set_data_type_id(
|
| + GetSpecificsFieldNumberFromModelType(PREFERENCES));
|
| + initial_state.progress_marker.set_token("some_saved_progress_token");
|
| +
|
| + initial_state.next_client_id = 10;
|
| + initial_state.type_root_id = kTypeParentId;
|
| +
|
| + InitializeWithState(initial_state);
|
| +}
|
| +
|
| +void NonBlockingTypeProcessorCoreTest::InitializeWithState(
|
| + const DataTypeState& state) {
|
| + DCHECK(!core_);
|
| +
|
| + // We don't get to own this interace. The |core_| keeps a scoped_ptr to it.
|
| + mock_processor_ = new MockNonBlockingTypeProcessor(this);
|
| + scoped_ptr<NonBlockingTypeProcessorInterface> interface(mock_processor_);
|
| +
|
| + core_.reset(
|
| + new NonBlockingTypeProcessorCore(PREFERENCES, state, interface.Pass()));
|
| +}
|
| +
|
| +void NonBlockingTypeProcessorCoreTest::CommitRequest(const std::string& tag,
|
| + const std::string& value) {
|
| + const std::string tag_hash = GenerateTagHash(tag);
|
| + const int64 base_version = GetModelVersion(tag_hash);
|
| +
|
| + CommitRequestData data;
|
| +
|
| + // Initial commits don't have IDs. Everything else does.
|
| + if (base_version > 0) {
|
| + data.id = GenerateId(tag_hash);
|
| + }
|
| +
|
| + data.client_tag_hash = tag_hash;
|
| + data.sequence_number = GetNextSequenceNumber(tag_hash);
|
| +
|
| + data.base_version = base_version;
|
| + data.ctime = base::Time::UnixEpoch() + base::TimeDelta::FromDays(1);
|
| + data.mtime = data.ctime + base::TimeDelta::FromSeconds(base_version);
|
| + data.non_unique_name = tag;
|
| +
|
| + data.deleted = false;
|
| + data.specifics = GenerateSpecifics(tag, value);
|
| +
|
| + CommitRequestDataList list;
|
| + list.push_back(data);
|
| +
|
| + core_->RequestCommits(list);
|
| +}
|
| +
|
| +void NonBlockingTypeProcessorCoreTest::DeleteRequest(const std::string& tag) {
|
| + const std::string tag_hash = GenerateTagHash(tag);
|
| + const int64 base_version = GetModelVersion(tag_hash);
|
| + CommitRequestData data;
|
| +
|
| + // Initial commits don't have IDs. Everything else does.
|
| + // FIXME: But deletions don't have initial commits...
|
| + if (base_version > 0) {
|
| + data.id = GenerateId(tag_hash);
|
| + }
|
| +
|
| + data.sequence_number = GetNextSequenceNumber(tag_hash);
|
| +
|
| + data.base_version = base_version;
|
| + data.ctime = base::Time::UnixEpoch() + base::TimeDelta::FromDays(1);
|
| + data.mtime = data.ctime + base::TimeDelta::FromSeconds(base_version);
|
| + data.deleted = true;
|
| +
|
| + CommitRequestDataList list;
|
| + list.push_back(data);
|
| +
|
| + core_->RequestCommits(list);
|
| +}
|
| +
|
| +void NonBlockingTypeProcessorCoreTest::TypeRootUpdateFromServer() {
|
| + sync_pb::SyncEntity entity;
|
| +
|
| + entity.set_id_string(kTypeParentId);
|
| + entity.set_parent_id_string("r");
|
| + entity.set_version(1000);
|
| + entity.set_ctime(TimeToProtoTime(base::Time::UnixEpoch()));
|
| + entity.set_mtime(TimeToProtoTime(base::Time::UnixEpoch()));
|
| + entity.set_server_defined_unique_tag(ModelTypeToRootTag(PREFERENCES));
|
| + entity.set_deleted(false);
|
| + AddDefaultFieldValue(PREFERENCES, entity.mutable_specifics());
|
| +
|
| + const sync_pb::DataTypeProgressMarker& progress =
|
| + GenerateResponseProgressMarker();
|
| + const sync_pb::DataTypeContext blank_context;
|
| + sessions::StatusController dummy_status;
|
| +
|
| + SyncEntityList entity_list;
|
| + entity_list.push_back(&entity);
|
| +
|
| + core_->ProcessGetUpdatesResponse(
|
| + progress, blank_context, entity_list, &dummy_status);
|
| + core_->ApplyUpdates(&dummy_status);
|
| +}
|
| +
|
| +void NonBlockingTypeProcessorCoreTest::UpdateFromServer(
|
| + int64 version_offset,
|
| + const std::string& tag,
|
| + const std::string& value) {
|
| + const std::string tag_hash = GenerateTagHash(tag);
|
| +
|
| + int64 old_version = GetServerVersion(tag_hash);
|
| + int64 version = old_version + version_offset;
|
| + if (version > old_version) {
|
| + SetServerVersion(tag_hash, version);
|
| + }
|
| +
|
| + sync_pb::SyncEntity entity;
|
| +
|
| + entity.set_id_string(GenerateId(tag_hash));
|
| + entity.set_parent_id_string(kTypeParentId);
|
| + entity.set_version(version);
|
| +
|
| + base::Time ctime = base::Time::UnixEpoch() + base::TimeDelta::FromDays(1);
|
| + base::Time mtime = ctime + base::TimeDelta::FromSeconds(version);
|
| + entity.set_ctime(TimeToProtoTime(ctime));
|
| + entity.set_mtime(TimeToProtoTime(mtime));
|
| +
|
| + entity.set_client_defined_unique_tag(GenerateTagHash(tag));
|
| + entity.set_deleted(false);
|
| + entity.mutable_specifics()->CopyFrom(GenerateSpecifics(tag, value));
|
| +
|
| + SyncEntityList entity_list;
|
| + entity_list.push_back(&entity);
|
| +
|
| + const sync_pb::DataTypeProgressMarker& progress =
|
| + GenerateResponseProgressMarker();
|
| + const sync_pb::DataTypeContext blank_context;
|
| + sessions::StatusController dummy_status;
|
| +
|
| + core_->ProcessGetUpdatesResponse(
|
| + progress, blank_context, entity_list, &dummy_status);
|
| + core_->ApplyUpdates(&dummy_status);
|
| +}
|
| +
|
| +void NonBlockingTypeProcessorCoreTest::TombstoneFromServer(
|
| + int64 version_offset,
|
| + const std::string& tag) {
|
| + const std::string tag_hash = GenerateTagHash(tag);
|
| + int64 old_version = GetServerVersion(tag_hash);
|
| + int64 version = old_version + version_offset;
|
| + if (version > old_version) {
|
| + SetServerVersion(tag_hash, version);
|
| + }
|
| +
|
| + UpdateResponseData data;
|
| +
|
| + data.id = GenerateId(tag_hash);
|
| + data.client_tag_hash = tag_hash;
|
| + data.response_version = version;
|
| + data.ctime = base::Time::UnixEpoch() + base::TimeDelta::FromDays(1);
|
| + data.mtime = data.ctime + base::TimeDelta::FromSeconds(version);
|
| + data.non_unique_name = tag;
|
| + data.deleted = true;
|
| +}
|
| +
|
| +void NonBlockingTypeProcessorCoreTest::ModelThreadReceivesCommitResponse(
|
| + const DataTypeState& type_state,
|
| + const CommitResponseDataList& response_list) {
|
| + base::Closure task = base::Bind(
|
| + &NonBlockingTypeProcessorCoreTest::ModelThreadReceiveCommitResponseImpl,
|
| + base::Unretained(this),
|
| + type_state,
|
| + response_list);
|
| + model_thread_tasks_.push_back(task);
|
| + if (model_thread_is_synchronous_)
|
| + PumpModelThread();
|
| +}
|
| +
|
| +void NonBlockingTypeProcessorCoreTest::ModelThreadReceivesUpdateResponse(
|
| + const DataTypeState& type_state,
|
| + const UpdateResponseDataList& response_list) {
|
| + base::Closure task = base::Bind(
|
| + &NonBlockingTypeProcessorCoreTest::ModelThreadReceiveUpdateResponseImpl,
|
| + base::Unretained(this),
|
| + type_state,
|
| + response_list);
|
| + model_thread_tasks_.push_back(task);
|
| + if (model_thread_is_synchronous_)
|
| + PumpModelThread();
|
| +}
|
| +
|
| +void NonBlockingTypeProcessorCoreTest::SetModelThreadIsSynchronous(
|
| + bool is_synchronous) {
|
| + model_thread_is_synchronous_ = is_synchronous;
|
| +}
|
| +
|
| +void NonBlockingTypeProcessorCoreTest::PumpModelThread() {
|
| + for (std::vector<base::Closure>::iterator it = model_thread_tasks_.begin();
|
| + it != model_thread_tasks_.end();
|
| + ++it) {
|
| + it->Run();
|
| + }
|
| + model_thread_tasks_.clear();
|
| +}
|
| +
|
| +bool NonBlockingTypeProcessorCoreTest::WillCommit() {
|
| + scoped_ptr<CommitContribution> contribution(core_->GetContribution(INT_MAX));
|
| +
|
| + if (contribution) {
|
| + contribution->CleanUp(); // Gracefully abort the commit.
|
| + return true;
|
| + } else {
|
| + return false;
|
| + }
|
| +}
|
| +
|
| +// Conveniently, this is all one big synchronous operation. The sync thread
|
| +// remains blocked while the commit is in progress, so we don't need to worry
|
| +// about other tasks being run between the time when the commit request is
|
| +// issued and the time when the commit response is received.
|
| +void NonBlockingTypeProcessorCoreTest::DoSuccessfulCommit() {
|
| + DCHECK(WillCommit());
|
| + scoped_ptr<CommitContribution> contribution(core_->GetContribution(INT_MAX));
|
| +
|
| + sync_pb::ClientToServerMessage message;
|
| + contribution->AddToCommitMessage(&message);
|
| + commit_messages_.push_back(message);
|
| +
|
| + sync_pb::ClientToServerResponse response;
|
| + sync_pb::CommitResponse* commit_response = response.mutable_commit();
|
| +
|
| + const RepeatedPtrField<sync_pb::SyncEntity>& entries =
|
| + message.commit().entries();
|
| + for (RepeatedPtrField<sync_pb::SyncEntity>::const_iterator it =
|
| + entries.begin();
|
| + it != entries.end();
|
| + ++it) {
|
| + const std::string tag_hash = it->client_defined_unique_tag();
|
| +
|
| + committed_items_[tag_hash] = *it;
|
| +
|
| + // Every commit increments the version number.
|
| + int64 version = GetServerVersion(tag_hash); version++;
|
| + SetServerVersion(tag_hash, version);
|
| +
|
| + sync_pb::CommitResponse_EntryResponse* entryresponse =
|
| + commit_response->add_entryresponse();
|
| + entryresponse->set_response_type(sync_pb::CommitResponse::SUCCESS);
|
| + entryresponse->set_id_string(GenerateId(tag_hash));
|
| + entryresponse->set_parent_id_string(it->parent_id_string());
|
| + entryresponse->set_version(version);
|
| + entryresponse->set_name(it->name());
|
| + entryresponse->set_mtime(it->mtime());
|
| + }
|
| +
|
| + sessions::StatusController dummy_status;
|
| + contribution->ProcessCommitResponse(response, &dummy_status);
|
| + contribution->CleanUp();
|
| +}
|
| +
|
| +size_t NonBlockingTypeProcessorCoreTest::GetNumCommitMessagesOnServer() const {
|
| + return commit_messages_.size();
|
| +}
|
| +
|
| +sync_pb::ClientToServerMessage
|
| +NonBlockingTypeProcessorCoreTest::GetNthCommitMessageOnServer(size_t n) const {
|
| + DCHECK_LT(n, GetNumCommitMessagesOnServer());
|
| + return commit_messages_[n];
|
| +}
|
| +
|
| +bool NonBlockingTypeProcessorCoreTest::HasCommitEntityOnServer(
|
| + const std::string& tag) const {
|
| + const std::string tag_hash = GenerateTagHash(tag);
|
| + std::map<const std::string, sync_pb::SyncEntity>::const_iterator it =
|
| + committed_items_.find(tag_hash);
|
| + return it != committed_items_.end();
|
| +}
|
| +
|
| +sync_pb::SyncEntity
|
| +NonBlockingTypeProcessorCoreTest::GetLatestCommitEntityOnServer(
|
| + const std::string& tag) const {
|
| + DCHECK(HasCommitEntityOnServer(tag));
|
| + const std::string tag_hash = GenerateTagHash(tag);
|
| + std::map<const std::string, sync_pb::SyncEntity>::const_iterator it =
|
| + committed_items_.find(tag_hash);
|
| + return it->second;
|
| +}
|
| +
|
| +size_t NonBlockingTypeProcessorCoreTest::GetNumModelThreadUpdateResponses()
|
| + const {
|
| + return updates_responses_to_model_thread_.size();
|
| +}
|
| +
|
| +UpdateResponseDataList
|
| +NonBlockingTypeProcessorCoreTest::GetNthModelThreadUpdateResponse(
|
| + size_t n) const {
|
| + DCHECK(GetNumModelThreadUpdateResponses());
|
| + return updates_responses_to_model_thread_[n];
|
| +}
|
| +
|
| +DataTypeState NonBlockingTypeProcessorCoreTest::GetNthModelThreadUpdateState(
|
| + size_t n) const {
|
| + DCHECK(GetNumModelThreadUpdateResponses());
|
| + return updates_states_to_model_thread_[n];
|
| +}
|
| +
|
| +bool NonBlockingTypeProcessorCoreTest::HasUpdateResponseOnModelThread(
|
| + const std::string& tag) const {
|
| + const std::string tag_hash = GenerateTagHash(tag);
|
| + std::map<const std::string, UpdateResponseData>::const_iterator it =
|
| + model_thread_update_response_items_.find(tag_hash);
|
| + return it != model_thread_update_response_items_.end();
|
| +}
|
| +
|
| +UpdateResponseData
|
| +NonBlockingTypeProcessorCoreTest::GetUpdateResponseOnModelThread(
|
| + const std::string& tag) const {
|
| + const std::string tag_hash = GenerateTagHash(tag);
|
| + DCHECK(HasUpdateResponseOnModelThread(tag_hash));
|
| + std::map<const std::string, UpdateResponseData>::const_iterator it =
|
| + model_thread_update_response_items_.find(tag_hash);
|
| + return it->second;
|
| +}
|
| +
|
| +size_t NonBlockingTypeProcessorCoreTest::GetNumModelThreadCommitResponses()
|
| + const {
|
| + return commit_responses_to_model_thread_.size();
|
| +}
|
| +
|
| +CommitResponseDataList
|
| +NonBlockingTypeProcessorCoreTest::GetNthModelThreadCommitResponse(
|
| + size_t n) const {
|
| + DCHECK(GetNumModelThreadCommitResponses());
|
| + return commit_responses_to_model_thread_[n];
|
| +}
|
| +
|
| +DataTypeState NonBlockingTypeProcessorCoreTest::GetNthModelThreadCommitState(
|
| + size_t n) const {
|
| + DCHECK(GetNumModelThreadCommitResponses());
|
| + return commit_states_to_model_thread_[n];
|
| +}
|
| +
|
| +std::string NonBlockingTypeProcessorCoreTest::GenerateId(
|
| + const std::string& tag_hash) const {
|
| + return "FakeId:" + tag_hash;
|
| +}
|
| +
|
| +bool NonBlockingTypeProcessorCoreTest::HasCommitResponseOnModelThread(
|
| + const std::string& tag) const {
|
| + const std::string tag_hash = GenerateTagHash(tag);
|
| + std::map<const std::string, CommitResponseData>::const_iterator it =
|
| + model_thread_commit_response_items_.find(tag_hash);
|
| + return it != model_thread_commit_response_items_.end();
|
| +}
|
| +
|
| +CommitResponseData
|
| +NonBlockingTypeProcessorCoreTest::GetCommitResponseOnModelThread(
|
| + const std::string& tag) const {
|
| + DCHECK(HasCommitResponseOnModelThread(tag));
|
| + const std::string tag_hash = GenerateTagHash(tag);
|
| + std::map<const std::string, CommitResponseData>::const_iterator it =
|
| + model_thread_commit_response_items_.find(tag_hash);
|
| + return it->second;
|
| +}
|
| +
|
| +std::string NonBlockingTypeProcessorCoreTest::GenerateTagHash(
|
| + const std::string& tag) const {
|
| + const std::string& client_tag_hash =
|
| + syncable::GenerateSyncableHash(PREFERENCES, tag);
|
| +
|
| + return client_tag_hash;
|
| +}
|
| +
|
| +sync_pb::EntitySpecifics NonBlockingTypeProcessorCoreTest::GenerateSpecifics(
|
| + const std::string& tag,
|
| + const std::string& value) const {
|
| + sync_pb::EntitySpecifics specifics;
|
| + specifics.mutable_preference()->set_name(tag);
|
| + specifics.mutable_preference()->set_value(value);
|
| + return specifics;
|
| +}
|
| +
|
| +int64 NonBlockingTypeProcessorCoreTest::GetServerVersion(
|
| + const std::string& tag_hash) {
|
| + std::map<const std::string, int64>::const_iterator it;
|
| + it = server_versions_.find(tag_hash);
|
| + if (it == server_versions_.end()) {
|
| + return 0;
|
| + } else {
|
| + return it->second;
|
| + }
|
| +}
|
| +
|
| +void NonBlockingTypeProcessorCoreTest::SetServerVersion(const std::string& tag_hash,
|
| + int64 version) {
|
| + server_versions_[tag_hash] = version;
|
| +}
|
| +
|
| +// Fetches the sequence number as of the most recent update request.
|
| +int64 NonBlockingTypeProcessorCoreTest::GetCurrentSequenceNumber(
|
| + const std::string& tag_hash) const {
|
| + std::map<const std::string, int64>::const_iterator it =
|
| + model_sequence_numbers_.find(tag_hash);
|
| + if (it == model_sequence_numbers_.end()) {
|
| + return 0;
|
| + } else {
|
| + return it->second;
|
| + }
|
| +}
|
| +
|
| +// The model thread should be sending us items with strictly increasing
|
| +// sequence numbers. Here's where we emulate that behavior.
|
| +int64 NonBlockingTypeProcessorCoreTest::GetNextSequenceNumber(
|
| + const std::string& tag_hash) {
|
| + int64 sequence_number = GetCurrentSequenceNumber(tag_hash);
|
| + sequence_number++;
|
| + model_sequence_numbers_[tag_hash] = sequence_number;
|
| + return sequence_number;
|
| +}
|
| +
|
| +// Fetches the model's base version.
|
| +int64 NonBlockingTypeProcessorCoreTest::GetModelVersion(
|
| + const std::string& tag_hash) const {
|
| + std::map<const std::string, int64>::const_iterator it =
|
| + model_base_versions_.find(tag_hash);
|
| + if (it == model_base_versions_.end()) {
|
| + return 0;
|
| + } else {
|
| + return it->second;
|
| + }
|
| +}
|
| +
|
| +void NonBlockingTypeProcessorCoreTest::SetModelVersion(const std::string& tag_hash,
|
| + int64 version) {
|
| + model_base_versions_[tag_hash] = version;
|
| +}
|
| +
|
| +void NonBlockingTypeProcessorCoreTest::ModelThreadReceiveCommitResponseImpl(
|
| + const DataTypeState& type_state,
|
| + const CommitResponseDataList& response_list) {
|
| + commit_responses_to_model_thread_.push_back(response_list);
|
| + commit_states_to_model_thread_.push_back(type_state);
|
| + for (CommitResponseDataList::const_iterator it = response_list.begin();
|
| + it != response_list.end();
|
| + ++it) {
|
| + model_thread_commit_response_items_.insert(std::make_pair(it->client_tag_hash, *it));
|
| +
|
| + // Server wins. Set the model's base version.
|
| + SetModelVersion(it->client_tag_hash, it->response_version);
|
| + }
|
| +}
|
| +
|
| +void NonBlockingTypeProcessorCoreTest::ModelThreadReceiveUpdateResponseImpl(
|
| + const DataTypeState& type_state,
|
| + const UpdateResponseDataList& response_list) {
|
| + updates_responses_to_model_thread_.push_back(response_list);
|
| + updates_states_to_model_thread_.push_back(type_state);
|
| + for (UpdateResponseDataList::const_iterator it = response_list.begin();
|
| + it != response_list.end();
|
| + ++it) {
|
| + model_thread_update_response_items_.insert(std::make_pair(it->client_tag_hash, *it));
|
| +
|
| + // Server wins. Set the model's base version.
|
| + SetModelVersion(it->client_tag_hash, it->response_version);
|
| + }
|
| +}
|
| +
|
| +sync_pb::DataTypeProgressMarker
|
| +NonBlockingTypeProcessorCoreTest::GenerateResponseProgressMarker() const {
|
| + sync_pb::DataTypeProgressMarker progress;
|
| + progress.set_data_type_id(PREFERENCES);
|
| + progress.set_token("non_null_progress_token");
|
| + return progress;
|
| +}
|
| +
|
| +// Requests a commit and verifies the messages sent to the client and server as
|
| +// a result.
|
| +//
|
| +// This test performs sanity checks on most of the fields in these messages.
|
| +// For the most part this is checking that the test code behaves as expected
|
| +// and the |core_| doesn't mess up its simple task of moving around these
|
| +// values. It makes sense to have one or two tests that are this thorough, but
|
| +// we shouldn't be this verbose in all tests.
|
| +TEST_F(NonBlockingTypeProcessorCoreTest, SimpleCommit) {
|
| + NormalInitialize();
|
| +
|
| + EXPECT_FALSE(WillCommit());
|
| + EXPECT_EQ(0U, GetNumCommitMessagesOnServer());
|
| + EXPECT_EQ(0U, GetNumModelThreadCommitResponses());
|
| +
|
| + CommitRequest("tag1", "value1");
|
| +
|
| + ASSERT_TRUE(WillCommit());
|
| + DoSuccessfulCommit();
|
| +
|
| + const std::string& client_tag_hash = GenerateTagHash("tag1");
|
| +
|
| + // Exhaustively verify the SyncEntity sent in the commit message.
|
| + ASSERT_EQ(1U, GetNumCommitMessagesOnServer());
|
| + EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size());
|
| + ASSERT_TRUE(HasCommitEntityOnServer("tag1"));
|
| + const sync_pb::SyncEntity& entity = GetLatestCommitEntityOnServer("tag1");
|
| + EXPECT_FALSE(entity.id_string().empty());
|
| + EXPECT_EQ(kTypeParentId, entity.parent_id_string());
|
| + EXPECT_EQ(entity.version(), 0);
|
| + EXPECT_NE(0, entity.mtime());
|
| + EXPECT_NE(0, entity.ctime());
|
| + EXPECT_EQ("tag1", entity.name());
|
| + EXPECT_EQ(client_tag_hash, entity.client_defined_unique_tag());
|
| + EXPECT_EQ("tag1", entity.specifics().preference().name());
|
| + EXPECT_EQ("value1", entity.specifics().preference().value());
|
| +
|
| + // Exhaustively verify the commit response returned to the model thread.
|
| + ASSERT_EQ(1U, GetNumModelThreadCommitResponses());
|
| + EXPECT_EQ(1U, GetNthModelThreadCommitResponse(0).size());
|
| + ASSERT_TRUE(HasCommitResponseOnModelThread("tag1"));
|
| + const CommitResponseData& commit_response =
|
| + GetCommitResponseOnModelThread("tag1");
|
| +
|
| + // The ID changes in a commit response to initial commit.
|
| + EXPECT_FALSE(commit_response.id.empty());
|
| + EXPECT_NE(entity.id_string(), commit_response.id);
|
| +
|
| + EXPECT_EQ(client_tag_hash, commit_response.client_tag_hash);
|
| + EXPECT_EQ(GetCurrentSequenceNumber(client_tag_hash),
|
| + commit_response.sequence_number);
|
| + EXPECT_LT(0, commit_response.response_version);
|
| +}
|
| +
|
| +TEST_F(NonBlockingTypeProcessorCoreTest, NoCommitUntilFirstUpdate) {
|
| + FirstInitialize(); // Initialize with no saved sync state.
|
| +
|
| + CommitRequest("tag1", "value1");
|
| + EXPECT_FALSE(WillCommit());
|
| +
|
| + // Receive an update response that contains only the type root node.
|
| + TypeRootUpdateFromServer();
|
| +
|
| + EXPECT_TRUE(WillCommit());
|
| +
|
| + // The type root node itself should not be sent to the model thread.
|
| + // However, we will still generate and send a message to the model
|
| + // thread to update its DataTypeState with the new information.
|
| + // (specifically the parent ID) so we won't have to fetch it again
|
| + // on the next restart.
|
| + ASSERT_EQ(1U, GetNumModelThreadUpdateResponses());
|
| + EXPECT_EQ(0U, GetNthModelThreadUpdateResponse(0).size());
|
| + const DataTypeState& state = GetNthModelThreadUpdateState(0);
|
| + EXPECT_FALSE(state.progress_marker.token().empty());
|
| + EXPECT_FALSE(state.type_root_id.empty());
|
| +}
|
| +
|
| +// Commit two new entities in two separate commit messages.
|
| +TEST_F(NonBlockingTypeProcessorCoreTest, TwoNewItemsCommittedSeparately) {
|
| + NormalInitialize();
|
| +
|
| + // Commit the first of two entities.
|
| + CommitRequest("tag1", "value1");
|
| + ASSERT_TRUE(WillCommit());
|
| + DoSuccessfulCommit();
|
| + ASSERT_EQ(1U, GetNumCommitMessagesOnServer());
|
| + EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size());
|
| + ASSERT_TRUE(HasCommitEntityOnServer("tag1"));
|
| + const sync_pb::SyncEntity& tag1_entity =
|
| + GetLatestCommitEntityOnServer("tag1");
|
| +
|
| + // Commit the second of two entities.
|
| + CommitRequest("tag2", "value2");
|
| + ASSERT_TRUE(WillCommit());
|
| + DoSuccessfulCommit();
|
| + ASSERT_EQ(2U, GetNumCommitMessagesOnServer());
|
| + EXPECT_EQ(1, GetNthCommitMessageOnServer(1).commit().entries_size());
|
| + ASSERT_TRUE(HasCommitEntityOnServer("tag2"));
|
| + const sync_pb::SyncEntity& tag2_entity =
|
| + GetLatestCommitEntityOnServer("tag2");
|
| +
|
| + EXPECT_FALSE(WillCommit());
|
| +
|
| + // The IDs assigned by the |core_| should be unique.
|
| + EXPECT_NE(tag1_entity.id_string(), tag2_entity.id_string());
|
| +
|
| + // Check that the committed specifics values are sane.
|
| + EXPECT_EQ(tag1_entity.specifics().preference().value(), "value1");
|
| + EXPECT_EQ(tag2_entity.specifics().preference().value(), "value2");
|
| +
|
| + // There should have been two separate commit responses sent to the model
|
| + // thread. They should be uninteresting, so we don't bother inspecting them.
|
| + EXPECT_EQ(2U, GetNumModelThreadCommitResponses());
|
| +}
|
| +
|
| +} // namespace syncer
|
|
|