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/run_loop.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/synchronization/waitable_event.h"
21 #include "base/task/cancelable_task_tracker.h"
22 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
23 #include "chrome/browser/bookmarks/managed_bookmark_service_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/await_match_status_change_checker.h"
29 #include "chrome/browser/sync/test/integration/multi_client_status_change_checker.h"
30 #include "chrome/browser/sync/test/integration/profile_sync_service_harness.h"
31 #include "chrome/browser/sync/test/integration/single_client_status_change_checker.h"
32 #include "chrome/browser/sync/test/integration/sync_datatype_helper.h"
33 #include "chrome/browser/sync/test/integration/sync_test.h"
34 #include "chrome/common/chrome_paths.h"
35 #include "components/bookmarks/browser/bookmark_client.h"
36 #include "components/bookmarks/browser/bookmark_model.h"
37 #include "components/bookmarks/browser/bookmark_model_observer.h"
38 #include "components/bookmarks/browser/bookmark_utils.h"
39 #include "components/bookmarks/managed/managed_bookmark_service.h"
40 #include "components/favicon/core/favicon_service.h"
41 #include "components/favicon_base/favicon_util.h"
42 #include "components/history/core/browser/history_db_task.h"
43 #include "components/history/core/browser/history_service.h"
44 #include "components/history/core/browser/history_types.h"
45 #include "content/public/test/test_utils.h"
46 #include "testing/gtest/include/gtest/gtest.h"
47 #include "third_party/skia/include/core/SkBitmap.h"
48 #include "ui/base/models/tree_node_iterator.h"
49 #include "ui/gfx/image/image_skia.h"
51 using bookmarks::BookmarkModel
;
52 using bookmarks::BookmarkNode
;
56 // History task which runs all pending tasks on the history thread and
57 // signals when the tasks have completed.
58 class HistoryEmptyTask
: public history::HistoryDBTask
{
60 explicit HistoryEmptyTask(base::WaitableEvent
* done
) : done_(done
) {}
62 bool RunOnDBThread(history::HistoryBackend
* backend
,
63 history::HistoryDatabase
* db
) override
{
64 content::RunAllPendingInMessageLoop();
69 void DoneRunOnMainThread() override
{}
72 ~HistoryEmptyTask() override
{}
74 base::WaitableEvent
* done_
;
77 // Helper class used to wait for changes to take effect on the favicon of a
78 // particular bookmark node in a particular bookmark model.
79 class FaviconChangeObserver
: public bookmarks::BookmarkModelObserver
{
81 FaviconChangeObserver(BookmarkModel
* model
, const BookmarkNode
* node
)
84 wait_for_load_(false) {
85 model
->AddObserver(this);
87 ~FaviconChangeObserver() override
{ model_
->RemoveObserver(this); }
88 void WaitForGetFavicon() {
89 wait_for_load_
= true;
90 content::RunMessageLoop();
91 ASSERT_TRUE(node_
->is_favicon_loaded());
92 ASSERT_FALSE(model_
->GetFavicon(node_
).IsEmpty());
94 void WaitForSetFavicon() {
95 wait_for_load_
= false;
96 content::RunMessageLoop();
99 // bookmarks::BookmarkModelObserver:
100 void BookmarkModelLoaded(BookmarkModel
* model
, bool ids_reassigned
) override
{
102 void BookmarkNodeMoved(BookmarkModel
* model
,
103 const BookmarkNode
* old_parent
,
105 const BookmarkNode
* new_parent
,
106 int new_index
) override
{}
107 void BookmarkNodeAdded(BookmarkModel
* model
,
108 const BookmarkNode
* parent
,
109 int index
) override
{}
110 void BookmarkNodeRemoved(BookmarkModel
* model
,
111 const BookmarkNode
* parent
,
113 const BookmarkNode
* node
,
114 const std::set
<GURL
>& removed_urls
) override
{}
115 void BookmarkAllUserNodesRemoved(
116 BookmarkModel
* model
,
117 const std::set
<GURL
>& removed_urls
) override
{}
119 void BookmarkNodeChanged(BookmarkModel
* model
,
120 const BookmarkNode
* node
) override
{
121 if (model
== model_
&& node
== node_
)
122 model
->GetFavicon(node
);
124 void BookmarkNodeChildrenReordered(BookmarkModel
* model
,
125 const BookmarkNode
* node
) override
{}
126 void BookmarkNodeFaviconChanged(BookmarkModel
* model
,
127 const BookmarkNode
* node
) override
{
128 if (model
== model_
&& node
== node_
) {
129 if (!wait_for_load_
|| (wait_for_load_
&& node
->is_favicon_loaded()))
130 base::MessageLoopForUI::current()->Quit();
135 BookmarkModel
* model_
;
136 const BookmarkNode
* node_
;
138 DISALLOW_COPY_AND_ASSIGN(FaviconChangeObserver
);
141 // A collection of URLs for which we have added favicons. Since loading a
142 // favicon is an asynchronous operation and doesn't necessarily invoke a
143 // callback, this collection is used to determine if we must wait for a URL's
144 // favicon to load or not.
145 std::set
<GURL
>* urls_with_favicons_
= NULL
;
147 // Returns the number of nodes of node type |node_type| in |model| whose
148 // titles match the string |title|.
149 int CountNodesWithTitlesMatching(BookmarkModel
* model
,
150 BookmarkNode::Type node_type
,
151 const base::string16
& title
) {
152 ui::TreeNodeIterator
<const BookmarkNode
> iterator(model
->root_node());
153 // Walk through the model tree looking for bookmark nodes of node type
154 // |node_type| whose titles match |title|.
156 while (iterator
.has_next()) {
157 const BookmarkNode
* node
= iterator
.Next();
158 if ((node
->type() == node_type
) && (node
->GetTitle() == title
))
164 // Returns the number of nodes of node type |node_type| in |model|.
165 int CountNodes(BookmarkModel
* model
, BookmarkNode::Type node_type
) {
166 ui::TreeNodeIterator
<const BookmarkNode
> iterator(model
->root_node());
167 // Walk through the model tree looking for bookmark nodes of node type
170 while (iterator
.has_next()) {
171 const BookmarkNode
* node
= iterator
.Next();
172 if (node
->type() == node_type
)
178 // Checks if the favicon data in |bitmap_a| and |bitmap_b| are equivalent.
179 // Returns true if they match.
180 bool FaviconRawBitmapsMatch(const SkBitmap
& bitmap_a
,
181 const SkBitmap
& bitmap_b
) {
182 if (bitmap_a
.getSize() == 0U && bitmap_b
.getSize() == 0U)
184 if ((bitmap_a
.getSize() != bitmap_b
.getSize()) ||
185 (bitmap_a
.width() != bitmap_b
.width()) ||
186 (bitmap_a
.height() != bitmap_b
.height())) {
187 LOG(ERROR
) << "Favicon size mismatch: " << bitmap_a
.getSize() << " ("
188 << bitmap_a
.width() << "x" << bitmap_a
.height() << ") vs. "
189 << bitmap_b
.getSize() << " (" << bitmap_b
.width() << "x"
190 << bitmap_b
.height() << ")";
193 SkAutoLockPixels
bitmap_lock_a(bitmap_a
);
194 SkAutoLockPixels
bitmap_lock_b(bitmap_b
);
195 void* node_pixel_addr_a
= bitmap_a
.getPixels();
196 EXPECT_TRUE(node_pixel_addr_a
);
197 void* node_pixel_addr_b
= bitmap_b
.getPixels();
198 EXPECT_TRUE(node_pixel_addr_b
);
199 if (memcmp(node_pixel_addr_a
, node_pixel_addr_b
, bitmap_a
.getSize()) != 0) {
200 LOG(ERROR
) << "Favicon bitmap mismatch";
207 // Represents a favicon image and the icon URL associated with it.
212 FaviconData(const gfx::Image
& favicon_image
,
213 const GURL
& favicon_url
)
214 : image(favicon_image
),
215 icon_url(favicon_url
) {
225 // Gets the favicon and icon URL associated with |node| in |model|.
226 FaviconData
GetFaviconData(BookmarkModel
* model
,
227 const BookmarkNode
* node
) {
228 // If a favicon wasn't explicitly set for a particular URL, simply return its
230 if (!urls_with_favicons_
||
231 urls_with_favicons_
->find(node
->url()) == urls_with_favicons_
->end()) {
232 return FaviconData();
234 // If a favicon was explicitly set, we may need to wait for it to be loaded
235 // via BookmarkModel::GetFavicon(), which is an asynchronous operation.
236 if (!node
->is_favicon_loaded()) {
237 FaviconChangeObserver
observer(model
, node
);
238 model
->GetFavicon(node
);
239 observer
.WaitForGetFavicon();
241 EXPECT_TRUE(node
->is_favicon_loaded());
242 EXPECT_FALSE(model
->GetFavicon(node
).IsEmpty());
243 return FaviconData(model
->GetFavicon(node
), node
->icon_url());
246 // Sets the favicon for |profile| and |node|. |profile| may be
247 // |test()->verifier()|.
248 void SetFaviconImpl(Profile
* profile
,
249 const BookmarkNode
* node
,
250 const GURL
& icon_url
,
251 const gfx::Image
& image
,
252 bookmarks_helper::FaviconSource favicon_source
) {
253 BookmarkModel
* model
= BookmarkModelFactory::GetForProfile(profile
);
255 FaviconChangeObserver
observer(model
, node
);
256 favicon::FaviconService
* favicon_service
=
257 FaviconServiceFactory::GetForProfile(
258 profile
, ServiceAccessType::EXPLICIT_ACCESS
);
259 if (favicon_source
== bookmarks_helper::FROM_UI
) {
260 favicon_service
->SetFavicons(
261 node
->url(), icon_url
, favicon_base::FAVICON
, image
);
263 browser_sync::BookmarkChangeProcessor::ApplyBookmarkFavicon(
264 node
, profile
, icon_url
, image
.As1xPNGBytes());
267 // Wait for the favicon for |node| to be invalidated.
268 observer
.WaitForSetFavicon();
269 // Wait for the BookmarkModel to fetch the updated favicon and for the new
270 // favicon to be sent to BookmarkChangeProcessor.
271 GetFaviconData(model
, node
);
274 // Expires the favicon for |profile| and |node|. |profile| may be
275 // |test()->verifier()|.
276 void ExpireFaviconImpl(Profile
* profile
, const BookmarkNode
* node
) {
277 favicon::FaviconService
* favicon_service
=
278 FaviconServiceFactory::GetForProfile(profile
,
279 ServiceAccessType::EXPLICIT_ACCESS
);
280 favicon_service
->SetFaviconOutOfDateForPage(node
->url());
283 // Called asynchronously from CheckFaviconExpired() with the favicon data from
285 void OnGotFaviconForExpiryCheck(
286 const base::Closure
& callback
,
287 const favicon_base::FaviconRawBitmapResult
& bitmap_result
) {
288 ASSERT_TRUE(bitmap_result
.is_valid());
289 ASSERT_TRUE(bitmap_result
.expired
);
293 // Wait for all currently scheduled tasks on the history thread for all
294 // profiles to complete and any notifications sent to the UI thread to have
295 // finished processing.
296 void WaitForHistoryToProcessPendingTasks() {
297 // Skip waiting for history to complete for tests without favicons.
298 if (!urls_with_favicons_
)
301 std::vector
<Profile
*> profiles_which_need_to_wait
;
302 if (sync_datatype_helper::test()->use_verifier())
303 profiles_which_need_to_wait
.push_back(
304 sync_datatype_helper::test()->verifier());
305 for (int i
= 0; i
< sync_datatype_helper::test()->num_clients(); ++i
)
306 profiles_which_need_to_wait
.push_back(
307 sync_datatype_helper::test()->GetProfile(i
));
309 for (size_t i
= 0; i
< profiles_which_need_to_wait
.size(); ++i
) {
310 Profile
* profile
= profiles_which_need_to_wait
[i
];
311 history::HistoryService
* history_service
=
312 HistoryServiceFactory::GetForProfileWithoutCreating(profile
);
313 base::WaitableEvent
done(false, false);
314 base::CancelableTaskTracker task_tracker
;
315 history_service
->ScheduleDBTask(
316 scoped_ptr
<history::HistoryDBTask
>(
317 new HistoryEmptyTask(&done
)),
321 // Wait such that any notifications broadcast from one of the history threads
322 // to the UI thread are processed.
323 content::RunAllPendingInMessageLoop();
326 // Checks if the favicon in |node_a| from |model_a| matches that of |node_b|
327 // from |model_b|. Returns true if they match.
328 bool FaviconsMatch(BookmarkModel
* model_a
,
329 BookmarkModel
* model_b
,
330 const BookmarkNode
* node_a
,
331 const BookmarkNode
* node_b
) {
332 FaviconData favicon_data_a
= GetFaviconData(model_a
, node_a
);
333 FaviconData favicon_data_b
= GetFaviconData(model_b
, node_b
);
335 if (favicon_data_a
.icon_url
!= favicon_data_b
.icon_url
)
338 gfx::Image image_a
= favicon_data_a
.image
;
339 gfx::Image image_b
= favicon_data_b
.image
;
341 if (image_a
.IsEmpty() && image_b
.IsEmpty())
342 return true; // Two empty images are equivalent.
344 if (image_a
.IsEmpty() != image_b
.IsEmpty())
347 // Compare only the 1x bitmaps as only those are synced.
348 SkBitmap bitmap_a
= image_a
.AsImageSkia().GetRepresentation(
350 SkBitmap bitmap_b
= image_b
.AsImageSkia().GetRepresentation(
352 return FaviconRawBitmapsMatch(bitmap_a
, bitmap_b
);
355 // Does a deep comparison of BookmarkNode fields in |model_a| and |model_b|.
356 // Returns true if they are all equal.
357 bool NodesMatch(const BookmarkNode
* node_a
, const BookmarkNode
* node_b
) {
358 if (node_a
== NULL
|| node_b
== NULL
)
359 return node_a
== node_b
;
360 if (node_a
->is_folder() != node_b
->is_folder()) {
361 LOG(ERROR
) << "Cannot compare folder with bookmark";
364 if (node_a
->GetTitle() != node_b
->GetTitle()) {
365 LOG(ERROR
) << "Title mismatch: " << node_a
->GetTitle() << " vs. "
366 << node_b
->GetTitle();
369 if (node_a
->url() != node_b
->url()) {
370 LOG(ERROR
) << "URL mismatch: " << node_a
->url() << " vs. "
374 if (node_a
->parent()->GetIndexOf(node_a
) !=
375 node_b
->parent()->GetIndexOf(node_b
)) {
376 LOG(ERROR
) << "Index mismatch: "
377 << node_a
->parent()->GetIndexOf(node_a
) << " vs. "
378 << node_b
->parent()->GetIndexOf(node_b
);
384 // Helper for BookmarkModelsMatch.
385 bool NodeCantBeSynced(bookmarks::BookmarkClient
* client
,
386 const BookmarkNode
* node
) {
387 // Return true to skip a node.
388 return !client
->CanSyncNode(node
);
391 // Checks if the hierarchies in |model_a| and |model_b| are equivalent in
392 // terms of the data model and favicon. Returns true if they both match.
393 // Note: Some peripheral fields like creation times are allowed to mismatch.
394 bool BookmarkModelsMatch(BookmarkModel
* model_a
, BookmarkModel
* model_b
) {
396 ui::TreeNodeIterator
<const BookmarkNode
> iterator_a(
397 model_a
->root_node(), base::Bind(&NodeCantBeSynced
, model_a
->client()));
398 ui::TreeNodeIterator
<const BookmarkNode
> iterator_b(
399 model_b
->root_node(), base::Bind(&NodeCantBeSynced
, model_b
->client()));
400 while (iterator_a
.has_next()) {
401 const BookmarkNode
* node_a
= iterator_a
.Next();
402 if (!iterator_b
.has_next()) {
403 LOG(ERROR
) << "Models do not match.";
406 const BookmarkNode
* node_b
= iterator_b
.Next();
407 ret_val
= ret_val
&& NodesMatch(node_a
, node_b
);
408 if (node_a
->is_folder() || node_b
->is_folder())
410 ret_val
= ret_val
&& FaviconsMatch(model_a
, model_b
, node_a
, node_b
);
412 ret_val
= ret_val
&& (!iterator_b
.has_next());
416 // Finds the node in the verifier bookmark model that corresponds to
417 // |foreign_node| in |foreign_model| and stores its address in |result|.
418 void FindNodeInVerifier(BookmarkModel
* foreign_model
,
419 const BookmarkNode
* foreign_node
,
420 const BookmarkNode
** result
) {
422 std::stack
<int> path
;
423 const BookmarkNode
* walker
= foreign_node
;
424 while (walker
!= foreign_model
->root_node()) {
425 path
.push(walker
->parent()->GetIndexOf(walker
));
426 walker
= walker
->parent();
429 // Swing over to the other tree.
430 walker
= bookmarks_helper::GetVerifierBookmarkModel()->root_node();
433 while (!path
.empty()) {
434 ASSERT_TRUE(walker
->is_folder());
435 ASSERT_LT(path
.top(), walker
->child_count());
436 walker
= walker
->GetChild(path
.top());
440 ASSERT_TRUE(NodesMatch(foreign_node
, walker
));
447 namespace bookmarks_helper
{
449 BookmarkModel
* GetBookmarkModel(int index
) {
450 return BookmarkModelFactory::GetForProfile(
451 sync_datatype_helper::test()->GetProfile(index
));
454 const BookmarkNode
* GetBookmarkBarNode(int index
) {
455 return GetBookmarkModel(index
)->bookmark_bar_node();
458 const BookmarkNode
* GetOtherNode(int index
) {
459 return GetBookmarkModel(index
)->other_node();
462 const BookmarkNode
* GetSyncedBookmarksNode(int index
) {
463 return GetBookmarkModel(index
)->mobile_node();
466 const BookmarkNode
* GetManagedNode(int index
) {
467 return ManagedBookmarkServiceFactory::GetForProfile(
468 sync_datatype_helper::test()->GetProfile(index
))
472 BookmarkModel
* GetVerifierBookmarkModel() {
473 return BookmarkModelFactory::GetForProfile(
474 sync_datatype_helper::test()->verifier());
477 const BookmarkNode
* AddURL(int profile
,
478 const std::string
& title
,
480 return AddURL(profile
, GetBookmarkBarNode(profile
), 0, title
, url
);
483 const BookmarkNode
* AddURL(int profile
,
485 const std::string
& title
,
487 return AddURL(profile
, GetBookmarkBarNode(profile
), index
, title
, url
);
490 const BookmarkNode
* AddURL(int profile
,
491 const BookmarkNode
* parent
,
493 const std::string
& title
,
495 BookmarkModel
* model
= GetBookmarkModel(profile
);
496 if (bookmarks::GetBookmarkNodeByID(model
, parent
->id()) != parent
) {
497 LOG(ERROR
) << "Node " << parent
->GetTitle() << " does not belong to "
498 << "Profile " << profile
;
501 const BookmarkNode
* result
=
502 model
->AddURL(parent
, index
, base::UTF8ToUTF16(title
), url
);
504 LOG(ERROR
) << "Could not add bookmark " << title
<< " to Profile "
508 if (sync_datatype_helper::test()->use_verifier()) {
509 const BookmarkNode
* v_parent
= NULL
;
510 FindNodeInVerifier(model
, parent
, &v_parent
);
511 const BookmarkNode
* v_node
= GetVerifierBookmarkModel()->AddURL(
512 v_parent
, index
, base::UTF8ToUTF16(title
), url
);
514 LOG(ERROR
) << "Could not add bookmark " << title
<< " to the verifier";
517 EXPECT_TRUE(NodesMatch(v_node
, result
));
522 const BookmarkNode
* AddFolder(int profile
,
523 const std::string
& title
) {
524 return AddFolder(profile
, GetBookmarkBarNode(profile
), 0, title
);
527 const BookmarkNode
* AddFolder(int profile
,
529 const std::string
& title
) {
530 return AddFolder(profile
, GetBookmarkBarNode(profile
), index
, title
);
533 const BookmarkNode
* AddFolder(int profile
,
534 const BookmarkNode
* parent
,
536 const std::string
& title
) {
537 BookmarkModel
* model
= GetBookmarkModel(profile
);
538 if (bookmarks::GetBookmarkNodeByID(model
, parent
->id()) != parent
) {
539 LOG(ERROR
) << "Node " << parent
->GetTitle() << " does not belong to "
540 << "Profile " << profile
;
543 const BookmarkNode
* result
=
544 model
->AddFolder(parent
, index
, base::UTF8ToUTF16(title
));
547 LOG(ERROR
) << "Could not add folder " << title
<< " to Profile "
551 if (sync_datatype_helper::test()->use_verifier()) {
552 const BookmarkNode
* v_parent
= NULL
;
553 FindNodeInVerifier(model
, parent
, &v_parent
);
554 const BookmarkNode
* v_node
= GetVerifierBookmarkModel()->AddFolder(
555 v_parent
, index
, base::UTF8ToUTF16(title
));
557 LOG(ERROR
) << "Could not add folder " << title
<< " to the verifier";
560 EXPECT_TRUE(NodesMatch(v_node
, result
));
565 void SetTitle(int profile
,
566 const BookmarkNode
* node
,
567 const std::string
& new_title
) {
568 BookmarkModel
* model
= GetBookmarkModel(profile
);
569 ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model
, node
->id()), node
)
570 << "Node " << node
->GetTitle() << " does not belong to "
571 << "Profile " << profile
;
572 if (sync_datatype_helper::test()->use_verifier()) {
573 const BookmarkNode
* v_node
= NULL
;
574 FindNodeInVerifier(model
, node
, &v_node
);
575 GetVerifierBookmarkModel()->SetTitle(v_node
, base::UTF8ToUTF16(new_title
));
577 model
->SetTitle(node
, base::UTF8ToUTF16(new_title
));
580 void SetFavicon(int profile
,
581 const BookmarkNode
* node
,
582 const GURL
& icon_url
,
583 const gfx::Image
& image
,
584 FaviconSource favicon_source
) {
585 BookmarkModel
* model
= GetBookmarkModel(profile
);
586 ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model
, node
->id()), node
)
587 << "Node " << node
->GetTitle() << " does not belong to "
588 << "Profile " << profile
;
589 ASSERT_EQ(BookmarkNode::URL
, node
->type()) << "Node " << node
->GetTitle()
590 << " must be a url.";
591 if (urls_with_favicons_
== NULL
)
592 urls_with_favicons_
= new std::set
<GURL
>();
593 urls_with_favicons_
->insert(node
->url());
594 if (sync_datatype_helper::test()->use_verifier()) {
595 const BookmarkNode
* v_node
= NULL
;
596 FindNodeInVerifier(model
, node
, &v_node
);
597 SetFaviconImpl(sync_datatype_helper::test()->verifier(),
603 SetFaviconImpl(sync_datatype_helper::test()->GetProfile(profile
),
610 void ExpireFavicon(int profile
, const BookmarkNode
* node
) {
611 BookmarkModel
* model
= GetBookmarkModel(profile
);
612 ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model
, node
->id()), node
)
613 << "Node " << node
->GetTitle() << " does not belong to "
614 << "Profile " << profile
;
615 ASSERT_EQ(BookmarkNode::URL
, node
->type()) << "Node " << node
->GetTitle()
616 << " must be a url.";
617 ASSERT_EQ(1u, urls_with_favicons_
->count(node
->url()));
619 if (sync_datatype_helper::test()->use_verifier()) {
620 const BookmarkNode
* v_node
= nullptr;
621 FindNodeInVerifier(model
, node
, &v_node
);
622 ExpireFaviconImpl(sync_datatype_helper::test()->verifier(), node
);
624 ExpireFaviconImpl(sync_datatype_helper::test()->GetProfile(profile
), node
);
626 WaitForHistoryToProcessPendingTasks();
629 void CheckFaviconExpired(int profile
, const GURL
& icon_url
) {
630 base::RunLoop run_loop
;
632 favicon::FaviconService
* favicon_service
=
633 FaviconServiceFactory::GetForProfile(
634 sync_datatype_helper::test()->GetProfile(profile
),
635 ServiceAccessType::EXPLICIT_ACCESS
);
636 base::CancelableTaskTracker task_tracker
;
637 favicon_service
->GetRawFavicon(
638 icon_url
, favicon_base::FAVICON
, 0,
639 base::Bind(&OnGotFaviconForExpiryCheck
, run_loop
.QuitClosure()),
645 const BookmarkNode
* SetURL(int profile
,
646 const BookmarkNode
* node
,
647 const GURL
& new_url
) {
648 BookmarkModel
* model
= GetBookmarkModel(profile
);
649 if (bookmarks::GetBookmarkNodeByID(model
, node
->id()) != node
) {
650 LOG(ERROR
) << "Node " << node
->GetTitle() << " does not belong to "
651 << "Profile " << profile
;
654 if (sync_datatype_helper::test()->use_verifier()) {
655 const BookmarkNode
* v_node
= NULL
;
656 FindNodeInVerifier(model
, node
, &v_node
);
657 if (v_node
->is_url())
658 GetVerifierBookmarkModel()->SetURL(v_node
, new_url
);
661 model
->SetURL(node
, new_url
);
665 void Move(int profile
,
666 const BookmarkNode
* node
,
667 const BookmarkNode
* new_parent
,
669 BookmarkModel
* model
= GetBookmarkModel(profile
);
670 ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model
, node
->id()), node
)
671 << "Node " << node
->GetTitle() << " does not belong to "
672 << "Profile " << profile
;
673 if (sync_datatype_helper::test()->use_verifier()) {
674 const BookmarkNode
* v_new_parent
= NULL
;
675 const BookmarkNode
* v_node
= NULL
;
676 FindNodeInVerifier(model
, new_parent
, &v_new_parent
);
677 FindNodeInVerifier(model
, node
, &v_node
);
678 GetVerifierBookmarkModel()->Move(v_node
, v_new_parent
, index
);
680 model
->Move(node
, new_parent
, index
);
683 void Remove(int profile
, const BookmarkNode
* parent
, int index
) {
684 BookmarkModel
* model
= GetBookmarkModel(profile
);
685 ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model
, parent
->id()), parent
)
686 << "Node " << parent
->GetTitle() << " does not belong to "
687 << "Profile " << profile
;
688 if (sync_datatype_helper::test()->use_verifier()) {
689 const BookmarkNode
* v_parent
= NULL
;
690 FindNodeInVerifier(model
, parent
, &v_parent
);
691 ASSERT_TRUE(NodesMatch(parent
->GetChild(index
), v_parent
->GetChild(index
)));
692 GetVerifierBookmarkModel()->Remove(v_parent
->GetChild(index
));
694 model
->Remove(parent
->GetChild(index
));
697 void RemoveAll(int profile
) {
698 if (sync_datatype_helper::test()->use_verifier()) {
699 const BookmarkNode
* root_node
= GetVerifierBookmarkModel()->root_node();
700 for (int i
= 0; i
< root_node
->child_count(); ++i
) {
701 const BookmarkNode
* permanent_node
= root_node
->GetChild(i
);
702 for (int j
= permanent_node
->child_count() - 1; j
>= 0; --j
) {
703 GetVerifierBookmarkModel()->Remove(permanent_node
->GetChild(j
));
707 GetBookmarkModel(profile
)->RemoveAllUserBookmarks();
710 void SortChildren(int profile
, const BookmarkNode
* parent
) {
711 BookmarkModel
* model
= GetBookmarkModel(profile
);
712 ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model
, parent
->id()), parent
)
713 << "Node " << parent
->GetTitle() << " does not belong to "
714 << "Profile " << profile
;
715 if (sync_datatype_helper::test()->use_verifier()) {
716 const BookmarkNode
* v_parent
= NULL
;
717 FindNodeInVerifier(model
, parent
, &v_parent
);
718 GetVerifierBookmarkModel()->SortChildren(v_parent
);
720 model
->SortChildren(parent
);
723 void ReverseChildOrder(int profile
, const BookmarkNode
* parent
) {
725 bookmarks::GetBookmarkNodeByID(GetBookmarkModel(profile
), parent
->id()),
727 << "Node " << parent
->GetTitle() << " does not belong to "
728 << "Profile " << profile
;
729 int child_count
= parent
->child_count();
730 if (child_count
<= 0)
732 for (int index
= 0; index
< child_count
; ++index
) {
733 Move(profile
, parent
->GetChild(index
), parent
, child_count
- index
);
737 bool ModelMatchesVerifier(int profile
) {
738 if (!sync_datatype_helper::test()->use_verifier()) {
739 LOG(ERROR
) << "Illegal to call ModelMatchesVerifier() after "
740 << "DisableVerifier(). Use ModelsMatch() instead.";
743 return BookmarkModelsMatch(GetVerifierBookmarkModel(),
744 GetBookmarkModel(profile
));
747 bool AllModelsMatchVerifier() {
748 // Ensure that all tasks have finished processing on the history thread
749 // and that any notifications the history thread may have sent have been
750 // processed before comparing models.
751 WaitForHistoryToProcessPendingTasks();
753 for (int i
= 0; i
< sync_datatype_helper::test()->num_clients(); ++i
) {
754 if (!ModelMatchesVerifier(i
)) {
755 LOG(ERROR
) << "Model " << i
<< " does not match the verifier.";
762 bool ModelsMatch(int profile_a
, int profile_b
) {
763 return BookmarkModelsMatch(GetBookmarkModel(profile_a
),
764 GetBookmarkModel(profile_b
));
767 bool AllModelsMatch() {
768 // Ensure that all tasks have finished processing on the history thread
769 // and that any notifications the history thread may have sent have been
770 // processed before comparing models.
771 WaitForHistoryToProcessPendingTasks();
773 for (int i
= 1; i
< sync_datatype_helper::test()->num_clients(); ++i
) {
774 if (!ModelsMatch(0, i
)) {
775 LOG(ERROR
) << "Model " << i
<< " does not match Model 0.";
784 // Helper class used in the implementation of AwaitAllModelsMatch.
785 class AllModelsMatchChecker
: public MultiClientStatusChangeChecker
{
787 AllModelsMatchChecker();
788 ~AllModelsMatchChecker() override
;
790 bool IsExitConditionSatisfied() override
;
791 std::string
GetDebugMessage() const override
;
794 AllModelsMatchChecker::AllModelsMatchChecker()
795 : MultiClientStatusChangeChecker(
796 sync_datatype_helper::test()->GetSyncServices()) {}
798 AllModelsMatchChecker::~AllModelsMatchChecker() {}
800 bool AllModelsMatchChecker::IsExitConditionSatisfied() {
801 return AllModelsMatch();
804 std::string
AllModelsMatchChecker::GetDebugMessage() const {
805 return "Waiting for matching models";
810 bool AwaitAllModelsMatch() {
811 AllModelsMatchChecker checker
;
813 return !checker
.TimedOut();
818 // TODO(pvalenzuela): Remove this class and instead use
819 // AwaitMatchStatusChangeChecker.
820 class CountBookmarksWithTitlesMatchingChecker
821 : public SingleClientStatusChangeChecker
{
823 CountBookmarksWithTitlesMatchingChecker(ProfileSyncService
* service
,
825 const std::string
& title
,
827 : SingleClientStatusChangeChecker(service
),
828 profile_index_(profile_index
),
830 expected_count_(expected_count
) {
831 DCHECK_GE(expected_count
, 0) << "expected_count must be non-negative.";
834 bool IsExitConditionSatisfied() override
{
835 int actual_count
= CountBookmarksWithTitlesMatching(profile_index_
, title_
);
836 return expected_count_
== actual_count
;
839 std::string
GetDebugMessage() const override
{
840 return "Waiting for bookmark count to match";
844 const int profile_index_
;
845 const std::string title_
;
846 const int expected_count_
;
851 bool AwaitCountBookmarksWithTitlesMatching(int profile
,
852 const std::string
& title
,
853 int expected_count
) {
854 ProfileSyncService
* service
=
855 sync_datatype_helper::test()->GetSyncService(profile
);
856 CountBookmarksWithTitlesMatchingChecker
checker(service
,
861 return !checker
.TimedOut();
865 bool BookmarkCountsByUrlMatch(int profile
,
867 int expected_count
) {
868 int actual_count
= CountBookmarksWithUrlsMatching(profile
, url
);
869 if (expected_count
!= actual_count
) {
870 DVLOG(1) << base::StringPrintf("Expected %d URL(s), but there were %d.",
878 bool AwaitCountBookmarksWithUrlsMatching(int profile
,
880 int expected_count
) {
881 AwaitMatchStatusChangeChecker
checker(base::Bind(BookmarkCountsByUrlMatch
,
885 "Bookmark URL counts match.");
887 return !checker
.TimedOut();
890 bool ContainsDuplicateBookmarks(int profile
) {
891 ui::TreeNodeIterator
<const BookmarkNode
> iterator(
892 GetBookmarkModel(profile
)->root_node());
893 while (iterator
.has_next()) {
894 const BookmarkNode
* node
= iterator
.Next();
895 if (node
->is_folder())
897 std::vector
<const BookmarkNode
*> nodes
;
898 GetBookmarkModel(profile
)->GetNodesByURL(node
->url(), &nodes
);
899 EXPECT_TRUE(nodes
.size() >= 1);
900 for (std::vector
<const BookmarkNode
*>::const_iterator it
= nodes
.begin();
901 it
!= nodes
.end(); ++it
) {
902 if (node
->id() != (*it
)->id() &&
903 node
->parent() == (*it
)->parent() &&
904 node
->GetTitle() == (*it
)->GetTitle()){
912 bool HasNodeWithURL(int profile
, const GURL
& url
) {
913 std::vector
<const BookmarkNode
*> nodes
;
914 GetBookmarkModel(profile
)->GetNodesByURL(url
, &nodes
);
915 return !nodes
.empty();
918 const BookmarkNode
* GetUniqueNodeByURL(int profile
, const GURL
& url
) {
919 std::vector
<const BookmarkNode
*> nodes
;
920 GetBookmarkModel(profile
)->GetNodesByURL(url
, &nodes
);
921 EXPECT_EQ(1U, nodes
.size());
927 int CountAllBookmarks(int profile
) {
928 return CountNodes(GetBookmarkModel(profile
), BookmarkNode::URL
);
931 int CountBookmarksWithTitlesMatching(int profile
, const std::string
& title
) {
932 return CountNodesWithTitlesMatching(GetBookmarkModel(profile
),
934 base::UTF8ToUTF16(title
));
937 int CountBookmarksWithUrlsMatching(int profile
, const GURL
& url
) {
938 std::vector
<const BookmarkNode
*> nodes
;
939 GetBookmarkModel(profile
)->GetNodesByURL(url
, &nodes
);
943 int CountFoldersWithTitlesMatching(int profile
, const std::string
& title
) {
944 return CountNodesWithTitlesMatching(GetBookmarkModel(profile
),
945 BookmarkNode::FOLDER
,
946 base::UTF8ToUTF16(title
));
949 gfx::Image
CreateFavicon(SkColor color
) {
950 const int dip_width
= 16;
951 const int dip_height
= 16;
952 std::vector
<float> favicon_scales
= favicon_base::GetFaviconScales();
953 gfx::ImageSkia favicon
;
954 for (size_t i
= 0; i
< favicon_scales
.size(); ++i
) {
955 float scale
= favicon_scales
[i
];
956 int pixel_width
= dip_width
* scale
;
957 int pixel_height
= dip_height
* scale
;
959 bmp
.allocN32Pixels(pixel_width
, pixel_height
);
960 bmp
.eraseColor(color
);
961 favicon
.AddRepresentation(gfx::ImageSkiaRep(bmp
, scale
));
963 return gfx::Image(favicon
);
966 gfx::Image
Create1xFaviconFromPNGFile(const std::string
& path
) {
967 const char* kPNGExtension
= ".png";
968 if (!base::EndsWith(path
, kPNGExtension
,
969 base::CompareCase::INSENSITIVE_ASCII
))
972 base::FilePath full_path
;
973 if (!PathService::Get(chrome::DIR_TEST_DATA
, &full_path
))
976 full_path
= full_path
.AppendASCII("sync").AppendASCII(path
);
977 std::string contents
;
978 base::ReadFileToString(full_path
, &contents
);
979 return gfx::Image::CreateFrom1xPNGBytes(
980 base::RefCountedString::TakeString(&contents
));
983 std::string
IndexedURL(int i
) {
984 return base::StringPrintf("http://www.host.ext:1234/path/filename/%d", i
);
987 std::string
IndexedURLTitle(int i
) {
988 return base::StringPrintf("URL Title %d", i
);
991 std::string
IndexedFolderName(int i
) {
992 return base::StringPrintf("Folder Name %d", i
);
995 std::string
IndexedSubfolderName(int i
) {
996 return base::StringPrintf("Subfolder Name %d", i
);
999 std::string
IndexedSubsubfolderName(int i
) {
1000 return base::StringPrintf("Subsubfolder Name %d", i
);
1003 } // namespace bookmarks_helper