Home | History | Annotate | Download | only in integration
      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