NaCl: Update revision in DEPS, r9066 -> r9086
[chromium-blink-merge.git] / sync / engine / process_commit_response_command_unittest.cc
blobbe9fa22216b231f3e5ef2a58ac2d78dbae8074e1
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.
5 #include "sync/engine/process_commit_response_command.h"
7 #include <vector>
9 #include "base/location.h"
10 #include "base/stringprintf.h"
11 #include "sync/protocol/bookmark_specifics.pb.h"
12 #include "sync/protocol/sync.pb.h"
13 #include "sync/sessions/sync_session.h"
14 #include "sync/syncable/entry.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/write_transaction.h"
19 #include "sync/test/engine/fake_model_worker.h"
20 #include "sync/test/engine/syncer_command_test.h"
21 #include "sync/test/engine/test_id_factory.h"
22 #include "testing/gtest/include/gtest/gtest.h"
24 namespace syncer {
26 using sessions::SyncSession;
27 using std::string;
28 using syncable::BASE_VERSION;
29 using syncable::Entry;
30 using syncable::IS_DIR;
31 using syncable::IS_UNSYNCED;
32 using syncable::Id;
33 using syncable::MutableEntry;
34 using syncable::NON_UNIQUE_NAME;
35 using syncable::UNITTEST;
36 using syncable::WriteTransaction;
38 // A test fixture for tests exercising ProcessCommitResponseCommand.
39 class ProcessCommitResponseCommandTest : public SyncerCommandTest {
40 public:
41 virtual void SetUp() {
42 workers()->clear();
43 mutable_routing_info()->clear();
45 workers()->push_back(
46 make_scoped_refptr(new FakeModelWorker(GROUP_DB)));
47 workers()->push_back(
48 make_scoped_refptr(new FakeModelWorker(GROUP_UI)));
49 (*mutable_routing_info())[syncer::BOOKMARKS] = GROUP_UI;
50 (*mutable_routing_info())[syncer::PREFERENCES] = GROUP_UI;
51 (*mutable_routing_info())[syncer::AUTOFILL] = GROUP_DB;
53 SyncerCommandTest::SetUp();
56 protected:
58 ProcessCommitResponseCommandTest()
59 : next_old_revision_(1),
60 next_new_revision_(4000),
61 next_server_position_(10000) {
64 void CheckEntry(Entry* e, const std::string& name,
65 syncer::ModelType model_type, const Id& parent_id) {
66 EXPECT_TRUE(e->good());
67 ASSERT_EQ(name, e->Get(NON_UNIQUE_NAME));
68 ASSERT_EQ(model_type, e->GetModelType());
69 ASSERT_EQ(parent_id, e->Get(syncable::PARENT_ID));
70 ASSERT_LT(0, e->Get(BASE_VERSION))
71 << "Item should have a valid (positive) server base revision";
74 // Create an unsynced item in the database. If item_id is a local ID, it
75 // will be treated as a create-new. Otherwise, if it's a server ID, we'll
76 // fake the server data so that it looks like it exists on the server.
77 // Returns the methandle of the created item in |metahandle_out| if not NULL.
78 void CreateUnsyncedItem(const Id& item_id,
79 const Id& parent_id,
80 const string& name,
81 bool is_folder,
82 syncer::ModelType model_type,
83 int64* metahandle_out) {
84 WriteTransaction trans(FROM_HERE, UNITTEST, directory());
85 Id predecessor_id;
86 ASSERT_TRUE(
87 directory()->GetLastChildIdForTest(&trans, parent_id, &predecessor_id));
88 MutableEntry entry(&trans, syncable::CREATE, parent_id, name);
89 ASSERT_TRUE(entry.good());
90 entry.Put(syncable::ID, item_id);
91 entry.Put(syncable::BASE_VERSION,
92 item_id.ServerKnows() ? next_old_revision_++ : 0);
93 entry.Put(syncable::IS_UNSYNCED, true);
94 entry.Put(syncable::IS_DIR, is_folder);
95 entry.Put(syncable::IS_DEL, false);
96 entry.Put(syncable::PARENT_ID, parent_id);
97 entry.PutPredecessor(predecessor_id);
98 sync_pb::EntitySpecifics default_specifics;
99 syncer::AddDefaultFieldValue(model_type, &default_specifics);
100 entry.Put(syncable::SPECIFICS, default_specifics);
101 if (item_id.ServerKnows()) {
102 entry.Put(syncable::SERVER_SPECIFICS, default_specifics);
103 entry.Put(syncable::SERVER_IS_DIR, is_folder);
104 entry.Put(syncable::SERVER_PARENT_ID, parent_id);
105 entry.Put(syncable::SERVER_IS_DEL, false);
107 if (metahandle_out)
108 *metahandle_out = entry.Get(syncable::META_HANDLE);
111 // Create a new unsynced item in the database, and synthesize a commit
112 // record and a commit response for it in the syncer session. If item_id
113 // is a local ID, the item will be a create operation. Otherwise, it
114 // will be an edit.
115 void CreateUnprocessedCommitResult(
116 const Id& item_id,
117 const Id& parent_id,
118 const string& name,
119 syncer::ModelType model_type,
120 sessions::OrderedCommitSet *commit_set,
121 syncer::ClientToServerMessage *commit,
122 syncer::ClientToServerResponse *response) {
123 bool is_folder = true;
124 int64 metahandle = 0;
125 CreateUnsyncedItem(item_id, parent_id, name, is_folder, model_type,
126 &metahandle);
128 // ProcessCommitResponseCommand consumes commit_ids from the session
129 // state, so we need to update that. O(n^2) because it's a test.
130 commit_set->AddCommitItem(metahandle, item_id, model_type);
132 WriteTransaction trans(FROM_HERE, UNITTEST, directory());
133 MutableEntry entry(&trans, syncable::GET_BY_ID, item_id);
134 ASSERT_TRUE(entry.good());
135 entry.Put(syncable::SYNCING, true);
137 // Add to the commit message.
138 commit->set_message_contents(ClientToServerMessage::COMMIT);
139 SyncEntity* entity = static_cast<SyncEntity*>(
140 commit->mutable_commit()->add_entries());
141 entity->set_non_unique_name(name);
142 entity->set_folder(is_folder);
143 entity->set_parent_id(parent_id);
144 entity->set_version(entry.Get(syncable::BASE_VERSION));
145 entity->mutable_specifics()->CopyFrom(entry.Get(syncable::SPECIFICS));
146 entity->set_id(item_id);
148 // Add to the response message.
149 response->set_error_code(sync_pb::SyncEnums::SUCCESS);
150 sync_pb::CommitResponse_EntryResponse* entry_response =
151 response->mutable_commit()->add_entryresponse();
152 entry_response->set_response_type(CommitResponse::SUCCESS);
153 entry_response->set_name("Garbage.");
154 entry_response->set_non_unique_name(entity->name());
155 if (item_id.ServerKnows())
156 entry_response->set_id_string(entity->id_string());
157 else
158 entry_response->set_id_string(id_factory_.NewServerId().GetServerId());
159 entry_response->set_version(next_new_revision_++);
160 entry_response->set_position_in_parent(next_server_position_++);
162 // If the ID of our parent item committed earlier in the batch was
163 // rewritten, rewrite it in the entry response. This matches
164 // the server behavior.
165 entry_response->set_parent_id_string(entity->parent_id_string());
166 for (int i = 0; i < commit->commit().entries_size(); ++i) {
167 if (commit->commit().entries(i).id_string() ==
168 entity->parent_id_string()) {
169 entry_response->set_parent_id_string(
170 response->commit().entryresponse(i).id_string());
175 void SetLastErrorCode(CommitResponse::ResponseType error_code,
176 sync_pb::ClientToServerResponse* response) {
177 sync_pb::CommitResponse_EntryResponse* entry_response =
178 response->mutable_commit()->mutable_entryresponse(
179 response->mutable_commit()->entryresponse_size() - 1);
180 entry_response->set_response_type(error_code);
183 TestIdFactory id_factory_;
184 private:
185 int64 next_old_revision_;
186 int64 next_new_revision_;
187 int64 next_server_position_;
188 DISALLOW_COPY_AND_ASSIGN(ProcessCommitResponseCommandTest);
191 TEST_F(ProcessCommitResponseCommandTest, MultipleCommitIdProjections) {
192 sessions::OrderedCommitSet commit_set(session()->routing_info());
193 syncer::ClientToServerMessage request;
194 syncer::ClientToServerResponse response;
196 Id bookmark_folder_id = id_factory_.NewLocalId();
197 Id bookmark_id1 = id_factory_.NewLocalId();
198 Id bookmark_id2 = id_factory_.NewLocalId();
199 Id pref_id1 = id_factory_.NewLocalId(), pref_id2 = id_factory_.NewLocalId();
200 Id autofill_id1 = id_factory_.NewLocalId();
201 Id autofill_id2 = id_factory_.NewLocalId();
202 CreateUnprocessedCommitResult(bookmark_folder_id, id_factory_.root(),
203 "A bookmark folder", syncer::BOOKMARKS,
204 &commit_set, &request, &response);
205 CreateUnprocessedCommitResult(bookmark_id1, bookmark_folder_id,
206 "bookmark 1", syncer::BOOKMARKS,
207 &commit_set, &request, &response);
208 CreateUnprocessedCommitResult(bookmark_id2, bookmark_folder_id,
209 "bookmark 2", syncer::BOOKMARKS,
210 &commit_set, &request, &response);
211 CreateUnprocessedCommitResult(pref_id1, id_factory_.root(),
212 "Pref 1", syncer::PREFERENCES,
213 &commit_set, &request, &response);
214 CreateUnprocessedCommitResult(pref_id2, id_factory_.root(),
215 "Pref 2", syncer::PREFERENCES,
216 &commit_set, &request, &response);
217 CreateUnprocessedCommitResult(autofill_id1, id_factory_.root(),
218 "Autofill 1", syncer::AUTOFILL,
219 &commit_set, &request, &response);
220 CreateUnprocessedCommitResult(autofill_id2, id_factory_.root(),
221 "Autofill 2", syncer::AUTOFILL,
222 &commit_set, &request, &response);
224 ProcessCommitResponseCommand command(commit_set, request, response);
225 ExpectGroupsToChange(command, GROUP_UI, GROUP_DB);
226 command.ExecuteImpl(session());
228 syncable::ReadTransaction trans(FROM_HERE, directory());
229 Id new_fid;
230 ASSERT_TRUE(directory()->GetFirstChildId(
231 &trans, id_factory_.root(), &new_fid));
232 ASSERT_FALSE(new_fid.IsRoot());
233 EXPECT_TRUE(new_fid.ServerKnows());
234 EXPECT_FALSE(bookmark_folder_id.ServerKnows());
235 EXPECT_FALSE(new_fid == bookmark_folder_id);
236 Entry b_folder(&trans, syncable::GET_BY_ID, new_fid);
237 ASSERT_TRUE(b_folder.good());
238 ASSERT_EQ("A bookmark folder", b_folder.Get(NON_UNIQUE_NAME))
239 << "Name of bookmark folder should not change.";
240 ASSERT_LT(0, b_folder.Get(BASE_VERSION))
241 << "Bookmark folder should have a valid (positive) server base revision";
243 // Look at the two bookmarks in bookmark_folder.
244 Id cid;
245 ASSERT_TRUE(directory()->GetFirstChildId(&trans, new_fid, &cid));
246 Entry b1(&trans, syncable::GET_BY_ID, cid);
247 Entry b2(&trans, syncable::GET_BY_ID, b1.Get(syncable::NEXT_ID));
248 CheckEntry(&b1, "bookmark 1", syncer::BOOKMARKS, new_fid);
249 CheckEntry(&b2, "bookmark 2", syncer::BOOKMARKS, new_fid);
250 ASSERT_TRUE(b2.Get(syncable::NEXT_ID).IsRoot());
252 // Look at the prefs and autofill items.
253 Entry p1(&trans, syncable::GET_BY_ID, b_folder.Get(syncable::NEXT_ID));
254 Entry p2(&trans, syncable::GET_BY_ID, p1.Get(syncable::NEXT_ID));
255 CheckEntry(&p1, "Pref 1", syncer::PREFERENCES, id_factory_.root());
256 CheckEntry(&p2, "Pref 2", syncer::PREFERENCES, id_factory_.root());
258 Entry a1(&trans, syncable::GET_BY_ID, p2.Get(syncable::NEXT_ID));
259 Entry a2(&trans, syncable::GET_BY_ID, a1.Get(syncable::NEXT_ID));
260 CheckEntry(&a1, "Autofill 1", syncer::AUTOFILL, id_factory_.root());
261 CheckEntry(&a2, "Autofill 2", syncer::AUTOFILL, id_factory_.root());
262 ASSERT_TRUE(a2.Get(syncable::NEXT_ID).IsRoot());
265 // In this test, we test processing a commit response for a commit batch that
266 // includes a newly created folder and some (but not all) of its children.
267 // In particular, the folder has 50 children, which alternate between being
268 // new items and preexisting items. This mixture of new and old is meant to
269 // be a torture test of the code in ProcessCommitResponseCommand that changes
270 // an item's ID from a local ID to a server-generated ID on the first commit.
271 // We commit only the first 25 children in the sibling order, leaving the
272 // second 25 children as unsynced items. http://crbug.com/33081 describes
273 // how this scenario used to fail, reversing the order for the second half
274 // of the children.
275 TEST_F(ProcessCommitResponseCommandTest, NewFolderCommitKeepsChildOrder) {
276 sessions::OrderedCommitSet commit_set(session()->routing_info());
277 syncer::ClientToServerMessage request;
278 syncer::ClientToServerResponse response;
280 // Create the parent folder, a new item whose ID will change on commit.
281 Id folder_id = id_factory_.NewLocalId();
282 CreateUnprocessedCommitResult(folder_id, id_factory_.root(), "A",
283 syncer::BOOKMARKS,
284 &commit_set, &request, &response);
286 // Verify that the item is reachable.
288 syncable::ReadTransaction trans(FROM_HERE, directory());
289 Id child_id;
290 ASSERT_TRUE(directory()->GetFirstChildId(
291 &trans, id_factory_.root(), &child_id));
292 ASSERT_EQ(folder_id, child_id);
295 // The first 25 children of the parent folder will be part of the commit
296 // batch.
297 int batch_size = 25;
298 int i = 0;
299 for (; i < batch_size; ++i) {
300 // Alternate between new and old child items, just for kicks.
301 Id id = (i % 4 < 2) ? id_factory_.NewLocalId() : id_factory_.NewServerId();
302 CreateUnprocessedCommitResult(
303 id, folder_id, base::StringPrintf("Item %d", i), syncer::BOOKMARKS,
304 &commit_set, &request, &response);
306 // The second 25 children will be unsynced items but NOT part of the commit
307 // batch. When the ID of the parent folder changes during the commit,
308 // these items PARENT_ID should be updated, and their ordering should be
309 // preserved.
310 for (; i < 2*batch_size; ++i) {
311 // Alternate between new and old child items, just for kicks.
312 Id id = (i % 4 < 2) ? id_factory_.NewLocalId() : id_factory_.NewServerId();
313 CreateUnsyncedItem(id, folder_id, base::StringPrintf("Item %d", i),
314 false, syncer::BOOKMARKS, NULL);
317 // Process the commit response for the parent folder and the first
318 // 25 items. This should apply the values indicated by
319 // each CommitResponse_EntryResponse to the syncable Entries. All new
320 // items in the commit batch should have their IDs changed to server IDs.
321 ProcessCommitResponseCommand command(commit_set, request, response);
322 ExpectGroupToChange(command, GROUP_UI);
323 command.ExecuteImpl(session());
325 syncable::ReadTransaction trans(FROM_HERE, directory());
326 // Lookup the parent folder by finding a child of the root. We can't use
327 // folder_id here, because it changed during the commit.
328 Id new_fid;
329 ASSERT_TRUE(directory()->GetFirstChildId(
330 &trans, id_factory_.root(), &new_fid));
331 ASSERT_FALSE(new_fid.IsRoot());
332 EXPECT_TRUE(new_fid.ServerKnows());
333 EXPECT_FALSE(folder_id.ServerKnows());
334 EXPECT_TRUE(new_fid != folder_id);
335 Entry parent(&trans, syncable::GET_BY_ID, new_fid);
336 ASSERT_TRUE(parent.good());
337 ASSERT_EQ("A", parent.Get(NON_UNIQUE_NAME))
338 << "Name of parent folder should not change.";
339 ASSERT_LT(0, parent.Get(BASE_VERSION))
340 << "Parent should have a valid (positive) server base revision";
342 Id cid;
343 ASSERT_TRUE(directory()->GetFirstChildId(&trans, new_fid, &cid));
344 int child_count = 0;
345 // Now loop over all the children of the parent folder, verifying
346 // that they are in their original order by checking to see that their
347 // names are still sequential.
348 while (!cid.IsRoot()) {
349 SCOPED_TRACE(::testing::Message("Examining item #") << child_count);
350 Entry c(&trans, syncable::GET_BY_ID, cid);
351 DCHECK(c.good());
352 ASSERT_EQ(base::StringPrintf("Item %d", child_count),
353 c.Get(NON_UNIQUE_NAME));
354 ASSERT_EQ(new_fid, c.Get(syncable::PARENT_ID));
355 if (child_count < batch_size) {
356 ASSERT_FALSE(c.Get(IS_UNSYNCED)) << "Item should be committed";
357 ASSERT_TRUE(cid.ServerKnows());
358 ASSERT_LT(0, c.Get(BASE_VERSION));
359 } else {
360 ASSERT_TRUE(c.Get(IS_UNSYNCED)) << "Item should be uncommitted";
361 // We alternated between creates and edits; double check that these items
362 // have been preserved.
363 if (child_count % 4 < 2) {
364 ASSERT_FALSE(cid.ServerKnows());
365 ASSERT_GE(0, c.Get(BASE_VERSION));
366 } else {
367 ASSERT_TRUE(cid.ServerKnows());
368 ASSERT_LT(0, c.Get(BASE_VERSION));
371 cid = c.Get(syncable::NEXT_ID);
372 child_count++;
374 ASSERT_EQ(batch_size*2, child_count)
375 << "Too few or too many children in parent folder after commit.";
378 // This test fixture runs across a Cartesian product of per-type fail/success
379 // possibilities.
380 enum {
381 TEST_PARAM_BOOKMARK_ENABLE_BIT,
382 TEST_PARAM_AUTOFILL_ENABLE_BIT,
383 TEST_PARAM_BIT_COUNT
385 class MixedResult :
386 public ProcessCommitResponseCommandTest,
387 public ::testing::WithParamInterface<int> {
388 protected:
389 bool ShouldFailBookmarkCommit() {
390 return (GetParam() & (1 << TEST_PARAM_BOOKMARK_ENABLE_BIT)) == 0;
392 bool ShouldFailAutofillCommit() {
393 return (GetParam() & (1 << TEST_PARAM_AUTOFILL_ENABLE_BIT)) == 0;
396 INSTANTIATE_TEST_CASE_P(ProcessCommitResponse,
397 MixedResult,
398 testing::Range(0, 1 << TEST_PARAM_BIT_COUNT));
400 // This test commits 2 items (one bookmark, one autofill) and validates what
401 // happens to the extensions activity records. Commits could fail or succeed,
402 // depending on the test parameter.
403 TEST_P(MixedResult, ExtensionActivity) {
404 sessions::OrderedCommitSet commit_set(session()->routing_info());
405 syncer::ClientToServerMessage request;
406 syncer::ClientToServerResponse response;
408 EXPECT_NE(routing_info().find(syncer::BOOKMARKS)->second,
409 routing_info().find(syncer::AUTOFILL)->second)
410 << "To not be lame, this test requires more than one active group.";
412 // Bookmark item setup.
413 CreateUnprocessedCommitResult(id_factory_.NewServerId(),
414 id_factory_.root(), "Some bookmark", syncer::BOOKMARKS,
415 &commit_set, &request, &response);
416 if (ShouldFailBookmarkCommit())
417 SetLastErrorCode(CommitResponse::TRANSIENT_ERROR, &response);
418 // Autofill item setup.
419 CreateUnprocessedCommitResult(id_factory_.NewServerId(),
420 id_factory_.root(), "Some autofill", syncer::AUTOFILL,
421 &commit_set, &request, &response);
422 if (ShouldFailAutofillCommit())
423 SetLastErrorCode(CommitResponse::TRANSIENT_ERROR, &response);
425 // Put some extensions activity in the session.
427 ExtensionsActivityMonitor::Records* records =
428 session()->mutable_extensions_activity();
429 (*records)["ABC"].extension_id = "ABC";
430 (*records)["ABC"].bookmark_write_count = 2049U;
431 (*records)["xyz"].extension_id = "xyz";
432 (*records)["xyz"].bookmark_write_count = 4U;
434 ProcessCommitResponseCommand command(commit_set, request, response);
435 command.ExecuteImpl(session());
436 ExpectGroupsToChange(command, GROUP_UI, GROUP_DB);
438 ExtensionsActivityMonitor::Records final_monitor_records;
439 context()->extensions_monitor()->GetAndClearRecords(&final_monitor_records);
441 if (ShouldFailBookmarkCommit()) {
442 ASSERT_EQ(2U, final_monitor_records.size())
443 << "Should restore records after unsuccessful bookmark commit.";
444 EXPECT_EQ("ABC", final_monitor_records["ABC"].extension_id);
445 EXPECT_EQ("xyz", final_monitor_records["xyz"].extension_id);
446 EXPECT_EQ(2049U, final_monitor_records["ABC"].bookmark_write_count);
447 EXPECT_EQ(4U, final_monitor_records["xyz"].bookmark_write_count);
448 } else {
449 EXPECT_TRUE(final_monitor_records.empty())
450 << "Should not restore records after successful bookmark commit.";
454 } // namespace syncer