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 // TODO(akalin): This file is basically just a unit test for
6 // BookmarkChangeProcessor. Write unit tests for
7 // BookmarkModelAssociator separately.
14 #include "base/command_line.h"
15 #include "base/file_path.h"
16 #include "base/file_util.h"
17 #include "base/location.h"
18 #include "base/memory/scoped_ptr.h"
19 #include "base/message_loop.h"
20 #include "base/string16.h"
21 #include "base/string_number_conversions.h"
22 #include "base/string_util.h"
23 #include "base/time.h"
24 #include "base/utf_string_conversions.h"
25 #include "chrome/browser/bookmarks/base_bookmark_model_observer.h"
26 #include "chrome/browser/bookmarks/bookmark_model.h"
27 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
28 #include "chrome/browser/sync/glue/bookmark_change_processor.h"
29 #include "chrome/browser/sync/glue/bookmark_model_associator.h"
30 #include "chrome/browser/sync/glue/data_type_error_handler.h"
31 #include "chrome/browser/sync/glue/data_type_error_handler_mock.h"
32 #include "chrome/common/chrome_switches.h"
33 #include "chrome/test/base/testing_profile.h"
34 #include "content/public/test/test_browser_thread.h"
35 #include "sync/api/sync_error.h"
36 #include "sync/internal_api/public/change_record.h"
37 #include "sync/internal_api/public/read_node.h"
38 #include "sync/internal_api/public/read_transaction.h"
39 #include "sync/internal_api/public/test/test_user_share.h"
40 #include "sync/internal_api/public/write_node.h"
41 #include "sync/internal_api/public/write_transaction.h"
42 #include "sync/syncable/mutable_entry.h" // TODO(tim): Remove. Bug 131130.
43 #include "testing/gmock/include/gmock/gmock.h"
44 #include "testing/gtest/include/gtest/gtest.h"
46 namespace browser_sync
{
48 using content::BrowserThread
;
49 using syncer::BaseNode
;
51 using testing::InvokeWithoutArgs
;
53 using testing::StrictMock
;
55 #if defined(OS_ANDROID)
56 static const bool kExpectMobileBookmarks
= true;
58 static const bool kExpectMobileBookmarks
= false;
59 #endif // defined(OS_ANDROID)
63 // FakeServerChange constructs a list of syncer::ChangeRecords while modifying
64 // the sync model, and can pass the ChangeRecord list to a
65 // syncer::SyncObserver (i.e., the ProfileSyncService) to test the client
66 // change-application behavior.
67 // Tests using FakeServerChange should be careful to avoid back-references,
68 // since FakeServerChange will send the edits in the order specified.
69 class FakeServerChange
{
71 explicit FakeServerChange(syncer::WriteTransaction
* trans
) : trans_(trans
) {
74 // Pretend that the server told the syncer to add a bookmark object.
75 int64
Add(const std::wstring
& title
,
76 const std::string
& url
,
79 int64 predecessor_id
) {
80 syncer::ReadNode
parent(trans_
);
81 EXPECT_EQ(BaseNode::INIT_OK
, parent
.InitByIdLookup(parent_id
));
82 syncer::WriteNode
node(trans_
);
83 if (predecessor_id
== 0) {
84 EXPECT_TRUE(node
.InitByCreation(syncer::BOOKMARKS
, parent
, NULL
));
86 syncer::ReadNode
predecessor(trans_
);
87 EXPECT_EQ(BaseNode::INIT_OK
, predecessor
.InitByIdLookup(predecessor_id
));
88 EXPECT_EQ(predecessor
.GetParentId(), parent
.GetId());
89 EXPECT_TRUE(node
.InitByCreation(syncer::BOOKMARKS
, parent
,
92 EXPECT_EQ(node
.GetPredecessorId(), predecessor_id
);
93 EXPECT_EQ(node
.GetParentId(), parent_id
);
94 node
.SetIsFolder(is_folder
);
97 node
.SetURL(GURL(url
));
98 syncer::ChangeRecord record
;
99 record
.action
= syncer::ChangeRecord::ACTION_ADD
;
100 record
.id
= node
.GetId();
101 changes_
.push_back(record
);
105 // Add a bookmark folder.
106 int64
AddFolder(const std::wstring
& title
,
108 int64 predecessor_id
) {
109 return Add(title
, std::string(), true, parent_id
, predecessor_id
);
113 int64
AddURL(const std::wstring
& title
,
114 const std::string
& url
,
116 int64 predecessor_id
) {
117 return Add(title
, url
, false, parent_id
, predecessor_id
);
120 // Pretend that the server told the syncer to delete an object.
121 void Delete(int64 id
) {
123 // Delete the sync node.
124 syncer::WriteNode
node(trans_
);
125 EXPECT_EQ(BaseNode::INIT_OK
, node
.InitByIdLookup(id
));
126 EXPECT_FALSE(node
.GetFirstChildId());
130 // Verify the deletion.
131 syncer::ReadNode
node(trans_
);
132 EXPECT_EQ(BaseNode::INIT_FAILED_ENTRY_IS_DEL
, node
.InitByIdLookup(id
));
135 syncer::ChangeRecord record
;
136 record
.action
= syncer::ChangeRecord::ACTION_DELETE
;
138 // Deletions are always first in the changelist, but we can't actually do
139 // WriteNode::Remove() on the node until its children are moved. So, as
140 // a practical matter, users of FakeServerChange must move or delete
141 // children before parents. Thus, we must insert the deletion record
142 // at the front of the vector.
143 changes_
.insert(changes_
.begin(), record
);
146 // Set a new title value, and return the old value.
147 std::wstring
ModifyTitle(int64 id
, const std::wstring
& new_title
) {
148 syncer::WriteNode
node(trans_
);
149 EXPECT_EQ(BaseNode::INIT_OK
, node
.InitByIdLookup(id
));
150 std::string old_title
= node
.GetTitle();
151 node
.SetTitle(new_title
);
153 return UTF8ToWide(old_title
);
156 // Set a new parent and predecessor value. Return the old parent id.
157 // We could return the old predecessor id, but it turns out not to be
158 // very useful for assertions.
159 int64
ModifyPosition(int64 id
, int64 parent_id
, int64 predecessor_id
) {
160 syncer::ReadNode
parent(trans_
);
161 EXPECT_EQ(BaseNode::INIT_OK
, parent
.InitByIdLookup(parent_id
));
162 syncer::WriteNode
node(trans_
);
163 EXPECT_EQ(BaseNode::INIT_OK
, node
.InitByIdLookup(id
));
164 int64 old_parent_id
= node
.GetParentId();
165 if (predecessor_id
== 0) {
166 EXPECT_TRUE(node
.SetPosition(parent
, NULL
));
168 syncer::ReadNode
predecessor(trans_
);
169 EXPECT_EQ(BaseNode::INIT_OK
, predecessor
.InitByIdLookup(predecessor_id
));
170 EXPECT_EQ(predecessor
.GetParentId(), parent
.GetId());
171 EXPECT_TRUE(node
.SetPosition(parent
, &predecessor
));
174 return old_parent_id
;
177 void ModifyCreationTime(int64 id
, int64 creation_time_us
) {
178 syncer::WriteNode
node(trans_
);
179 ASSERT_EQ(BaseNode::INIT_OK
, node
.InitByIdLookup(id
));
180 sync_pb::BookmarkSpecifics specifics
= node
.GetBookmarkSpecifics();
181 specifics
.set_creation_time_us(creation_time_us
);
182 node
.SetBookmarkSpecifics(specifics
);
186 // Pass the fake change list to |service|.
187 void ApplyPendingChanges(ChangeProcessor
* processor
) {
188 processor
->ApplyChangesFromSyncModel(
189 trans_
, 0, syncer::ImmutableChangeRecordList(&changes_
));
192 const syncer::ChangeRecordList
& changes() {
197 // Helper function to push an ACTION_UPDATE record onto the back
198 // of the changelist.
199 void SetModified(int64 id
) {
200 // Coalesce multi-property edits.
201 if (!changes_
.empty() && changes_
.back().id
== id
&&
202 changes_
.back().action
==
203 syncer::ChangeRecord::ACTION_UPDATE
)
205 syncer::ChangeRecord record
;
206 record
.action
= syncer::ChangeRecord::ACTION_UPDATE
;
208 changes_
.push_back(record
);
211 // The transaction on which everything happens.
212 syncer::WriteTransaction
*trans_
;
214 // The change list we construct.
215 syncer::ChangeRecordList changes_
;
218 class ExtensiveChangesBookmarkModelObserver
: public BaseBookmarkModelObserver
{
220 explicit ExtensiveChangesBookmarkModelObserver()
222 completed_count_at_started_(0),
223 completed_count_(0) {}
225 virtual void ExtensiveBookmarkChangesBeginning(
226 BookmarkModel
* model
) OVERRIDE
{
228 completed_count_at_started_
= completed_count_
;
231 virtual void ExtensiveBookmarkChangesEnded(BookmarkModel
* model
) OVERRIDE
{
235 void BookmarkModelChanged() {}
237 int get_started() const {
238 return started_count_
;
241 int get_completed_count_at_started() const {
242 return completed_count_at_started_
;
245 int get_completed() const {
246 return completed_count_
;
251 int completed_count_at_started_
;
252 int completed_count_
;
254 DISALLOW_COPY_AND_ASSIGN(ExtensiveChangesBookmarkModelObserver
);
258 class ProfileSyncServiceBookmarkTest
: public testing::Test
{
260 enum LoadOption
{ LOAD_FROM_STORAGE
, DELETE_EXISTING_STORAGE
};
261 enum SaveOption
{ SAVE_TO_STORAGE
, DONT_SAVE_TO_STORAGE
};
263 ProfileSyncServiceBookmarkTest()
265 ui_thread_(BrowserThread::UI
, &message_loop_
),
266 file_thread_(BrowserThread::FILE, &message_loop_
),
267 local_merge_result_(syncer::BOOKMARKS
),
268 syncer_merge_result_(syncer::BOOKMARKS
) {
271 virtual ~ProfileSyncServiceBookmarkTest() {
273 UnloadBookmarkModel();
276 virtual void SetUp() {
277 test_user_share_
.SetUp();
280 virtual void TearDown() {
281 test_user_share_
.TearDown();
284 // Load (or re-load) the bookmark model. |load| controls use of the
285 // bookmarks file on disk. |save| controls whether the newly loaded
286 // bookmark model will write out a bookmark file as it goes.
287 void LoadBookmarkModel(LoadOption load
, SaveOption save
) {
288 bool delete_bookmarks
= load
== DELETE_EXISTING_STORAGE
;
289 profile_
.CreateBookmarkModel(delete_bookmarks
);
290 model_
= BookmarkModelFactory::GetForProfile(&profile_
);
291 // Wait for the bookmarks model to load.
292 profile_
.BlockUntilBookmarkModelLoaded();
293 // This noticeably speeds up the unit tests that request it.
294 if (save
== DONT_SAVE_TO_STORAGE
)
295 model_
->ClearStore();
296 message_loop_
.RunAllPending();
299 int GetSyncBookmarkCount() {
300 syncer::ReadTransaction
trans(FROM_HERE
, test_user_share_
.user_share());
301 syncer::ReadNode
node(&trans
);
302 if (node
.InitByTagLookup(syncer::ModelTypeToRootTag(syncer::BOOKMARKS
)) !=
303 syncer::BaseNode::INIT_OK
)
305 return node
.GetTotalNodeCount();
308 // Creates the bookmark root node and the permanent nodes if they don't
310 bool CreatePermanentBookmarkNodes() {
311 bool root_exists
= false;
312 syncer::ModelType type
= syncer::BOOKMARKS
;
314 syncer::WriteTransaction
trans(FROM_HERE
,
315 test_user_share_
.user_share());
316 syncer::ReadNode
uber_root(&trans
);
317 uber_root
.InitByRootLookup();
319 syncer::ReadNode
root(&trans
);
320 root_exists
= (root
.InitByTagLookup(syncer::ModelTypeToRootTag(type
)) ==
325 if (!syncer::TestUserShare::CreateRoot(type
,
326 test_user_share_
.user_share()))
330 const int kNumPermanentNodes
= 3;
331 const std::string permanent_tags
[kNumPermanentNodes
] = {
332 "bookmark_bar", "other_bookmarks", "synced_bookmarks"
334 syncer::WriteTransaction
trans(FROM_HERE
, test_user_share_
.user_share());
335 syncer::ReadNode
root(&trans
);
336 EXPECT_EQ(BaseNode::INIT_OK
, root
.InitByTagLookup(
337 syncer::ModelTypeToRootTag(type
)));
339 // Loop through creating permanent nodes as necessary.
340 int64 last_child_id
= syncer::kInvalidId
;
341 for (int i
= 0; i
< kNumPermanentNodes
; ++i
) {
342 // First check if the node already exists. This is for tests that involve
343 // persistence and set up sync more than once.
344 syncer::ReadNode
lookup(&trans
);
345 if (lookup
.InitByTagLookup(permanent_tags
[i
]) ==
346 syncer::ReadNode::INIT_OK
) {
347 last_child_id
= lookup
.GetId();
351 // If it doesn't exist, create the permanent node at the end of the
353 syncer::ReadNode
predecessor_node(&trans
);
354 syncer::ReadNode
* predecessor
= NULL
;
355 if (last_child_id
!= syncer::kInvalidId
) {
356 EXPECT_EQ(BaseNode::INIT_OK
,
357 predecessor_node
.InitByIdLookup(last_child_id
));
358 predecessor
= &predecessor_node
;
360 syncer::WriteNode
node(&trans
);
361 if (!node
.InitByCreation(type
, root
, predecessor
))
363 node
.SetIsFolder(true);
364 node
.GetMutableEntryForTest()->Put(
365 syncer::syncable::UNIQUE_SERVER_TAG
, permanent_tags
[i
]);
366 node
.SetTitle(UTF8ToWide(permanent_tags
[i
]));
367 node
.SetExternalId(0);
368 last_child_id
= node
.GetId();
374 ASSERT_TRUE(CreatePermanentBookmarkNodes());
376 // Set up model associator.
377 model_associator_
.reset(new BookmarkModelAssociator(
378 BookmarkModelFactory::GetForProfile(&profile_
),
379 test_user_share_
.user_share(),
380 &mock_error_handler_
,
381 kExpectMobileBookmarks
));
383 local_merge_result_
= syncer::SyncMergeResult(syncer::BOOKMARKS
);
384 syncer_merge_result_
= syncer::SyncMergeResult(syncer::BOOKMARKS
);
385 int local_count_before
= model_
->root_node()->GetTotalNodeCount();
386 int syncer_count_before
= GetSyncBookmarkCount();
388 syncer::SyncError error
= model_associator_
->AssociateModels(
389 &local_merge_result_
,
390 &syncer_merge_result_
);
391 EXPECT_FALSE(error
.IsSet());
393 // Verify the merge results were calculated properly.
394 EXPECT_EQ(local_count_before
,
395 local_merge_result_
.num_items_before_association());
396 EXPECT_EQ(syncer_count_before
,
397 syncer_merge_result_
.num_items_before_association());
398 EXPECT_EQ(local_merge_result_
.num_items_after_association(),
399 local_merge_result_
.num_items_before_association() +
400 local_merge_result_
.num_items_added() -
401 local_merge_result_
.num_items_deleted());
402 EXPECT_EQ(syncer_merge_result_
.num_items_after_association(),
403 syncer_merge_result_
.num_items_before_association() +
404 syncer_merge_result_
.num_items_added() -
405 syncer_merge_result_
.num_items_deleted());
406 EXPECT_EQ(model_
->root_node()->GetTotalNodeCount(),
407 local_merge_result_
.num_items_after_association());
408 EXPECT_EQ(GetSyncBookmarkCount(),
409 syncer_merge_result_
.num_items_after_association());
411 MessageLoop::current()->RunAllPending();
413 // Set up change processor.
414 change_processor_
.reset(
415 new BookmarkChangeProcessor(model_associator_
.get(),
416 &mock_error_handler_
));
417 change_processor_
->Start(&profile_
, test_user_share_
.user_share());
421 change_processor_
.reset();
422 syncer::SyncError error
= model_associator_
->DisassociateModels();
423 EXPECT_FALSE(error
.IsSet());
424 model_associator_
.reset();
426 message_loop_
.RunAllPending();
428 // TODO(akalin): Actually close the database and flush it to disk
429 // (and make StartSync reload from disk). This would require
430 // refactoring TestUserShare.
433 void UnloadBookmarkModel() {
434 profile_
.CreateBookmarkModel(false /* delete_bookmarks */);
436 message_loop_
.RunAllPending();
439 bool InitSyncNodeFromChromeNode(const BookmarkNode
* bnode
,
440 syncer::BaseNode
* sync_node
) {
441 return model_associator_
->InitSyncNodeFromChromeId(bnode
->id(),
445 void ExpectSyncerNodeMatching(syncer::BaseTransaction
* trans
,
446 const BookmarkNode
* bnode
) {
447 syncer::ReadNode
gnode(trans
);
448 ASSERT_TRUE(InitSyncNodeFromChromeNode(bnode
, &gnode
));
449 // Non-root node titles and parents must match.
450 if (!model_
->is_permanent_node(bnode
)) {
451 EXPECT_EQ(bnode
->GetTitle(), UTF8ToUTF16(gnode
.GetTitle()));
453 model_associator_
->GetChromeNodeFromSyncId(gnode
.GetParentId()),
456 EXPECT_EQ(bnode
->is_folder(), gnode
.GetIsFolder());
458 EXPECT_EQ(bnode
->url(), gnode
.GetURL());
460 // Check for position matches.
461 int browser_index
= bnode
->parent()->GetIndexOf(bnode
);
462 if (browser_index
== 0) {
463 EXPECT_EQ(gnode
.GetPredecessorId(), 0);
465 const BookmarkNode
* bprev
=
466 bnode
->parent()->GetChild(browser_index
- 1);
467 syncer::ReadNode
gprev(trans
);
468 ASSERT_TRUE(InitSyncNodeFromChromeNode(bprev
, &gprev
));
469 EXPECT_EQ(gnode
.GetPredecessorId(), gprev
.GetId());
470 EXPECT_EQ(gnode
.GetParentId(), gprev
.GetParentId());
472 if (browser_index
== bnode
->parent()->child_count() - 1) {
473 EXPECT_EQ(gnode
.GetSuccessorId(), 0);
475 const BookmarkNode
* bnext
=
476 bnode
->parent()->GetChild(browser_index
+ 1);
477 syncer::ReadNode
gnext(trans
);
478 ASSERT_TRUE(InitSyncNodeFromChromeNode(bnext
, &gnext
));
479 EXPECT_EQ(gnode
.GetSuccessorId(), gnext
.GetId());
480 EXPECT_EQ(gnode
.GetParentId(), gnext
.GetParentId());
483 EXPECT_TRUE(gnode
.GetFirstChildId());
486 void ExpectSyncerNodeMatching(const BookmarkNode
* bnode
) {
487 syncer::ReadTransaction
trans(FROM_HERE
, test_user_share_
.user_share());
488 ExpectSyncerNodeMatching(&trans
, bnode
);
491 void ExpectBrowserNodeMatching(syncer::BaseTransaction
* trans
,
493 EXPECT_TRUE(sync_id
);
494 const BookmarkNode
* bnode
=
495 model_associator_
->GetChromeNodeFromSyncId(sync_id
);
497 int64 id
= model_associator_
->GetSyncIdFromChromeId(bnode
->id());
498 EXPECT_EQ(id
, sync_id
);
499 ExpectSyncerNodeMatching(trans
, bnode
);
502 void ExpectBrowserNodeUnknown(int64 sync_id
) {
503 EXPECT_FALSE(model_associator_
->GetChromeNodeFromSyncId(sync_id
));
506 void ExpectBrowserNodeKnown(int64 sync_id
) {
507 EXPECT_TRUE(model_associator_
->GetChromeNodeFromSyncId(sync_id
));
510 void ExpectSyncerNodeKnown(const BookmarkNode
* node
) {
511 int64 sync_id
= model_associator_
->GetSyncIdFromChromeId(node
->id());
512 EXPECT_NE(sync_id
, syncer::kInvalidId
);
515 void ExpectSyncerNodeUnknown(const BookmarkNode
* node
) {
516 int64 sync_id
= model_associator_
->GetSyncIdFromChromeId(node
->id());
517 EXPECT_EQ(sync_id
, syncer::kInvalidId
);
520 void ExpectBrowserNodeTitle(int64 sync_id
, const std::wstring
& title
) {
521 const BookmarkNode
* bnode
=
522 model_associator_
->GetChromeNodeFromSyncId(sync_id
);
524 EXPECT_EQ(bnode
->GetTitle(), WideToUTF16Hack(title
));
527 void ExpectBrowserNodeURL(int64 sync_id
, const std::string
& url
) {
528 const BookmarkNode
* bnode
=
529 model_associator_
->GetChromeNodeFromSyncId(sync_id
);
531 EXPECT_EQ(GURL(url
), bnode
->url());
534 void ExpectBrowserNodeParent(int64 sync_id
, int64 parent_sync_id
) {
535 const BookmarkNode
* node
=
536 model_associator_
->GetChromeNodeFromSyncId(sync_id
);
538 const BookmarkNode
* parent
=
539 model_associator_
->GetChromeNodeFromSyncId(parent_sync_id
);
541 EXPECT_EQ(node
->parent(), parent
);
544 void ExpectModelMatch(syncer::BaseTransaction
* trans
) {
545 const BookmarkNode
* root
= model_
->root_node();
546 EXPECT_EQ(root
->GetIndexOf(model_
->bookmark_bar_node()), 0);
547 EXPECT_EQ(root
->GetIndexOf(model_
->other_node()), 1);
548 EXPECT_EQ(root
->GetIndexOf(model_
->mobile_node()), 2);
550 std::stack
<int64
> stack
;
551 stack
.push(bookmark_bar_id());
552 while (!stack
.empty()) {
553 int64 id
= stack
.top();
557 ExpectBrowserNodeMatching(trans
, id
);
559 syncer::ReadNode
gnode(trans
);
560 ASSERT_EQ(BaseNode::INIT_OK
, gnode
.InitByIdLookup(id
));
561 stack
.push(gnode
.GetFirstChildId());
562 stack
.push(gnode
.GetSuccessorId());
566 void ExpectModelMatch() {
567 syncer::ReadTransaction
trans(FROM_HERE
, test_user_share_
.user_share());
568 ExpectModelMatch(&trans
);
571 int64
mobile_bookmarks_id() {
573 model_associator_
->GetSyncIdFromChromeId(model_
->mobile_node()->id());
576 int64
other_bookmarks_id() {
578 model_associator_
->GetSyncIdFromChromeId(model_
->other_node()->id());
581 int64
bookmark_bar_id() {
582 return model_associator_
->GetSyncIdFromChromeId(
583 model_
->bookmark_bar_node()->id());
587 BookmarkModel
* model_
;
588 syncer::TestUserShare test_user_share_
;
589 scoped_ptr
<BookmarkChangeProcessor
> change_processor_
;
590 StrictMock
<DataTypeErrorHandlerMock
> mock_error_handler_
;
591 scoped_ptr
<BookmarkModelAssociator
> model_associator_
;
594 // Used by both |ui_thread_| and |file_thread_|.
595 MessageLoop message_loop_
;
596 content::TestBrowserThread ui_thread_
;
597 // Needed by |model_|.
598 content::TestBrowserThread file_thread_
;
600 syncer::SyncMergeResult local_merge_result_
;
601 syncer::SyncMergeResult syncer_merge_result_
;
603 TestingProfile profile_
;
606 TEST_F(ProfileSyncServiceBookmarkTest
, InitialState
) {
607 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, DONT_SAVE_TO_STORAGE
);
610 EXPECT_TRUE(other_bookmarks_id());
611 EXPECT_TRUE(bookmark_bar_id());
612 EXPECT_TRUE(mobile_bookmarks_id());
617 TEST_F(ProfileSyncServiceBookmarkTest
, BookmarkModelOperations
) {
618 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, DONT_SAVE_TO_STORAGE
);
622 const BookmarkNode
* folder
=
623 model_
->AddFolder(model_
->other_node(), 0, ASCIIToUTF16("foobar"));
624 ExpectSyncerNodeMatching(folder
);
626 const BookmarkNode
* folder2
=
627 model_
->AddFolder(folder
, 0, ASCIIToUTF16("nested"));
628 ExpectSyncerNodeMatching(folder2
);
630 const BookmarkNode
* url1
= model_
->AddURL(
631 folder
, 0, ASCIIToUTF16("Internets #1 Pies Site"),
632 GURL("http://www.easypie.com/"));
633 ExpectSyncerNodeMatching(url1
);
635 const BookmarkNode
* url2
= model_
->AddURL(
636 folder
, 1, ASCIIToUTF16("Airplanes"), GURL("http://www.easyjet.com/"));
637 ExpectSyncerNodeMatching(url2
);
640 const BookmarkNode
* mobile_folder
=
641 model_
->AddFolder(model_
->mobile_node(), 0, ASCIIToUTF16("pie"));
642 ExpectSyncerNodeMatching(mobile_folder
);
645 // Test modification.
646 model_
->SetTitle(url2
, ASCIIToUTF16("EasyJet"));
648 model_
->Move(url1
, folder2
, 0);
650 model_
->Move(folder2
, model_
->bookmark_bar_node(), 0);
652 model_
->SetTitle(folder2
, ASCIIToUTF16("Not Nested"));
654 model_
->Move(folder
, folder2
, 0);
656 model_
->SetTitle(folder
, ASCIIToUTF16("who's nested now?"));
658 model_
->Copy(url2
, model_
->bookmark_bar_node(), 0);
660 model_
->SetTitle(mobile_folder
, ASCIIToUTF16("strawberry"));
664 // Delete a single item.
665 model_
->Remove(url2
->parent(), url2
->parent()->GetIndexOf(url2
));
667 // Delete an item with several children.
668 model_
->Remove(folder2
->parent(),
669 folder2
->parent()->GetIndexOf(folder2
));
671 model_
->Remove(model_
->mobile_node(), 0);
675 TEST_F(ProfileSyncServiceBookmarkTest
, ServerChangeProcessing
) {
676 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, DONT_SAVE_TO_STORAGE
);
679 syncer::WriteTransaction
trans(FROM_HERE
, test_user_share_
.user_share());
681 FakeServerChange
adds(&trans
);
682 int64 f1
= adds
.AddFolder(L
"Server Folder B", bookmark_bar_id(), 0);
683 int64 f2
= adds
.AddFolder(L
"Server Folder A", bookmark_bar_id(), f1
);
684 int64 u1
= adds
.AddURL(L
"Some old site", "ftp://nifty.andrew.cmu.edu/",
685 bookmark_bar_id(), f2
);
686 int64 u2
= adds
.AddURL(L
"Nifty", "ftp://nifty.andrew.cmu.edu/", f1
, 0);
687 // u3 is a duplicate URL
688 int64 u3
= adds
.AddURL(L
"Nifty2", "ftp://nifty.andrew.cmu.edu/", f1
, u2
);
689 // u4 is a duplicate title, different URL.
690 adds
.AddURL(L
"Some old site", "http://slog.thestranger.com/",
691 bookmark_bar_id(), u1
);
692 // u5 tests an empty-string title.
693 std::string
javascript_url(
694 "javascript:(function(){var w=window.open(" \
695 "'about:blank','gnotesWin','location=0,menubar=0," \
696 "scrollbars=0,status=0,toolbar=0,width=300," \
697 "height=300,resizable');});");
698 adds
.AddURL(L
"", javascript_url
, other_bookmarks_id(), 0);
699 int64 u6
= adds
.AddURL(L
"Sync1", "http://www.syncable.edu/",
700 mobile_bookmarks_id(), 0);
702 syncer::ChangeRecordList::const_iterator it
;
703 // The bookmark model shouldn't yet have seen any of the nodes of |adds|.
704 for (it
= adds
.changes().begin(); it
!= adds
.changes().end(); ++it
)
705 ExpectBrowserNodeUnknown(it
->id
);
707 adds
.ApplyPendingChanges(change_processor_
.get());
709 // Make sure the bookmark model received all of the nodes in |adds|.
710 for (it
= adds
.changes().begin(); it
!= adds
.changes().end(); ++it
)
711 ExpectBrowserNodeMatching(&trans
, it
->id
);
712 ExpectModelMatch(&trans
);
714 // Part two: test modifications.
715 FakeServerChange
mods(&trans
);
716 // Mess with u2, and move it into empty folder f2
717 // TODO(ncarter): Determine if we allow ModifyURL ops or not.
718 /* std::wstring u2_old_url = mods.ModifyURL(u2, L"http://www.google.com"); */
719 std::wstring u2_old_title
= mods
.ModifyTitle(u2
, L
"The Google");
720 int64 u2_old_parent
= mods
.ModifyPosition(u2
, f2
, 0);
722 // Now move f1 after u2.
723 std::wstring f1_old_title
= mods
.ModifyTitle(f1
, L
"Server Folder C");
724 int64 f1_old_parent
= mods
.ModifyPosition(f1
, f2
, u2
);
726 // Then add u3 after f1.
727 int64 u3_old_parent
= mods
.ModifyPosition(u3
, f2
, f1
);
729 std::wstring u6_old_title
= mods
.ModifyTitle(u6
, L
"Mobile Folder A");
731 // Test that the property changes have not yet taken effect.
732 ExpectBrowserNodeTitle(u2
, u2_old_title
);
733 /* ExpectBrowserNodeURL(u2, u2_old_url); */
734 ExpectBrowserNodeParent(u2
, u2_old_parent
);
736 ExpectBrowserNodeTitle(f1
, f1_old_title
);
737 ExpectBrowserNodeParent(f1
, f1_old_parent
);
739 ExpectBrowserNodeParent(u3
, u3_old_parent
);
741 ExpectBrowserNodeTitle(u6
, u6_old_title
);
743 // Apply the changes.
744 mods
.ApplyPendingChanges(change_processor_
.get());
746 // Check for successful application.
747 for (it
= mods
.changes().begin(); it
!= mods
.changes().end(); ++it
)
748 ExpectBrowserNodeMatching(&trans
, it
->id
);
749 ExpectModelMatch(&trans
);
751 // Part 3: Test URL deletion.
752 FakeServerChange
dels(&trans
);
757 ExpectBrowserNodeKnown(u2
);
758 ExpectBrowserNodeKnown(u3
);
760 dels
.ApplyPendingChanges(change_processor_
.get());
762 ExpectBrowserNodeUnknown(u2
);
763 ExpectBrowserNodeUnknown(u3
);
764 ExpectBrowserNodeUnknown(u6
);
765 ExpectModelMatch(&trans
);
768 // Tests a specific case in ApplyModelChanges where we move the
769 // children out from under a parent, and then delete the parent
770 // in the same changelist. The delete shows up first in the changelist,
771 // requiring the children to be moved to a temporary location.
772 TEST_F(ProfileSyncServiceBookmarkTest
, ServerChangeRequiringFosterParent
) {
773 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, DONT_SAVE_TO_STORAGE
);
776 syncer::WriteTransaction
trans(FROM_HERE
, test_user_share_
.user_share());
778 // Stress the immediate children of other_node because that's where
779 // ApplyModelChanges puts a temporary foster parent node.
780 std::string
url("http://dev.chromium.org/");
781 FakeServerChange
adds(&trans
);
782 int64 f0
= other_bookmarks_id(); // + other_node
783 int64 f1
= adds
.AddFolder(L
"f1", f0
, 0); // + f1
784 int64 f2
= adds
.AddFolder(L
"f2", f1
, 0); // + f2
785 int64 u3
= adds
.AddURL( L
"u3", url
, f2
, 0); // + u3 NOLINT
786 int64 u4
= adds
.AddURL( L
"u4", url
, f2
, u3
); // + u4 NOLINT
787 int64 u5
= adds
.AddURL( L
"u5", url
, f1
, f2
); // + u5 NOLINT
788 int64 f6
= adds
.AddFolder(L
"f6", f1
, u5
); // + f6
789 int64 u7
= adds
.AddURL( L
"u7", url
, f0
, f1
); // + u7 NOLINT
791 syncer::ChangeRecordList::const_iterator it
;
792 // The bookmark model shouldn't yet have seen any of the nodes of |adds|.
793 for (it
= adds
.changes().begin(); it
!= adds
.changes().end(); ++it
)
794 ExpectBrowserNodeUnknown(it
->id
);
796 adds
.ApplyPendingChanges(change_processor_
.get());
798 // Make sure the bookmark model received all of the nodes in |adds|.
799 for (it
= adds
.changes().begin(); it
!= adds
.changes().end(); ++it
)
800 ExpectBrowserNodeMatching(&trans
, it
->id
);
801 ExpectModelMatch(&trans
);
803 // We have to do the moves before the deletions, but FakeServerChange will
804 // put the deletion at the front of the changelist.
805 FakeServerChange
ops(&trans
);
806 ops
.ModifyPosition(f6
, other_bookmarks_id(), 0);
807 ops
.ModifyPosition(u3
, other_bookmarks_id(), f1
); // Prev == f1 is OK here.
808 ops
.ModifyPosition(f2
, other_bookmarks_id(), u7
);
809 ops
.ModifyPosition(u7
, f2
, 0);
810 ops
.ModifyPosition(u4
, other_bookmarks_id(), f2
);
811 ops
.ModifyPosition(u5
, f6
, 0);
814 ops
.ApplyPendingChanges(change_processor_
.get());
816 ExpectModelMatch(&trans
);
819 // Simulate a server change record containing a valid but non-canonical URL.
820 TEST_F(ProfileSyncServiceBookmarkTest
, ServerChangeWithNonCanonicalURL
) {
821 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, SAVE_TO_STORAGE
);
825 syncer::WriteTransaction
trans(FROM_HERE
, test_user_share_
.user_share());
827 FakeServerChange
adds(&trans
);
828 std::string
url("http://dev.chromium.org");
829 EXPECT_NE(GURL(url
).spec(), url
);
830 adds
.AddURL(L
"u1", url
, other_bookmarks_id(), 0);
832 adds
.ApplyPendingChanges(change_processor_
.get());
834 EXPECT_TRUE(model_
->other_node()->child_count() == 1);
835 ExpectModelMatch(&trans
);
838 // Now reboot the sync service, forcing a merge step.
840 LoadBookmarkModel(LOAD_FROM_STORAGE
, SAVE_TO_STORAGE
);
843 // There should still be just the one bookmark.
844 EXPECT_TRUE(model_
->other_node()->child_count() == 1);
848 // Simulate a server change record containing an invalid URL (per GURL).
849 // TODO(ncarter): Disabled due to crashes. Fix bug 1677563.
850 TEST_F(ProfileSyncServiceBookmarkTest
, DISABLED_ServerChangeWithInvalidURL
) {
851 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, SAVE_TO_STORAGE
);
856 syncer::WriteTransaction
trans(FROM_HERE
, test_user_share_
.user_share());
858 FakeServerChange
adds(&trans
);
859 std::string
url("x");
860 EXPECT_FALSE(GURL(url
).is_valid());
861 adds
.AddURL(L
"u1", url
, other_bookmarks_id(), 0);
863 adds
.ApplyPendingChanges(change_processor_
.get());
865 // We're lenient about what should happen -- the model could wind up with
866 // the node or without it; but things should be consistent, and we
868 child_count
= model_
->other_node()->child_count();
869 EXPECT_TRUE(child_count
== 0 || child_count
== 1);
870 ExpectModelMatch(&trans
);
873 // Now reboot the sync service, forcing a merge step.
875 LoadBookmarkModel(LOAD_FROM_STORAGE
, SAVE_TO_STORAGE
);
878 // Things ought not to have changed.
879 EXPECT_EQ(model_
->other_node()->child_count(), child_count
);
884 // Test strings that might pose a problem if the titles ever became used as
885 // file names in the sync backend.
886 TEST_F(ProfileSyncServiceBookmarkTest
, CornerCaseNames
) {
887 // TODO(ncarter): Bug 1570238 explains the failure of this test.
888 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, DONT_SAVE_TO_STORAGE
);
891 const char* names
[] = {
894 // Illegal Windows filenames.
895 "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4",
896 "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3",
897 "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
898 // Current/parent directory markers.
900 // Files created automatically by the Windows shell.
901 "Thumbs.db", ".DS_Store",
902 // Names including Win32-illegal characters, and path separators.
903 "foo/bar", "foo\\bar", "foo?bar", "foo:bar", "foo|bar", "foo\"bar",
904 "foo'bar", "foo<bar", "foo>bar", "foo%bar", "foo*bar", "foo]bar",
907 // Create both folders and bookmarks using each name.
908 GURL
url("http://www.doublemint.com");
909 for (size_t i
= 0; i
< arraysize(names
); ++i
) {
910 model_
->AddFolder(model_
->other_node(), 0, ASCIIToUTF16(names
[i
]));
911 model_
->AddURL(model_
->other_node(), 0, ASCIIToUTF16(names
[i
]), url
);
914 // Verify that the browser model matches the sync model.
915 EXPECT_TRUE(model_
->other_node()->child_count() == 2*arraysize(names
));
919 // Stress the internal representation of position by sparse numbers. We want
920 // to repeatedly bisect the range of available positions, to force the
921 // syncer code to renumber its ranges. Pick a number big enough so that it
922 // would exhaust 32bits of room between items a couple of times.
923 TEST_F(ProfileSyncServiceBookmarkTest
, RepeatedMiddleInsertion
) {
924 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, DONT_SAVE_TO_STORAGE
);
927 static const int kTimesToInsert
= 256;
929 // Create two book-end nodes to insert between.
930 model_
->AddFolder(model_
->other_node(), 0, ASCIIToUTF16("Alpha"));
931 model_
->AddFolder(model_
->other_node(), 1, ASCIIToUTF16("Omega"));
934 // Test insertion in first half of range by repeatedly inserting in second
936 for (int i
= 0; i
< kTimesToInsert
; ++i
) {
937 string16 title
= ASCIIToUTF16("Pre-insertion ") + base::IntToString16(i
);
938 model_
->AddFolder(model_
->other_node(), 1, title
);
942 // Test insertion in second half of range by repeatedly inserting in
943 // second-to-last position.
944 for (int i
= 0; i
< kTimesToInsert
; ++i
) {
945 string16 title
= ASCIIToUTF16("Post-insertion ") + base::IntToString16(i
);
946 model_
->AddFolder(model_
->other_node(), count
- 1, title
);
950 // Verify that the browser model matches the sync model.
951 EXPECT_EQ(model_
->other_node()->child_count(), count
);
955 // Introduce a consistency violation into the model, and see that it
956 // puts itself into a lame, error state.
957 TEST_F(ProfileSyncServiceBookmarkTest
, UnrecoverableErrorSuspendsService
) {
958 EXPECT_CALL(mock_error_handler_
,
959 OnSingleDatatypeUnrecoverableError(_
, _
));
961 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, DONT_SAVE_TO_STORAGE
);
964 // Add a node which will be the target of the consistency violation.
965 const BookmarkNode
* node
=
966 model_
->AddFolder(model_
->other_node(), 0, ASCIIToUTF16("node"));
967 ExpectSyncerNodeMatching(node
);
969 // Now destroy the syncer node as if we were the ProfileSyncService without
970 // updating the ProfileSyncService state. This should introduce
971 // inconsistency between the two models.
973 syncer::WriteTransaction
trans(FROM_HERE
, test_user_share_
.user_share());
974 syncer::WriteNode
sync_node(&trans
);
975 ASSERT_TRUE(InitSyncNodeFromChromeNode(node
, &sync_node
));
978 // The models don't match at this point, but the ProfileSyncService
979 // doesn't know it yet.
980 ExpectSyncerNodeKnown(node
);
982 // Add a child to the inconsistent node. This should cause detection of the
983 // problem and the syncer should stop processing changes.
984 model_
->AddFolder(node
, 0, ASCIIToUTF16("nested"));
987 // See what happens if we run model association when there are two exact URL
988 // duplicate bookmarks. The BookmarkModelAssociator should not fall over when
990 TEST_F(ProfileSyncServiceBookmarkTest
, MergeDuplicates
) {
991 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, SAVE_TO_STORAGE
);
994 model_
->AddURL(model_
->other_node(), 0, ASCIIToUTF16("Dup"),
995 GURL("http://dup.com/"));
996 model_
->AddURL(model_
->other_node(), 0, ASCIIToUTF16("Dup"),
997 GURL("http://dup.com/"));
999 EXPECT_EQ(2, model_
->other_node()->child_count());
1001 // Restart the sync service to trigger model association.
1005 EXPECT_EQ(2, model_
->other_node()->child_count());
1010 const wchar_t* title
;
1014 // Map from bookmark node ID to its version.
1015 typedef std::map
<int64
, int64
> BookmarkNodeVersionMap
;
1017 // TODO(ncarter): Integrate the existing TestNode/PopulateNodeFromString code
1018 // in the bookmark model unittest, to make it simpler to set up test data
1019 // here (and reduce the amount of duplication among tests), and to reduce the
1021 class ProfileSyncServiceBookmarkTestWithData
1022 : public ProfileSyncServiceBookmarkTest
{
1024 ProfileSyncServiceBookmarkTestWithData();
1027 // Populates or compares children of the given bookmark node from/with the
1028 // given test data array with the given size. |running_count| is updated as
1029 // urls are added. It is used to set the creation date (or test the creation
1030 // date for CompareWithTestData()).
1031 void PopulateFromTestData(const BookmarkNode
* node
,
1032 const TestData
* data
,
1034 int* running_count
);
1035 void CompareWithTestData(const BookmarkNode
* node
,
1036 const TestData
* data
,
1038 int* running_count
);
1040 void ExpectBookmarkModelMatchesTestData();
1041 void WriteTestDataToBookmarkModel();
1043 // Verify transaction versions of bookmark nodes and sync nodes are equal
1044 // recursively. If node is in |version_expected|, versions should match
1046 void ExpectTransactionVersionMatch(
1047 const BookmarkNode
* node
,
1048 const BookmarkNodeVersionMap
& version_expected
);
1051 const base::Time start_time_
;
1053 DISALLOW_COPY_AND_ASSIGN(ProfileSyncServiceBookmarkTestWithData
);
1058 // Constants for bookmark model that looks like:
1060 // | |-- u2, http://www.u2.com/
1062 // | | |-- f1u4, http://www.f1u4.com/
1063 // | | |-- f1u2, http://www.f1u2.com/
1064 // | | |-- f1u3, http://www.f1u3.com/
1065 // | | +-- f1u1, http://www.f1u1.com/
1066 // | |-- u1, http://www.u1.com/
1068 // | |-- f2u2, http://www.f2u2.com/
1069 // | |-- f2u4, http://www.f2u4.com/
1070 // | |-- f2u3, http://www.f2u3.com/
1071 // | +-- f2u1, http://www.f2u1.com/
1072 // +-- Other bookmarks
1074 // | | |-- f3u4, http://www.f3u4.com/
1075 // | | |-- f3u2, http://www.f3u2.com/
1076 // | | |-- f3u3, http://www.f3u3.com/
1077 // | | +-- f3u1, http://www.f3u1.com/
1078 // | |-- u4, http://www.u4.com/
1079 // | |-- u3, http://www.u3.com/
1081 // | | |-- f4u1, http://www.f4u1.com/
1082 // | | |-- f4u2, http://www.f4u2.com/
1083 // | | |-- f4u3, http://www.f4u3.com/
1084 // | | +-- f4u4, http://www.f4u4.com/
1086 // | | +-- dupu1, http://www.dupu1.com/
1088 // | | +-- dupu2, http://www.dupu1.com/
1089 // | +-- ls , http://www.ls.com/
1091 // +-- Mobile bookmarks
1093 // | |-- f5u1, http://www.f5u1.com/
1095 // | |-- f6u1, http://www.f6u1.com/
1096 // | |-- f6u2, http://www.f6u2.com/
1097 // +-- u5, http://www.u5.com/
1099 static TestData kBookmarkBarChildren
[] = {
1100 { L
"u2", "http://www.u2.com/" },
1102 { L
"u1", "http://www.u1.com/" },
1105 static TestData kF1Children
[] = {
1106 { L
"f1u4", "http://www.f1u4.com/" },
1107 { L
"f1u2", "http://www.f1u2.com/" },
1108 { L
"f1u3", "http://www.f1u3.com/" },
1109 { L
"f1u1", "http://www.f1u1.com/" },
1111 static TestData kF2Children
[] = {
1112 { L
"f2u2", "http://www.f2u2.com/" },
1113 { L
"f2u4", "http://www.f2u4.com/" },
1114 { L
"f2u3", "http://www.f2u3.com/" },
1115 { L
"f2u1", "http://www.f2u1.com/" },
1118 static TestData kOtherBookmarkChildren
[] = {
1120 { L
"u4", "http://www.u4.com/" },
1121 { L
"u3", "http://www.u3.com/" },
1125 { L
" ls ", "http://www.ls.com/" }
1127 static TestData kF3Children
[] = {
1128 { L
"f3u4", "http://www.f3u4.com/" },
1129 { L
"f3u2", "http://www.f3u2.com/" },
1130 { L
"f3u3", "http://www.f3u3.com/" },
1131 { L
"f3u1", "http://www.f3u1.com/" },
1133 static TestData kF4Children
[] = {
1134 { L
"f4u1", "http://www.f4u1.com/" },
1135 { L
"f4u2", "http://www.f4u2.com/" },
1136 { L
"f4u3", "http://www.f4u3.com/" },
1137 { L
"f4u4", "http://www.f4u4.com/" },
1139 static TestData kDup1Children
[] = {
1140 { L
"dupu1", "http://www.dupu1.com/" },
1142 static TestData kDup2Children
[] = {
1143 { L
"dupu2", "http://www.dupu2.com/" },
1146 static TestData kMobileBookmarkChildren
[] = {
1149 { L
"u5", "http://www.u5.com/" },
1151 static TestData kF5Children
[] = {
1152 { L
"f5u1", "http://www.f5u1.com/" },
1153 { L
"f5u2", "http://www.f5u2.com/" },
1155 static TestData kF6Children
[] = {
1156 { L
"f6u1", "http://www.f6u1.com/" },
1157 { L
"f6u2", "http://www.f6u2.com/" },
1160 } // anonymous namespace.
1162 ProfileSyncServiceBookmarkTestWithData::
1163 ProfileSyncServiceBookmarkTestWithData()
1164 : start_time_(base::Time::Now()) {
1167 void ProfileSyncServiceBookmarkTestWithData::PopulateFromTestData(
1168 const BookmarkNode
* node
,
1169 const TestData
* data
,
1171 int* running_count
) {
1174 DCHECK(node
->is_folder());
1175 for (int i
= 0; i
< size
; ++i
) {
1176 const TestData
& item
= data
[i
];
1178 const base::Time add_time
=
1179 start_time_
+ base::TimeDelta::FromMinutes(*running_count
);
1180 model_
->AddURLWithCreationTime(node
, i
, WideToUTF16Hack(item
.title
),
1181 GURL(item
.url
), add_time
);
1183 model_
->AddFolder(node
, i
, WideToUTF16Hack(item
.title
));
1189 void ProfileSyncServiceBookmarkTestWithData::CompareWithTestData(
1190 const BookmarkNode
* node
,
1191 const TestData
* data
,
1193 int* running_count
) {
1196 DCHECK(node
->is_folder());
1197 ASSERT_EQ(size
, node
->child_count());
1198 for (int i
= 0; i
< size
; ++i
) {
1199 const BookmarkNode
* child_node
= node
->GetChild(i
);
1200 const TestData
& item
= data
[i
];
1201 GURL url
= GURL(item
.url
== NULL
? "" : item
.url
);
1202 BookmarkNode
test_node(url
);
1203 test_node
.SetTitle(WideToUTF16Hack(item
.title
));
1204 EXPECT_EQ(child_node
->GetTitle(), test_node
.GetTitle());
1206 EXPECT_FALSE(child_node
->is_folder());
1207 EXPECT_TRUE(child_node
->is_url());
1208 EXPECT_EQ(child_node
->url(), test_node
.url());
1209 const base::Time expected_time
=
1210 start_time_
+ base::TimeDelta::FromMinutes(*running_count
);
1211 EXPECT_EQ(expected_time
.ToInternalValue(),
1212 child_node
->date_added().ToInternalValue());
1214 EXPECT_TRUE(child_node
->is_folder());
1215 EXPECT_FALSE(child_node
->is_url());
1221 // TODO(munjal): We should implement some way of generating random data and can
1222 // use the same seed to generate the same sequence.
1223 void ProfileSyncServiceBookmarkTestWithData::WriteTestDataToBookmarkModel() {
1224 const BookmarkNode
* bookmarks_bar_node
= model_
->bookmark_bar_node();
1226 PopulateFromTestData(bookmarks_bar_node
,
1227 kBookmarkBarChildren
,
1228 arraysize(kBookmarkBarChildren
),
1231 ASSERT_GE(bookmarks_bar_node
->child_count(), 4);
1232 const BookmarkNode
* f1_node
= bookmarks_bar_node
->GetChild(1);
1233 PopulateFromTestData(f1_node
, kF1Children
, arraysize(kF1Children
), &count
);
1234 const BookmarkNode
* f2_node
= bookmarks_bar_node
->GetChild(3);
1235 PopulateFromTestData(f2_node
, kF2Children
, arraysize(kF2Children
), &count
);
1237 const BookmarkNode
* other_bookmarks_node
= model_
->other_node();
1238 PopulateFromTestData(other_bookmarks_node
,
1239 kOtherBookmarkChildren
,
1240 arraysize(kOtherBookmarkChildren
),
1243 ASSERT_GE(other_bookmarks_node
->child_count(), 6);
1244 const BookmarkNode
* f3_node
= other_bookmarks_node
->GetChild(0);
1245 PopulateFromTestData(f3_node
, kF3Children
, arraysize(kF3Children
), &count
);
1246 const BookmarkNode
* f4_node
= other_bookmarks_node
->GetChild(3);
1247 PopulateFromTestData(f4_node
, kF4Children
, arraysize(kF4Children
), &count
);
1248 const BookmarkNode
* dup_node
= other_bookmarks_node
->GetChild(4);
1249 PopulateFromTestData(dup_node
, kDup1Children
, arraysize(kDup1Children
),
1251 dup_node
= other_bookmarks_node
->GetChild(5);
1252 PopulateFromTestData(dup_node
, kDup2Children
, arraysize(kDup2Children
),
1255 const BookmarkNode
* mobile_bookmarks_node
= model_
->mobile_node();
1256 PopulateFromTestData(mobile_bookmarks_node
,
1257 kMobileBookmarkChildren
,
1258 arraysize(kMobileBookmarkChildren
),
1261 ASSERT_GE(mobile_bookmarks_node
->child_count(), 3);
1262 const BookmarkNode
* f5_node
= mobile_bookmarks_node
->GetChild(0);
1263 PopulateFromTestData(f5_node
, kF5Children
, arraysize(kF5Children
), &count
);
1264 const BookmarkNode
* f6_node
= mobile_bookmarks_node
->GetChild(1);
1265 PopulateFromTestData(f6_node
, kF6Children
, arraysize(kF6Children
), &count
);
1267 ExpectBookmarkModelMatchesTestData();
1270 void ProfileSyncServiceBookmarkTestWithData::
1271 ExpectBookmarkModelMatchesTestData() {
1272 const BookmarkNode
* bookmark_bar_node
= model_
->bookmark_bar_node();
1274 CompareWithTestData(bookmark_bar_node
,
1275 kBookmarkBarChildren
,
1276 arraysize(kBookmarkBarChildren
),
1279 ASSERT_GE(bookmark_bar_node
->child_count(), 4);
1280 const BookmarkNode
* f1_node
= bookmark_bar_node
->GetChild(1);
1281 CompareWithTestData(f1_node
, kF1Children
, arraysize(kF1Children
), &count
);
1282 const BookmarkNode
* f2_node
= bookmark_bar_node
->GetChild(3);
1283 CompareWithTestData(f2_node
, kF2Children
, arraysize(kF2Children
), &count
);
1285 const BookmarkNode
* other_bookmarks_node
= model_
->other_node();
1286 CompareWithTestData(other_bookmarks_node
,
1287 kOtherBookmarkChildren
,
1288 arraysize(kOtherBookmarkChildren
),
1291 ASSERT_GE(other_bookmarks_node
->child_count(), 6);
1292 const BookmarkNode
* f3_node
= other_bookmarks_node
->GetChild(0);
1293 CompareWithTestData(f3_node
, kF3Children
, arraysize(kF3Children
), &count
);
1294 const BookmarkNode
* f4_node
= other_bookmarks_node
->GetChild(3);
1295 CompareWithTestData(f4_node
, kF4Children
, arraysize(kF4Children
), &count
);
1296 const BookmarkNode
* dup_node
= other_bookmarks_node
->GetChild(4);
1297 CompareWithTestData(dup_node
, kDup1Children
, arraysize(kDup1Children
),
1299 dup_node
= other_bookmarks_node
->GetChild(5);
1300 CompareWithTestData(dup_node
, kDup2Children
, arraysize(kDup2Children
),
1303 const BookmarkNode
* mobile_bookmarks_node
= model_
->mobile_node();
1304 CompareWithTestData(mobile_bookmarks_node
,
1305 kMobileBookmarkChildren
,
1306 arraysize(kMobileBookmarkChildren
),
1309 ASSERT_GE(mobile_bookmarks_node
->child_count(), 3);
1310 const BookmarkNode
* f5_node
= mobile_bookmarks_node
->GetChild(0);
1311 CompareWithTestData(f5_node
, kF5Children
, arraysize(kF5Children
), &count
);
1312 const BookmarkNode
* f6_node
= mobile_bookmarks_node
->GetChild(1);
1313 CompareWithTestData(f6_node
, kF6Children
, arraysize(kF6Children
), &count
);
1317 // Tests persistence of the profile sync service by unloading the
1318 // database and then reloading it from disk.
1319 TEST_F(ProfileSyncServiceBookmarkTestWithData
, Persistence
) {
1320 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, SAVE_TO_STORAGE
);
1323 WriteTestDataToBookmarkModel();
1327 // Force both models to discard their data and reload from disk. This
1328 // simulates what would happen if the browser were to shutdown normally,
1329 // and then relaunch.
1331 UnloadBookmarkModel();
1332 LoadBookmarkModel(LOAD_FROM_STORAGE
, SAVE_TO_STORAGE
);
1335 ExpectBookmarkModelMatchesTestData();
1337 // With the BookmarkModel contents verified, ExpectModelMatch will
1338 // verify the contents of the sync model.
1342 // Tests the merge case when the BookmarkModel is non-empty but the
1343 // sync model is empty. This corresponds to uploading browser
1344 // bookmarks to an initially empty, new account.
1345 TEST_F(ProfileSyncServiceBookmarkTestWithData
, MergeWithEmptySyncModel
) {
1346 // Don't start the sync service until we've populated the bookmark model.
1347 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, SAVE_TO_STORAGE
);
1349 WriteTestDataToBookmarkModel();
1351 // Restart sync. This should trigger a merge step during
1352 // initialization -- we expect the browser bookmarks to be written
1353 // to the sync service during this call.
1356 // Verify that the bookmark model hasn't changed, and that the sync model
1357 // matches it exactly.
1358 ExpectBookmarkModelMatchesTestData();
1362 // Tests the merge case when the BookmarkModel is empty but the sync model is
1363 // non-empty. This corresponds (somewhat) to a clean install of the browser,
1364 // with no bookmarks, connecting to a sync account that has some bookmarks.
1365 TEST_F(ProfileSyncServiceBookmarkTestWithData
, MergeWithEmptyBookmarkModel
) {
1366 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, DONT_SAVE_TO_STORAGE
);
1369 WriteTestDataToBookmarkModel();
1373 // Force the databse to unload and write itself to disk.
1376 // Blow away the bookmark model -- it should be empty afterwards.
1377 UnloadBookmarkModel();
1378 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, DONT_SAVE_TO_STORAGE
);
1379 EXPECT_EQ(model_
->bookmark_bar_node()->child_count(), 0);
1380 EXPECT_EQ(model_
->other_node()->child_count(), 0);
1381 EXPECT_EQ(model_
->mobile_node()->child_count(), 0);
1383 // Now restart the sync service. Starting it should populate the bookmark
1384 // model -- test for consistency.
1386 ExpectBookmarkModelMatchesTestData();
1390 // Tests the merge cases when both the models are expected to be identical
1392 TEST_F(ProfileSyncServiceBookmarkTestWithData
, MergeExpectedIdenticalModels
) {
1393 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, SAVE_TO_STORAGE
);
1395 WriteTestDataToBookmarkModel();
1398 UnloadBookmarkModel();
1400 // At this point both the bookmark model and the server should have the
1401 // exact same data and it should match the test data.
1402 LoadBookmarkModel(LOAD_FROM_STORAGE
, DONT_SAVE_TO_STORAGE
);
1404 ExpectBookmarkModelMatchesTestData();
1407 UnloadBookmarkModel();
1409 // Now reorder some bookmarks in the bookmark model and then merge. Make
1410 // sure we get the order of the server after merge.
1411 LoadBookmarkModel(LOAD_FROM_STORAGE
, DONT_SAVE_TO_STORAGE
);
1412 ExpectBookmarkModelMatchesTestData();
1413 const BookmarkNode
* bookmark_bar
= model_
->bookmark_bar_node();
1414 ASSERT_TRUE(bookmark_bar
);
1415 ASSERT_GT(bookmark_bar
->child_count(), 1);
1416 model_
->Move(bookmark_bar
->GetChild(0), bookmark_bar
, 1);
1419 ExpectBookmarkModelMatchesTestData();
1422 // Tests the merge cases when both the models are expected to be identical
1424 TEST_F(ProfileSyncServiceBookmarkTestWithData
, MergeModelsWithSomeExtras
) {
1425 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, DONT_SAVE_TO_STORAGE
);
1426 WriteTestDataToBookmarkModel();
1427 ExpectBookmarkModelMatchesTestData();
1429 // Remove some nodes and reorder some nodes.
1430 const BookmarkNode
* bookmark_bar_node
= model_
->bookmark_bar_node();
1431 int remove_index
= 2;
1432 ASSERT_GT(bookmark_bar_node
->child_count(), remove_index
);
1433 const BookmarkNode
* child_node
= bookmark_bar_node
->GetChild(remove_index
);
1434 ASSERT_TRUE(child_node
);
1435 ASSERT_TRUE(child_node
->is_url());
1436 model_
->Remove(bookmark_bar_node
, remove_index
);
1437 ASSERT_GT(bookmark_bar_node
->child_count(), remove_index
);
1438 child_node
= bookmark_bar_node
->GetChild(remove_index
);
1439 ASSERT_TRUE(child_node
);
1440 ASSERT_TRUE(child_node
->is_folder());
1441 model_
->Remove(bookmark_bar_node
, remove_index
);
1443 const BookmarkNode
* other_node
= model_
->other_node();
1444 ASSERT_GE(other_node
->child_count(), 1);
1445 const BookmarkNode
* f3_node
= other_node
->GetChild(0);
1446 ASSERT_TRUE(f3_node
);
1447 ASSERT_TRUE(f3_node
->is_folder());
1449 ASSERT_GT(f3_node
->child_count(), remove_index
);
1450 model_
->Remove(f3_node
, remove_index
);
1451 ASSERT_GT(f3_node
->child_count(), remove_index
);
1452 model_
->Remove(f3_node
, remove_index
);
1458 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, DONT_SAVE_TO_STORAGE
);
1459 WriteTestDataToBookmarkModel();
1460 ExpectBookmarkModelMatchesTestData();
1462 // Remove some nodes and reorder some nodes.
1463 bookmark_bar_node
= model_
->bookmark_bar_node();
1465 ASSERT_GT(bookmark_bar_node
->child_count(), remove_index
);
1466 child_node
= bookmark_bar_node
->GetChild(remove_index
);
1467 ASSERT_TRUE(child_node
);
1468 ASSERT_TRUE(child_node
->is_url());
1469 model_
->Remove(bookmark_bar_node
, remove_index
);
1470 ASSERT_GT(bookmark_bar_node
->child_count(), remove_index
);
1471 child_node
= bookmark_bar_node
->GetChild(remove_index
);
1472 ASSERT_TRUE(child_node
);
1473 ASSERT_TRUE(child_node
->is_folder());
1474 model_
->Remove(bookmark_bar_node
, remove_index
);
1476 ASSERT_GE(bookmark_bar_node
->child_count(), 2);
1477 model_
->Move(bookmark_bar_node
->GetChild(0), bookmark_bar_node
, 1);
1479 other_node
= model_
->other_node();
1480 ASSERT_GE(other_node
->child_count(), 1);
1481 f3_node
= other_node
->GetChild(0);
1482 ASSERT_TRUE(f3_node
);
1483 ASSERT_TRUE(f3_node
->is_folder());
1485 ASSERT_GT(f3_node
->child_count(), remove_index
);
1486 model_
->Remove(f3_node
, remove_index
);
1487 ASSERT_GT(f3_node
->child_count(), remove_index
);
1488 model_
->Remove(f3_node
, remove_index
);
1490 ASSERT_GE(other_node
->child_count(), 4);
1491 model_
->Move(other_node
->GetChild(0), other_node
, 1);
1492 model_
->Move(other_node
->GetChild(2), other_node
, 3);
1497 // After the merge, the model should match the test data.
1498 ExpectBookmarkModelMatchesTestData();
1501 // Tests that when persisted model associations are used, things work fine.
1502 TEST_F(ProfileSyncServiceBookmarkTestWithData
, ModelAssociationPersistence
) {
1503 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, DONT_SAVE_TO_STORAGE
);
1504 WriteTestDataToBookmarkModel();
1507 // Force sync to shut down and write itself to disk.
1509 // Now restart sync. This time it should use the persistent
1515 // Tests that when persisted model associations are used, things work fine.
1516 TEST_F(ProfileSyncServiceBookmarkTestWithData
,
1517 ModelAssociationInvalidPersistence
) {
1518 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, DONT_SAVE_TO_STORAGE
);
1519 WriteTestDataToBookmarkModel();
1522 // Force sync to shut down and write itself to disk.
1524 // Change the bookmark model before restarting sync service to simulate
1525 // the situation where bookmark model is different from sync model and
1526 // make sure model associator correctly rebuilds associations.
1527 const BookmarkNode
* bookmark_bar_node
= model_
->bookmark_bar_node();
1528 model_
->AddURL(bookmark_bar_node
, 0, ASCIIToUTF16("xtra"),
1529 GURL("http://www.xtra.com"));
1530 // Now restart sync. This time it will try to use the persistent
1531 // associations and realize that they are invalid and hence will rebuild
1537 TEST_F(ProfileSyncServiceBookmarkTestWithData
, SortChildren
) {
1538 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, DONT_SAVE_TO_STORAGE
);
1541 // Write test data to bookmark model and verify that the models match.
1542 WriteTestDataToBookmarkModel();
1543 const BookmarkNode
* folder_added
= model_
->other_node()->GetChild(0);
1544 ASSERT_TRUE(folder_added
);
1545 ASSERT_TRUE(folder_added
->is_folder());
1549 // Sort the other-bookmarks children and expect that hte models match.
1550 model_
->SortChildren(folder_added
);
1554 // See what happens if we enable sync but then delete the "Sync Data"
1556 TEST_F(ProfileSyncServiceBookmarkTestWithData
,
1557 RecoverAfterDeletingSyncDataDirectory
) {
1558 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, SAVE_TO_STORAGE
);
1561 WriteTestDataToBookmarkModel();
1565 // Nuke the sync DB and reload.
1571 // Make sure we're back in sync. In real life, the user would need
1572 // to reauthenticate before this happens, but in the test, authentication
1574 ExpectBookmarkModelMatchesTestData();
1578 // Verify that the bookmark model is updated about whether the
1579 // associator is currently running.
1580 TEST_F(ProfileSyncServiceBookmarkTest
, AssociationState
) {
1581 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, DONT_SAVE_TO_STORAGE
);
1583 ExtensiveChangesBookmarkModelObserver observer
;
1584 model_
->AddObserver(&observer
);
1588 EXPECT_EQ(1, observer
.get_started());
1589 EXPECT_EQ(0, observer
.get_completed_count_at_started());
1590 EXPECT_EQ(1, observer
.get_completed());
1592 model_
->RemoveObserver(&observer
);
1595 // Verify that the creation_time_us changes are applied in the local model at
1596 // association time and update time.
1597 TEST_F(ProfileSyncServiceBookmarkTestWithData
, UpdateDateAdded
) {
1598 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, DONT_SAVE_TO_STORAGE
);
1599 WriteTestDataToBookmarkModel();
1601 // Start and stop sync in order to create bookmark nodes in the sync db.
1605 // Modify the date_added field of a bookmark so it doesn't match with
1607 const BookmarkNode
* bookmark_bar_node
= model_
->bookmark_bar_node();
1608 int remove_index
= 2;
1609 ASSERT_GT(bookmark_bar_node
->child_count(), remove_index
);
1610 const BookmarkNode
* child_node
= bookmark_bar_node
->GetChild(remove_index
);
1611 ASSERT_TRUE(child_node
);
1612 EXPECT_TRUE(child_node
->is_url());
1613 model_
->SetDateAdded(child_node
, base::Time::FromInternalValue(10));
1617 // Everything should be back in sync after model association.
1618 ExpectBookmarkModelMatchesTestData();
1621 // Now trigger a change while syncing. We add a new bookmark, sync it, then
1622 // updates it's creation time.
1623 syncer::WriteTransaction
trans(FROM_HERE
, test_user_share_
.user_share());
1624 FakeServerChange
adds(&trans
);
1625 const std::wstring kTitle
= L
"Some site";
1626 const std::string kUrl
= "http://www.whatwhat.yeah/";
1627 const int kCreationTime
= 30;
1628 int64 id
= adds
.AddURL(kTitle
, kUrl
,
1629 bookmark_bar_id(), 0);
1630 adds
.ApplyPendingChanges(change_processor_
.get());
1631 FakeServerChange
updates(&trans
);
1632 updates
.ModifyCreationTime(id
, kCreationTime
);
1633 updates
.ApplyPendingChanges(change_processor_
.get());
1635 const BookmarkNode
* node
= model_
->bookmark_bar_node()->GetChild(0);
1637 EXPECT_TRUE(node
->is_url());
1638 EXPECT_EQ(WideToUTF16Hack(kTitle
), node
->GetTitle());
1639 EXPECT_EQ(kUrl
, node
->url().possibly_invalid_spec());
1640 EXPECT_EQ(node
->date_added(), base::Time::FromInternalValue(30));
1643 // Output transaction versions of |node| and nodes under it to |node_versions|.
1644 void GetTransactionVersions(
1645 const BookmarkNode
* root
,
1646 BookmarkNodeVersionMap
* node_versions
) {
1647 node_versions
->clear();
1648 std::queue
<const BookmarkNode
*> nodes
;
1650 while (!nodes
.empty()) {
1651 const BookmarkNode
* n
= nodes
.front();
1654 std::string version_str
;
1656 EXPECT_TRUE(n
->GetMetaInfo(kBookmarkTransactionVersionKey
, &version_str
));
1657 EXPECT_TRUE(base::StringToInt64(version_str
, &version
));
1659 (*node_versions
)[n
->id()] = version
;
1660 for (int i
= 0; i
< n
->child_count(); ++i
)
1661 nodes
.push(n
->GetChild(i
));
1665 void ProfileSyncServiceBookmarkTestWithData::ExpectTransactionVersionMatch(
1666 const BookmarkNode
* node
,
1667 const BookmarkNodeVersionMap
& version_expected
) {
1668 syncer::ReadTransaction
trans(FROM_HERE
, test_user_share_
.user_share());
1670 BookmarkNodeVersionMap bnodes_versions
;
1671 GetTransactionVersions(node
, &bnodes_versions
);
1672 for (BookmarkNodeVersionMap::const_iterator it
= bnodes_versions
.begin();
1673 it
!= bnodes_versions
.end(); ++it
) {
1674 syncer::ReadNode
sync_node(&trans
);
1675 ASSERT_TRUE(model_associator_
->InitSyncNodeFromChromeId(it
->first
,
1677 EXPECT_EQ(sync_node
.GetEntry()->Get(syncer::syncable::TRANSACTION_VERSION
),
1679 BookmarkNodeVersionMap::const_iterator expected_ver_it
=
1680 version_expected
.find(it
->first
);
1681 if (expected_ver_it
!= version_expected
.end())
1682 EXPECT_EQ(expected_ver_it
->second
, it
->second
);
1686 // Test transaction versions of model and nodes are incremented after changes
1688 TEST_F(ProfileSyncServiceBookmarkTestWithData
, UpdateTransactionVersion
) {
1689 LoadBookmarkModel(DELETE_EXISTING_STORAGE
, DONT_SAVE_TO_STORAGE
);
1691 WriteTestDataToBookmarkModel();
1692 MessageLoop::current()->RunUntilIdle();
1694 BookmarkNodeVersionMap initial_versions
;
1696 // Verify transaction versions in sync model and bookmark model (saved as
1697 // transaction version of root node) are equal after
1698 // WriteTestDataToBookmarkModel() created bookmarks.
1700 syncer::ReadTransaction
trans(FROM_HERE
, test_user_share_
.user_share());
1701 EXPECT_GT(trans
.GetModelVersion(syncer::BOOKMARKS
), 0);
1702 GetTransactionVersions(model_
->root_node(), &initial_versions
);
1703 EXPECT_EQ(trans
.GetModelVersion(syncer::BOOKMARKS
),
1704 initial_versions
[model_
->root_node()->id()]);
1706 ExpectTransactionVersionMatch(model_
->bookmark_bar_node(),
1707 BookmarkNodeVersionMap());
1708 ExpectTransactionVersionMatch(model_
->other_node(),
1709 BookmarkNodeVersionMap());
1710 ExpectTransactionVersionMatch(model_
->mobile_node(),
1711 BookmarkNodeVersionMap());
1713 // Verify model version is incremented and bookmark node versions remain
1715 const BookmarkNode
* bookmark_bar
= model_
->bookmark_bar_node();
1716 model_
->Remove(bookmark_bar
, 0);
1717 MessageLoop::current()->RunUntilIdle();
1718 BookmarkNodeVersionMap new_versions
;
1719 GetTransactionVersions(model_
->root_node(), &new_versions
);
1720 EXPECT_EQ(initial_versions
[model_
->root_node()->id()] + 1,
1721 new_versions
[model_
->root_node()->id()]);
1722 // HACK(haitaol): siblings of removed node are actually updated in sync model
1723 // because of NEXT_ID/PREV_ID. After switching to ordinal,
1724 // siblings will not get updated and the hack below can be
1726 model_
->SetNodeMetaInfo(bookmark_bar
->GetChild(0),
1727 kBookmarkTransactionVersionKey
, "41");
1728 initial_versions
[bookmark_bar
->GetChild(0)->id()] = 41;
1729 ExpectTransactionVersionMatch(model_
->bookmark_bar_node(), initial_versions
);
1730 ExpectTransactionVersionMatch(model_
->other_node(), initial_versions
);
1731 ExpectTransactionVersionMatch(model_
->mobile_node(), initial_versions
);
1733 // Verify model version and version of changed bookmark are incremented and
1734 // versions of others remain same.
1735 const BookmarkNode
* changed_bookmark
=
1736 model_
->bookmark_bar_node()->GetChild(0);
1737 model_
->SetTitle(changed_bookmark
, WideToUTF16Hack(L
"test"));
1738 MessageLoop::current()->RunUntilIdle();
1739 GetTransactionVersions(model_
->root_node(), &new_versions
);
1740 EXPECT_EQ(initial_versions
[model_
->root_node()->id()] + 2,
1741 new_versions
[model_
->root_node()->id()]);
1742 EXPECT_EQ(initial_versions
[changed_bookmark
->id()] + 1,
1743 new_versions
[changed_bookmark
->id()]);
1744 initial_versions
.erase(changed_bookmark
->id());
1745 ExpectTransactionVersionMatch(model_
->bookmark_bar_node(), initial_versions
);
1746 ExpectTransactionVersionMatch(model_
->other_node(), initial_versions
);
1747 ExpectTransactionVersionMatch(model_
->mobile_node(), initial_versions
);
1752 } // namespace browser_sync