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
19static const std::string kTypeParentId = "PrefsRootNodeID";
20static const syncer::ModelType kModelType = syncer::PREFERENCES;
21
22namespace 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.
50class 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
154NonBlockingTypeProcessorCoreTest::NonBlockingTypeProcessorCoreTest()
155    : mock_processor_(NULL), mock_server_(kModelType) {
156}
157
158NonBlockingTypeProcessorCoreTest::~NonBlockingTypeProcessorCoreTest() {
159}
160
161void 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
170void 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
183void 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
195void 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
205void 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
213void 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
227void 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
245void 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
262void NonBlockingTypeProcessorCoreTest::SetModelThreadIsSynchronous(
263    bool is_synchronous) {
264  mock_processor_->SetSynchronousExecution(is_synchronous);
265}
266
267void NonBlockingTypeProcessorCoreTest::PumpModelThread() {
268  mock_processor_->RunQueuedTasks();
269}
270
271bool 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.
286void 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
301size_t NonBlockingTypeProcessorCoreTest::GetNumCommitMessagesOnServer() const {
302  return mock_server_.GetNumCommitMessages();
303}
304
305sync_pb::ClientToServerMessage
306NonBlockingTypeProcessorCoreTest::GetNthCommitMessageOnServer(size_t n) const {
307  DCHECK_LT(n, GetNumCommitMessagesOnServer());
308  return mock_server_.GetNthCommitMessage(n);
309}
310
311bool 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
317sync_pb::SyncEntity
318NonBlockingTypeProcessorCoreTest::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
325size_t NonBlockingTypeProcessorCoreTest::GetNumModelThreadUpdateResponses()
326    const {
327  return mock_processor_->GetNumUpdateResponses();
328}
329
330UpdateResponseDataList
331NonBlockingTypeProcessorCoreTest::GetNthModelThreadUpdateResponse(
332    size_t n) const {
333  DCHECK_LT(n, GetNumModelThreadUpdateResponses());
334  return mock_processor_->GetNthUpdateResponse(n);
335}
336
337DataTypeState NonBlockingTypeProcessorCoreTest::GetNthModelThreadUpdateState(
338    size_t n) const {
339  DCHECK_LT(n, GetNumModelThreadUpdateResponses());
340  return mock_processor_->GetNthTypeStateReceivedInUpdateResponse(n);
341}
342
343bool 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
349UpdateResponseData
350NonBlockingTypeProcessorCoreTest::GetUpdateResponseOnModelThread(
351    const std::string& tag) const {
352  const std::string tag_hash = GenerateTagHash(tag);
353  return mock_processor_->GetUpdateResponse(tag_hash);
354}
355
356size_t NonBlockingTypeProcessorCoreTest::GetNumModelThreadCommitResponses()
357    const {
358  return mock_processor_->GetNumCommitResponses();
359}
360
361CommitResponseDataList
362NonBlockingTypeProcessorCoreTest::GetNthModelThreadCommitResponse(
363    size_t n) const {
364  DCHECK_LT(n, GetNumModelThreadCommitResponses());
365  return mock_processor_->GetNthCommitResponse(n);
366}
367
368DataTypeState NonBlockingTypeProcessorCoreTest::GetNthModelThreadCommitState(
369    size_t n) const {
370  DCHECK_LT(n, GetNumModelThreadCommitResponses());
371  return mock_processor_->GetNthTypeStateReceivedInCommitResponse(n);
372}
373
374bool 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
380CommitResponseData
381NonBlockingTypeProcessorCoreTest::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
388std::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
395sync_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.
412TEST_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
457TEST_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.
505TEST_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.
518TEST_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.
542TEST_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
579TEST_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