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.h" 18 #include "chrome/browser/favicon/favicon_service_factory.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/browser/bookmark_model.h" 31 #include "components/bookmarks/browser/bookmark_model_observer.h" 32 #include "components/bookmarks/browser/bookmark_utils.h" 33 #include "components/favicon_base/favicon_util.h" 34 #include "testing/gtest/include/gtest/gtest.h" 35 #include "third_party/skia/include/core/SkBitmap.h" 36 #include "ui/base/models/tree_node_iterator.h" 37 #include "ui/gfx/image/image_skia.h" 38 39 namespace { 40 41 // History task which runs all pending tasks on the history thread and 42 // signals when the tasks have completed. 43 class HistoryEmptyTask : public history::HistoryDBTask { 44 public: 45 explicit HistoryEmptyTask(base::WaitableEvent* done) : done_(done) {} 46 47 virtual bool RunOnDBThread(history::HistoryBackend* backend, 48 history::HistoryDatabase* db) OVERRIDE { 49 content::RunAllPendingInMessageLoop(); 50 done_->Signal(); 51 return true; 52 } 53 54 virtual void DoneRunOnMainThread() OVERRIDE {} 55 56 private: 57 virtual ~HistoryEmptyTask() {} 58 59 base::WaitableEvent* done_; 60 }; 61 62 // Helper class used to wait for changes to take effect on the favicon of a 63 // particular bookmark node in a particular bookmark model. 64 class FaviconChangeObserver : public BookmarkModelObserver { 65 public: 66 FaviconChangeObserver(BookmarkModel* model, const BookmarkNode* node) 67 : model_(model), 68 node_(node), 69 wait_for_load_(false) { 70 model->AddObserver(this); 71 } 72 virtual ~FaviconChangeObserver() { 73 model_->RemoveObserver(this); 74 } 75 void WaitForGetFavicon() { 76 wait_for_load_ = true; 77 content::RunMessageLoop(); 78 ASSERT_TRUE(node_->is_favicon_loaded()); 79 ASSERT_FALSE(model_->GetFavicon(node_).IsEmpty()); 80 } 81 void WaitForSetFavicon() { 82 wait_for_load_ = false; 83 content::RunMessageLoop(); 84 } 85 virtual void BookmarkModelLoaded(BookmarkModel* model, 86 bool ids_reassigned) OVERRIDE {} 87 virtual void BookmarkNodeMoved(BookmarkModel* model, 88 const BookmarkNode* old_parent, 89 int old_index, 90 const BookmarkNode* new_parent, 91 int new_index) OVERRIDE {} 92 virtual void BookmarkNodeAdded(BookmarkModel* model, 93 const BookmarkNode* parent, 94 int index) OVERRIDE {} 95 virtual void BookmarkNodeRemoved( 96 BookmarkModel* model, 97 const BookmarkNode* parent, 98 int old_index, 99 const BookmarkNode* node, 100 const std::set<GURL>& removed_urls) OVERRIDE {} 101 virtual void BookmarkAllUserNodesRemoved( 102 BookmarkModel* model, 103 const std::set<GURL>& removed_urls) OVERRIDE {} 104 105 virtual void BookmarkNodeChanged(BookmarkModel* model, 106 const BookmarkNode* node) OVERRIDE { 107 if (model == model_ && node == node_) 108 model->GetFavicon(node); 109 } 110 virtual void BookmarkNodeChildrenReordered( 111 BookmarkModel* model, 112 const BookmarkNode* node) OVERRIDE {} 113 virtual void BookmarkNodeFaviconChanged( 114 BookmarkModel* model, 115 const BookmarkNode* node) OVERRIDE { 116 if (model == model_ && node == node_) { 117 if (!wait_for_load_ || (wait_for_load_ && node->is_favicon_loaded())) 118 base::MessageLoopForUI::current()->Quit(); 119 } 120 } 121 122 private: 123 BookmarkModel* model_; 124 const BookmarkNode* node_; 125 bool wait_for_load_; 126 DISALLOW_COPY_AND_ASSIGN(FaviconChangeObserver); 127 }; 128 129 // A collection of URLs for which we have added favicons. Since loading a 130 // favicon is an asynchronous operation and doesn't necessarily invoke a 131 // callback, this collection is used to determine if we must wait for a URL's 132 // favicon to load or not. 133 std::set<GURL>* urls_with_favicons_ = NULL; 134 135 // Returns the number of nodes of node type |node_type| in |model| whose 136 // titles match the string |title|. 137 int CountNodesWithTitlesMatching(BookmarkModel* model, 138 BookmarkNode::Type node_type, 139 const base::string16& title) { 140 ui::TreeNodeIterator<const BookmarkNode> iterator(model->root_node()); 141 // Walk through the model tree looking for bookmark nodes of node type 142 // |node_type| whose titles match |title|. 143 int count = 0; 144 while (iterator.has_next()) { 145 const BookmarkNode* node = iterator.Next(); 146 if ((node->type() == node_type) && (node->GetTitle() == title)) 147 ++count; 148 } 149 return count; 150 } 151 152 // Checks if the favicon data in |bitmap_a| and |bitmap_b| are equivalent. 153 // Returns true if they match. 154 bool FaviconRawBitmapsMatch(const SkBitmap& bitmap_a, 155 const SkBitmap& bitmap_b) { 156 if (bitmap_a.getSize() == 0U && bitmap_b.getSize() == 0U) 157 return true; 158 if ((bitmap_a.getSize() != bitmap_b.getSize()) || 159 (bitmap_a.width() != bitmap_b.width()) || 160 (bitmap_a.height() != bitmap_b.height())) { 161 LOG(ERROR) << "Favicon size mismatch: " << bitmap_a.getSize() << " (" 162 << bitmap_a.width() << "x" << bitmap_a.height() << ") vs. " 163 << bitmap_b.getSize() << " (" << bitmap_b.width() << "x" 164 << bitmap_b.height() << ")"; 165 return false; 166 } 167 SkAutoLockPixels bitmap_lock_a(bitmap_a); 168 SkAutoLockPixels bitmap_lock_b(bitmap_b); 169 void* node_pixel_addr_a = bitmap_a.getPixels(); 170 EXPECT_TRUE(node_pixel_addr_a); 171 void* node_pixel_addr_b = bitmap_b.getPixels(); 172 EXPECT_TRUE(node_pixel_addr_b); 173 if (memcmp(node_pixel_addr_a, node_pixel_addr_b, bitmap_a.getSize()) != 0) { 174 LOG(ERROR) << "Favicon bitmap mismatch"; 175 return false; 176 } else { 177 return true; 178 } 179 } 180 181 // Represents a favicon image and the icon URL associated with it. 182 struct FaviconData { 183 FaviconData() { 184 } 185 186 FaviconData(const gfx::Image& favicon_image, 187 const GURL& favicon_url) 188 : image(favicon_image), 189 icon_url(favicon_url) { 190 } 191 192 ~FaviconData() { 193 } 194 195 gfx::Image image; 196 GURL icon_url; 197 }; 198 199 // Gets the favicon and icon URL associated with |node| in |model|. 200 FaviconData GetFaviconData(BookmarkModel* model, 201 const BookmarkNode* node) { 202 // If a favicon wasn't explicitly set for a particular URL, simply return its 203 // blank favicon. 204 if (!urls_with_favicons_ || 205 urls_with_favicons_->find(node->url()) == urls_with_favicons_->end()) { 206 return FaviconData(); 207 } 208 // If a favicon was explicitly set, we may need to wait for it to be loaded 209 // via BookmarkModel::GetFavicon(), which is an asynchronous operation. 210 if (!node->is_favicon_loaded()) { 211 FaviconChangeObserver observer(model, node); 212 model->GetFavicon(node); 213 observer.WaitForGetFavicon(); 214 } 215 EXPECT_TRUE(node->is_favicon_loaded()); 216 EXPECT_FALSE(model->GetFavicon(node).IsEmpty()); 217 return FaviconData(model->GetFavicon(node), node->icon_url()); 218 } 219 220 // Sets the favicon for |profile| and |node|. |profile| may be 221 // |test()->verifier()|. 222 void SetFaviconImpl(Profile* profile, 223 const BookmarkNode* node, 224 const GURL& icon_url, 225 const gfx::Image& image, 226 bookmarks_helper::FaviconSource favicon_source) { 227 BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile); 228 229 FaviconChangeObserver observer(model, node); 230 FaviconService* favicon_service = 231 FaviconServiceFactory::GetForProfile(profile, 232 Profile::EXPLICIT_ACCESS); 233 if (favicon_source == bookmarks_helper::FROM_UI) { 234 favicon_service->SetFavicons( 235 node->url(), icon_url, favicon_base::FAVICON, image); 236 } else { 237 browser_sync::BookmarkChangeProcessor::ApplyBookmarkFavicon( 238 node, profile, icon_url, image.As1xPNGBytes()); 239 } 240 241 // Wait for the favicon for |node| to be invalidated. 242 observer.WaitForSetFavicon(); 243 // Wait for the BookmarkModel to fetch the updated favicon and for the new 244 // favicon to be sent to BookmarkChangeProcessor. 245 GetFaviconData(model, node); 246 } 247 248 // Wait for all currently scheduled tasks on the history thread for all 249 // profiles to complete and any notifications sent to the UI thread to have 250 // finished processing. 251 void WaitForHistoryToProcessPendingTasks() { 252 // Skip waiting for history to complete for tests without favicons. 253 if (!urls_with_favicons_) 254 return; 255 256 std::vector<Profile*> profiles_which_need_to_wait; 257 if (sync_datatype_helper::test()->use_verifier()) 258 profiles_which_need_to_wait.push_back( 259 sync_datatype_helper::test()->verifier()); 260 for (int i = 0; i < sync_datatype_helper::test()->num_clients(); ++i) 261 profiles_which_need_to_wait.push_back( 262 sync_datatype_helper::test()->GetProfile(i)); 263 264 for (size_t i = 0; i < profiles_which_need_to_wait.size(); ++i) { 265 Profile* profile = profiles_which_need_to_wait[i]; 266 HistoryService* history_service = 267 HistoryServiceFactory::GetForProfileWithoutCreating(profile); 268 base::WaitableEvent done(false, false); 269 CancelableRequestConsumer request_consumer; 270 history_service->ScheduleDBTask(new HistoryEmptyTask(&done), 271 &request_consumer); 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. 281 bool 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. 310 bool 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. 340 bool 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|. 362 void 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 391 namespace bookmarks_helper { 392 393 BookmarkModel* GetBookmarkModel(int index) { 394 return BookmarkModelFactory::GetForProfile( 395 sync_datatype_helper::test()->GetProfile(index)); 396 } 397 398 const BookmarkNode* GetBookmarkBarNode(int index) { 399 return GetBookmarkModel(index)->bookmark_bar_node(); 400 } 401 402 const BookmarkNode* GetOtherNode(int index) { 403 return GetBookmarkModel(index)->other_node(); 404 } 405 406 const BookmarkNode* GetSyncedBookmarksNode(int index) { 407 return GetBookmarkModel(index)->mobile_node(); 408 } 409 410 BookmarkModel* GetVerifierBookmarkModel() { 411 return BookmarkModelFactory::GetForProfile( 412 sync_datatype_helper::test()->verifier()); 413 } 414 415 const BookmarkNode* AddURL(int profile, 416 const std::string& title, 417 const GURL& url) { 418 return AddURL(profile, GetBookmarkBarNode(profile), 0, title, url); 419 } 420 421 const 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 428 const 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 (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 460 const BookmarkNode* AddFolder(int profile, 461 const std::string& title) { 462 return AddFolder(profile, GetBookmarkBarNode(profile), 0, title); 463 } 464 465 const BookmarkNode* AddFolder(int profile, 466 int index, 467 const std::string& title) { 468 return AddFolder(profile, GetBookmarkBarNode(profile), index, title); 469 } 470 471 const BookmarkNode* AddFolder(int profile, 472 const BookmarkNode* parent, 473 int index, 474 const std::string& title) { 475 BookmarkModel* model = GetBookmarkModel(profile); 476 if (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 503 void SetTitle(int profile, 504 const BookmarkNode* node, 505 const std::string& new_title) { 506 BookmarkModel* model = GetBookmarkModel(profile); 507 ASSERT_EQ(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 518 void 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(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 548 const BookmarkNode* SetURL(int profile, 549 const BookmarkNode* node, 550 const GURL& new_url) { 551 BookmarkModel* model = GetBookmarkModel(profile); 552 if (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 568 void Move(int profile, 569 const BookmarkNode* node, 570 const BookmarkNode* new_parent, 571 int index) { 572 BookmarkModel* model = GetBookmarkModel(profile); 573 ASSERT_EQ(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 586 void Remove(int profile, const BookmarkNode* parent, int index) { 587 BookmarkModel* model = GetBookmarkModel(profile); 588 ASSERT_EQ(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 600 void 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 613 void SortChildren(int profile, const BookmarkNode* parent) { 614 BookmarkModel* model = GetBookmarkModel(profile); 615 ASSERT_EQ(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 626 void ReverseChildOrder(int profile, const BookmarkNode* parent) { 627 ASSERT_EQ(GetBookmarkNodeByID(GetBookmarkModel(profile), parent->id()), 628 parent) 629 << "Node " << parent->GetTitle() << " does not belong to " 630 << "Profile " << profile; 631 int child_count = parent->child_count(); 632 if (child_count <= 0) 633 return; 634 for (int index = 0; index < child_count; ++index) { 635 Move(profile, parent->GetChild(index), parent, child_count - index); 636 } 637 } 638 639 bool ModelMatchesVerifier(int profile) { 640 if (!sync_datatype_helper::test()->use_verifier()) { 641 LOG(ERROR) << "Illegal to call ModelMatchesVerifier() after " 642 << "DisableVerifier(). Use ModelsMatch() instead."; 643 return false; 644 } 645 return BookmarkModelsMatch(GetVerifierBookmarkModel(), 646 GetBookmarkModel(profile)); 647 } 648 649 bool AllModelsMatchVerifier() { 650 // Ensure that all tasks have finished processing on the history thread 651 // and that any notifications the history thread may have sent have been 652 // processed before comparing models. 653 WaitForHistoryToProcessPendingTasks(); 654 655 for (int i = 0; i < sync_datatype_helper::test()->num_clients(); ++i) { 656 if (!ModelMatchesVerifier(i)) { 657 LOG(ERROR) << "Model " << i << " does not match the verifier."; 658 return false; 659 } 660 } 661 return true; 662 } 663 664 bool ModelsMatch(int profile_a, int profile_b) { 665 return BookmarkModelsMatch(GetBookmarkModel(profile_a), 666 GetBookmarkModel(profile_b)); 667 } 668 669 bool AllModelsMatch() { 670 // Ensure that all tasks have finished processing on the history thread 671 // and that any notifications the history thread may have sent have been 672 // processed before comparing models. 673 WaitForHistoryToProcessPendingTasks(); 674 675 for (int i = 1; i < sync_datatype_helper::test()->num_clients(); ++i) { 676 if (!ModelsMatch(0, i)) { 677 LOG(ERROR) << "Model " << i << " does not match Model 0."; 678 return false; 679 } 680 } 681 return true; 682 } 683 684 namespace { 685 686 // Helper class used in the implementation of AwaitAllModelsMatch. 687 class AllModelsMatchChecker : public MultiClientStatusChangeChecker { 688 public: 689 AllModelsMatchChecker(); 690 virtual ~AllModelsMatchChecker(); 691 692 virtual bool IsExitConditionSatisfied() OVERRIDE; 693 virtual std::string GetDebugMessage() const OVERRIDE; 694 }; 695 696 AllModelsMatchChecker::AllModelsMatchChecker() 697 : MultiClientStatusChangeChecker( 698 sync_datatype_helper::test()->GetSyncServices()) {} 699 700 AllModelsMatchChecker::~AllModelsMatchChecker() {} 701 702 bool AllModelsMatchChecker::IsExitConditionSatisfied() { 703 return AllModelsMatch(); 704 } 705 706 std::string AllModelsMatchChecker::GetDebugMessage() const { 707 return "Waiting for matching models"; 708 } 709 710 } // namespace 711 712 bool AwaitAllModelsMatch() { 713 AllModelsMatchChecker checker; 714 checker.Wait(); 715 return !checker.TimedOut(); 716 } 717 718 719 bool ContainsDuplicateBookmarks(int profile) { 720 ui::TreeNodeIterator<const BookmarkNode> iterator( 721 GetBookmarkModel(profile)->root_node()); 722 while (iterator.has_next()) { 723 const BookmarkNode* node = iterator.Next(); 724 if (node->is_folder()) 725 continue; 726 std::vector<const BookmarkNode*> nodes; 727 GetBookmarkModel(profile)->GetNodesByURL(node->url(), &nodes); 728 EXPECT_TRUE(nodes.size() >= 1); 729 for (std::vector<const BookmarkNode*>::const_iterator it = nodes.begin(); 730 it != nodes.end(); ++it) { 731 if (node->id() != (*it)->id() && 732 node->parent() == (*it)->parent() && 733 node->GetTitle() == (*it)->GetTitle()){ 734 return true; 735 } 736 } 737 } 738 return false; 739 } 740 741 bool HasNodeWithURL(int profile, const GURL& url) { 742 std::vector<const BookmarkNode*> nodes; 743 GetBookmarkModel(profile)->GetNodesByURL(url, &nodes); 744 return !nodes.empty(); 745 } 746 747 const BookmarkNode* GetUniqueNodeByURL(int profile, const GURL& url) { 748 std::vector<const BookmarkNode*> nodes; 749 GetBookmarkModel(profile)->GetNodesByURL(url, &nodes); 750 EXPECT_EQ(1U, nodes.size()); 751 if (nodes.empty()) 752 return NULL; 753 return nodes[0]; 754 } 755 756 int CountBookmarksWithTitlesMatching(int profile, const std::string& title) { 757 return CountNodesWithTitlesMatching(GetBookmarkModel(profile), 758 BookmarkNode::URL, 759 base::UTF8ToUTF16(title)); 760 } 761 762 int CountFoldersWithTitlesMatching(int profile, const std::string& title) { 763 return CountNodesWithTitlesMatching(GetBookmarkModel(profile), 764 BookmarkNode::FOLDER, 765 base::UTF8ToUTF16(title)); 766 } 767 768 gfx::Image CreateFavicon(SkColor color) { 769 const int dip_width = 16; 770 const int dip_height = 16; 771 std::vector<float> favicon_scales = favicon_base::GetFaviconScales(); 772 gfx::ImageSkia favicon; 773 for (size_t i = 0; i < favicon_scales.size(); ++i) { 774 float scale = favicon_scales[i]; 775 int pixel_width = dip_width * scale; 776 int pixel_height = dip_height * scale; 777 SkBitmap bmp; 778 bmp.setConfig(SkBitmap::kARGB_8888_Config, pixel_width, pixel_height); 779 bmp.allocPixels(); 780 bmp.eraseColor(color); 781 favicon.AddRepresentation(gfx::ImageSkiaRep(bmp, scale)); 782 } 783 return gfx::Image(favicon); 784 } 785 786 gfx::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 802 std::string IndexedURL(int i) { 803 return base::StringPrintf("http://www.host.ext:1234/path/filename/%d", i); 804 } 805 806 std::string IndexedURLTitle(int i) { 807 return base::StringPrintf("URL Title %d", i); 808 } 809 810 std::string IndexedFolderName(int i) { 811 return base::StringPrintf("Folder Name %d", i); 812 } 813 814 std::string IndexedSubfolderName(int i) { 815 return base::StringPrintf("Subfolder Name %d", i); 816 } 817 818 std::string IndexedSubsubfolderName(int i) { 819 return base::StringPrintf("Subsubfolder Name %d", i); 820 } 821 822 } // namespace bookmarks_helper 823