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