bookmarks_helper.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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. 4 5#include "chrome/browser/sync/test/integration/bookmarks_helper.h" 6 7#include "base/compiler_specific.h" 8#include "base/file_util.h" 9#include "base/path_service.h" 10#include "base/rand_util.h" 11#include "base/strings/string_number_conversions.h" 12#include "base/strings/string_util.h" 13#include "base/strings/stringprintf.h" 14#include "base/strings/utf_string_conversions.h" 15#include "base/synchronization/waitable_event.h" 16#include "base/task/cancelable_task_tracker.h" 17#include "chrome/browser/bookmarks/bookmark_model_factory.h" 18#include "chrome/browser/favicon/favicon_service.h" 19#include "chrome/browser/favicon/favicon_service_factory.h" 20#include "chrome/browser/history/history_db_task.h" 21#include "chrome/browser/history/history_service_factory.h" 22#include "chrome/browser/history/history_types.h" 23#include "chrome/browser/profiles/profile.h" 24#include "chrome/browser/sync/glue/bookmark_change_processor.h" 25#include "chrome/browser/sync/test/integration/multi_client_status_change_checker.h" 26#include "chrome/browser/sync/test/integration/profile_sync_service_harness.h" 27#include "chrome/browser/sync/test/integration/sync_datatype_helper.h" 28#include "chrome/browser/sync/test/integration/sync_test.h" 29#include "chrome/common/chrome_paths.h" 30#include "chrome/test/base/ui_test_utils.h" 31#include "components/bookmarks/browser/bookmark_model.h" 32#include "components/bookmarks/browser/bookmark_model_observer.h" 33#include "components/bookmarks/browser/bookmark_utils.h" 34#include "components/favicon_base/favicon_util.h" 35#include "testing/gtest/include/gtest/gtest.h" 36#include "third_party/skia/include/core/SkBitmap.h" 37#include "ui/base/models/tree_node_iterator.h" 38#include "ui/gfx/image/image_skia.h" 39 40namespace { 41 42// History task which runs all pending tasks on the history thread and 43// signals when the tasks have completed. 44class HistoryEmptyTask : public history::HistoryDBTask { 45 public: 46 explicit HistoryEmptyTask(base::WaitableEvent* done) : done_(done) {} 47 48 virtual bool RunOnDBThread(history::HistoryBackend* backend, 49 history::HistoryDatabase* db) OVERRIDE { 50 content::RunAllPendingInMessageLoop(); 51 done_->Signal(); 52 return true; 53 } 54 55 virtual void DoneRunOnMainThread() OVERRIDE {} 56 57 private: 58 virtual ~HistoryEmptyTask() {} 59 60 base::WaitableEvent* done_; 61}; 62 63// Helper class used to wait for changes to take effect on the favicon of a 64// particular bookmark node in a particular bookmark model. 65class FaviconChangeObserver : public BookmarkModelObserver { 66 public: 67 FaviconChangeObserver(BookmarkModel* model, const BookmarkNode* node) 68 : model_(model), 69 node_(node), 70 wait_for_load_(false) { 71 model->AddObserver(this); 72 } 73 virtual ~FaviconChangeObserver() { 74 model_->RemoveObserver(this); 75 } 76 void WaitForGetFavicon() { 77 wait_for_load_ = true; 78 content::RunMessageLoop(); 79 ASSERT_TRUE(node_->is_favicon_loaded()); 80 ASSERT_FALSE(model_->GetFavicon(node_).IsEmpty()); 81 } 82 void WaitForSetFavicon() { 83 wait_for_load_ = false; 84 content::RunMessageLoop(); 85 } 86 virtual void BookmarkModelLoaded(BookmarkModel* model, 87 bool ids_reassigned) OVERRIDE {} 88 virtual void BookmarkNodeMoved(BookmarkModel* model, 89 const BookmarkNode* old_parent, 90 int old_index, 91 const BookmarkNode* new_parent, 92 int new_index) OVERRIDE {} 93 virtual void BookmarkNodeAdded(BookmarkModel* model, 94 const BookmarkNode* parent, 95 int index) OVERRIDE {} 96 virtual void BookmarkNodeRemoved( 97 BookmarkModel* model, 98 const BookmarkNode* parent, 99 int old_index, 100 const BookmarkNode* node, 101 const std::set<GURL>& removed_urls) OVERRIDE {} 102 virtual void BookmarkAllUserNodesRemoved( 103 BookmarkModel* model, 104 const std::set<GURL>& removed_urls) OVERRIDE {} 105 106 virtual void BookmarkNodeChanged(BookmarkModel* model, 107 const BookmarkNode* node) OVERRIDE { 108 if (model == model_ && node == node_) 109 model->GetFavicon(node); 110 } 111 virtual void BookmarkNodeChildrenReordered( 112 BookmarkModel* model, 113 const BookmarkNode* node) OVERRIDE {} 114 virtual void BookmarkNodeFaviconChanged( 115 BookmarkModel* model, 116 const BookmarkNode* node) OVERRIDE { 117 if (model == model_ && node == node_) { 118 if (!wait_for_load_ || (wait_for_load_ && node->is_favicon_loaded())) 119 base::MessageLoopForUI::current()->Quit(); 120 } 121 } 122 123 private: 124 BookmarkModel* model_; 125 const BookmarkNode* node_; 126 bool wait_for_load_; 127 DISALLOW_COPY_AND_ASSIGN(FaviconChangeObserver); 128}; 129 130// A collection of URLs for which we have added favicons. Since loading a 131// favicon is an asynchronous operation and doesn't necessarily invoke a 132// callback, this collection is used to determine if we must wait for a URL's 133// favicon to load or not. 134std::set<GURL>* urls_with_favicons_ = NULL; 135 136// Returns the number of nodes of node type |node_type| in |model| whose 137// titles match the string |title|. 138int CountNodesWithTitlesMatching(BookmarkModel* model, 139 BookmarkNode::Type node_type, 140 const base::string16& title) { 141 ui::TreeNodeIterator<const BookmarkNode> iterator(model->root_node()); 142 // Walk through the model tree looking for bookmark nodes of node type 143 // |node_type| whose titles match |title|. 144 int count = 0; 145 while (iterator.has_next()) { 146 const BookmarkNode* node = iterator.Next(); 147 if ((node->type() == node_type) && (node->GetTitle() == title)) 148 ++count; 149 } 150 return count; 151} 152 153// Checks if the favicon data in |bitmap_a| and |bitmap_b| are equivalent. 154// Returns true if they match. 155bool FaviconRawBitmapsMatch(const SkBitmap& bitmap_a, 156 const SkBitmap& bitmap_b) { 157 if (bitmap_a.getSize() == 0U && bitmap_b.getSize() == 0U) 158 return true; 159 if ((bitmap_a.getSize() != bitmap_b.getSize()) || 160 (bitmap_a.width() != bitmap_b.width()) || 161 (bitmap_a.height() != bitmap_b.height())) { 162 LOG(ERROR) << "Favicon size mismatch: " << bitmap_a.getSize() << " (" 163 << bitmap_a.width() << "x" << bitmap_a.height() << ") vs. " 164 << bitmap_b.getSize() << " (" << bitmap_b.width() << "x" 165 << bitmap_b.height() << ")"; 166 return false; 167 } 168 SkAutoLockPixels bitmap_lock_a(bitmap_a); 169 SkAutoLockPixels bitmap_lock_b(bitmap_b); 170 void* node_pixel_addr_a = bitmap_a.getPixels(); 171 EXPECT_TRUE(node_pixel_addr_a); 172 void* node_pixel_addr_b = bitmap_b.getPixels(); 173 EXPECT_TRUE(node_pixel_addr_b); 174 if (memcmp(node_pixel_addr_a, node_pixel_addr_b, bitmap_a.getSize()) != 0) { 175 LOG(ERROR) << "Favicon bitmap mismatch"; 176 return false; 177 } else { 178 return true; 179 } 180} 181 182// Represents a favicon image and the icon URL associated with it. 183struct FaviconData { 184 FaviconData() { 185 } 186 187 FaviconData(const gfx::Image& favicon_image, 188 const GURL& favicon_url) 189 : image(favicon_image), 190 icon_url(favicon_url) { 191 } 192 193 ~FaviconData() { 194 } 195 196 gfx::Image image; 197 GURL icon_url; 198}; 199 200// Gets the favicon and icon URL associated with |node| in |model|. 201FaviconData GetFaviconData(BookmarkModel* model, 202 const BookmarkNode* node) { 203 // If a favicon wasn't explicitly set for a particular URL, simply return its 204 // blank favicon. 205 if (!urls_with_favicons_ || 206 urls_with_favicons_->find(node->url()) == urls_with_favicons_->end()) { 207 return FaviconData(); 208 } 209 // If a favicon was explicitly set, we may need to wait for it to be loaded 210 // via BookmarkModel::GetFavicon(), which is an asynchronous operation. 211 if (!node->is_favicon_loaded()) { 212 FaviconChangeObserver observer(model, node); 213 model->GetFavicon(node); 214 observer.WaitForGetFavicon(); 215 } 216 EXPECT_TRUE(node->is_favicon_loaded()); 217 EXPECT_FALSE(model->GetFavicon(node).IsEmpty()); 218 return FaviconData(model->GetFavicon(node), node->icon_url()); 219} 220 221// Sets the favicon for |profile| and |node|. |profile| may be 222// |test()->verifier()|. 223void SetFaviconImpl(Profile* profile, 224 const BookmarkNode* node, 225 const GURL& icon_url, 226 const gfx::Image& image, 227 bookmarks_helper::FaviconSource favicon_source) { 228 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile); 229 230 FaviconChangeObserver observer(model, node); 231 FaviconService* favicon_service = 232 FaviconServiceFactory::GetForProfile(profile, 233 Profile::EXPLICIT_ACCESS); 234 if (favicon_source == bookmarks_helper::FROM_UI) { 235 favicon_service->SetFavicons( 236 node->url(), icon_url, favicon_base::FAVICON, image); 237 } else { 238 browser_sync::BookmarkChangeProcessor::ApplyBookmarkFavicon( 239 node, profile, icon_url, image.As1xPNGBytes()); 240 } 241 242 // Wait for the favicon for |node| to be invalidated. 243 observer.WaitForSetFavicon(); 244 // Wait for the BookmarkModel to fetch the updated favicon and for the new 245 // favicon to be sent to BookmarkChangeProcessor. 246 GetFaviconData(model, node); 247} 248 249// Wait for all currently scheduled tasks on the history thread for all 250// profiles to complete and any notifications sent to the UI thread to have 251// finished processing. 252void WaitForHistoryToProcessPendingTasks() { 253 // Skip waiting for history to complete for tests without favicons. 254 if (!urls_with_favicons_) 255 return; 256 257 std::vector<Profile*> profiles_which_need_to_wait; 258 if (sync_datatype_helper::test()->use_verifier()) 259 profiles_which_need_to_wait.push_back( 260 sync_datatype_helper::test()->verifier()); 261 for (int i = 0; i < sync_datatype_helper::test()->num_clients(); ++i) 262 profiles_which_need_to_wait.push_back( 263 sync_datatype_helper::test()->GetProfile(i)); 264 265 for (size_t i = 0; i < profiles_which_need_to_wait.size(); ++i) { 266 Profile* profile = profiles_which_need_to_wait[i]; 267 HistoryService* history_service = 268 HistoryServiceFactory::GetForProfileWithoutCreating(profile); 269 base::WaitableEvent done(false, false); 270 base::CancelableTaskTracker task_tracker; 271 history_service->ScheduleDBTask(new HistoryEmptyTask(&done), &task_tracker); 272 done.Wait(); 273 } 274 // Wait such that any notifications broadcast from one of the history threads 275 // to the UI thread are processed. 276 content::RunAllPendingInMessageLoop(); 277} 278 279// Checks if the favicon in |node_a| from |model_a| matches that of |node_b| 280// from |model_b|. Returns true if they match. 281bool FaviconsMatch(BookmarkModel* model_a, 282 BookmarkModel* model_b, 283 const BookmarkNode* node_a, 284 const BookmarkNode* node_b) { 285 FaviconData favicon_data_a = GetFaviconData(model_a, node_a); 286 FaviconData favicon_data_b = GetFaviconData(model_b, node_b); 287 288 if (favicon_data_a.icon_url != favicon_data_b.icon_url) 289 return false; 290 291 gfx::Image image_a = favicon_data_a.image; 292 gfx::Image image_b = favicon_data_b.image; 293 294 if (image_a.IsEmpty() && image_b.IsEmpty()) 295 return true; // Two empty images are equivalent. 296 297 if (image_a.IsEmpty() != image_b.IsEmpty()) 298 return false; 299 300 // Compare only the 1x bitmaps as only those are synced. 301 SkBitmap bitmap_a = image_a.AsImageSkia().GetRepresentation( 302 1.0f).sk_bitmap(); 303 SkBitmap bitmap_b = image_b.AsImageSkia().GetRepresentation( 304 1.0f).sk_bitmap(); 305 return FaviconRawBitmapsMatch(bitmap_a, bitmap_b); 306} 307 308// Does a deep comparison of BookmarkNode fields in |model_a| and |model_b|. 309// Returns true if they are all equal. 310bool NodesMatch(const BookmarkNode* node_a, const BookmarkNode* node_b) { 311 if (node_a == NULL || node_b == NULL) 312 return node_a == node_b; 313 if (node_a->is_folder() != node_b->is_folder()) { 314 LOG(ERROR) << "Cannot compare folder with bookmark"; 315 return false; 316 } 317 if (node_a->GetTitle() != node_b->GetTitle()) { 318 LOG(ERROR) << "Title mismatch: " << node_a->GetTitle() << " vs. " 319 << node_b->GetTitle(); 320 return false; 321 } 322 if (node_a->url() != node_b->url()) { 323 LOG(ERROR) << "URL mismatch: " << node_a->url() << " vs. " 324 << node_b->url(); 325 return false; 326 } 327 if (node_a->parent()->GetIndexOf(node_a) != 328 node_b->parent()->GetIndexOf(node_b)) { 329 LOG(ERROR) << "Index mismatch: " 330 << node_a->parent()->GetIndexOf(node_a) << " vs. " 331 << node_b->parent()->GetIndexOf(node_b); 332 return false; 333 } 334 return true; 335} 336 337// Checks if the hierarchies in |model_a| and |model_b| are equivalent in 338// terms of the data model and favicon. Returns true if they both match. 339// Note: Some peripheral fields like creation times are allowed to mismatch. 340bool BookmarkModelsMatch(BookmarkModel* model_a, BookmarkModel* model_b) { 341 bool ret_val = true; 342 ui::TreeNodeIterator<const BookmarkNode> iterator_a(model_a->root_node()); 343 ui::TreeNodeIterator<const BookmarkNode> iterator_b(model_b->root_node()); 344 while (iterator_a.has_next()) { 345 const BookmarkNode* node_a = iterator_a.Next(); 346 if (!iterator_b.has_next()) { 347 LOG(ERROR) << "Models do not match."; 348 return false; 349 } 350 const BookmarkNode* node_b = iterator_b.Next(); 351 ret_val = ret_val && NodesMatch(node_a, node_b); 352 if (node_a->is_folder() || node_b->is_folder()) 353 continue; 354 ret_val = ret_val && FaviconsMatch(model_a, model_b, node_a, node_b); 355 } 356 ret_val = ret_val && (!iterator_b.has_next()); 357 return ret_val; 358} 359 360// Finds the node in the verifier bookmark model that corresponds to 361// |foreign_node| in |foreign_model| and stores its address in |result|. 362void FindNodeInVerifier(BookmarkModel* foreign_model, 363 const BookmarkNode* foreign_node, 364 const BookmarkNode** result) { 365 // Climb the tree. 366 std::stack<int> path; 367 const BookmarkNode* walker = foreign_node; 368 while (walker != foreign_model->root_node()) { 369 path.push(walker->parent()->GetIndexOf(walker)); 370 walker = walker->parent(); 371 } 372 373 // Swing over to the other tree. 374 walker = bookmarks_helper::GetVerifierBookmarkModel()->root_node(); 375 376 // Climb down. 377 while (!path.empty()) { 378 ASSERT_TRUE(walker->is_folder()); 379 ASSERT_LT(path.top(), walker->child_count()); 380 walker = walker->GetChild(path.top()); 381 path.pop(); 382 } 383 384 ASSERT_TRUE(NodesMatch(foreign_node, walker)); 385 *result = walker; 386} 387 388} // namespace 389 390 391namespace bookmarks_helper { 392 393BookmarkModel* GetBookmarkModel(int index) { 394 return BookmarkModelFactory::GetForProfile( 395 sync_datatype_helper::test()->GetProfile(index)); 396} 397 398const BookmarkNode* GetBookmarkBarNode(int index) { 399 return GetBookmarkModel(index)->bookmark_bar_node(); 400} 401 402const BookmarkNode* GetOtherNode(int index) { 403 return GetBookmarkModel(index)->other_node(); 404} 405 406const BookmarkNode* GetSyncedBookmarksNode(int index) { 407 return GetBookmarkModel(index)->mobile_node(); 408} 409 410BookmarkModel* GetVerifierBookmarkModel() { 411 return BookmarkModelFactory::GetForProfile( 412 sync_datatype_helper::test()->verifier()); 413} 414 415const BookmarkNode* AddURL(int profile, 416 const std::string& title, 417 const GURL& url) { 418 return AddURL(profile, GetBookmarkBarNode(profile), 0, title, url); 419} 420 421const BookmarkNode* AddURL(int profile, 422 int index, 423 const std::string& title, 424 const GURL& url) { 425 return AddURL(profile, GetBookmarkBarNode(profile), index, title, url); 426} 427 428const BookmarkNode* AddURL(int profile, 429 const BookmarkNode* parent, 430 int index, 431 const std::string& title, 432 const GURL& url) { 433 BookmarkModel* model = GetBookmarkModel(profile); 434 if (bookmarks::GetBookmarkNodeByID(model, parent->id()) != parent) { 435 LOG(ERROR) << "Node " << parent->GetTitle() << " does not belong to " 436 << "Profile " << profile; 437 return NULL; 438 } 439 const BookmarkNode* result = 440 model->AddURL(parent, index, base::UTF8ToUTF16(title), url); 441 if (!result) { 442 LOG(ERROR) << "Could not add bookmark " << title << " to Profile " 443 << profile; 444 return NULL; 445 } 446 if (sync_datatype_helper::test()->use_verifier()) { 447 const BookmarkNode* v_parent = NULL; 448 FindNodeInVerifier(model, parent, &v_parent); 449 const BookmarkNode* v_node = GetVerifierBookmarkModel()->AddURL( 450 v_parent, index, base::UTF8ToUTF16(title), url); 451 if (!v_node) { 452 LOG(ERROR) << "Could not add bookmark " << title << " to the verifier"; 453 return NULL; 454 } 455 EXPECT_TRUE(NodesMatch(v_node, result)); 456 } 457 return result; 458} 459 460const BookmarkNode* AddFolder(int profile, 461 const std::string& title) { 462 return AddFolder(profile, GetBookmarkBarNode(profile), 0, title); 463} 464 465const BookmarkNode* AddFolder(int profile, 466 int index, 467 const std::string& title) { 468 return AddFolder(profile, GetBookmarkBarNode(profile), index, title); 469} 470 471const BookmarkNode* AddFolder(int profile, 472 const BookmarkNode* parent, 473 int index, 474 const std::string& title) { 475 BookmarkModel* model = GetBookmarkModel(profile); 476 if (bookmarks::GetBookmarkNodeByID(model, parent->id()) != parent) { 477 LOG(ERROR) << "Node " << parent->GetTitle() << " does not belong to " 478 << "Profile " << profile; 479 return NULL; 480 } 481 const BookmarkNode* result = 482 model->AddFolder(parent, index, base::UTF8ToUTF16(title)); 483 EXPECT_TRUE(result); 484 if (!result) { 485 LOG(ERROR) << "Could not add folder " << title << " to Profile " 486 << profile; 487 return NULL; 488 } 489 if (sync_datatype_helper::test()->use_verifier()) { 490 const BookmarkNode* v_parent = NULL; 491 FindNodeInVerifier(model, parent, &v_parent); 492 const BookmarkNode* v_node = GetVerifierBookmarkModel()->AddFolder( 493 v_parent, index, base::UTF8ToUTF16(title)); 494 if (!v_node) { 495 LOG(ERROR) << "Could not add folder " << title << " to the verifier"; 496 return NULL; 497 } 498 EXPECT_TRUE(NodesMatch(v_node, result)); 499 } 500 return result; 501} 502 503void SetTitle(int profile, 504 const BookmarkNode* node, 505 const std::string& new_title) { 506 BookmarkModel* model = GetBookmarkModel(profile); 507 ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model, node->id()), node) 508 << "Node " << node->GetTitle() << " does not belong to " 509 << "Profile " << profile; 510 if (sync_datatype_helper::test()->use_verifier()) { 511 const BookmarkNode* v_node = NULL; 512 FindNodeInVerifier(model, node, &v_node); 513 GetVerifierBookmarkModel()->SetTitle(v_node, base::UTF8ToUTF16(new_title)); 514 } 515 model->SetTitle(node, base::UTF8ToUTF16(new_title)); 516} 517 518void SetFavicon(int profile, 519 const BookmarkNode* node, 520 const GURL& icon_url, 521 const gfx::Image& image, 522 FaviconSource favicon_source) { 523 BookmarkModel* model = GetBookmarkModel(profile); 524 ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model, node->id()), node) 525 << "Node " << node->GetTitle() << " does not belong to " 526 << "Profile " << profile; 527 ASSERT_EQ(BookmarkNode::URL, node->type()) << "Node " << node->GetTitle() 528 << " must be a url."; 529 if (urls_with_favicons_ == NULL) 530 urls_with_favicons_ = new std::set<GURL>(); 531 urls_with_favicons_->insert(node->url()); 532 if (sync_datatype_helper::test()->use_verifier()) { 533 const BookmarkNode* v_node = NULL; 534 FindNodeInVerifier(model, node, &v_node); 535 SetFaviconImpl(sync_datatype_helper::test()->verifier(), 536 v_node, 537 icon_url, 538 image, 539 favicon_source); 540 } 541 SetFaviconImpl(sync_datatype_helper::test()->GetProfile(profile), 542 node, 543 icon_url, 544 image, 545 favicon_source); 546} 547 548const BookmarkNode* SetURL(int profile, 549 const BookmarkNode* node, 550 const GURL& new_url) { 551 BookmarkModel* model = GetBookmarkModel(profile); 552 if (bookmarks::GetBookmarkNodeByID(model, node->id()) != node) { 553 LOG(ERROR) << "Node " << node->GetTitle() << " does not belong to " 554 << "Profile " << profile; 555 return NULL; 556 } 557 if (sync_datatype_helper::test()->use_verifier()) { 558 const BookmarkNode* v_node = NULL; 559 FindNodeInVerifier(model, node, &v_node); 560 if (v_node->is_url()) 561 GetVerifierBookmarkModel()->SetURL(v_node, new_url); 562 } 563 if (node->is_url()) 564 model->SetURL(node, new_url); 565 return node; 566} 567 568void Move(int profile, 569 const BookmarkNode* node, 570 const BookmarkNode* new_parent, 571 int index) { 572 BookmarkModel* model = GetBookmarkModel(profile); 573 ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model, node->id()), node) 574 << "Node " << node->GetTitle() << " does not belong to " 575 << "Profile " << profile; 576 if (sync_datatype_helper::test()->use_verifier()) { 577 const BookmarkNode* v_new_parent = NULL; 578 const BookmarkNode* v_node = NULL; 579 FindNodeInVerifier(model, new_parent, &v_new_parent); 580 FindNodeInVerifier(model, node, &v_node); 581 GetVerifierBookmarkModel()->Move(v_node, v_new_parent, index); 582 } 583 model->Move(node, new_parent, index); 584} 585 586void Remove(int profile, const BookmarkNode* parent, int index) { 587 BookmarkModel* model = GetBookmarkModel(profile); 588 ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model, parent->id()), parent) 589 << "Node " << parent->GetTitle() << " does not belong to " 590 << "Profile " << profile; 591 if (sync_datatype_helper::test()->use_verifier()) { 592 const BookmarkNode* v_parent = NULL; 593 FindNodeInVerifier(model, parent, &v_parent); 594 ASSERT_TRUE(NodesMatch(parent->GetChild(index), v_parent->GetChild(index))); 595 GetVerifierBookmarkModel()->Remove(v_parent, index); 596 } 597 model->Remove(parent, index); 598} 599 600void RemoveAll(int profile) { 601 if (sync_datatype_helper::test()->use_verifier()) { 602 const BookmarkNode* root_node = GetVerifierBookmarkModel()->root_node(); 603 for (int i = 0; i < root_node->child_count(); ++i) { 604 const BookmarkNode* permanent_node = root_node->GetChild(i); 605 for (int j = permanent_node->child_count() - 1; j >= 0; --j) { 606 GetVerifierBookmarkModel()->Remove(permanent_node, j); 607 } 608 } 609 } 610 GetBookmarkModel(profile)->RemoveAllUserBookmarks(); 611} 612 613void SortChildren(int profile, const BookmarkNode* parent) { 614 BookmarkModel* model = GetBookmarkModel(profile); 615 ASSERT_EQ(bookmarks::GetBookmarkNodeByID(model, parent->id()), parent) 616 << "Node " << parent->GetTitle() << " does not belong to " 617 << "Profile " << profile; 618 if (sync_datatype_helper::test()->use_verifier()) { 619 const BookmarkNode* v_parent = NULL; 620 FindNodeInVerifier(model, parent, &v_parent); 621 GetVerifierBookmarkModel()->SortChildren(v_parent); 622 } 623 model->SortChildren(parent); 624} 625 626void ReverseChildOrder(int profile, const BookmarkNode* parent) { 627 ASSERT_EQ( 628 bookmarks::GetBookmarkNodeByID(GetBookmarkModel(profile), parent->id()), 629 parent) 630 << "Node " << parent->GetTitle() << " does not belong to " 631 << "Profile " << profile; 632 int child_count = parent->child_count(); 633 if (child_count <= 0) 634 return; 635 for (int index = 0; index < child_count; ++index) { 636 Move(profile, parent->GetChild(index), parent, child_count - index); 637 } 638} 639 640bool ModelMatchesVerifier(int profile) { 641 if (!sync_datatype_helper::test()->use_verifier()) { 642 LOG(ERROR) << "Illegal to call ModelMatchesVerifier() after " 643 << "DisableVerifier(). Use ModelsMatch() instead."; 644 return false; 645 } 646 return BookmarkModelsMatch(GetVerifierBookmarkModel(), 647 GetBookmarkModel(profile)); 648} 649 650bool AllModelsMatchVerifier() { 651 // Ensure that all tasks have finished processing on the history thread 652 // and that any notifications the history thread may have sent have been 653 // processed before comparing models. 654 WaitForHistoryToProcessPendingTasks(); 655 656 for (int i = 0; i < sync_datatype_helper::test()->num_clients(); ++i) { 657 if (!ModelMatchesVerifier(i)) { 658 LOG(ERROR) << "Model " << i << " does not match the verifier."; 659 return false; 660 } 661 } 662 return true; 663} 664 665bool ModelsMatch(int profile_a, int profile_b) { 666 return BookmarkModelsMatch(GetBookmarkModel(profile_a), 667 GetBookmarkModel(profile_b)); 668} 669 670bool AllModelsMatch() { 671 // Ensure that all tasks have finished processing on the history thread 672 // and that any notifications the history thread may have sent have been 673 // processed before comparing models. 674 WaitForHistoryToProcessPendingTasks(); 675 676 for (int i = 1; i < sync_datatype_helper::test()->num_clients(); ++i) { 677 if (!ModelsMatch(0, i)) { 678 LOG(ERROR) << "Model " << i << " does not match Model 0."; 679 return false; 680 } 681 } 682 return true; 683} 684 685namespace { 686 687// Helper class used in the implementation of AwaitAllModelsMatch. 688class AllModelsMatchChecker : public MultiClientStatusChangeChecker { 689 public: 690 AllModelsMatchChecker(); 691 virtual ~AllModelsMatchChecker(); 692 693 virtual bool IsExitConditionSatisfied() OVERRIDE; 694 virtual std::string GetDebugMessage() const OVERRIDE; 695}; 696 697AllModelsMatchChecker::AllModelsMatchChecker() 698 : MultiClientStatusChangeChecker( 699 sync_datatype_helper::test()->GetSyncServices()) {} 700 701AllModelsMatchChecker::~AllModelsMatchChecker() {} 702 703bool AllModelsMatchChecker::IsExitConditionSatisfied() { 704 return AllModelsMatch(); 705} 706 707std::string AllModelsMatchChecker::GetDebugMessage() const { 708 return "Waiting for matching models"; 709} 710 711} // namespace 712 713bool AwaitAllModelsMatch() { 714 AllModelsMatchChecker checker; 715 checker.Wait(); 716 return !checker.TimedOut(); 717} 718 719 720bool ContainsDuplicateBookmarks(int profile) { 721 ui::TreeNodeIterator<const BookmarkNode> iterator( 722 GetBookmarkModel(profile)->root_node()); 723 while (iterator.has_next()) { 724 const BookmarkNode* node = iterator.Next(); 725 if (node->is_folder()) 726 continue; 727 std::vector<const BookmarkNode*> nodes; 728 GetBookmarkModel(profile)->GetNodesByURL(node->url(), &nodes); 729 EXPECT_TRUE(nodes.size() >= 1); 730 for (std::vector<const BookmarkNode*>::const_iterator it = nodes.begin(); 731 it != nodes.end(); ++it) { 732 if (node->id() != (*it)->id() && 733 node->parent() == (*it)->parent() && 734 node->GetTitle() == (*it)->GetTitle()){ 735 return true; 736 } 737 } 738 } 739 return false; 740} 741 742bool HasNodeWithURL(int profile, const GURL& url) { 743 std::vector<const BookmarkNode*> nodes; 744 GetBookmarkModel(profile)->GetNodesByURL(url, &nodes); 745 return !nodes.empty(); 746} 747 748const BookmarkNode* GetUniqueNodeByURL(int profile, const GURL& url) { 749 std::vector<const BookmarkNode*> nodes; 750 GetBookmarkModel(profile)->GetNodesByURL(url, &nodes); 751 EXPECT_EQ(1U, nodes.size()); 752 if (nodes.empty()) 753 return NULL; 754 return nodes[0]; 755} 756 757int CountBookmarksWithTitlesMatching(int profile, const std::string& title) { 758 return CountNodesWithTitlesMatching(GetBookmarkModel(profile), 759 BookmarkNode::URL, 760 base::UTF8ToUTF16(title)); 761} 762 763int CountFoldersWithTitlesMatching(int profile, const std::string& title) { 764 return CountNodesWithTitlesMatching(GetBookmarkModel(profile), 765 BookmarkNode::FOLDER, 766 base::UTF8ToUTF16(title)); 767} 768 769gfx::Image CreateFavicon(SkColor color) { 770 const int dip_width = 16; 771 const int dip_height = 16; 772 std::vector<float> favicon_scales = favicon_base::GetFaviconScales(); 773 gfx::ImageSkia favicon; 774 for (size_t i = 0; i < favicon_scales.size(); ++i) { 775 float scale = favicon_scales[i]; 776 int pixel_width = dip_width * scale; 777 int pixel_height = dip_height * scale; 778 SkBitmap bmp; 779 bmp.allocN32Pixels(pixel_width, pixel_height); 780 bmp.eraseColor(color); 781 favicon.AddRepresentation(gfx::ImageSkiaRep(bmp, scale)); 782 } 783 return gfx::Image(favicon); 784} 785 786gfx::Image Create1xFaviconFromPNGFile(const std::string& path) { 787 const char* kPNGExtension = ".png"; 788 if (!EndsWith(path, kPNGExtension, false)) 789 return gfx::Image(); 790 791 base::FilePath full_path; 792 if (!PathService::Get(chrome::DIR_TEST_DATA, &full_path)) 793 return gfx::Image(); 794 795 full_path = full_path.AppendASCII("sync").AppendASCII(path); 796 std::string contents; 797 base::ReadFileToString(full_path, &contents); 798 return gfx::Image::CreateFrom1xPNGBytes( 799 base::RefCountedString::TakeString(&contents)); 800} 801 802std::string IndexedURL(int i) { 803 return base::StringPrintf("http://www.host.ext:1234/path/filename/%d", i); 804} 805 806std::string IndexedURLTitle(int i) { 807 return base::StringPrintf("URL Title %d", i); 808} 809 810std::string IndexedFolderName(int i) { 811 return base::StringPrintf("Folder Name %d", i); 812} 813 814std::string IndexedSubfolderName(int i) { 815 return base::StringPrintf("Subfolder Name %d", i); 816} 817 818std::string IndexedSubsubfolderName(int i) { 819 return base::StringPrintf("Subsubfolder Name %d", i); 820} 821 822} // namespace bookmarks_helper 823