Home | History | Annotate | Download | only in engine
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "sync/engine/non_blocking_type_processor_core.h"
      6 
      7 #include "sync/engine/commit_contribution.h"
      8 #include "sync/engine/non_blocking_sync_common.h"
      9 #include "sync/engine/non_blocking_type_processor_interface.h"
     10 #include "sync/internal_api/public/base/model_type.h"
     11 #include "sync/protocol/sync.pb.h"
     12 #include "sync/sessions/status_controller.h"
     13 #include "sync/syncable/syncable_util.h"
     14 #include "sync/test/engine/mock_non_blocking_type_processor.h"
     15 #include "sync/test/engine/single_type_mock_server.h"
     16 
     17 #include "testing/gtest/include/gtest/gtest.h"
     18 
     19 static const std::string kTypeParentId = "PrefsRootNodeID";
     20 static const syncer::ModelType kModelType = syncer::PREFERENCES;
     21 
     22 namespace syncer {
     23 
     24 // Tests the NonBlockingTypeProcessorCore.
     25 //
     26 // This class passes messages between the model thread and sync server.
     27 // As such, its code is subject to lots of different race conditions.  This
     28 // test harness lets us exhaustively test all possible races.  We try to
     29 // focus on just a few interesting cases.
     30 //
     31 // Inputs:
     32 // - Initial data type state from the model thread.
     33 // - Commit requests from the model thread.
     34 // - Update responses from the server.
     35 // - Commit responses from the server.
     36 //
     37 // Outputs:
     38 // - Commit requests to the server.
     39 // - Commit responses to the model thread.
     40 // - Update responses to the model thread.
     41 // - Nudges to the sync scheduler.
     42 //
     43 // We use the MockNonBlockingTypeProcessor to stub out all communication
     44 // with the model thread.  That interface is synchronous, which makes it
     45 // much easier to test races.
     46 //
     47 // The interface with the server is built around "pulling" data from this
     48 // class, so we don't have to mock out any of it.  We wrap it with some
     49 // convenience functions to we can emulate server behavior.
     50 class NonBlockingTypeProcessorCoreTest : public ::testing::Test {
     51  public:
     52   NonBlockingTypeProcessorCoreTest();
     53   virtual ~NonBlockingTypeProcessorCoreTest();
     54 
     55   // One of these Initialize functions should be called at the beginning of
     56   // each test.
     57 
     58   // Initializes with no data type state.  We will be unable to perform any
     59   // significant server action until we receive an update response that
     60   // contains the type root node for this type.
     61   void FirstInitialize();
     62 
     63   // Initializes with some existing data type state.  Allows us to start
     64   // committing items right away.
     65   void NormalInitialize();
     66 
     67   // Initialize with a custom initial DataTypeState.
     68   void InitializeWithState(const DataTypeState& state);
     69 
     70   // Modifications on the model thread that get sent to the core under test.
     71   void CommitRequest(const std::string& tag, const std::string& value);
     72   void DeleteRequest(const std::string& tag);
     73 
     74   // Pretends to receive update messages from the server.
     75   void TriggerTypeRootUpdateFromServer();
     76   void TriggerUpdateFromServer(int64 version_offset,
     77                                const std::string& tag,
     78                                const std::string& value);
     79   void TriggerTombstoneFromServer(int64 version_offset, const std::string& tag);
     80 
     81   // By default, this harness behaves as if all tasks posted to the model
     82   // thread are executed immediately.  However, this is not necessarily true.
     83   // The model's TaskRunner has a queue, and the tasks we post to it could
     84   // linger there for a while.  In the meantime, the model thread could
     85   // continue posting tasks to the core based on its stale state.
     86   //
     87   // If you want to test those race cases, then these functions are for you.
     88   void SetModelThreadIsSynchronous(bool is_synchronous);
     89   void PumpModelThread();
     90 
     91   // Returns true if the |core_| is ready to commit something.
     92   bool WillCommit();
     93 
     94   // Pretend to successfully commit all outstanding unsynced items.
     95   // It is safe to call this only if WillCommit() returns true.
     96   void DoSuccessfulCommit();
     97 
     98   // Read commit messages the core_ sent to the emulated server.
     99   size_t GetNumCommitMessagesOnServer() const;
    100   sync_pb::ClientToServerMessage GetNthCommitMessageOnServer(size_t n) const;
    101 
    102   // Read the latest version of sync entities committed to the emulated server.
    103   bool HasCommitEntityOnServer(const std::string& tag) const;
    104   sync_pb::SyncEntity GetLatestCommitEntityOnServer(
    105       const std::string& tag) const;
    106 
    107   // Read the latest update messages received on the model thread.
    108   // Note that if the model thread is in non-blocking mode, this data will not
    109   // be updated until the response is actually processed by the model thread.
    110   size_t GetNumModelThreadUpdateResponses() const;
    111   UpdateResponseDataList GetNthModelThreadUpdateResponse(size_t n) const;
    112   DataTypeState GetNthModelThreadUpdateState(size_t n) const;
    113 
    114   // Reads the latest update response datas on the model thread.
    115   // Note that if the model thread is in non-blocking mode, this data will not
    116   // be updated until the response is actually processed by the model thread.
    117   bool HasUpdateResponseOnModelThread(const std::string& tag) const;
    118   UpdateResponseData GetUpdateResponseOnModelThread(
    119       const std::string& tag) const;
    120 
    121   // Read the latest commit messages received on the model thread.
    122   // Note that if the model thread is in non-blocking mode, this data will not
    123   // be updated until the response is actually processed by the model thread.
    124   size_t GetNumModelThreadCommitResponses() const;
    125   CommitResponseDataList GetNthModelThreadCommitResponse(size_t n) const;
    126   DataTypeState GetNthModelThreadCommitState(size_t n) const;
    127 
    128   // Reads the latest commit response datas on the model thread.
    129   // Note that if the model thread is in non-blocking mode, this data will not
    130   // be updated until the response is actually processed by the model thread.
    131   bool HasCommitResponseOnModelThread(const std::string& tag) const;
    132   CommitResponseData GetCommitResponseOnModelThread(
    133       const std::string& tag) const;
    134 
    135   // Helpers for building various messages and structures.
    136   static std::string GenerateTagHash(const std::string& tag);
    137   static sync_pb::EntitySpecifics GenerateSpecifics(const std::string& tag,
    138                                                     const std::string& value);
    139 
    140  private:
    141   // The NonBlockingTypeProcessorCore being tested.
    142   scoped_ptr<NonBlockingTypeProcessorCore> core_;
    143 
    144   // Non-owned, possibly NULL pointer.  This object belongs to the
    145   // NonBlockingTypeProcessorCore under test.
    146   MockNonBlockingTypeProcessor* mock_processor_;
    147 
    148   // A mock that emulates enough of the sync server that it can be used
    149   // a single UpdateHandler and CommitContributor pair.  In this test
    150   // harness, the |core_| is both of them.
    151   SingleTypeMockServer mock_server_;
    152 };
    153 
    154 NonBlockingTypeProcessorCoreTest::NonBlockingTypeProcessorCoreTest()
    155     : mock_processor_(NULL), mock_server_(kModelType) {
    156 }
    157 
    158 NonBlockingTypeProcessorCoreTest::~NonBlockingTypeProcessorCoreTest() {
    159 }
    160 
    161 void NonBlockingTypeProcessorCoreTest::FirstInitialize() {
    162   DataTypeState initial_state;
    163   initial_state.progress_marker.set_data_type_id(
    164       GetSpecificsFieldNumberFromModelType(kModelType));
    165   initial_state.next_client_id = 0;
    166 
    167   InitializeWithState(initial_state);
    168 }
    169 
    170 void NonBlockingTypeProcessorCoreTest::NormalInitialize() {
    171   DataTypeState initial_state;
    172   initial_state.progress_marker.set_data_type_id(
    173       GetSpecificsFieldNumberFromModelType(kModelType));
    174   initial_state.progress_marker.set_token("some_saved_progress_token");
    175 
    176   initial_state.next_client_id = 10;
    177   initial_state.type_root_id = kTypeParentId;
    178   initial_state.initial_sync_done = true;
    179 
    180   InitializeWithState(initial_state);
    181 }
    182 
    183 void NonBlockingTypeProcessorCoreTest::InitializeWithState(
    184     const DataTypeState& state) {
    185   DCHECK(!core_);
    186 
    187   // We don't get to own this interace.  The |core_| keeps a scoped_ptr to it.
    188   mock_processor_ = new MockNonBlockingTypeProcessor();
    189   scoped_ptr<NonBlockingTypeProcessorInterface> interface(mock_processor_);
    190 
    191   core_.reset(
    192       new NonBlockingTypeProcessorCore(kModelType, state, interface.Pass()));
    193 }
    194 
    195 void NonBlockingTypeProcessorCoreTest::CommitRequest(const std::string& name,
    196                                                      const std::string& value) {
    197   const std::string tag_hash = GenerateTagHash(name);
    198   CommitRequestData data =
    199       mock_processor_->CommitRequest(tag_hash, GenerateSpecifics(name, value));
    200   CommitRequestDataList list;
    201   list.push_back(data);
    202   core_->EnqueueForCommit(list);
    203 }
    204 
    205 void NonBlockingTypeProcessorCoreTest::DeleteRequest(const std::string& tag) {
    206   const std::string tag_hash = GenerateTagHash(tag);
    207   CommitRequestData data = mock_processor_->DeleteRequest(tag_hash);
    208   CommitRequestDataList list;
    209   list.push_back(data);
    210   core_->EnqueueForCommit(list);
    211 }
    212 
    213 void NonBlockingTypeProcessorCoreTest::TriggerTypeRootUpdateFromServer() {
    214   sync_pb::SyncEntity entity = mock_server_.TypeRootUpdate();
    215   SyncEntityList entity_list;
    216   entity_list.push_back(&entity);
    217 
    218   sessions::StatusController dummy_status;
    219 
    220   core_->ProcessGetUpdatesResponse(mock_server_.GetProgress(),
    221                                    mock_server_.GetContext(),
    222                                    entity_list,
    223                                    &dummy_status);
    224   core_->ApplyUpdates(&dummy_status);
    225 }
    226 
    227 void NonBlockingTypeProcessorCoreTest::TriggerUpdateFromServer(
    228     int64 version_offset,
    229     const std::string& tag,
    230     const std::string& value) {
    231   sync_pb::SyncEntity entity = mock_server_.UpdateFromServer(
    232       version_offset, GenerateTagHash(tag), GenerateSpecifics(tag, value));
    233   SyncEntityList entity_list;
    234   entity_list.push_back(&entity);
    235 
    236   sessions::StatusController dummy_status;
    237 
    238   core_->ProcessGetUpdatesResponse(mock_server_.GetProgress(),
    239                                    mock_server_.GetContext(),
    240                                    entity_list,
    241                                    &dummy_status);
    242   core_->ApplyUpdates(&dummy_status);
    243 }
    244 
    245 void NonBlockingTypeProcessorCoreTest::TriggerTombstoneFromServer(
    246     int64 version_offset,
    247     const std::string& tag) {
    248   sync_pb::SyncEntity entity =
    249       mock_server_.TombstoneFromServer(version_offset, GenerateTagHash(tag));
    250   SyncEntityList entity_list;
    251   entity_list.push_back(&entity);
    252 
    253   sessions::StatusController dummy_status;
    254 
    255   core_->ProcessGetUpdatesResponse(mock_server_.GetProgress(),
    256                                    mock_server_.GetContext(),
    257                                    entity_list,
    258                                    &dummy_status);
    259   core_->ApplyUpdates(&dummy_status);
    260 }
    261 
    262 void NonBlockingTypeProcessorCoreTest::SetModelThreadIsSynchronous(
    263     bool is_synchronous) {
    264   mock_processor_->SetSynchronousExecution(is_synchronous);
    265 }
    266 
    267 void NonBlockingTypeProcessorCoreTest::PumpModelThread() {
    268   mock_processor_->RunQueuedTasks();
    269 }
    270 
    271 bool NonBlockingTypeProcessorCoreTest::WillCommit() {
    272   scoped_ptr<CommitContribution> contribution(core_->GetContribution(INT_MAX));
    273 
    274   if (contribution) {
    275     contribution->CleanUp();  // Gracefully abort the commit.
    276     return true;
    277   } else {
    278     return false;
    279   }
    280 }
    281 
    282 // Conveniently, this is all one big synchronous operation.  The sync thread
    283 // remains blocked while the commit is in progress, so we don't need to worry
    284 // about other tasks being run between the time when the commit request is
    285 // issued and the time when the commit response is received.
    286 void NonBlockingTypeProcessorCoreTest::DoSuccessfulCommit() {
    287   DCHECK(WillCommit());
    288   scoped_ptr<CommitContribution> contribution(core_->GetContribution(INT_MAX));
    289 
    290   sync_pb::ClientToServerMessage message;
    291   contribution->AddToCommitMessage(&message);
    292 
    293   sync_pb::ClientToServerResponse response =
    294       mock_server_.DoSuccessfulCommit(message);
    295 
    296   sessions::StatusController dummy_status;
    297   contribution->ProcessCommitResponse(response, &dummy_status);
    298   contribution->CleanUp();
    299 }
    300 
    301 size_t NonBlockingTypeProcessorCoreTest::GetNumCommitMessagesOnServer() const {
    302   return mock_server_.GetNumCommitMessages();
    303 }
    304 
    305 sync_pb::ClientToServerMessage
    306 NonBlockingTypeProcessorCoreTest::GetNthCommitMessageOnServer(size_t n) const {
    307   DCHECK_LT(n, GetNumCommitMessagesOnServer());
    308   return mock_server_.GetNthCommitMessage(n);
    309 }
    310 
    311 bool NonBlockingTypeProcessorCoreTest::HasCommitEntityOnServer(
    312     const std::string& tag) const {
    313   const std::string tag_hash = GenerateTagHash(tag);
    314   return mock_server_.HasCommitEntity(tag_hash);
    315 }
    316 
    317 sync_pb::SyncEntity
    318 NonBlockingTypeProcessorCoreTest::GetLatestCommitEntityOnServer(
    319     const std::string& tag) const {
    320   DCHECK(HasCommitEntityOnServer(tag));
    321   const std::string tag_hash = GenerateTagHash(tag);
    322   return mock_server_.GetLastCommittedEntity(tag_hash);
    323 }
    324 
    325 size_t NonBlockingTypeProcessorCoreTest::GetNumModelThreadUpdateResponses()
    326     const {
    327   return mock_processor_->GetNumUpdateResponses();
    328 }
    329 
    330 UpdateResponseDataList
    331 NonBlockingTypeProcessorCoreTest::GetNthModelThreadUpdateResponse(
    332     size_t n) const {
    333   DCHECK_LT(n, GetNumModelThreadUpdateResponses());
    334   return mock_processor_->GetNthUpdateResponse(n);
    335 }
    336 
    337 DataTypeState NonBlockingTypeProcessorCoreTest::GetNthModelThreadUpdateState(
    338     size_t n) const {
    339   DCHECK_LT(n, GetNumModelThreadUpdateResponses());
    340   return mock_processor_->GetNthTypeStateReceivedInUpdateResponse(n);
    341 }
    342 
    343 bool NonBlockingTypeProcessorCoreTest::HasUpdateResponseOnModelThread(
    344     const std::string& tag) const {
    345   const std::string tag_hash = GenerateTagHash(tag);
    346   return mock_processor_->HasUpdateResponse(tag_hash);
    347 }
    348 
    349 UpdateResponseData
    350 NonBlockingTypeProcessorCoreTest::GetUpdateResponseOnModelThread(
    351     const std::string& tag) const {
    352   const std::string tag_hash = GenerateTagHash(tag);
    353   return mock_processor_->GetUpdateResponse(tag_hash);
    354 }
    355 
    356 size_t NonBlockingTypeProcessorCoreTest::GetNumModelThreadCommitResponses()
    357     const {
    358   return mock_processor_->GetNumCommitResponses();
    359 }
    360 
    361 CommitResponseDataList
    362 NonBlockingTypeProcessorCoreTest::GetNthModelThreadCommitResponse(
    363     size_t n) const {
    364   DCHECK_LT(n, GetNumModelThreadCommitResponses());
    365   return mock_processor_->GetNthCommitResponse(n);
    366 }
    367 
    368 DataTypeState NonBlockingTypeProcessorCoreTest::GetNthModelThreadCommitState(
    369     size_t n) const {
    370   DCHECK_LT(n, GetNumModelThreadCommitResponses());
    371   return mock_processor_->GetNthTypeStateReceivedInCommitResponse(n);
    372 }
    373 
    374 bool NonBlockingTypeProcessorCoreTest::HasCommitResponseOnModelThread(
    375     const std::string& tag) const {
    376   const std::string tag_hash = GenerateTagHash(tag);
    377   return mock_processor_->HasCommitResponse(tag_hash);
    378 }
    379 
    380 CommitResponseData
    381 NonBlockingTypeProcessorCoreTest::GetCommitResponseOnModelThread(
    382     const std::string& tag) const {
    383   DCHECK(HasCommitResponseOnModelThread(tag));
    384   const std::string tag_hash = GenerateTagHash(tag);
    385   return mock_processor_->GetCommitResponse(tag_hash);
    386 }
    387 
    388 std::string NonBlockingTypeProcessorCoreTest::GenerateTagHash(
    389     const std::string& tag) {
    390   const std::string& client_tag_hash =
    391       syncable::GenerateSyncableHash(kModelType, tag);
    392   return client_tag_hash;
    393 }
    394 
    395 sync_pb::EntitySpecifics NonBlockingTypeProcessorCoreTest::GenerateSpecifics(
    396     const std::string& tag,
    397     const std::string& value) {
    398   sync_pb::EntitySpecifics specifics;
    399   specifics.mutable_preference()->set_name(tag);
    400   specifics.mutable_preference()->set_value(value);
    401   return specifics;
    402 }
    403 
    404 // Requests a commit and verifies the messages sent to the client and server as
    405 // a result.
    406 //
    407 // This test performs sanity checks on most of the fields in these messages.
    408 // For the most part this is checking that the test code behaves as expected
    409 // and the |core_| doesn't mess up its simple task of moving around these
    410 // values.  It makes sense to have one or two tests that are this thorough, but
    411 // we shouldn't be this verbose in all tests.
    412 TEST_F(NonBlockingTypeProcessorCoreTest, SimpleCommit) {
    413   NormalInitialize();
    414 
    415   EXPECT_FALSE(WillCommit());
    416   EXPECT_EQ(0U, GetNumCommitMessagesOnServer());
    417   EXPECT_EQ(0U, GetNumModelThreadCommitResponses());
    418 
    419   CommitRequest("tag1", "value1");
    420 
    421   ASSERT_TRUE(WillCommit());
    422   DoSuccessfulCommit();
    423 
    424   const std::string& client_tag_hash = GenerateTagHash("tag1");
    425 
    426   // Exhaustively verify the SyncEntity sent in the commit message.
    427   ASSERT_EQ(1U, GetNumCommitMessagesOnServer());
    428   EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size());
    429   ASSERT_TRUE(HasCommitEntityOnServer("tag1"));
    430   const sync_pb::SyncEntity& entity = GetLatestCommitEntityOnServer("tag1");
    431   EXPECT_FALSE(entity.id_string().empty());
    432   EXPECT_EQ(kTypeParentId, entity.parent_id_string());
    433   EXPECT_EQ(kUncommittedVersion, entity.version());
    434   EXPECT_NE(0, entity.mtime());
    435   EXPECT_NE(0, entity.ctime());
    436   EXPECT_FALSE(entity.name().empty());
    437   EXPECT_EQ(client_tag_hash, entity.client_defined_unique_tag());
    438   EXPECT_EQ("tag1", entity.specifics().preference().name());
    439   EXPECT_FALSE(entity.deleted());
    440   EXPECT_EQ("value1", entity.specifics().preference().value());
    441 
    442   // Exhaustively verify the commit response returned to the model thread.
    443   ASSERT_EQ(1U, GetNumModelThreadCommitResponses());
    444   EXPECT_EQ(1U, GetNthModelThreadCommitResponse(0).size());
    445   ASSERT_TRUE(HasCommitResponseOnModelThread("tag1"));
    446   const CommitResponseData& commit_response =
    447       GetCommitResponseOnModelThread("tag1");
    448 
    449   // The ID changes in a commit response to initial commit.
    450   EXPECT_FALSE(commit_response.id.empty());
    451   EXPECT_NE(entity.id_string(), commit_response.id);
    452 
    453   EXPECT_EQ(client_tag_hash, commit_response.client_tag_hash);
    454   EXPECT_LT(0, commit_response.response_version);
    455 }
    456 
    457 TEST_F(NonBlockingTypeProcessorCoreTest, SimpleDelete) {
    458   NormalInitialize();
    459 
    460   // We can't delete an entity that was never committed.
    461   // Step 1 is to create and commit a new entity.
    462   CommitRequest("tag1", "value1");
    463   ASSERT_TRUE(WillCommit());
    464   DoSuccessfulCommit();
    465 
    466   ASSERT_TRUE(HasCommitResponseOnModelThread("tag1"));
    467   const CommitResponseData& initial_commit_response =
    468       GetCommitResponseOnModelThread("tag1");
    469   int64 base_version = initial_commit_response.response_version;
    470 
    471   // Now that we have an entity, we can delete it.
    472   DeleteRequest("tag1");
    473   ASSERT_TRUE(WillCommit());
    474   DoSuccessfulCommit();
    475 
    476   // Verify the SyncEntity sent in the commit message.
    477   ASSERT_EQ(2U, GetNumCommitMessagesOnServer());
    478   EXPECT_EQ(1, GetNthCommitMessageOnServer(1).commit().entries_size());
    479   ASSERT_TRUE(HasCommitEntityOnServer("tag1"));
    480   const sync_pb::SyncEntity& entity = GetLatestCommitEntityOnServer("tag1");
    481   EXPECT_FALSE(entity.id_string().empty());
    482   EXPECT_EQ(GenerateTagHash("tag1"), entity.client_defined_unique_tag());
    483   EXPECT_EQ(base_version, entity.version());
    484   EXPECT_TRUE(entity.deleted());
    485 
    486   // Deletions should contain enough specifics to identify the type.
    487   EXPECT_TRUE(entity.has_specifics());
    488   EXPECT_EQ(kModelType, GetModelTypeFromSpecifics(entity.specifics()));
    489 
    490   // Verify the commit response returned to the model thread.
    491   ASSERT_EQ(2U, GetNumModelThreadCommitResponses());
    492   EXPECT_EQ(1U, GetNthModelThreadCommitResponse(1).size());
    493   ASSERT_TRUE(HasCommitResponseOnModelThread("tag1"));
    494   const CommitResponseData& commit_response =
    495       GetCommitResponseOnModelThread("tag1");
    496 
    497   EXPECT_EQ(entity.id_string(), commit_response.id);
    498   EXPECT_EQ(entity.client_defined_unique_tag(),
    499             commit_response.client_tag_hash);
    500   EXPECT_EQ(entity.version(), commit_response.response_version);
    501 }
    502 
    503 // The server doesn't like it when we try to delete an entity it's never heard
    504 // of before.  This test helps ensure we avoid that scenario.
    505 TEST_F(NonBlockingTypeProcessorCoreTest, NoDeleteUncommitted) {
    506   NormalInitialize();
    507 
    508   // Request the commit of a new, never-before-seen item.
    509   CommitRequest("tag1", "value1");
    510   EXPECT_TRUE(WillCommit());
    511 
    512   // Request a deletion of that item before we've had a chance to commit it.
    513   DeleteRequest("tag1");
    514   EXPECT_FALSE(WillCommit());
    515 }
    516 
    517 // Verifies the sending of an "initial sync done" signal.
    518 TEST_F(NonBlockingTypeProcessorCoreTest, SendInitialSyncDone) {
    519   FirstInitialize();  // Initialize with no saved sync state.
    520   EXPECT_EQ(0U, GetNumModelThreadUpdateResponses());
    521 
    522   // Receive an update response that contains only the type root node.
    523   TriggerTypeRootUpdateFromServer();
    524 
    525   // Two updates:
    526   // - One triggered by process updates to forward the type root ID.
    527   // - One triggered by apply updates, which the core interprets to mean
    528   //   "initial sync done".  This triggers a model thread update, too.
    529   EXPECT_EQ(2U, GetNumModelThreadUpdateResponses());
    530 
    531   // The type root and initial sync done updates both contain no entities.
    532   EXPECT_EQ(0U, GetNthModelThreadUpdateResponse(0).size());
    533   EXPECT_EQ(0U, GetNthModelThreadUpdateResponse(1).size());
    534 
    535   const DataTypeState& state = GetNthModelThreadUpdateState(1);
    536   EXPECT_FALSE(state.progress_marker.token().empty());
    537   EXPECT_FALSE(state.type_root_id.empty());
    538   EXPECT_TRUE(state.initial_sync_done);
    539 }
    540 
    541 // Commit two new entities in two separate commit messages.
    542 TEST_F(NonBlockingTypeProcessorCoreTest, TwoNewItemsCommittedSeparately) {
    543   NormalInitialize();
    544 
    545   // Commit the first of two entities.
    546   CommitRequest("tag1", "value1");
    547   ASSERT_TRUE(WillCommit());
    548   DoSuccessfulCommit();
    549   ASSERT_EQ(1U, GetNumCommitMessagesOnServer());
    550   EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size());
    551   ASSERT_TRUE(HasCommitEntityOnServer("tag1"));
    552   const sync_pb::SyncEntity& tag1_entity =
    553       GetLatestCommitEntityOnServer("tag1");
    554 
    555   // Commit the second of two entities.
    556   CommitRequest("tag2", "value2");
    557   ASSERT_TRUE(WillCommit());
    558   DoSuccessfulCommit();
    559   ASSERT_EQ(2U, GetNumCommitMessagesOnServer());
    560   EXPECT_EQ(1, GetNthCommitMessageOnServer(1).commit().entries_size());
    561   ASSERT_TRUE(HasCommitEntityOnServer("tag2"));
    562   const sync_pb::SyncEntity& tag2_entity =
    563       GetLatestCommitEntityOnServer("tag2");
    564 
    565   EXPECT_FALSE(WillCommit());
    566 
    567   // The IDs assigned by the |core_| should be unique.
    568   EXPECT_NE(tag1_entity.id_string(), tag2_entity.id_string());
    569 
    570   // Check that the committed specifics values are sane.
    571   EXPECT_EQ(tag1_entity.specifics().preference().value(), "value1");
    572   EXPECT_EQ(tag2_entity.specifics().preference().value(), "value2");
    573 
    574   // There should have been two separate commit responses sent to the model
    575   // thread.  They should be uninteresting, so we don't bother inspecting them.
    576   EXPECT_EQ(2U, GetNumModelThreadCommitResponses());
    577 }
    578 
    579 TEST_F(NonBlockingTypeProcessorCoreTest, ReceiveUpdates) {
    580   NormalInitialize();
    581 
    582   const std::string& tag_hash = GenerateTagHash("tag1");
    583 
    584   TriggerUpdateFromServer(10, "tag1", "value1");
    585 
    586   ASSERT_EQ(1U, GetNumModelThreadUpdateResponses());
    587   UpdateResponseDataList updates_list = GetNthModelThreadUpdateResponse(0);
    588   ASSERT_EQ(1U, updates_list.size());
    589 
    590   ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1"));
    591   UpdateResponseData update = GetUpdateResponseOnModelThread("tag1");
    592 
    593   EXPECT_FALSE(update.id.empty());
    594   EXPECT_EQ(tag_hash, update.client_tag_hash);
    595   EXPECT_LT(0, update.response_version);
    596   EXPECT_FALSE(update.ctime.is_null());
    597   EXPECT_FALSE(update.mtime.is_null());
    598   EXPECT_FALSE(update.non_unique_name.empty());
    599   EXPECT_FALSE(update.deleted);
    600   EXPECT_EQ("tag1", update.specifics.preference().name());
    601   EXPECT_EQ("value1", update.specifics.preference().value());
    602 }
    603 
    604 }  // namespace syncer
    605