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