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