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