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.h"
      6 
      7 #include "sync/engine/non_blocking_sync_common.h"
      8 #include "sync/engine/non_blocking_type_processor_core_interface.h"
      9 #include "sync/internal_api/public/base/model_type.h"
     10 #include "sync/internal_api/public/sync_core_proxy.h"
     11 #include "sync/protocol/sync.pb.h"
     12 #include "sync/syncable/syncable_util.h"
     13 #include "sync/test/engine/injectable_sync_core_proxy.h"
     14 #include "sync/test/engine/mock_non_blocking_type_processor_core.h"
     15 #include "testing/gtest/include/gtest/gtest.h"
     16 
     17 namespace syncer {
     18 
     19 static const ModelType kModelType = PREFERENCES;
     20 
     21 // Tests the sync engine parts of NonBlockingTypeProcessor.
     22 //
     23 // The NonBlockingTypeProcessor contains a non-trivial amount of code dedicated
     24 // to turning the sync engine on and off again.  That code is fairly well
     25 // tested in the NonBlockingDataTypeController unit tests and it doesn't need
     26 // to be re-tested here.
     27 //
     28 // These tests skip past initialization and focus on steady state sync engine
     29 // behvior.  This is where we test how the processor responds to the model's
     30 // requests to make changes to its data, the messages incoming fro the sync
     31 // server, and what happens when the two conflict.
     32 //
     33 // Inputs:
     34 // - Initial state from permanent storage. (TODO)
     35 // - Create, update or delete requests from the model.
     36 // - Update responses and commit responses from the server.
     37 //
     38 // Outputs:
     39 // - Writes to permanent storage. (TODO)
     40 // - Callbacks into the model. (TODO)
     41 // - Requests to the sync thread.  Tested with MockNonBlockingTypeProcessorCore.
     42 class NonBlockingTypeProcessorTest : public ::testing::Test {
     43  public:
     44   NonBlockingTypeProcessorTest();
     45   virtual ~NonBlockingTypeProcessorTest();
     46 
     47   // Initialize with no local state.  The processor will be unable to commit
     48   // until it receives notification that initial sync has completed.
     49   void FirstTimeInitialize();
     50 
     51   // Initialize to a "ready-to-commit" state.
     52   void InitializeToReadyState();
     53 
     54   // Local data modification.  Emulates signals from the model thread.
     55   void WriteItem(const std::string& tag, const std::string& value);
     56   void DeleteItem(const std::string& tag);
     57 
     58   // Emulates an "initial sync done" message from the
     59   // NonBlockingTypeProcessorCore.
     60   void OnInitialSyncDone();
     61 
     62   // Emulate updates from the server.
     63   // This harness has some functionality to help emulate server behavior.
     64   // See the definitions of these methods for more information.
     65   void UpdateFromServer(int64 version_offset,
     66                         const std::string& tag,
     67                         const std::string& value);
     68   void TombstoneFromServer(int64 version_offset, const std::string& tag);
     69 
     70   // Read emitted commit requests as batches.
     71   size_t GetNumCommitRequestLists();
     72   CommitRequestDataList GetNthCommitRequestList(size_t n);
     73 
     74   // Read emitted commit requests by tag, most recent only.
     75   bool HasCommitRequestForTag(const std::string& tag);
     76   CommitRequestData GetLatestCommitRequestForTag(const std::string& tag);
     77 
     78   // Sends the processor a successful commit response.
     79   void SuccessfulCommitResponse(const CommitRequestData& request_data);
     80 
     81  private:
     82   static std::string GenerateTagHash(const std::string& tag);
     83   static sync_pb::EntitySpecifics GenerateSpecifics(const std::string& tag,
     84                                                     const std::string& value);
     85 
     86   int64 GetServerVersion(const std::string& tag);
     87   void SetServerVersion(const std::string& tag, int64 version);
     88 
     89   MockNonBlockingTypeProcessorCore* mock_processor_core_;
     90   scoped_ptr<InjectableSyncCoreProxy> injectable_sync_core_proxy_;
     91   scoped_ptr<NonBlockingTypeProcessor> processor_;
     92 
     93   DataTypeState data_type_state_;
     94 };
     95 
     96 NonBlockingTypeProcessorTest::NonBlockingTypeProcessorTest()
     97     : mock_processor_core_(new MockNonBlockingTypeProcessorCore()),
     98       injectable_sync_core_proxy_(
     99           new InjectableSyncCoreProxy(mock_processor_core_)),
    100       processor_(new NonBlockingTypeProcessor(kModelType)) {
    101 }
    102 
    103 NonBlockingTypeProcessorTest::~NonBlockingTypeProcessorTest() {
    104 }
    105 
    106 void NonBlockingTypeProcessorTest::FirstTimeInitialize() {
    107   processor_->Enable(injectable_sync_core_proxy_->Clone());
    108 }
    109 
    110 void NonBlockingTypeProcessorTest::InitializeToReadyState() {
    111   // TODO(rlarocque): This should be updated to inject on-disk state.
    112   // At the time this code was written, there was no support for on-disk
    113   // state so this was the only way to inject a data_type_state into
    114   // the |processor_|.
    115   FirstTimeInitialize();
    116   OnInitialSyncDone();
    117 }
    118 
    119 void NonBlockingTypeProcessorTest::WriteItem(const std::string& tag,
    120                                              const std::string& value) {
    121   const std::string tag_hash = GenerateTagHash(tag);
    122   processor_->Put(tag, GenerateSpecifics(tag, value));
    123 }
    124 
    125 void NonBlockingTypeProcessorTest::DeleteItem(const std::string& tag) {
    126   processor_->Delete(tag);
    127 }
    128 
    129 void NonBlockingTypeProcessorTest::OnInitialSyncDone() {
    130   data_type_state_.initial_sync_done = true;
    131   UpdateResponseDataList empty_update_list;
    132 
    133   processor_->OnUpdateReceived(data_type_state_, empty_update_list);
    134 }
    135 
    136 void NonBlockingTypeProcessorTest::UpdateFromServer(int64 version_offset,
    137                                                     const std::string& tag,
    138                                                     const std::string& value) {
    139   const std::string tag_hash = GenerateTagHash(tag);
    140   UpdateResponseData data = mock_processor_core_->UpdateFromServer(
    141       version_offset, tag_hash, GenerateSpecifics(tag, value));
    142 
    143   UpdateResponseDataList list;
    144   list.push_back(data);
    145   processor_->OnUpdateReceived(data_type_state_, list);
    146 }
    147 
    148 void NonBlockingTypeProcessorTest::TombstoneFromServer(int64 version_offset,
    149                                                        const std::string& tag) {
    150   // Overwrite the existing server version if this is the new highest version.
    151   std::string tag_hash = GenerateTagHash(tag);
    152 
    153   UpdateResponseData data =
    154       mock_processor_core_->TombstoneFromServer(version_offset, tag_hash);
    155 
    156   UpdateResponseDataList list;
    157   list.push_back(data);
    158   processor_->OnUpdateReceived(data_type_state_, list);
    159 }
    160 
    161 void NonBlockingTypeProcessorTest::SuccessfulCommitResponse(
    162     const CommitRequestData& request_data) {
    163   CommitResponseDataList list;
    164   list.push_back(mock_processor_core_->SuccessfulCommitResponse(request_data));
    165   processor_->OnCommitCompletion(data_type_state_, list);
    166 }
    167 
    168 std::string NonBlockingTypeProcessorTest::GenerateTagHash(
    169     const std::string& tag) {
    170   return syncable::GenerateSyncableHash(kModelType, tag);
    171 }
    172 
    173 sync_pb::EntitySpecifics NonBlockingTypeProcessorTest::GenerateSpecifics(
    174     const std::string& tag,
    175     const std::string& value) {
    176   sync_pb::EntitySpecifics specifics;
    177   specifics.mutable_preference()->set_name(tag);
    178   specifics.mutable_preference()->set_value(value);
    179   return specifics;
    180 }
    181 
    182 size_t NonBlockingTypeProcessorTest::GetNumCommitRequestLists() {
    183   return mock_processor_core_->GetNumCommitRequestLists();
    184 }
    185 
    186 CommitRequestDataList NonBlockingTypeProcessorTest::GetNthCommitRequestList(
    187     size_t n) {
    188   return mock_processor_core_->GetNthCommitRequestList(n);
    189 }
    190 
    191 bool NonBlockingTypeProcessorTest::HasCommitRequestForTag(
    192     const std::string& tag) {
    193   const std::string tag_hash = GenerateTagHash(tag);
    194   return mock_processor_core_->HasCommitRequestForTagHash(tag_hash);
    195 }
    196 
    197 CommitRequestData NonBlockingTypeProcessorTest::GetLatestCommitRequestForTag(
    198     const std::string& tag) {
    199   const std::string tag_hash = GenerateTagHash(tag);
    200   return mock_processor_core_->GetLatestCommitRequestForTagHash(tag_hash);
    201 }
    202 
    203 // Creates a new item locally.
    204 // Thoroughly tests the data generated by a local item creation.
    205 TEST_F(NonBlockingTypeProcessorTest, CreateLocalItem) {
    206   InitializeToReadyState();
    207   EXPECT_EQ(0U, GetNumCommitRequestLists());
    208 
    209   WriteItem("tag1", "value1");
    210 
    211   // Verify the commit request this operation has triggered.
    212   EXPECT_EQ(1U, GetNumCommitRequestLists());
    213   ASSERT_TRUE(HasCommitRequestForTag("tag1"));
    214   const CommitRequestData& tag1_data = GetLatestCommitRequestForTag("tag1");
    215 
    216   EXPECT_TRUE(tag1_data.id.empty());
    217   EXPECT_EQ(kUncommittedVersion, tag1_data.base_version);
    218   EXPECT_FALSE(tag1_data.ctime.is_null());
    219   EXPECT_FALSE(tag1_data.mtime.is_null());
    220   EXPECT_EQ("tag1", tag1_data.non_unique_name);
    221   EXPECT_FALSE(tag1_data.deleted);
    222   EXPECT_EQ("tag1", tag1_data.specifics.preference().name());
    223   EXPECT_EQ("value1", tag1_data.specifics.preference().value());
    224 }
    225 
    226 // Creates a new local item then modifies it.
    227 // Thoroughly tests data generated by modification of server-unknown item.
    228 TEST_F(NonBlockingTypeProcessorTest, CreateAndModifyLocalItem) {
    229   InitializeToReadyState();
    230   EXPECT_EQ(0U, GetNumCommitRequestLists());
    231 
    232   WriteItem("tag1", "value1");
    233   EXPECT_EQ(1U, GetNumCommitRequestLists());
    234   ASSERT_TRUE(HasCommitRequestForTag("tag1"));
    235   const CommitRequestData& tag1_v1_data = GetLatestCommitRequestForTag("tag1");
    236 
    237   WriteItem("tag1", "value2");
    238   EXPECT_EQ(2U, GetNumCommitRequestLists());
    239 
    240   ASSERT_TRUE(HasCommitRequestForTag("tag1"));
    241   const CommitRequestData& tag1_v2_data = GetLatestCommitRequestForTag("tag1");
    242 
    243   // Test some of the relations between old and new commit requests.
    244   EXPECT_EQ(tag1_v1_data.specifics.preference().value(), "value1");
    245   EXPECT_GT(tag1_v2_data.sequence_number, tag1_v1_data.sequence_number);
    246 
    247   // Perform a thorough examination of the update-generated request.
    248   EXPECT_TRUE(tag1_v2_data.id.empty());
    249   EXPECT_EQ(kUncommittedVersion, tag1_v2_data.base_version);
    250   EXPECT_FALSE(tag1_v2_data.ctime.is_null());
    251   EXPECT_FALSE(tag1_v2_data.mtime.is_null());
    252   EXPECT_EQ("tag1", tag1_v2_data.non_unique_name);
    253   EXPECT_FALSE(tag1_v2_data.deleted);
    254   EXPECT_EQ("tag1", tag1_v2_data.specifics.preference().name());
    255   EXPECT_EQ("value2", tag1_v2_data.specifics.preference().value());
    256 }
    257 
    258 // Deletes an item we've never seen before.
    259 // Should have no effect and not crash.
    260 TEST_F(NonBlockingTypeProcessorTest, DeleteUnknown) {
    261   InitializeToReadyState();
    262 
    263   DeleteItem("tag1");
    264   EXPECT_EQ(0U, GetNumCommitRequestLists());
    265 }
    266 
    267 // Creates an item locally then deletes it.
    268 //
    269 // In this test, no commit responses are received, so the deleted item is
    270 // server-unknown as far as the model thread is concerned.  That behavior
    271 // is race-dependent; other tests are used to test other races.
    272 TEST_F(NonBlockingTypeProcessorTest, DeleteServerUnknown) {
    273   InitializeToReadyState();
    274 
    275   WriteItem("tag1", "value1");
    276   EXPECT_EQ(1U, GetNumCommitRequestLists());
    277   ASSERT_TRUE(HasCommitRequestForTag("tag1"));
    278   const CommitRequestData& tag1_v1_data = GetLatestCommitRequestForTag("tag1");
    279 
    280   DeleteItem("tag1");
    281   EXPECT_EQ(2U, GetNumCommitRequestLists());
    282   ASSERT_TRUE(HasCommitRequestForTag("tag1"));
    283   const CommitRequestData& tag1_v2_data = GetLatestCommitRequestForTag("tag1");
    284 
    285   EXPECT_GT(tag1_v2_data.sequence_number, tag1_v1_data.sequence_number);
    286 
    287   EXPECT_TRUE(tag1_v2_data.id.empty());
    288   EXPECT_EQ(kUncommittedVersion, tag1_v2_data.base_version);
    289   EXPECT_TRUE(tag1_v2_data.deleted);
    290 }
    291 
    292 // Creates an item locally then deletes it.
    293 //
    294 // The item is created locally then enqueued for commit.  The sync thread
    295 // successfully commits it, but, before the commit response is picked up
    296 // by the model thread, the item is deleted by the model thread.
    297 TEST_F(NonBlockingTypeProcessorTest, DeleteServerUnknown_RacyCommitResponse) {
    298   InitializeToReadyState();
    299 
    300   WriteItem("tag1", "value1");
    301   EXPECT_EQ(1U, GetNumCommitRequestLists());
    302   ASSERT_TRUE(HasCommitRequestForTag("tag1"));
    303   const CommitRequestData& tag1_v1_data = GetLatestCommitRequestForTag("tag1");
    304 
    305   DeleteItem("tag1");
    306   EXPECT_EQ(2U, GetNumCommitRequestLists());
    307   ASSERT_TRUE(HasCommitRequestForTag("tag1"));
    308 
    309   // This commit happened while the deletion was in progress, but the commit
    310   // response didn't arrive on our thread until after the delete was issued to
    311   // the sync thread.  It will update some metadata, but won't do much else.
    312   SuccessfulCommitResponse(tag1_v1_data);
    313 
    314   // TODO(rlarocque): Verify the state of the item is correct once we get
    315   // storage hooked up in these tests.  For example, verify the item is still
    316   // marked as deleted.
    317 }
    318 
    319 // Creates two different sync items.
    320 // Verifies that the second has no effect on the first.
    321 TEST_F(NonBlockingTypeProcessorTest, TwoIndependentItems) {
    322   InitializeToReadyState();
    323   EXPECT_EQ(0U, GetNumCommitRequestLists());
    324 
    325   WriteItem("tag1", "value1");
    326 
    327   // There should be one commit request for this item only.
    328   ASSERT_EQ(1U, GetNumCommitRequestLists());
    329   EXPECT_EQ(1U, GetNthCommitRequestList(0).size());
    330   ASSERT_TRUE(HasCommitRequestForTag("tag1"));
    331 
    332   WriteItem("tag2", "value2");
    333 
    334   // The second write should trigger another single-item commit request.
    335   ASSERT_EQ(2U, GetNumCommitRequestLists());
    336   EXPECT_EQ(1U, GetNthCommitRequestList(1).size());
    337   ASSERT_TRUE(HasCommitRequestForTag("tag2"));
    338 }
    339 
    340 // Starts the processor with no local state.
    341 // Verify that it waits until initial sync is complete before requesting
    342 // commits.
    343 TEST_F(NonBlockingTypeProcessorTest, NoCommitsUntilInitialSyncDone) {
    344   FirstTimeInitialize();
    345 
    346   WriteItem("tag1", "value1");
    347   EXPECT_EQ(0U, GetNumCommitRequestLists());
    348 
    349   OnInitialSyncDone();
    350   EXPECT_EQ(1U, GetNumCommitRequestLists());
    351   EXPECT_TRUE(HasCommitRequestForTag("tag1"));
    352 }
    353 
    354 // TODO(rlarocque): Add more testing of non_unique_name fields.
    355 
    356 }  // namespace syncer
    357