BookmarkManager: Fix 'new folder text field size changes on clicking it' issue.
[chromium-blink-merge.git] / chrome / browser / sync / test / integration / bookmarks_helper.cc
blob8526cac0e118021145dbb09b577e26910992f7f6
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"
7 #include <set>
8 #include <vector>
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;
54 namespace {
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 {
59 public:
60 explicit HistoryEmptyTask(base::WaitableEvent* done) : done_(done) {}
62 bool RunOnDBThread(history::HistoryBackend* backend,
63 history::HistoryDatabase* db) override {
64 content::RunAllPendingInMessageLoop();
65 done_->Signal();
66 return true;
69 void DoneRunOnMainThread() override {}
71 private:
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 {
80 public:
81 FaviconChangeObserver(BookmarkModel* model, const BookmarkNode* node)
82 : model_(model),
83 node_(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,
104 int old_index,
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,
112 int old_index,
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();
134 private:
135 BookmarkModel* model_;
136 const BookmarkNode* node_;
137 bool wait_for_load_;
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|.
155 int count = 0;
156 while (iterator.has_next()) {
157 const BookmarkNode* node = iterator.Next();
158 if ((node->type() == node_type) && (node->GetTitle() == title))
159 ++count;
161 return count;
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
168 // |node_type|.
169 int count = 0;
170 while (iterator.has_next()) {
171 const BookmarkNode* node = iterator.Next();
172 if (node->type() == node_type)
173 ++count;
175 return count;
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)
183 return true;
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() << ")";
191 return false;
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";
201 return false;
202 } else {
203 return true;
207 // Represents a favicon image and the icon URL associated with it.
208 struct FaviconData {
209 FaviconData() {
212 FaviconData(const gfx::Image& favicon_image,
213 const GURL& favicon_url)
214 : image(favicon_image),
215 icon_url(favicon_url) {
218 ~FaviconData() {
221 gfx::Image image;
222 GURL icon_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
229 // blank favicon.
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);
262 } else {
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
284 // the database.
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);
290 callback.Run();
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_)
299 return;
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)),
318 &task_tracker);
319 done.Wait();
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)
336 return false;
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())
345 return false;
347 // Compare only the 1x bitmaps as only those are synced.
348 SkBitmap bitmap_a = image_a.AsImageSkia().GetRepresentation(
349 1.0f).sk_bitmap();
350 SkBitmap bitmap_b = image_b.AsImageSkia().GetRepresentation(
351 1.0f).sk_bitmap();
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";
362 return false;
364 if (node_a->GetTitle() != node_b->GetTitle()) {
365 LOG(ERROR) << "Title mismatch: " << node_a->GetTitle() << " vs. "
366 << node_b->GetTitle();
367 return false;
369 if (node_a->url() != node_b->url()) {
370 LOG(ERROR) << "URL mismatch: " << node_a->url() << " vs. "
371 << node_b->url();
372 return false;
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);
379 return false;
381 return true;
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) {
395 bool ret_val = true;
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.";
404 return false;
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())
409 continue;
410 ret_val = ret_val && FaviconsMatch(model_a, model_b, node_a, node_b);
412 ret_val = ret_val && (!iterator_b.has_next());
413 return ret_val;
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) {
421 // Climb the tree.
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();
432 // Climb down.
433 while (!path.empty()) {
434 ASSERT_TRUE(walker->is_folder());
435 ASSERT_LT(path.top(), walker->child_count());
436 walker = walker->GetChild(path.top());
437 path.pop();
440 ASSERT_TRUE(NodesMatch(foreign_node, walker));
441 *result = walker;
444 } // namespace
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))
469 ->managed_node();
472 BookmarkModel* GetVerifierBookmarkModel() {
473 return BookmarkModelFactory::GetForProfile(
474 sync_datatype_helper::test()->verifier());
477 const BookmarkNode* AddURL(int profile,
478 const std::string& title,
479 const GURL& url) {
480 return AddURL(profile, GetBookmarkBarNode(profile), 0, title, url);
483 const BookmarkNode* AddURL(int profile,
484 int index,
485 const std::string& title,
486 const GURL& url) {
487 return AddURL(profile, GetBookmarkBarNode(profile), index, title, url);
490 const BookmarkNode* AddURL(int profile,
491 const BookmarkNode* parent,
492 int index,
493 const std::string& title,
494 const GURL& url) {
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;
499 return NULL;
501 const BookmarkNode* result =
502 model->AddURL(parent, index, base::UTF8ToUTF16(title), url);
503 if (!result) {
504 LOG(ERROR) << "Could not add bookmark " << title << " to Profile "
505 << profile;
506 return NULL;
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);
513 if (!v_node) {
514 LOG(ERROR) << "Could not add bookmark " << title << " to the verifier";
515 return NULL;
517 EXPECT_TRUE(NodesMatch(v_node, result));
519 return 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,
528 int index,
529 const std::string& title) {
530 return AddFolder(profile, GetBookmarkBarNode(profile), index, title);
533 const BookmarkNode* AddFolder(int profile,
534 const BookmarkNode* parent,
535 int index,
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;
541 return NULL;
543 const BookmarkNode* result =
544 model->AddFolder(parent, index, base::UTF8ToUTF16(title));
545 EXPECT_TRUE(result);
546 if (!result) {
547 LOG(ERROR) << "Could not add folder " << title << " to Profile "
548 << profile;
549 return NULL;
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));
556 if (!v_node) {
557 LOG(ERROR) << "Could not add folder " << title << " to the verifier";
558 return NULL;
560 EXPECT_TRUE(NodesMatch(v_node, result));
562 return 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(),
598 v_node,
599 icon_url,
600 image,
601 favicon_source);
603 SetFaviconImpl(sync_datatype_helper::test()->GetProfile(profile),
604 node,
605 icon_url,
606 image,
607 favicon_source);
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()),
640 &task_tracker);
642 run_loop.Run();
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;
652 return NULL;
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);
660 if (node->is_url())
661 model->SetURL(node, new_url);
662 return node;
665 void Move(int profile,
666 const BookmarkNode* node,
667 const BookmarkNode* new_parent,
668 int index) {
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) {
724 ASSERT_EQ(
725 bookmarks::GetBookmarkNodeByID(GetBookmarkModel(profile), parent->id()),
726 parent)
727 << "Node " << parent->GetTitle() << " does not belong to "
728 << "Profile " << profile;
729 int child_count = parent->child_count();
730 if (child_count <= 0)
731 return;
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.";
741 return false;
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.";
756 return false;
759 return true;
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.";
776 return false;
779 return true;
782 namespace {
784 // Helper class used in the implementation of AwaitAllModelsMatch.
785 class AllModelsMatchChecker : public MultiClientStatusChangeChecker {
786 public:
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";
808 } // namespace
810 bool AwaitAllModelsMatch() {
811 AllModelsMatchChecker checker;
812 checker.Wait();
813 return !checker.TimedOut();
816 namespace {
818 // TODO(pvalenzuela): Remove this class and instead use
819 // AwaitMatchStatusChangeChecker.
820 class CountBookmarksWithTitlesMatchingChecker
821 : public SingleClientStatusChangeChecker {
822 public:
823 CountBookmarksWithTitlesMatchingChecker(ProfileSyncService* service,
824 int profile_index,
825 const std::string& title,
826 int expected_count)
827 : SingleClientStatusChangeChecker(service),
828 profile_index_(profile_index),
829 title_(title),
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";
843 private:
844 const int profile_index_;
845 const std::string title_;
846 const int expected_count_;
849 } // namespace
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,
857 profile,
858 title,
859 expected_count);
860 checker.Wait();
861 return !checker.TimedOut();
865 bool BookmarkCountsByUrlMatch(int profile,
866 const GURL& url,
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.",
871 expected_count,
872 actual_count);
873 return false;
875 return true;
878 bool AwaitCountBookmarksWithUrlsMatching(int profile,
879 const GURL& url,
880 int expected_count) {
881 AwaitMatchStatusChangeChecker checker(base::Bind(BookmarkCountsByUrlMatch,
882 profile,
883 base::ConstRef(url),
884 expected_count),
885 "Bookmark URL counts match.");
886 checker.Wait();
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())
896 continue;
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()){
905 return true;
909 return false;
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());
922 if (nodes.empty())
923 return NULL;
924 return nodes[0];
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),
933 BookmarkNode::URL,
934 base::UTF8ToUTF16(title));
937 int CountBookmarksWithUrlsMatching(int profile, const GURL& url) {
938 std::vector<const BookmarkNode*> nodes;
939 GetBookmarkModel(profile)->GetNodesByURL(url, &nodes);
940 return nodes.size();
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;
958 SkBitmap bmp;
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))
970 return gfx::Image();
972 base::FilePath full_path;
973 if (!PathService::Get(chrome::DIR_TEST_DATA, &full_path))
974 return gfx::Image();
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