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