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 "chrome/browser/sync/test/integration/bookmarks_helper.h"
10 #include "base/bind.h"
11 #include "base/compiler_specific.h"
12 #include "base/files/file_util.h"
13 #include "base/path_service.h"
14 #include "base/rand_util.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/synchronization/waitable_event.h"
20 #include "base/task/cancelable_task_tracker.h"
21 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
22 #include "chrome/browser/bookmarks/chrome_bookmark_client.h"
23 #include "chrome/browser/bookmarks/chrome_bookmark_client_factory.h"
24 #include "chrome/browser/favicon/favicon_service_factory.h"
25 #include "chrome/browser/history/history_service_factory.h"
26 #include "chrome/browser/profiles/profile.h"
27 #include "chrome/browser/sync/glue/bookmark_change_processor.h"
28 #include "chrome/browser/sync/test/integration/multi_client_status_change_checker.h"
29 #include "chrome/browser/sync/test/integration/profile_sync_service_harness.h"
30 #include "chrome/browser/sync/test/integration/sync_datatype_helper.h"
31 #include "chrome/browser/sync/test/integration/sync_test.h"
32 #include "chrome/common/chrome_paths.h"
33 #include "components/bookmarks/browser/bookmark_client.h"
34 #include "components/bookmarks/browser/bookmark_model.h"
35 #include "components/bookmarks/browser/bookmark_model_observer.h"
36 #include "components/bookmarks/browser/bookmark_utils.h"
37 #include "components/favicon/core/favicon_service.h"
38 #include "components/favicon_base/favicon_util.h"
39 #include "components/history/core/browser/history_db_task.h"
40 #include "components/history/core/browser/history_service.h"
41 #include "components/history/core/browser/history_types.h"
42 #include "content/public/test/test_utils.h"
43 #include "testing/gtest/include/gtest/gtest.h"
44 #include "third_party/skia/include/core/SkBitmap.h"
45 #include "ui/base/models/tree_node_iterator.h"
46 #include "ui/gfx/image/image_skia.h"
48 using bookmarks::BookmarkModel
;
49 using bookmarks::BookmarkNode
;
53 // History task which runs all pending tasks on the history thread and
54 // signals when the tasks have completed.
55 class HistoryEmptyTask
: public history::HistoryDBTask
{
57 explicit HistoryEmptyTask(base::WaitableEvent
* done
) : done_(done
) {}
59 bool RunOnDBThread(history::HistoryBackend
* backend
,
60 history::HistoryDatabase
* db
) override
{
61 content::RunAllPendingInMessageLoop();
66 void DoneRunOnMainThread() override
{}
69 ~HistoryEmptyTask() override
{}
71 base::WaitableEvent
* done_
;
74 // Helper class used to wait for changes to take effect on the favicon of a
75 // particular bookmark node in a particular bookmark model.
76 class FaviconChangeObserver
: public bookmarks::BookmarkModelObserver
{
78 FaviconChangeObserver(BookmarkModel
* model
, const BookmarkNode
* node
)
81 wait_for_load_(false) {
82 model
->AddObserver(this);
84 ~FaviconChangeObserver() override
{ model_
->RemoveObserver(this); }
85 void WaitForGetFavicon() {
86 wait_for_load_
= true;
87 content::RunMessageLoop();
88 ASSERT_TRUE(node_
->is_favicon_loaded());
89 ASSERT_FALSE(model_
->GetFavicon(node_
).IsEmpty());
91 void WaitForSetFavicon() {
92 wait_for_load_
= false;
93 content::RunMessageLoop();
96 // bookmarks::BookmarkModelObserver:
97 void BookmarkModelLoaded(BookmarkModel
* model
, bool ids_reassigned
) override
{
99 void BookmarkNodeMoved(BookmarkModel
* model
,
100 const BookmarkNode
* old_parent
,
102 const BookmarkNode
* new_parent
,
103 int new_index
) override
{}
104 void BookmarkNodeAdded(BookmarkModel
* model
,
105 const BookmarkNode
* parent
,
106 int index
) override
{}
107 void BookmarkNodeRemoved(BookmarkModel
* model
,
108 const BookmarkNode
* parent
,
110 const BookmarkNode
* node
,
111 const std::set
<GURL
>& removed_urls
) override
{}
112 void BookmarkAllUserNodesRemoved(
113 BookmarkModel
* model
,
114 const std::set
<GURL
>& removed_urls
) override
{}
116 void BookmarkNodeChanged(BookmarkModel
* model
,
117 const BookmarkNode
* node
) override
{
118 if (model
== model_
&& node
== node_
)
119 model
->GetFavicon(node
);
121 void BookmarkNodeChildrenReordered(BookmarkModel
* model
,
122 const BookmarkNode
* node
) override
{}
123 void BookmarkNodeFaviconChanged(BookmarkModel
* model
,
124 const BookmarkNode
* node
) override
{
125 if (model
== model_
&& node
== node_
) {
126 if (!wait_for_load_
|| (wait_for_load_
&& node
->is_favicon_loaded()))
127 base::MessageLoopForUI::current()->Quit();
132 BookmarkModel
* model_
;
133 const BookmarkNode
* node_
;
135 DISALLOW_COPY_AND_ASSIGN(FaviconChangeObserver
);
138 // A collection of URLs for which we have added favicons. Since loading a
139 // favicon is an asynchronous operation and doesn't necessarily invoke a
140 // callback, this collection is used to determine if we must wait for a URL's
141 // favicon to load or not.
142 std::set
<GURL
>* urls_with_favicons_
= NULL
;
144 // Returns the number of nodes of node type |node_type| in |model| whose
145 // titles match the string |title|.
146 int CountNodesWithTitlesMatching(BookmarkModel
* model
,
147 BookmarkNode::Type node_type
,
148 const base::string16
& title
) {
149 ui::TreeNodeIterator
<const BookmarkNode
> iterator(model
->root_node());
150 // Walk through the model tree looking for bookmark nodes of node type
151 // |node_type| whose titles match |title|.
153 while (iterator
.has_next()) {
154 const BookmarkNode
* node
= iterator
.Next();
155 if ((node
->type() == node_type
) && (node
->GetTitle() == title
))
161 // Returns the number of nodes of node type |node_type| in |model|.
162 int CountNodes(BookmarkModel
* model
, BookmarkNode::Type node_type
) {
163 ui::TreeNodeIterator
<const BookmarkNode
> iterator(model
->root_node());
164 // Walk through the model tree looking for bookmark nodes of node type
167 while (iterator
.has_next()) {
168 const BookmarkNode
* node
= iterator
.Next();
169 if (node
->type() == node_type
)
175 // Checks if the favicon data in |bitmap_a| and |bitmap_b| are equivalent.
176 // Returns true if they match.
177 bool FaviconRawBitmapsMatch(const SkBitmap
& bitmap_a
,
178 const SkBitmap
& bitmap_b
) {
179 if (bitmap_a
.getSize() == 0U && bitmap_b
.getSize() == 0U)
181 if ((bitmap_a
.getSize() != bitmap_b
.getSize()) ||
182 (bitmap_a
.width() != bitmap_b
.width()) ||
183 (bitmap_a
.height() != bitmap_b
.height())) {
184 LOG(ERROR
) << "Favicon size mismatch: " << bitmap_a
.getSize() << " ("
185 << bitmap_a
.width() << "x" << bitmap_a
.height() << ") vs. "
186 << bitmap_b
.getSize() << " (" << bitmap_b
.width() << "x"
187 << bitmap_b
.height() << ")";
190 SkAutoLockPixels
bitmap_lock_a(bitmap_a
);
191 SkAutoLockPixels
bitmap_lock_b(bitmap_b
);
192 void* node_pixel_addr_a
= bitmap_a
.getPixels();
193 EXPECT_TRUE(node_pixel_addr_a
);
194 void* node_pixel_addr_b
= bitmap_b
.getPixels();
195 EXPECT_TRUE(node_pixel_addr_b
);
196 if (memcmp(node_pixel_addr_a
, node_pixel_addr_b
, bitmap_a
.getSize()) != 0) {
197 LOG(ERROR
) << "Favicon bitmap mismatch";
204 // Represents a favicon image and the icon URL associated with it.
209 FaviconData(const gfx::Image
& favicon_image
,
210 const GURL
& favicon_url
)
211 : image(favicon_image
),
212 icon_url(favicon_url
) {
222 // Gets the favicon and icon URL associated with |node| in |model|.
223 FaviconData
GetFaviconData(BookmarkModel
* model
,
224 const BookmarkNode
* node
) {
225 // If a favicon wasn't explicitly set for a particular URL, simply return its
227 if (!urls_with_favicons_
||
228 urls_with_favicons_
->find(node
->url()) == urls_with_favicons_
->end()) {
229 return FaviconData();
231 // If a favicon was explicitly set, we may need to wait for it to be loaded
232 // via BookmarkModel::GetFavicon(), which is an asynchronous operation.
233 if (!node
->is_favicon_loaded()) {
234 FaviconChangeObserver
observer(model
, node
);
235 model
->GetFavicon(node
);
236 observer
.WaitForGetFavicon();
238 EXPECT_TRUE(node
->is_favicon_loaded());
239 EXPECT_FALSE(model
->GetFavicon(node
).IsEmpty());
240 return FaviconData(model
->GetFavicon(node
), node
->icon_url());
243 // Sets the favicon for |profile| and |node|. |profile| may be
244 // |test()->verifier()|.
245 void SetFaviconImpl(Profile
* profile
,
246 const BookmarkNode
* node
,
247 const GURL
& icon_url
,
248 const gfx::Image
& image
,
249 bookmarks_helper::FaviconSource favicon_source
) {
250 BookmarkModel
* model
= BookmarkModelFactory::GetForProfile(profile
);
252 FaviconChangeObserver
observer(model
, node
);
253 FaviconService
* favicon_service
= FaviconServiceFactory::GetForProfile(
254 profile
, ServiceAccessType::EXPLICIT_ACCESS
);
255 if (favicon_source
== bookmarks_helper::FROM_UI
) {
256 favicon_service
->SetFavicons(
257 node
->url(), icon_url
, favicon_base::FAVICON
, image
);
259 browser_sync::BookmarkChangeProcessor::ApplyBookmarkFavicon(
260 node
, profile
, icon_url
, image
.As1xPNGBytes());
263 // Wait for the favicon for |node| to be invalidated.
264 observer
.WaitForSetFavicon();
265 // Wait for the BookmarkModel to fetch the updated favicon and for the new
266 // favicon to be sent to BookmarkChangeProcessor.
267 GetFaviconData(model
, node
);
270 // Wait for all currently scheduled tasks on the history thread for all
271 // profiles to complete and any notifications sent to the UI thread to have
272 // finished processing.
273 void WaitForHistoryToProcessPendingTasks() {
274 // Skip waiting for history to complete for tests without favicons.
275 if (!urls_with_favicons_
)
278 std::vector
<Profile
*> profiles_which_need_to_wait
;
279 if (sync_datatype_helper::test()->use_verifier())
280 profiles_which_need_to_wait
.push_back(
281 sync_datatype_helper::test()->verifier());
282 for (int i
= 0; i
< sync_datatype_helper::test()->num_clients(); ++i
)
283 profiles_which_need_to_wait
.push_back(
284 sync_datatype_helper::test()->GetProfile(i
));
286 for (size_t i
= 0; i
< profiles_which_need_to_wait
.size(); ++i
) {
287 Profile
* profile
= profiles_which_need_to_wait
[i
];
288 history::HistoryService
* history_service
=
289 HistoryServiceFactory::GetForProfileWithoutCreating(profile
);
290 base::WaitableEvent
done(false, false);
291 base::CancelableTaskTracker task_tracker
;
292 history_service
->ScheduleDBTask(
293 scoped_ptr
<history::HistoryDBTask
>(
294 new HistoryEmptyTask(&done
)),
298 // Wait such that any notifications broadcast from one of the history threads
299 // to the UI thread are processed.
300 content::RunAllPendingInMessageLoop();
303 // Checks if the favicon in |node_a| from |model_a| matches that of |node_b|
304 // from |model_b|. Returns true if they match.
305 bool FaviconsMatch(BookmarkModel
* model_a
,
306 BookmarkModel
* model_b
,
307 const BookmarkNode
* node_a
,
308 const BookmarkNode
* node_b
) {
309 FaviconData favicon_data_a
= GetFaviconData(model_a
, node_a
);
310 FaviconData favicon_data_b
= GetFaviconData(model_b
, node_b
);
312 if (favicon_data_a
.icon_url
!= favicon_data_b
.icon_url
)
315 gfx::Image image_a
= favicon_data_a
.image
;
316 gfx::Image image_b
= favicon_data_b
.image
;
318 if (image_a
.IsEmpty() && image_b
.IsEmpty())
319 return true; // Two empty images are equivalent.
321 if (image_a
.IsEmpty() != image_b
.IsEmpty())
324 // Compare only the 1x bitmaps as only those are synced.
325 SkBitmap bitmap_a
= image_a
.AsImageSkia().GetRepresentation(
327 SkBitmap bitmap_b
= image_b
.AsImageSkia().GetRepresentation(
329 return FaviconRawBitmapsMatch(bitmap_a
, bitmap_b
);
332 // Does a deep comparison of BookmarkNode fields in |model_a| and |model_b|.
333 // Returns true if they are all equal.
334 bool NodesMatch(const BookmarkNode
* node_a
, const BookmarkNode
* node_b
) {
335 if (node_a
== NULL
|| node_b
== NULL
)
336 return node_a
== node_b
;
337 if (node_a
->is_folder() != node_b
->is_folder()) {
338 LOG(ERROR
) << "Cannot compare folder with bookmark";
341 if (node_a
->GetTitle() != node_b
->GetTitle()) {
342 LOG(ERROR
) << "Title mismatch: " << node_a
->GetTitle() << " vs. "
343 << node_b
->GetTitle();
346 if (node_a
->url() != node_b
->url()) {
347 LOG(ERROR
) << "URL mismatch: " << node_a
->url() << " vs. "
351 if (node_a
->parent()->GetIndexOf(node_a
) !=
352 node_b
->parent()->GetIndexOf(node_b
)) {
353 LOG(ERROR
) << "Index mismatch: "
354 << node_a
->parent()->GetIndexOf(node_a
) << " vs. "
355 << node_b
->parent()->GetIndexOf(node_b
);
361 // Helper for BookmarkModelsMatch.
362 bool NodeCantBeSynced(bookmarks::BookmarkClient
* client
,
363 const BookmarkNode
* node
) {
364 // Return true to skip a node.
365 return !client
->CanSyncNode(node
);
368 // Checks if the hierarchies in |model_a| and |model_b| are equivalent in
369 // terms of the data model and favicon. Returns true if they both match.
370 // Note: Some peripheral fields like creation times are allowed to mismatch.
371 bool BookmarkModelsMatch(BookmarkModel
* model_a
, BookmarkModel
* model_b
) {
373 ui::TreeNodeIterator
<const BookmarkNode
> iterator_a(
374 model_a
->root_node(), base::Bind(&NodeCantBeSynced
, model_a
->client()));
375 ui::TreeNodeIterator
<const BookmarkNode
> iterator_b(
376 model_b
->root_node(), base::Bind(&NodeCantBeSynced
, model_b
->client()));
377 while (iterator_a
.has_next()) {
378 const BookmarkNode
* node_a
= iterator_a
.Next();
379 if (!iterator_b
.has_next()) {
380 LOG(ERROR
) << "Models do not match.";
383 const BookmarkNode
* node_b
= iterator_b
.Next();
384 ret_val
= ret_val
&& NodesMatch(node_a
, node_b
);
385 if (node_a
->is_folder() || node_b
->is_folder())
387 ret_val
= ret_val
&& FaviconsMatch(model_a
, model_b
, node_a
, node_b
);
389 ret_val
= ret_val
&& (!iterator_b
.has_next());
393 // Finds the node in the verifier bookmark model that corresponds to
394 // |foreign_node| in |foreign_model| and stores its address in |result|.
395 void FindNodeInVerifier(BookmarkModel
* foreign_model
,
396 const BookmarkNode
* foreign_node
,
397 const BookmarkNode
** result
) {
399 std::stack
<int> path
;
400 const BookmarkNode
* walker
= foreign_node
;
401 while (walker
!= foreign_model
->root_node()) {
402 path
.push(walker
->parent()->GetIndexOf(walker
));
403 walker
= walker
->parent();
406 // Swing over to the other tree.
407 walker
= bookmarks_helper::GetVerifierBookmarkModel()->root_node();
410 while (!path
.empty()) {
411 ASSERT_TRUE(walker
->is_folder());
412 ASSERT_LT(path
.top(), walker
->child_count());
413 walker
= walker
->GetChild(path
.top());
417 ASSERT_TRUE(NodesMatch(foreign_node
, walker
));
424 namespace bookmarks_helper
{
426 BookmarkModel
* GetBookmarkModel(int index
) {
427 return BookmarkModelFactory::GetForProfile(
428 sync_datatype_helper::test()->GetProfile(index
));
431 const BookmarkNode
* GetBookmarkBarNode(int index
) {
432 return GetBookmarkModel(index
)->bookmark_bar_node();
435 const BookmarkNode
* GetOtherNode(int index
) {
436 return GetBookmarkModel(index
)->other_node();
439 const BookmarkNode
* GetSyncedBookmarksNode(int index
) {
440 return GetBookmarkModel(index
)->mobile_node();
443 const BookmarkNode
* GetManagedNode(int index
) {
444 return ChromeBookmarkClientFactory::GetForProfile(
445 sync_datatype_helper::test()->GetProfile(index
))->managed_node();
448 BookmarkModel
* GetVerifierBookmarkModel() {
449 return BookmarkModelFactory::GetForProfile(
450 sync_datatype_helper::test()->verifier());
453 const BookmarkNode
* AddURL(int profile
,
454 const std::string
& title
,
456 return AddURL(profile
, GetBookmarkBarNode(profile
), 0, title
, url
);
459 const BookmarkNode
* AddURL(int profile
,
461 const std::string
& title
,
463 return AddURL(profile
, GetBookmarkBarNode(profile
), index
, title
, url
);
466 const BookmarkNode
* AddURL(int profile
,
467 const BookmarkNode
* parent
,
469 const std::string
& title
,
471 BookmarkModel
* model
= GetBookmarkModel(profile
);
472 if (bookmarks::GetBookmarkNodeByID(model
, parent
->id()) != parent
) {
473 LOG(ERROR
) << "Node " << parent
->GetTitle() << " does not belong to "
474 << "Profile " << profile
;
477 const BookmarkNode
* result
=
478 model
->AddURL(parent
, index
, base::UTF8ToUTF16(title
), url
);
480 LOG(ERROR
) << "Could not add bookmark " << title
<< " to Profile "
484 if (sync_datatype_helper::test()->use_verifier()) {
485 const BookmarkNode
* v_parent
= NULL
;
486 FindNodeInVerifier(model
, parent
, &v_parent
);
487 const BookmarkNode
* v_node
= GetVerifierBookmarkModel()->AddURL(
488 v_parent
, index
, base::UTF8ToUTF16(title
), url
);
490 LOG(ERROR
) << "Could not add bookmark " << title
<< " to the verifier";
493 EXPECT_TRUE(NodesMatch(v_node
, result
));
498 const BookmarkNode
* AddFolder(int profile
,
499 const std::string
& title
) {
500 return AddFolder(profile
, GetBookmarkBarNode(profile
), 0, title
);
503 const BookmarkNode
* AddFolder(int profile
,
505 const std::string
& title
) {
506 return AddFolder(profile
, GetBookmarkBarNode(profile
), index
, title
);
509 const BookmarkNode
* AddFolder(int profile
,
510 const BookmarkNode
* parent
,
512 const std::string
& title
) {
513 BookmarkModel
* model
= GetBookmarkModel(profile
);
514 if (bookmarks::GetBookmarkNodeByID(model
, parent
->id()) != parent
) {
515 LOG(ERROR
) << "Node " << parent
->GetTitle() << " does not belong to "
516 << "Profile " << profile
;
519 const BookmarkNode
* result
=
520 model
->AddFolder(parent
, index
, base::UTF8ToUTF16(title
));
523 LOG(ERROR
) << "Could not add folder " << title
<< " to Profile "
527 if (sync_datatype_helper::test()->use_verifier()) {
528 const BookmarkNode
* v_parent
= NULL
;
529 FindNodeInVerifier(model
, parent
, &v_parent
);
530 const BookmarkNode
* v_node
= GetVerifierBookmarkModel()->AddFolder(
531 v_parent
, index
, base::UTF8ToUTF16(title
));
533 LOG(ERROR
) << "Could not add folder " << title
<< " to the verifier";
536 EXPECT_TRUE(NodesMatch(v_node
, result
));
541 void SetTitle(int profile
,
542 const BookmarkNode
* node
,
543 const std::string
& new_title
) {
544 BookmarkModel
* model
= GetBookmarkModel(profile
);
545 ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model
, node
->id()), node
)
546 << "Node " << node
->GetTitle() << " does not belong to "
547 << "Profile " << profile
;
548 if (sync_datatype_helper::test()->use_verifier()) {
549 const BookmarkNode
* v_node
= NULL
;
550 FindNodeInVerifier(model
, node
, &v_node
);
551 GetVerifierBookmarkModel()->SetTitle(v_node
, base::UTF8ToUTF16(new_title
));
553 model
->SetTitle(node
, base::UTF8ToUTF16(new_title
));
556 void SetFavicon(int profile
,
557 const BookmarkNode
* node
,
558 const GURL
& icon_url
,
559 const gfx::Image
& image
,
560 FaviconSource favicon_source
) {
561 BookmarkModel
* model
= GetBookmarkModel(profile
);
562 ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model
, node
->id()), node
)
563 << "Node " << node
->GetTitle() << " does not belong to "
564 << "Profile " << profile
;
565 ASSERT_EQ(BookmarkNode::URL
, node
->type()) << "Node " << node
->GetTitle()
566 << " must be a url.";
567 if (urls_with_favicons_
== NULL
)
568 urls_with_favicons_
= new std::set
<GURL
>();
569 urls_with_favicons_
->insert(node
->url());
570 if (sync_datatype_helper::test()->use_verifier()) {
571 const BookmarkNode
* v_node
= NULL
;
572 FindNodeInVerifier(model
, node
, &v_node
);
573 SetFaviconImpl(sync_datatype_helper::test()->verifier(),
579 SetFaviconImpl(sync_datatype_helper::test()->GetProfile(profile
),
586 const BookmarkNode
* SetURL(int profile
,
587 const BookmarkNode
* node
,
588 const GURL
& new_url
) {
589 BookmarkModel
* model
= GetBookmarkModel(profile
);
590 if (bookmarks::GetBookmarkNodeByID(model
, node
->id()) != node
) {
591 LOG(ERROR
) << "Node " << node
->GetTitle() << " does not belong to "
592 << "Profile " << profile
;
595 if (sync_datatype_helper::test()->use_verifier()) {
596 const BookmarkNode
* v_node
= NULL
;
597 FindNodeInVerifier(model
, node
, &v_node
);
598 if (v_node
->is_url())
599 GetVerifierBookmarkModel()->SetURL(v_node
, new_url
);
602 model
->SetURL(node
, new_url
);
606 void Move(int profile
,
607 const BookmarkNode
* node
,
608 const BookmarkNode
* new_parent
,
610 BookmarkModel
* model
= GetBookmarkModel(profile
);
611 ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model
, node
->id()), node
)
612 << "Node " << node
->GetTitle() << " does not belong to "
613 << "Profile " << profile
;
614 if (sync_datatype_helper::test()->use_verifier()) {
615 const BookmarkNode
* v_new_parent
= NULL
;
616 const BookmarkNode
* v_node
= NULL
;
617 FindNodeInVerifier(model
, new_parent
, &v_new_parent
);
618 FindNodeInVerifier(model
, node
, &v_node
);
619 GetVerifierBookmarkModel()->Move(v_node
, v_new_parent
, index
);
621 model
->Move(node
, new_parent
, index
);
624 void Remove(int profile
, const BookmarkNode
* parent
, int index
) {
625 BookmarkModel
* model
= GetBookmarkModel(profile
);
626 ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model
, parent
->id()), parent
)
627 << "Node " << parent
->GetTitle() << " does not belong to "
628 << "Profile " << profile
;
629 if (sync_datatype_helper::test()->use_verifier()) {
630 const BookmarkNode
* v_parent
= NULL
;
631 FindNodeInVerifier(model
, parent
, &v_parent
);
632 ASSERT_TRUE(NodesMatch(parent
->GetChild(index
), v_parent
->GetChild(index
)));
633 GetVerifierBookmarkModel()->Remove(v_parent
, index
);
635 model
->Remove(parent
, index
);
638 void RemoveAll(int profile
) {
639 if (sync_datatype_helper::test()->use_verifier()) {
640 const BookmarkNode
* root_node
= GetVerifierBookmarkModel()->root_node();
641 for (int i
= 0; i
< root_node
->child_count(); ++i
) {
642 const BookmarkNode
* permanent_node
= root_node
->GetChild(i
);
643 for (int j
= permanent_node
->child_count() - 1; j
>= 0; --j
) {
644 GetVerifierBookmarkModel()->Remove(permanent_node
, j
);
648 GetBookmarkModel(profile
)->RemoveAllUserBookmarks();
651 void SortChildren(int profile
, const BookmarkNode
* parent
) {
652 BookmarkModel
* model
= GetBookmarkModel(profile
);
653 ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model
, parent
->id()), parent
)
654 << "Node " << parent
->GetTitle() << " does not belong to "
655 << "Profile " << profile
;
656 if (sync_datatype_helper::test()->use_verifier()) {
657 const BookmarkNode
* v_parent
= NULL
;
658 FindNodeInVerifier(model
, parent
, &v_parent
);
659 GetVerifierBookmarkModel()->SortChildren(v_parent
);
661 model
->SortChildren(parent
);
664 void ReverseChildOrder(int profile
, const BookmarkNode
* parent
) {
666 bookmarks::GetBookmarkNodeByID(GetBookmarkModel(profile
), parent
->id()),
668 << "Node " << parent
->GetTitle() << " does not belong to "
669 << "Profile " << profile
;
670 int child_count
= parent
->child_count();
671 if (child_count
<= 0)
673 for (int index
= 0; index
< child_count
; ++index
) {
674 Move(profile
, parent
->GetChild(index
), parent
, child_count
- index
);
678 bool ModelMatchesVerifier(int profile
) {
679 if (!sync_datatype_helper::test()->use_verifier()) {
680 LOG(ERROR
) << "Illegal to call ModelMatchesVerifier() after "
681 << "DisableVerifier(). Use ModelsMatch() instead.";
684 return BookmarkModelsMatch(GetVerifierBookmarkModel(),
685 GetBookmarkModel(profile
));
688 bool AllModelsMatchVerifier() {
689 // Ensure that all tasks have finished processing on the history thread
690 // and that any notifications the history thread may have sent have been
691 // processed before comparing models.
692 WaitForHistoryToProcessPendingTasks();
694 for (int i
= 0; i
< sync_datatype_helper::test()->num_clients(); ++i
) {
695 if (!ModelMatchesVerifier(i
)) {
696 LOG(ERROR
) << "Model " << i
<< " does not match the verifier.";
703 bool ModelsMatch(int profile_a
, int profile_b
) {
704 return BookmarkModelsMatch(GetBookmarkModel(profile_a
),
705 GetBookmarkModel(profile_b
));
708 bool AllModelsMatch() {
709 // Ensure that all tasks have finished processing on the history thread
710 // and that any notifications the history thread may have sent have been
711 // processed before comparing models.
712 WaitForHistoryToProcessPendingTasks();
714 for (int i
= 1; i
< sync_datatype_helper::test()->num_clients(); ++i
) {
715 if (!ModelsMatch(0, i
)) {
716 LOG(ERROR
) << "Model " << i
<< " does not match Model 0.";
725 // Helper class used in the implementation of AwaitAllModelsMatch.
726 class AllModelsMatchChecker
: public MultiClientStatusChangeChecker
{
728 AllModelsMatchChecker();
729 ~AllModelsMatchChecker() override
;
731 bool IsExitConditionSatisfied() override
;
732 std::string
GetDebugMessage() const override
;
735 AllModelsMatchChecker::AllModelsMatchChecker()
736 : MultiClientStatusChangeChecker(
737 sync_datatype_helper::test()->GetSyncServices()) {}
739 AllModelsMatchChecker::~AllModelsMatchChecker() {}
741 bool AllModelsMatchChecker::IsExitConditionSatisfied() {
742 return AllModelsMatch();
745 std::string
AllModelsMatchChecker::GetDebugMessage() const {
746 return "Waiting for matching models";
751 bool AwaitAllModelsMatch() {
752 AllModelsMatchChecker checker
;
754 return !checker
.TimedOut();
758 bool ContainsDuplicateBookmarks(int profile
) {
759 ui::TreeNodeIterator
<const BookmarkNode
> iterator(
760 GetBookmarkModel(profile
)->root_node());
761 while (iterator
.has_next()) {
762 const BookmarkNode
* node
= iterator
.Next();
763 if (node
->is_folder())
765 std::vector
<const BookmarkNode
*> nodes
;
766 GetBookmarkModel(profile
)->GetNodesByURL(node
->url(), &nodes
);
767 EXPECT_TRUE(nodes
.size() >= 1);
768 for (std::vector
<const BookmarkNode
*>::const_iterator it
= nodes
.begin();
769 it
!= nodes
.end(); ++it
) {
770 if (node
->id() != (*it
)->id() &&
771 node
->parent() == (*it
)->parent() &&
772 node
->GetTitle() == (*it
)->GetTitle()){
780 bool HasNodeWithURL(int profile
, const GURL
& url
) {
781 std::vector
<const BookmarkNode
*> nodes
;
782 GetBookmarkModel(profile
)->GetNodesByURL(url
, &nodes
);
783 return !nodes
.empty();
786 const BookmarkNode
* GetUniqueNodeByURL(int profile
, const GURL
& url
) {
787 std::vector
<const BookmarkNode
*> nodes
;
788 GetBookmarkModel(profile
)->GetNodesByURL(url
, &nodes
);
789 EXPECT_EQ(1U, nodes
.size());
795 int CountAllBookmarks(int profile
) {
796 return CountNodes(GetBookmarkModel(profile
), BookmarkNode::URL
);
799 int CountBookmarksWithTitlesMatching(int profile
, const std::string
& title
) {
800 return CountNodesWithTitlesMatching(GetBookmarkModel(profile
),
802 base::UTF8ToUTF16(title
));
805 int CountFoldersWithTitlesMatching(int profile
, const std::string
& title
) {
806 return CountNodesWithTitlesMatching(GetBookmarkModel(profile
),
807 BookmarkNode::FOLDER
,
808 base::UTF8ToUTF16(title
));
811 gfx::Image
CreateFavicon(SkColor color
) {
812 const int dip_width
= 16;
813 const int dip_height
= 16;
814 std::vector
<float> favicon_scales
= favicon_base::GetFaviconScales();
815 gfx::ImageSkia favicon
;
816 for (size_t i
= 0; i
< favicon_scales
.size(); ++i
) {
817 float scale
= favicon_scales
[i
];
818 int pixel_width
= dip_width
* scale
;
819 int pixel_height
= dip_height
* scale
;
821 bmp
.allocN32Pixels(pixel_width
, pixel_height
);
822 bmp
.eraseColor(color
);
823 favicon
.AddRepresentation(gfx::ImageSkiaRep(bmp
, scale
));
825 return gfx::Image(favicon
);
828 gfx::Image
Create1xFaviconFromPNGFile(const std::string
& path
) {
829 const char* kPNGExtension
= ".png";
830 if (!EndsWith(path
, kPNGExtension
, false))
833 base::FilePath full_path
;
834 if (!PathService::Get(chrome::DIR_TEST_DATA
, &full_path
))
837 full_path
= full_path
.AppendASCII("sync").AppendASCII(path
);
838 std::string contents
;
839 base::ReadFileToString(full_path
, &contents
);
840 return gfx::Image::CreateFrom1xPNGBytes(
841 base::RefCountedString::TakeString(&contents
));
844 std::string
IndexedURL(int i
) {
845 return base::StringPrintf("http://www.host.ext:1234/path/filename/%d", i
);
848 std::string
IndexedURLTitle(int i
) {
849 return base::StringPrintf("URL Title %d", i
);
852 std::string
IndexedFolderName(int i
) {
853 return base::StringPrintf("Folder Name %d", i
);
856 std::string
IndexedSubfolderName(int i
) {
857 return base::StringPrintf("Subfolder Name %d", i
);
860 std::string
IndexedSubsubfolderName(int i
) {
861 return base::StringPrintf("Subsubfolder Name %d", i
);
864 } // namespace bookmarks_helper