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