Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(809)

Side by Side Diff: sync/engine/apply_updates_command_unittest.cc

Issue 11192071: sync: Merge apply updates and resolve conflicts (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Retry (base files were missing) Created 8 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « sync/engine/apply_updates_command.cc ('k') | sync/engine/conflict_resolver.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 <string>
6
7 #include "base/location.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "base/stringprintf.h"
10 #include "sync/engine/apply_updates_command.h"
11 #include "sync/engine/syncer.h"
12 #include "sync/internal_api/public/test/test_entry_factory.h"
13 #include "sync/protocol/bookmark_specifics.pb.h"
14 #include "sync/protocol/password_specifics.pb.h"
15 #include "sync/syncable/mutable_entry.h"
16 #include "sync/syncable/read_transaction.h"
17 #include "sync/syncable/syncable_id.h"
18 #include "sync/syncable/syncable_util.h"
19 #include "sync/syncable/write_transaction.h"
20 #include "sync/test/engine/fake_model_worker.h"
21 #include "sync/test/engine/syncer_command_test.h"
22 #include "sync/test/engine/test_id_factory.h"
23 #include "sync/test/fake_sync_encryption_handler.h"
24 #include "sync/util/cryptographer.h"
25 #include "testing/gtest/include/gtest/gtest.h"
26
27 namespace syncer {
28
29 using std::string;
30 using syncable::Id;
31 using syncable::MutableEntry;
32 using syncable::UNITTEST;
33 using syncable::WriteTransaction;
34
35 namespace {
36 sync_pb::EntitySpecifics DefaultBookmarkSpecifics() {
37 sync_pb::EntitySpecifics result;
38 AddDefaultFieldValue(BOOKMARKS, &result);
39 return result;
40 }
41 } // namespace
42
43 // A test fixture for tests exercising ApplyUpdatesCommand.
44 class ApplyUpdatesCommandTest : public SyncerCommandTest {
45 public:
46 protected:
47 ApplyUpdatesCommandTest() {}
48 virtual ~ApplyUpdatesCommandTest() {}
49
50 virtual void SetUp() {
51 workers()->clear();
52 mutable_routing_info()->clear();
53 workers()->push_back(
54 make_scoped_refptr(new FakeModelWorker(GROUP_UI)));
55 workers()->push_back(
56 make_scoped_refptr(new FakeModelWorker(GROUP_PASSWORD)));
57 (*mutable_routing_info())[BOOKMARKS] = GROUP_UI;
58 (*mutable_routing_info())[PASSWORDS] = GROUP_PASSWORD;
59 (*mutable_routing_info())[NIGORI] = GROUP_PASSIVE;
60 SyncerCommandTest::SetUp();
61 entry_factory_.reset(new TestEntryFactory(directory()));
62 ExpectNoGroupsToChange(apply_updates_command_);
63 }
64
65 protected:
66 DISALLOW_COPY_AND_ASSIGN(ApplyUpdatesCommandTest);
67
68 ApplyUpdatesCommand apply_updates_command_;
69 scoped_ptr<TestEntryFactory> entry_factory_;
70 };
71
72 TEST_F(ApplyUpdatesCommandTest, Simple) {
73 string root_server_id = syncable::GetNullId().GetServerId();
74 entry_factory_->CreateUnappliedNewItemWithParent("parent",
75 DefaultBookmarkSpecifics(),
76 root_server_id);
77 entry_factory_->CreateUnappliedNewItemWithParent("child",
78 DefaultBookmarkSpecifics(),
79 "parent");
80
81 ExpectGroupToChange(apply_updates_command_, GROUP_UI);
82 apply_updates_command_.ExecuteImpl(session());
83
84 const sessions::StatusController& status = session()->status_controller();
85 EXPECT_EQ(0, status.num_simple_conflicts())
86 << "Simple update shouldn't result in conflicts";
87 EXPECT_EQ(0, status.num_encryption_conflicts())
88 << "Simple update shouldn't result in conflicts";
89 EXPECT_EQ(0, status.num_hierarchy_conflicts())
90 << "Simple update shouldn't result in conflicts";
91 EXPECT_EQ(2, status.num_updates_applied())
92 << "All items should have been successfully applied";
93 }
94
95 TEST_F(ApplyUpdatesCommandTest, UpdateWithChildrenBeforeParents) {
96 // Set a bunch of updates which are difficult to apply in the order
97 // they're received due to dependencies on other unseen items.
98 string root_server_id = syncable::GetNullId().GetServerId();
99 entry_factory_->CreateUnappliedNewItemWithParent(
100 "a_child_created_first", DefaultBookmarkSpecifics(), "parent");
101 entry_factory_->CreateUnappliedNewItemWithParent(
102 "x_child_created_first", DefaultBookmarkSpecifics(), "parent");
103 entry_factory_->CreateUnappliedNewItemWithParent(
104 "parent", DefaultBookmarkSpecifics(), root_server_id);
105 entry_factory_->CreateUnappliedNewItemWithParent(
106 "a_child_created_second", DefaultBookmarkSpecifics(), "parent");
107 entry_factory_->CreateUnappliedNewItemWithParent(
108 "x_child_created_second", DefaultBookmarkSpecifics(), "parent");
109
110 ExpectGroupToChange(apply_updates_command_, GROUP_UI);
111 apply_updates_command_.ExecuteImpl(session());
112
113 const sessions::StatusController& status = session()->status_controller();
114 EXPECT_EQ(0, status.num_simple_conflicts())
115 << "Simple update shouldn't result in conflicts, even if out-of-order";
116 EXPECT_EQ(5, status.num_updates_applied())
117 << "All updates should have been successfully applied";
118 }
119
120 // Runs the ApplyUpdatesCommand on an item that has both local and remote
121 // modifications (IS_UNSYNCED and IS_UNAPPLIED_UPDATE). We expect the command
122 // to detect that this update can't be applied because it is in a CONFLICT
123 // state.
124 TEST_F(ApplyUpdatesCommandTest, SimpleConflict) {
125 entry_factory_->CreateUnappliedAndUnsyncedItem("item", BOOKMARKS);
126
127 ExpectGroupToChange(apply_updates_command_, GROUP_UI);
128 apply_updates_command_.ExecuteImpl(session());
129
130 const sessions::StatusController& status = session()->status_controller();
131 EXPECT_EQ(1, status.num_simple_conflicts())
132 << "Unsynced and unapplied item should be a simple conflict";
133 }
134
135 // Runs the ApplyUpdatesCommand on an item that has both local and remote
136 // modifications *and* the remote modification cannot be applied without
137 // violating the tree constraints. We expect the command to detect that this
138 // update can't be applied and that this situation can't be resolved with the
139 // simple conflict processing logic; it is in a CONFLICT_HIERARCHY state.
140 TEST_F(ApplyUpdatesCommandTest, HierarchyAndSimpleConflict) {
141 // Create a simply-conflicting item. It will start with valid parent ids.
142 int64 handle = entry_factory_->CreateUnappliedAndUnsyncedItem(
143 "orphaned_by_server", BOOKMARKS);
144 {
145 // Manually set the SERVER_PARENT_ID to bad value.
146 // A bad parent indicates a hierarchy conflict.
147 WriteTransaction trans(FROM_HERE, UNITTEST, directory());
148 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle);
149 ASSERT_TRUE(entry.good());
150
151 entry.Put(syncable::SERVER_PARENT_ID,
152 TestIdFactory::MakeServer("bogus_parent"));
153 }
154
155 ExpectGroupToChange(apply_updates_command_, GROUP_UI);
156 apply_updates_command_.ExecuteImpl(session());
157
158 const sessions::StatusController& status = session()->status_controller();
159
160 // An update that is both a simple conflict and a hierarchy conflict should be
161 // treated as a hierarchy conflict.
162 EXPECT_EQ(1, status.num_hierarchy_conflicts());
163 EXPECT_EQ(0, status.num_simple_conflicts());
164 }
165
166
167 // Runs the ApplyUpdatesCommand on an item with remote modifications that would
168 // create a directory loop if the update were applied. We expect the command to
169 // detect that this update can't be applied because it is in a
170 // CONFLICT_HIERARCHY state.
171 TEST_F(ApplyUpdatesCommandTest, HierarchyConflictDirectoryLoop) {
172 // Item 'X' locally has parent of 'root'. Server is updating it to have
173 // parent of 'Y'.
174 {
175 // Create it as a child of root node.
176 int64 handle = entry_factory_->CreateSyncedItem("X", BOOKMARKS, true);
177
178 WriteTransaction trans(FROM_HERE, UNITTEST, directory());
179 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle);
180 ASSERT_TRUE(entry.good());
181
182 // Re-parent from root to "Y"
183 entry.Put(syncable::SERVER_VERSION, entry_factory_->GetNextRevision());
184 entry.Put(syncable::IS_UNAPPLIED_UPDATE, true);
185 entry.Put(syncable::SERVER_PARENT_ID, TestIdFactory::MakeServer("Y"));
186 }
187
188 // Item 'Y' is child of 'X'.
189 entry_factory_->CreateUnsyncedItem(
190 TestIdFactory::MakeServer("Y"), TestIdFactory::MakeServer("X"), "Y", true,
191 BOOKMARKS, NULL);
192
193 // If the server's update were applied, we would have X be a child of Y, and Y
194 // as a child of X. That's a directory loop. The UpdateApplicator should
195 // prevent the update from being applied and note that this is a hierarchy
196 // conflict.
197
198 ExpectGroupToChange(apply_updates_command_, GROUP_UI);
199 apply_updates_command_.ExecuteImpl(session());
200
201 const sessions::StatusController& status = session()->status_controller();
202
203 // This should count as a hierarchy conflict.
204 EXPECT_EQ(1, status.num_hierarchy_conflicts());
205 EXPECT_EQ(0, status.num_simple_conflicts());
206 }
207
208 // Runs the ApplyUpdatesCommand on a directory where the server sent us an
209 // update to add a child to a locally deleted (and unsynced) parent. We expect
210 // the command to not apply the update and to indicate the update is in a
211 // CONFLICT_HIERARCHY state.
212 TEST_F(ApplyUpdatesCommandTest, HierarchyConflictDeletedParent) {
213 // Create a locally deleted parent item.
214 int64 parent_handle;
215 entry_factory_->CreateUnsyncedItem(
216 Id::CreateFromServerId("parent"), TestIdFactory::root(),
217 "parent", true, BOOKMARKS, &parent_handle);
218 {
219 WriteTransaction trans(FROM_HERE, UNITTEST, directory());
220 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, parent_handle);
221 entry.Put(syncable::IS_DEL, true);
222 }
223
224 // Create an incoming child from the server.
225 entry_factory_->CreateUnappliedNewItemWithParent(
226 "child", DefaultBookmarkSpecifics(), "parent");
227
228 // The server's update may seem valid to some other client, but on this client
229 // that new item's parent no longer exists. The update should not be applied
230 // and the update applicator should indicate this is a hierarchy conflict.
231
232 ExpectGroupToChange(apply_updates_command_, GROUP_UI);
233 apply_updates_command_.ExecuteImpl(session());
234
235 const sessions::StatusController& status = session()->status_controller();
236 EXPECT_EQ(1, status.num_hierarchy_conflicts());
237 EXPECT_EQ(0, status.num_simple_conflicts());
238 }
239
240 // Runs the ApplyUpdatesCommand on a directory where the server is trying to
241 // delete a folder that has a recently added (and unsynced) child. We expect
242 // the command to not apply the update because it is in a CONFLICT_HIERARCHY
243 // state.
244 TEST_F(ApplyUpdatesCommandTest, HierarchyConflictDeleteNonEmptyDirectory) {
245 // Create a server-deleted directory.
246 {
247 // Create it as a child of root node.
248 int64 handle =
249 entry_factory_->CreateSyncedItem("parent", BOOKMARKS, true);
250
251 WriteTransaction trans(FROM_HERE, UNITTEST, directory());
252 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle);
253 ASSERT_TRUE(entry.good());
254
255 // Delete it on the server.
256 entry.Put(syncable::SERVER_VERSION, entry_factory_->GetNextRevision());
257 entry.Put(syncable::IS_UNAPPLIED_UPDATE, true);
258 entry.Put(syncable::SERVER_PARENT_ID, TestIdFactory::root());
259 entry.Put(syncable::SERVER_IS_DEL, true);
260 }
261
262 // Create a local child of the server-deleted directory.
263 entry_factory_->CreateUnsyncedItem(
264 TestIdFactory::MakeServer("child"), TestIdFactory::MakeServer("parent"),
265 "child", false, BOOKMARKS, NULL);
266
267 // The server's request to delete the directory must be ignored, otherwise our
268 // unsynced new child would be orphaned. This is a hierarchy conflict.
269
270 ExpectGroupToChange(apply_updates_command_, GROUP_UI);
271 apply_updates_command_.ExecuteImpl(session());
272
273 const sessions::StatusController& status = session()->status_controller();
274 // This should count as a hierarchy conflict.
275 EXPECT_EQ(1, status.num_hierarchy_conflicts());
276 EXPECT_EQ(0, status.num_simple_conflicts());
277 }
278
279 // Runs the ApplyUpdatesCommand on a server-created item that has a locally
280 // unknown parent. We expect the command to not apply the update because the
281 // item is in a CONFLICT_HIERARCHY state.
282 TEST_F(ApplyUpdatesCommandTest, HierarchyConflictUnknownParent) {
283 // We shouldn't be able to do anything with either of these items.
284 entry_factory_->CreateUnappliedNewItemWithParent(
285 "some_item", DefaultBookmarkSpecifics(), "unknown_parent");
286 entry_factory_->CreateUnappliedNewItemWithParent(
287 "some_other_item", DefaultBookmarkSpecifics(), "some_item");
288
289 ExpectGroupToChange(apply_updates_command_, GROUP_UI);
290 apply_updates_command_.ExecuteImpl(session());
291
292 const sessions::StatusController& status = session()->status_controller();
293 EXPECT_EQ(0, status.num_simple_conflicts())
294 << "Updates with unknown parent should not be treated as 'simple'"
295 << " conflicts";
296 EXPECT_EQ(2, status.num_hierarchy_conflicts())
297 << "All updates with an unknown ancestors should be in conflict";
298 EXPECT_EQ(0, status.num_updates_applied())
299 << "No item with an unknown ancestor should be applied";
300 }
301
302 TEST_F(ApplyUpdatesCommandTest, ItemsBothKnownAndUnknown) {
303 // See what happens when there's a mixture of good and bad updates.
304 string root_server_id = syncable::GetNullId().GetServerId();
305 entry_factory_->CreateUnappliedNewItemWithParent(
306 "first_unknown_item", DefaultBookmarkSpecifics(), "unknown_parent");
307 entry_factory_->CreateUnappliedNewItemWithParent(
308 "first_known_item", DefaultBookmarkSpecifics(), root_server_id);
309 entry_factory_->CreateUnappliedNewItemWithParent(
310 "second_unknown_item", DefaultBookmarkSpecifics(), "unknown_parent");
311 entry_factory_->CreateUnappliedNewItemWithParent(
312 "second_known_item", DefaultBookmarkSpecifics(), "first_known_item");
313 entry_factory_->CreateUnappliedNewItemWithParent(
314 "third_known_item", DefaultBookmarkSpecifics(), "fourth_known_item");
315 entry_factory_->CreateUnappliedNewItemWithParent(
316 "fourth_known_item", DefaultBookmarkSpecifics(), root_server_id);
317
318 ExpectGroupToChange(apply_updates_command_, GROUP_UI);
319 apply_updates_command_.ExecuteImpl(session());
320
321 const sessions::StatusController& status = session()->status_controller();
322 EXPECT_EQ(2, status.num_hierarchy_conflicts())
323 << "The updates with unknown ancestors should be in conflict";
324 EXPECT_EQ(4, status.num_updates_applied())
325 << "The updates with known ancestors should be successfully applied";
326 }
327
328 TEST_F(ApplyUpdatesCommandTest, DecryptablePassword) {
329 // Decryptable password updates should be applied.
330 Cryptographer* cryptographer;
331 {
332 // Storing the cryptographer separately is bad, but for this test we
333 // know it's safe.
334 syncable::ReadTransaction trans(FROM_HERE, directory());
335 cryptographer = directory()->GetCryptographer(&trans);
336 }
337
338 KeyParams params = {"localhost", "dummy", "foobar"};
339 cryptographer->AddKey(params);
340
341 sync_pb::EntitySpecifics specifics;
342 sync_pb::PasswordSpecificsData data;
343 data.set_origin("http://example.com");
344
345 cryptographer->Encrypt(data,
346 specifics.mutable_password()->mutable_encrypted());
347 entry_factory_->CreateUnappliedNewItem("item", specifics, false);
348
349 ExpectGroupToChange(apply_updates_command_, GROUP_PASSWORD);
350 apply_updates_command_.ExecuteImpl(session());
351
352 const sessions::StatusController& status = session()->status_controller();
353 EXPECT_EQ(0, status.num_simple_conflicts())
354 << "No update should be in conflict because they're all decryptable";
355 EXPECT_EQ(1, status.num_updates_applied())
356 << "The updates that can be decrypted should be applied";
357 }
358
359 TEST_F(ApplyUpdatesCommandTest, UndecryptableData) {
360 // Undecryptable updates should not be applied.
361 sync_pb::EntitySpecifics encrypted_bookmark;
362 encrypted_bookmark.mutable_encrypted();
363 AddDefaultFieldValue(BOOKMARKS, &encrypted_bookmark);
364 string root_server_id = syncable::GetNullId().GetServerId();
365 entry_factory_->CreateUnappliedNewItemWithParent(
366 "folder", encrypted_bookmark, root_server_id);
367 entry_factory_->CreateUnappliedNewItem("item2", encrypted_bookmark, false);
368 sync_pb::EntitySpecifics encrypted_password;
369 encrypted_password.mutable_password();
370 entry_factory_->CreateUnappliedNewItem("item3", encrypted_password, false);
371
372 ExpectGroupsToChange(apply_updates_command_, GROUP_UI, GROUP_PASSWORD);
373 apply_updates_command_.ExecuteImpl(session());
374
375 const sessions::StatusController& status = session()->status_controller();
376 EXPECT_TRUE(status.HasConflictingUpdates())
377 << "Updates that can't be decrypted should trigger the syncer to have "
378 << "conflicting updates.";
379 EXPECT_EQ(0, status.num_simple_conflicts())
380 << "Updates that can't be decrypted should not be in regular conflict";
381 EXPECT_EQ(3, status.num_encryption_conflicts())
382 << "Updates that can't be decrypted should be in encryption conflict";
383 EXPECT_EQ(0, status.num_updates_applied())
384 << "No update that can't be decrypted should be applied";
385 }
386
387 TEST_F(ApplyUpdatesCommandTest, SomeUndecryptablePassword) {
388 Cryptographer* cryptographer;
389 // Only decryptable password updates should be applied.
390 {
391 sync_pb::EntitySpecifics specifics;
392 sync_pb::PasswordSpecificsData data;
393 data.set_origin("http://example.com/1");
394 {
395 syncable::ReadTransaction trans(FROM_HERE, directory());
396 cryptographer = directory()->GetCryptographer(&trans);
397
398 KeyParams params = {"localhost", "dummy", "foobar"};
399 cryptographer->AddKey(params);
400
401 cryptographer->Encrypt(data,
402 specifics.mutable_password()->mutable_encrypted());
403 }
404 entry_factory_->CreateUnappliedNewItem("item1", specifics, false);
405 }
406 {
407 // Create a new cryptographer, independent of the one in the session.
408 Cryptographer other_cryptographer(cryptographer->encryptor());
409 KeyParams params = {"localhost", "dummy", "bazqux"};
410 other_cryptographer.AddKey(params);
411
412 sync_pb::EntitySpecifics specifics;
413 sync_pb::PasswordSpecificsData data;
414 data.set_origin("http://example.com/2");
415
416 other_cryptographer.Encrypt(data,
417 specifics.mutable_password()->mutable_encrypted());
418 entry_factory_->CreateUnappliedNewItem("item2", specifics, false);
419 }
420
421 ExpectGroupToChange(apply_updates_command_, GROUP_PASSWORD);
422 apply_updates_command_.ExecuteImpl(session());
423
424 const sessions::StatusController& status = session()->status_controller();
425 EXPECT_TRUE(status.HasConflictingUpdates())
426 << "Updates that can't be decrypted should trigger the syncer to have "
427 << "conflicting updates.";
428 EXPECT_EQ(0, status.num_simple_conflicts())
429 << "The updates that can't be decrypted should not be in regular "
430 << "conflict";
431 EXPECT_EQ(1, status.num_encryption_conflicts())
432 << "The updates that can't be decrypted should be in encryption "
433 << "conflict";
434 EXPECT_EQ(1, status.num_updates_applied())
435 << "The undecryptable password update shouldn't be applied";
436 }
437
438 } // namespace syncer
OLDNEW
« no previous file with comments | « sync/engine/apply_updates_command.cc ('k') | sync/engine/conflict_resolver.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698