root/chrome/browser/history/top_sites_impl_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. RunOnDBThread
  2. DoneRunOnMainThread
  3. waiting_
  4. QueryTopSites
  5. QueryAllTopSites
  6. CancelRequest
  7. set_urls
  8. urls
  9. number_of_callbacks
  10. OnTopSitesAvailable
  11. ExtractThumbnail
  12. ThumbnailsAreEqual
  13. db_thread_
  14. SetUp
  15. TearDown
  16. CreateHistoryAndTopSites
  17. GetThumbnail
  18. CreateBitmap
  19. RefreshTopSitesAndRecreate
  20. WaitForHistory
  21. WaitForTopSites
  22. top_sites
  23. consumer
  24. profile
  25. history_service
  26. GetPrepopulatePages
  27. ContainsPrepopulatePages
  28. EmptyCallback
  29. QuitCallback
  30. AddPageToHistory
  31. AddPageToHistory
  32. AddPageToHistory
  33. DeleteURL
  34. ThumbnailEqualsBytes
  35. RecreateTopSitesAndBlock
  36. GetCanonicalURL
  37. SetTopSites
  38. AddForcedURL
  39. StartQueryForMostVisited
  40. SetLastNumUrlsChanged
  41. last_num_urls_changed
  42. GetUpdateDelay
  43. IsTopSitesLoaded
  44. AddPrepopulatedPages
  45. EmptyThreadSafeCache
  46. AppendMostVisitedURL
  47. AppendForcedMostVisitedURL
  48. AppendMostVisitedURLWithRedirect
  49. TEST_F
  50. TEST_F
  51. TEST_F
  52. TEST_F
  53. TEST_F
  54. TEST_F
  55. TEST_F
  56. TEST_F
  57. TEST_F
  58. TEST_F
  59. TEST_F
  60. TEST_F
  61. TEST_F
  62. TEST_F
  63. TEST_F
  64. TEST_F
  65. TEST_F
  66. TEST_F
  67. TEST_F
  68. TEST_F
  69. TEST_F

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/bind.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/path_service.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/cancelable_task_tracker.h"
#include "chrome/browser/history/history_db_task.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/history/history_types.h"
#include "chrome/browser/history/history_unittest_base.h"
#include "chrome/browser/history/top_sites_cache.h"
#include "chrome/browser/history/top_sites_impl.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/test/test_browser_thread.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "url/gurl.h"

using content::BrowserThread;

namespace history {

namespace {

// Used by WaitForHistory, see it for details.
class WaitForHistoryTask : public HistoryDBTask {
 public:
  WaitForHistoryTask() {}

  virtual bool RunOnDBThread(HistoryBackend* backend,
                             HistoryDatabase* db) OVERRIDE {
    return true;
  }

  virtual void DoneRunOnMainThread() OVERRIDE {
    base::MessageLoop::current()->Quit();
  }

 private:
  virtual ~WaitForHistoryTask() {}

  DISALLOW_COPY_AND_ASSIGN(WaitForHistoryTask);
};

// Used for querying top sites. Either runs sequentially, or runs a nested
// nested message loop until the response is complete. The later is used when
// TopSites is queried before it finishes loading.
class TopSitesQuerier {
 public:
  TopSitesQuerier()
      : weak_ptr_factory_(this),
        number_of_callbacks_(0),
        waiting_(false) {}

  // Queries top sites. If |wait| is true a nested message loop is run until the
  // callback is notified.
  void QueryTopSites(TopSitesImpl* top_sites, bool wait) {
    QueryAllTopSites(top_sites, wait, false);
  }

  // Queries top sites, including potentially forced URLs if
  // |include_forced_urls| is true.
  void QueryAllTopSites(TopSitesImpl* top_sites,
                        bool wait,
                        bool include_forced_urls) {
    int start_number_of_callbacks = number_of_callbacks_;
    top_sites->GetMostVisitedURLs(
        base::Bind(&TopSitesQuerier::OnTopSitesAvailable,
                   weak_ptr_factory_.GetWeakPtr()),
        include_forced_urls);
    if (wait && start_number_of_callbacks == number_of_callbacks_) {
      waiting_ = true;
      base::MessageLoop::current()->Run();
    }
  }

  void CancelRequest() {
    weak_ptr_factory_.InvalidateWeakPtrs();
  }

  void set_urls(const MostVisitedURLList& urls) { urls_ = urls; }
  const MostVisitedURLList& urls() const { return urls_; }

  int number_of_callbacks() const { return number_of_callbacks_; }

 private:
  // Callback for TopSitesImpl::GetMostVisitedURLs.
  void OnTopSitesAvailable(const history::MostVisitedURLList& data) {
    urls_ = data;
    number_of_callbacks_++;
    if (waiting_) {
      base::MessageLoop::current()->Quit();
      waiting_ = false;
    }
  }

  base::WeakPtrFactory<TopSitesQuerier> weak_ptr_factory_;
  MostVisitedURLList urls_;
  int number_of_callbacks_;
  bool waiting_;

  DISALLOW_COPY_AND_ASSIGN(TopSitesQuerier);
};

// Extracts the data from |t1| into a SkBitmap. This is intended for usage of
// thumbnail data, which is stored as jpgs.
SkBitmap ExtractThumbnail(const base::RefCountedMemory& t1) {
  scoped_ptr<SkBitmap> image(gfx::JPEGCodec::Decode(t1.front(),
                                                    t1.size()));
  return image.get() ? *image : SkBitmap();
}

// Returns true if t1 and t2 contain the same data.
bool ThumbnailsAreEqual(base::RefCountedMemory* t1,
                        base::RefCountedMemory* t2) {
  if (!t1 || !t2)
    return false;
  if (t1->size() != t2->size())
    return false;
  return !memcmp(t1->front(), t2->front(), t1->size());
}

}  // namespace

class TopSitesImplTest : public HistoryUnitTestBase {
 public:
  TopSitesImplTest()
      : ui_thread_(BrowserThread::UI, &message_loop_),
        db_thread_(BrowserThread::DB, &message_loop_) {
  }

  virtual void SetUp() {
    profile_.reset(new TestingProfile);
    if (CreateHistoryAndTopSites()) {
      ASSERT_TRUE(profile_->CreateHistoryService(false, false));
      profile_->CreateTopSites();
      profile_->BlockUntilTopSitesLoaded();
    }
  }

  virtual void TearDown() {
    profile_.reset();
  }

  // Returns true if history and top sites should be created in SetUp.
  virtual bool CreateHistoryAndTopSites() {
    return true;
  }

  // Gets the thumbnail for |url| from TopSites.
  SkBitmap GetThumbnail(const GURL& url) {
    scoped_refptr<base::RefCountedMemory> data;
    return top_sites()->GetPageThumbnail(url, false, &data) ?
        ExtractThumbnail(*data.get()) : SkBitmap();
  }

  // Creates a bitmap of the specified color. Caller takes ownership.
  gfx::Image CreateBitmap(SkColor color) {
    SkBitmap thumbnail;
    thumbnail.setConfig(SkBitmap::kARGB_8888_Config, 4, 4);
    thumbnail.allocPixels();
    thumbnail.eraseColor(color);
    return gfx::Image::CreateFrom1xBitmap(thumbnail);  // adds ref.
  }

  // Forces top sites to load top sites from history, then recreates top sites.
  // Recreating top sites makes sure the changes from history are saved and
  // loaded from the db.
  void RefreshTopSitesAndRecreate() {
    StartQueryForMostVisited();
    WaitForHistory();
    RecreateTopSitesAndBlock();
  }

  // Blocks the caller until history processes a task. This is useful if you
  // need to wait until you know history has processed a task.
  void WaitForHistory() {
    history_service()->ScheduleDBTask(new WaitForHistoryTask(), &consumer_);
    base::MessageLoop::current()->Run();
  }

  // Waits for top sites to finish processing a task. This is useful if you need
  // to wait until top sites finishes processing a task.
  void WaitForTopSites() {
    top_sites()->backend_->DoEmptyRequest(
        base::Bind(&TopSitesImplTest::QuitCallback, base::Unretained(this)),
        &cancelable_task_tracker_);
    base::MessageLoop::current()->Run();
  }

  TopSitesImpl* top_sites() {
    return static_cast<TopSitesImpl*>(profile_->GetTopSites());
  }
  CancelableRequestConsumer* consumer() { return &consumer_; }
  TestingProfile* profile() {return profile_.get();}
  HistoryService* history_service() {
    return HistoryServiceFactory::GetForProfile(profile_.get(),
                                                Profile::EXPLICIT_ACCESS);
  }

  MostVisitedURLList GetPrepopulatePages() {
    return top_sites()->GetPrepopulatePages();
  }

  // Returns true if the TopSitesQuerier contains the prepopulate data starting
  // at |start_index|.
  void ContainsPrepopulatePages(const TopSitesQuerier& querier,
                                size_t start_index) {
    MostVisitedURLList prepopulate_urls = GetPrepopulatePages();
    ASSERT_LE(start_index + prepopulate_urls.size(), querier.urls().size());
    for (size_t i = 0; i < prepopulate_urls.size(); ++i) {
      EXPECT_EQ(prepopulate_urls[i].url.spec(),
                querier.urls()[start_index + i].url.spec()) << " @ index " <<
          i;
    }
  }

  // Used for callbacks from history.
  void EmptyCallback() {
  }

  // Quit the current message loop when invoked. Useful when running a nested
  // message loop.
  void QuitCallback() {
    base::MessageLoop::current()->Quit();
  }

  // Adds a page to history.
  void AddPageToHistory(const GURL& url) {
    RedirectList redirects;
    redirects.push_back(url);
    history_service()->AddPage(
        url, base::Time::Now(), static_cast<void*>(this), 0, GURL(),
        redirects, content::PAGE_TRANSITION_TYPED, history::SOURCE_BROWSED,
        false);
  }

  // Adds a page to history.
  void AddPageToHistory(const GURL& url, const base::string16& title) {
    RedirectList redirects;
    redirects.push_back(url);
    history_service()->AddPage(
        url, base::Time::Now(), static_cast<void*>(this), 0, GURL(),
        redirects, content::PAGE_TRANSITION_TYPED, history::SOURCE_BROWSED,
        false);
    history_service()->SetPageTitle(url, title);
  }

  // Adds a page to history.
  void AddPageToHistory(const GURL& url,
                        const base::string16& title,
                        const history::RedirectList& redirects,
                        base::Time time) {
    history_service()->AddPage(
        url, time, static_cast<void*>(this), 0, GURL(),
        redirects, content::PAGE_TRANSITION_TYPED, history::SOURCE_BROWSED,
        false);
    history_service()->SetPageTitle(url, title);
  }

  // Delets a url.
  void DeleteURL(const GURL& url) {
    history_service()->DeleteURL(url);
  }

  // Returns true if the thumbnail equals the specified bytes.
  bool ThumbnailEqualsBytes(const gfx::Image& image,
                            base::RefCountedMemory* bytes) {
    scoped_refptr<base::RefCountedBytes> encoded_image;
    TopSitesImpl::EncodeBitmap(image, &encoded_image);
    return ThumbnailsAreEqual(encoded_image.get(), bytes);
  }

  // Recreates top sites. This forces top sites to reread from the db.
  void RecreateTopSitesAndBlock() {
    // Recreate TopSites and wait for it to load.
    profile()->CreateTopSites();
    // As history already loaded we have to fake this call.
    profile()->BlockUntilTopSitesLoaded();
  }

  // Wrappers that allow private TopSites functions to be called from the
  // individual tests without making them all be friends.
  GURL GetCanonicalURL(const GURL& url) {
    return top_sites()->cache_->GetCanonicalURL(url);
  }

  void SetTopSites(const MostVisitedURLList& new_top_sites) {
    top_sites()->SetTopSites(new_top_sites);
  }

  bool AddForcedURL(const GURL& url, base::Time time) {
    return top_sites()->AddForcedURL(url, time);
  }

  void StartQueryForMostVisited() {
    top_sites()->StartQueryForMostVisited();
  }

  void SetLastNumUrlsChanged(size_t value) {
    top_sites()->last_num_urls_changed_ = value;
  }

  size_t last_num_urls_changed() { return top_sites()->last_num_urls_changed_; }

  base::TimeDelta GetUpdateDelay() {
    return top_sites()->GetUpdateDelay();
  }

  bool IsTopSitesLoaded() { return top_sites()->loaded_; }

  bool AddPrepopulatedPages(MostVisitedURLList* urls) {
    return top_sites()->AddPrepopulatedPages(urls, 0u);
  }

  void EmptyThreadSafeCache() {
    base::AutoLock lock(top_sites()->lock_);
    MostVisitedURLList empty;
    top_sites()->thread_safe_cache_->SetTopSites(empty);
  }

 private:
  base::MessageLoopForUI message_loop_;
  content::TestBrowserThread ui_thread_;
  content::TestBrowserThread db_thread_;
  scoped_ptr<TestingProfile> profile_;

  // To cancel HistoryService tasks.
  CancelableRequestConsumer consumer_;

  // To cancel TopSitesBackend tasks.
  base::CancelableTaskTracker cancelable_task_tracker_;

  DISALLOW_COPY_AND_ASSIGN(TopSitesImplTest);
};  // Class TopSitesImplTest

// Helper function for appending a URL to a vector of "most visited" URLs,
// using the default values for everything but the URL.
static void AppendMostVisitedURL(std::vector<MostVisitedURL>* list,
                                 const GURL& url) {
  MostVisitedURL mv;
  mv.url = url;
  mv.redirects.push_back(url);
  list->push_back(mv);
}

// Helper function for appending a URL to a vector of "most visited" URLs,
// using the default values for everything but the URL.
static void AppendForcedMostVisitedURL(std::vector<MostVisitedURL>* list,
                                       const GURL& url,
                                       double last_forced_time) {
  MostVisitedURL mv;
  mv.url = url;
  mv.last_forced_time = base::Time::FromJsTime(last_forced_time);
  mv.redirects.push_back(url);
  list->push_back(mv);
}

// Same as AppendMostVisitedURL except that it adds a redirect from the first
// URL to the second.
static void AppendMostVisitedURLWithRedirect(
    std::vector<MostVisitedURL>* list,
    const GURL& redirect_source, const GURL& redirect_dest) {
  MostVisitedURL mv;
  mv.url = redirect_dest;
  mv.redirects.push_back(redirect_source);
  mv.redirects.push_back(redirect_dest);
  list->push_back(mv);
}

// Tests GetCanonicalURL.
TEST_F(TopSitesImplTest, GetCanonicalURL) {
  // Have two chains:
  //   google.com -> www.google.com
  //   news.google.com (no redirects)
  GURL news("http://news.google.com/");
  GURL source("http://google.com/");
  GURL dest("http://www.google.com/");

  std::vector<MostVisitedURL> most_visited;
  AppendMostVisitedURLWithRedirect(&most_visited, source, dest);
  AppendMostVisitedURL(&most_visited, news);
  SetTopSites(most_visited);

  // Random URLs not in the database are returned unchanged.
  GURL result = GetCanonicalURL(GURL("http://fark.com/"));
  EXPECT_EQ(GURL("http://fark.com/"), result);

  // Easy case, there are no redirects and the exact URL is stored.
  result = GetCanonicalURL(news);
  EXPECT_EQ(news, result);

  // The URL in question is the source URL in a redirect list.
  result = GetCanonicalURL(source);
  EXPECT_EQ(dest, result);

  // The URL in question is the destination of a redirect.
  result = GetCanonicalURL(dest);
  EXPECT_EQ(dest, result);
}

// Tests DiffMostVisited.
TEST_F(TopSitesImplTest, DiffMostVisited) {
  GURL stays_the_same("http://staysthesame/");
  GURL gets_added_1("http://getsadded1/");
  GURL gets_added_2("http://getsadded2/");
  GURL gets_deleted_1("http://getsdeleted1/");
  GURL gets_moved_1("http://getsmoved1/");

  std::vector<MostVisitedURL> old_list;
  AppendMostVisitedURL(&old_list, stays_the_same);  // 0  (unchanged)
  AppendMostVisitedURL(&old_list, gets_deleted_1);  // 1  (deleted)
  AppendMostVisitedURL(&old_list, gets_moved_1);    // 2  (moved to 3)

  std::vector<MostVisitedURL> new_list;
  AppendMostVisitedURL(&new_list, stays_the_same);  // 0  (unchanged)
  AppendMostVisitedURL(&new_list, gets_added_1);    // 1  (added)
  AppendMostVisitedURL(&new_list, gets_added_2);    // 2  (added)
  AppendMostVisitedURL(&new_list, gets_moved_1);    // 3  (moved from 2)

  history::TopSitesDelta delta;
  history::TopSitesImpl::DiffMostVisited(old_list, new_list, &delta);

  ASSERT_EQ(2u, delta.added.size());
  EXPECT_TRUE(gets_added_1 == delta.added[0].url.url);
  EXPECT_EQ(1, delta.added[0].rank);
  EXPECT_TRUE(gets_added_2 == delta.added[1].url.url);
  EXPECT_EQ(2, delta.added[1].rank);

  ASSERT_EQ(1u, delta.deleted.size());
  EXPECT_TRUE(gets_deleted_1 == delta.deleted[0].url);

  ASSERT_EQ(1u, delta.moved.size());
  EXPECT_TRUE(gets_moved_1 == delta.moved[0].url.url);
  EXPECT_EQ(3, delta.moved[0].rank);
}

// Tests DiffMostVisited with forced URLs.
TEST_F(TopSitesImplTest, DiffMostVisitedWithForced) {
  // Forced URLs.
  GURL stays_the_same_1("http://staysthesame1/");
  GURL new_last_forced_time("http://newlastforcedtime/");
  GURL stays_the_same_2("http://staysthesame2/");
  GURL move_to_nonforced("http://movetononforced/");
  GURL gets_added_1("http://getsadded1/");
  GURL gets_deleted_1("http://getsdeleted1/");
  // Non-forced URLs.
  GURL move_to_forced("http://movetoforced/");
  GURL stays_the_same_3("http://staysthesame3/");
  GURL gets_added_2("http://getsadded2/");
  GURL gets_deleted_2("http://getsdeleted2/");
  GURL gets_moved_1("http://getsmoved1/");

  std::vector<MostVisitedURL> old_list;
  AppendForcedMostVisitedURL(&old_list, stays_the_same_1, 1000);
  AppendForcedMostVisitedURL(&old_list, new_last_forced_time, 2000);
  AppendForcedMostVisitedURL(&old_list, stays_the_same_2, 3000);
  AppendForcedMostVisitedURL(&old_list, move_to_nonforced, 4000);
  AppendForcedMostVisitedURL(&old_list, gets_deleted_1, 5000);
  AppendMostVisitedURL(&old_list, move_to_forced);
  AppendMostVisitedURL(&old_list, stays_the_same_3);
  AppendMostVisitedURL(&old_list, gets_deleted_2);
  AppendMostVisitedURL(&old_list, gets_moved_1);

  std::vector<MostVisitedURL> new_list;
  AppendForcedMostVisitedURL(&new_list, stays_the_same_1, 1000);
  AppendForcedMostVisitedURL(&new_list, stays_the_same_2, 3000);
  AppendForcedMostVisitedURL(&new_list, new_last_forced_time, 4000);
  AppendForcedMostVisitedURL(&new_list, gets_added_1, 5000);
  AppendForcedMostVisitedURL(&new_list, move_to_forced, 6000);
  AppendMostVisitedURL(&new_list, move_to_nonforced);
  AppendMostVisitedURL(&new_list, stays_the_same_3);
  AppendMostVisitedURL(&new_list, gets_added_2);
  AppendMostVisitedURL(&new_list, gets_moved_1);

  history::TopSitesDelta delta;
  history::TopSitesImpl::DiffMostVisited(old_list, new_list, &delta);

  ASSERT_EQ(2u, delta.added.size());
  EXPECT_TRUE(gets_added_1 == delta.added[0].url.url);
  EXPECT_EQ(-1, delta.added[0].rank);
  EXPECT_TRUE(gets_added_2 == delta.added[1].url.url);
  EXPECT_EQ(2, delta.added[1].rank);

  ASSERT_EQ(2u, delta.deleted.size());
  EXPECT_TRUE(gets_deleted_1 == delta.deleted[0].url);
  EXPECT_TRUE(gets_deleted_2 == delta.deleted[1].url);

  ASSERT_EQ(3u, delta.moved.size());
  EXPECT_TRUE(new_last_forced_time == delta.moved[0].url.url);
  EXPECT_EQ(-1, delta.moved[0].rank);
  EXPECT_EQ(base::Time::FromJsTime(4000), delta.moved[0].url.last_forced_time);
  EXPECT_TRUE(move_to_forced == delta.moved[1].url.url);
  EXPECT_EQ(-1, delta.moved[1].rank);
  EXPECT_EQ(base::Time::FromJsTime(6000), delta.moved[1].url.last_forced_time);
  EXPECT_TRUE(move_to_nonforced == delta.moved[2].url.url);
  EXPECT_EQ(0, delta.moved[2].rank);
  EXPECT_TRUE(delta.moved[2].url.last_forced_time.is_null());
}

// Tests SetPageThumbnail.
TEST_F(TopSitesImplTest, SetPageThumbnail) {
  GURL url1a("http://google.com/");
  GURL url1b("http://www.google.com/");
  GURL url2("http://images.google.com/");
  GURL invalid_url("chrome://favicon/http://google.com/");

  std::vector<MostVisitedURL> list;
  AppendMostVisitedURL(&list, url2);

  MostVisitedURL mv;
  mv.url = url1b;
  mv.redirects.push_back(url1a);
  mv.redirects.push_back(url1b);
  list.push_back(mv);

  // Save our most visited data containing that one site.
  SetTopSites(list);

  // Create a dummy thumbnail.
  gfx::Image thumbnail(CreateBitmap(SK_ColorWHITE));

  base::Time now = base::Time::Now();
  ThumbnailScore low_score(1.0, true, true, now);
  ThumbnailScore medium_score(0.5, true, true, now);
  ThumbnailScore high_score(0.0, true, true, now);

  // Setting the thumbnail for invalid pages should fail.
  EXPECT_FALSE(top_sites()->SetPageThumbnail(invalid_url,
                                             thumbnail, medium_score));

  // Setting the thumbnail for url2 should succeed, lower scores shouldn't
  // replace it, higher scores should.
  EXPECT_TRUE(top_sites()->SetPageThumbnail(url2, thumbnail, medium_score));
  EXPECT_FALSE(top_sites()->SetPageThumbnail(url2, thumbnail, low_score));
  EXPECT_TRUE(top_sites()->SetPageThumbnail(url2, thumbnail, high_score));

  // Set on the redirect source should succeed. It should be replacable by
  // the same score on the redirect destination, which in turn should not
  // be replaced by the source again.
  EXPECT_TRUE(top_sites()->SetPageThumbnail(url1a, thumbnail, medium_score));
  EXPECT_TRUE(top_sites()->SetPageThumbnail(url1b, thumbnail, medium_score));
  EXPECT_FALSE(top_sites()->SetPageThumbnail(url1a, thumbnail, medium_score));
}

// Makes sure a thumbnail is correctly removed when the page is removed.
TEST_F(TopSitesImplTest, ThumbnailRemoved) {
  GURL url("http://google.com/");

  // Configure top sites with 'google.com'.
  std::vector<MostVisitedURL> list;
  AppendMostVisitedURL(&list, url);
  SetTopSites(list);

  // Create a dummy thumbnail.
  gfx::Image thumbnail(CreateBitmap(SK_ColorRED));

  base::Time now = base::Time::Now();
  ThumbnailScore low_score(1.0, true, true, now);
  ThumbnailScore medium_score(0.5, true, true, now);
  ThumbnailScore high_score(0.0, true, true, now);

  // Set the thumbnail.
  EXPECT_TRUE(top_sites()->SetPageThumbnail(url, thumbnail, medium_score));

  // Make sure the thumbnail was actually set.
  scoped_refptr<base::RefCountedMemory> result;
  EXPECT_TRUE(top_sites()->GetPageThumbnail(url, false, &result));
  EXPECT_TRUE(ThumbnailEqualsBytes(thumbnail, result.get()));

  // Reset the thumbnails and make sure we don't get it back.
  SetTopSites(MostVisitedURLList());
  RefreshTopSitesAndRecreate();
  EXPECT_FALSE(top_sites()->GetPageThumbnail(url, false, &result));
}

// Tests GetPageThumbnail.
TEST_F(TopSitesImplTest, GetPageThumbnail) {
  MostVisitedURLList url_list;
  MostVisitedURL url1;
  url1.url = GURL("http://asdf.com");
  url1.redirects.push_back(url1.url);
  url_list.push_back(url1);

  MostVisitedURL url2;
  url2.url = GURL("http://gmail.com");
  url2.redirects.push_back(url2.url);
  url2.redirects.push_back(GURL("http://mail.google.com"));
  url_list.push_back(url2);

  SetTopSites(url_list);

  // Create a dummy thumbnail.
  gfx::Image thumbnail(CreateBitmap(SK_ColorWHITE));
  ThumbnailScore score(0.5, true, true, base::Time::Now());

  scoped_refptr<base::RefCountedMemory> result;
  EXPECT_TRUE(top_sites()->SetPageThumbnail(url1.url, thumbnail, score));
  EXPECT_TRUE(top_sites()->GetPageThumbnail(url1.url, false, &result));

  EXPECT_TRUE(top_sites()->SetPageThumbnail(GURL("http://gmail.com"),
                                            thumbnail, score));
  EXPECT_TRUE(top_sites()->GetPageThumbnail(GURL("http://gmail.com"),
                                            false,
                                            &result));
  // Get a thumbnail via a redirect.
  EXPECT_TRUE(top_sites()->GetPageThumbnail(GURL("http://mail.google.com"),
                                            false,
                                            &result));

  EXPECT_TRUE(top_sites()->SetPageThumbnail(GURL("http://mail.google.com"),
                                            thumbnail, score));
  EXPECT_TRUE(top_sites()->GetPageThumbnail(url2.url, false, &result));

  EXPECT_TRUE(ThumbnailEqualsBytes(thumbnail, result.get()));
}

// Tests GetMostVisitedURLs.
TEST_F(TopSitesImplTest, GetMostVisited) {
  GURL news("http://news.google.com/");
  GURL google("http://google.com/");

  AddPageToHistory(news);
  AddPageToHistory(google);

  StartQueryForMostVisited();
  WaitForHistory();

  TopSitesQuerier querier;
  querier.QueryTopSites(top_sites(), false);

  ASSERT_EQ(1, querier.number_of_callbacks());

  // 2 extra prepopulated URLs.
  ASSERT_EQ(2u + GetPrepopulatePages().size(), querier.urls().size());
  EXPECT_EQ(news, querier.urls()[0].url);
  EXPECT_EQ(google, querier.urls()[1].url);
  ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 2));
}

// Makes sure changes done to top sites get mirrored to the db.
TEST_F(TopSitesImplTest, SaveToDB) {
  MostVisitedURL url;
  GURL asdf_url("http://asdf.com");
  base::string16 asdf_title(base::ASCIIToUTF16("ASDF"));
  GURL google_url("http://google.com");
  base::string16 google_title(base::ASCIIToUTF16("Google"));
  GURL news_url("http://news.google.com");
  base::string16 news_title(base::ASCIIToUTF16("Google News"));

  // Add asdf_url to history.
  AddPageToHistory(asdf_url, asdf_title);

  // Make TopSites reread from the db.
  StartQueryForMostVisited();
  WaitForHistory();

  // Add a thumbnail.
  gfx::Image tmp_bitmap(CreateBitmap(SK_ColorBLUE));
  ASSERT_TRUE(top_sites()->SetPageThumbnail(asdf_url, tmp_bitmap,
                                            ThumbnailScore()));

  RecreateTopSitesAndBlock();

  {
    TopSitesQuerier querier;
    querier.QueryTopSites(top_sites(), false);
    ASSERT_EQ(1u + GetPrepopulatePages().size(), querier.urls().size());
    EXPECT_EQ(asdf_url, querier.urls()[0].url);
    EXPECT_EQ(asdf_title, querier.urls()[0].title);
    ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 1));

    scoped_refptr<base::RefCountedMemory> read_data;
    EXPECT_TRUE(top_sites()->GetPageThumbnail(asdf_url, false, &read_data));
    EXPECT_TRUE(ThumbnailEqualsBytes(tmp_bitmap, read_data.get()));
  }

  MostVisitedURL url2;
  url2.url = google_url;
  url2.title = google_title;
  url2.redirects.push_back(url2.url);

  AddPageToHistory(url2.url, url2.title);

  // Add new thumbnail at rank 0 and shift the other result to 1.
  ASSERT_TRUE(top_sites()->SetPageThumbnail(google_url,
                                            tmp_bitmap,
                                            ThumbnailScore()));

  // Make TopSites reread from the db.
  RefreshTopSitesAndRecreate();

  {
    TopSitesQuerier querier;
    querier.QueryTopSites(top_sites(), false);
    ASSERT_EQ(2u + GetPrepopulatePages().size(), querier.urls().size());
    EXPECT_EQ(asdf_url, querier.urls()[0].url);
    EXPECT_EQ(asdf_title, querier.urls()[0].title);
    EXPECT_EQ(google_url, querier.urls()[1].url);
    EXPECT_EQ(google_title, querier.urls()[1].title);
    ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 2));
  }
}

// Makes sure forced URLs in top sites get mirrored to the db.
TEST_F(TopSitesImplTest, SaveForcedToDB) {
  MostVisitedURL url;
  GURL asdf_url("http://asdf.com");
  base::string16 asdf_title(base::ASCIIToUTF16("ASDF"));
  GURL google_url("http://google.com");
  base::string16 google_title(base::ASCIIToUTF16("Google"));
  GURL news_url("http://news.google.com");
  base::string16 news_title(base::ASCIIToUTF16("Google News"));

  // Add a number of forced URLs.
  std::vector<MostVisitedURL> list;
  AppendForcedMostVisitedURL(&list, GURL("http://forced1"), 1000);
  list[0].title = base::ASCIIToUTF16("forced1");
  AppendForcedMostVisitedURL(&list, GURL("http://forced2"), 2000);
  AppendForcedMostVisitedURL(&list, GURL("http://forced3"), 3000);
  AppendForcedMostVisitedURL(&list, GURL("http://forced4"), 4000);
  SetTopSites(list);

  // Add a thumbnail.
  gfx::Image red_thumbnail(CreateBitmap(SK_ColorRED));
  ASSERT_TRUE(top_sites()->SetPageThumbnail(
                  GURL("http://forced1"), red_thumbnail, ThumbnailScore()));

  // Get the original thumbnail for later comparison. Some compression can
  // happen in |top_sites| and we don't want to depend on that.
  SkBitmap orig_thumbnail = GetThumbnail(GURL("http://forced1"));

  // Force-flush the cache to ensure we don't reread from it inadvertently.
  EmptyThreadSafeCache();

  // Make TopSites reread from the db.
  StartQueryForMostVisited();
  WaitForHistory();

  TopSitesQuerier querier;
  querier.QueryAllTopSites(top_sites(), true, true);

  ASSERT_EQ(4u + GetPrepopulatePages().size(), querier.urls().size());
  EXPECT_EQ(GURL("http://forced1"), querier.urls()[0].url);
  EXPECT_EQ(base::ASCIIToUTF16("forced1"), querier.urls()[0].title);
  SkBitmap thumbnail = GetThumbnail(GURL("http://forced1"));
  ASSERT_EQ(orig_thumbnail.getSize(), thumbnail.getSize());
  orig_thumbnail.lockPixels();
  thumbnail.lockPixels();
  EXPECT_EQ(0, memcmp(orig_thumbnail.getPixels(), thumbnail.getPixels(),
                      orig_thumbnail.getSize()));
  thumbnail.unlockPixels();
  orig_thumbnail.unlockPixels();
  EXPECT_EQ(base::Time::FromJsTime(1000), querier.urls()[0].last_forced_time);
  EXPECT_EQ(GURL("http://forced2"), querier.urls()[1].url);
  EXPECT_EQ(base::Time::FromJsTime(2000), querier.urls()[1].last_forced_time);
  EXPECT_EQ(GURL("http://forced3"), querier.urls()[2].url);
  EXPECT_EQ(base::Time::FromJsTime(3000), querier.urls()[2].last_forced_time);
  EXPECT_EQ(GURL("http://forced4"), querier.urls()[3].url);
  EXPECT_EQ(base::Time::FromJsTime(4000), querier.urls()[3].last_forced_time);

  ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 4));
}

// More permutations of saving to db.
TEST_F(TopSitesImplTest, RealDatabase) {
  MostVisitedURL url;
  GURL asdf_url("http://asdf.com");
  base::string16 asdf_title(base::ASCIIToUTF16("ASDF"));
  GURL google1_url("http://google.com");
  GURL google2_url("http://google.com/redirect");
  GURL google3_url("http://www.google.com");
  base::string16 google_title(base::ASCIIToUTF16("Google"));
  GURL news_url("http://news.google.com");
  base::string16 news_title(base::ASCIIToUTF16("Google News"));

  url.url = asdf_url;
  url.title = asdf_title;
  url.redirects.push_back(url.url);
  gfx::Image asdf_thumbnail(CreateBitmap(SK_ColorRED));
  ASSERT_TRUE(top_sites()->SetPageThumbnail(
                  asdf_url, asdf_thumbnail, ThumbnailScore()));

  base::Time add_time(base::Time::Now());
  AddPageToHistory(url.url, url.title, url.redirects, add_time);

  RefreshTopSitesAndRecreate();

  {
    TopSitesQuerier querier;
    querier.QueryTopSites(top_sites(), false);

    ASSERT_EQ(1u + GetPrepopulatePages().size(), querier.urls().size());
    EXPECT_EQ(asdf_url, querier.urls()[0].url);
    EXPECT_EQ(asdf_title, querier.urls()[0].title);
    ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 1));

    scoped_refptr<base::RefCountedMemory> read_data;
    EXPECT_TRUE(top_sites()->GetPageThumbnail(asdf_url, false, &read_data));
    EXPECT_TRUE(ThumbnailEqualsBytes(asdf_thumbnail, read_data.get()));
  }

  MostVisitedURL url2;
  url2.url = google3_url;
  url2.title = google_title;
  url2.redirects.push_back(google1_url);
  url2.redirects.push_back(google2_url);
  url2.redirects.push_back(google3_url);

  AddPageToHistory(google3_url, url2.title, url2.redirects,
                   add_time - base::TimeDelta::FromMinutes(1));
  // Add google twice so that it becomes the first visited site.
  AddPageToHistory(google3_url, url2.title, url2.redirects,
                   add_time - base::TimeDelta::FromMinutes(2));

  gfx::Image google_thumbnail(CreateBitmap(SK_ColorBLUE));
  ASSERT_TRUE(top_sites()->SetPageThumbnail(
                  url2.url, google_thumbnail, ThumbnailScore()));

  RefreshTopSitesAndRecreate();

  {
    scoped_refptr<base::RefCountedMemory> read_data;
    TopSitesQuerier querier;
    querier.QueryTopSites(top_sites(), false);

    ASSERT_EQ(2u + GetPrepopulatePages().size(), querier.urls().size());
    EXPECT_EQ(google1_url, querier.urls()[0].url);
    EXPECT_EQ(google_title, querier.urls()[0].title);
    ASSERT_EQ(3u, querier.urls()[0].redirects.size());
    EXPECT_TRUE(top_sites()->GetPageThumbnail(google3_url, false, &read_data));
    EXPECT_TRUE(ThumbnailEqualsBytes(google_thumbnail, read_data.get()));

    EXPECT_EQ(asdf_url, querier.urls()[1].url);
    EXPECT_EQ(asdf_title, querier.urls()[1].title);
    ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 2));
  }

  gfx::Image weewar_bitmap(CreateBitmap(SK_ColorYELLOW));

  base::Time thumbnail_time(base::Time::Now());
  ThumbnailScore low_score(1.0, true, true, thumbnail_time);
  ThumbnailScore medium_score(0.5, true, true, thumbnail_time);
  ThumbnailScore high_score(0.0, true, true, thumbnail_time);

  // 1. Set to weewar. (Writes the thumbnail to the DB.)
  EXPECT_TRUE(top_sites()->SetPageThumbnail(google3_url,
                                            weewar_bitmap,
                                            medium_score));
  RefreshTopSitesAndRecreate();
  {
    scoped_refptr<base::RefCountedMemory> read_data;
    EXPECT_TRUE(top_sites()->GetPageThumbnail(google3_url, false, &read_data));
    EXPECT_TRUE(ThumbnailEqualsBytes(weewar_bitmap, read_data.get()));
  }

  gfx::Image green_bitmap(CreateBitmap(SK_ColorGREEN));

  // 2. Set to google - low score.
  EXPECT_FALSE(top_sites()->SetPageThumbnail(google3_url,
                                             green_bitmap,
                                             low_score));

  // 3. Set to google - high score.
  EXPECT_TRUE(top_sites()->SetPageThumbnail(google1_url,
                                            green_bitmap,
                                            high_score));

  // Check that the thumbnail was updated.
  RefreshTopSitesAndRecreate();
  {
    scoped_refptr<base::RefCountedMemory> read_data;
    EXPECT_TRUE(top_sites()->GetPageThumbnail(google3_url, false, &read_data));
    EXPECT_FALSE(ThumbnailEqualsBytes(weewar_bitmap, read_data.get()));
    EXPECT_TRUE(ThumbnailEqualsBytes(green_bitmap, read_data.get()));
  }
}

TEST_F(TopSitesImplTest, DeleteNotifications) {
  GURL google1_url("http://google.com");
  GURL google2_url("http://google.com/redirect");
  GURL google3_url("http://www.google.com");
  base::string16 google_title(base::ASCIIToUTF16("Google"));
  GURL news_url("http://news.google.com");
  base::string16 news_title(base::ASCIIToUTF16("Google News"));

  AddPageToHistory(google1_url, google_title);
  AddPageToHistory(news_url, news_title);

  RefreshTopSitesAndRecreate();

  {
    TopSitesQuerier querier;
    querier.QueryTopSites(top_sites(), false);

    ASSERT_EQ(GetPrepopulatePages().size() + 2, querier.urls().size());
  }

  DeleteURL(news_url);

  // Wait for history to process the deletion.
  WaitForHistory();

  {
    TopSitesQuerier querier;
    querier.QueryTopSites(top_sites(), false);

    ASSERT_EQ(1u + GetPrepopulatePages().size(), querier.urls().size());
    EXPECT_EQ(google_title, querier.urls()[0].title);
    ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 1));
  }

  // Now reload. This verifies topsites actually wrote the deletion to disk.
  RefreshTopSitesAndRecreate();

  {
    TopSitesQuerier querier;
    querier.QueryTopSites(top_sites(), false);

    ASSERT_EQ(1u + GetPrepopulatePages().size(), querier.urls().size());
    EXPECT_EQ(google_title, querier.urls()[0].title);
    ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 1));
  }

  DeleteURL(google1_url);

  // Wait for history to process the deletion.
  WaitForHistory();

  {
    TopSitesQuerier querier;
    querier.QueryTopSites(top_sites(), false);

    ASSERT_EQ(GetPrepopulatePages().size(), querier.urls().size());
    ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 0));
  }

  // Now reload. This verifies topsites actually wrote the deletion to disk.
  RefreshTopSitesAndRecreate();

  {
    TopSitesQuerier querier;
    querier.QueryTopSites(top_sites(), false);

    ASSERT_EQ(GetPrepopulatePages().size(), querier.urls().size());
    ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 0));
  }
}

// Makes sure GetUpdateDelay is updated appropriately.
TEST_F(TopSitesImplTest, GetUpdateDelay) {
  SetLastNumUrlsChanged(0);
  EXPECT_EQ(30, GetUpdateDelay().InSeconds());

  MostVisitedURLList url_list;
  url_list.resize(20);
  GURL tmp_url(GURL("http://x"));
  for (size_t i = 0; i < url_list.size(); ++i) {
    url_list[i].url = tmp_url;
    url_list[i].redirects.push_back(tmp_url);
  }
  SetTopSites(url_list);
  EXPECT_EQ(20u, last_num_urls_changed());
  SetLastNumUrlsChanged(0);
  EXPECT_EQ(60, GetUpdateDelay().InMinutes());

  SetLastNumUrlsChanged(3);
  EXPECT_EQ(52, GetUpdateDelay().InMinutes());

  SetLastNumUrlsChanged(20);
  EXPECT_EQ(1, GetUpdateDelay().InMinutes());
}

// Verifies that callbacks are notified correctly if requested before top sites
// has loaded.
TEST_F(TopSitesImplTest, NotifyCallbacksWhenLoaded) {
  // Recreate top sites. It won't be loaded now.
  profile()->CreateTopSites();

  EXPECT_FALSE(IsTopSitesLoaded());

  TopSitesQuerier querier1;
  TopSitesQuerier querier2;
  TopSitesQuerier querier3;

  // Starts the queries.
  querier1.QueryTopSites(top_sites(), false);
  querier2.QueryTopSites(top_sites(), false);
  querier3.QueryTopSites(top_sites(), false);

  // We shouldn't have gotten a callback.
  EXPECT_EQ(0, querier1.number_of_callbacks());
  EXPECT_EQ(0, querier2.number_of_callbacks());
  EXPECT_EQ(0, querier3.number_of_callbacks());

  // Wait for loading to complete.
  profile()->BlockUntilTopSitesLoaded();

  // Now we should have gotten the callbacks.
  EXPECT_EQ(1, querier1.number_of_callbacks());
  EXPECT_EQ(GetPrepopulatePages().size(), querier1.urls().size());
  EXPECT_EQ(1, querier2.number_of_callbacks());
  EXPECT_EQ(GetPrepopulatePages().size(), querier2.urls().size());
  EXPECT_EQ(1, querier3.number_of_callbacks());
  EXPECT_EQ(GetPrepopulatePages().size(), querier3.urls().size());

  // Reset the top sites.
  MostVisitedURLList pages;
  MostVisitedURL url;
  url.url = GURL("http://1.com/");
  url.redirects.push_back(url.url);
  pages.push_back(url);
  url.url = GURL("http://2.com/");
  url.redirects.push_back(url.url);
  pages.push_back(url);
  SetTopSites(pages);

  // Recreate top sites. It won't be loaded now.
  profile()->CreateTopSites();

  EXPECT_FALSE(IsTopSitesLoaded());

  TopSitesQuerier querier4;

  // Query again.
  querier4.QueryTopSites(top_sites(), false);

  // We shouldn't have gotten a callback.
  EXPECT_EQ(0, querier4.number_of_callbacks());

  // Wait for loading to complete.
  profile()->BlockUntilTopSitesLoaded();

  // Now we should have gotten the callbacks.
  EXPECT_EQ(1, querier4.number_of_callbacks());
  ASSERT_EQ(2u + GetPrepopulatePages().size(), querier4.urls().size());

  EXPECT_EQ("http://1.com/", querier4.urls()[0].url.spec());
  EXPECT_EQ("http://2.com/", querier4.urls()[1].url.spec());
  ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier4, 2));

  // Reset the top sites again, this time don't reload.
  url.url = GURL("http://3.com/");
  url.redirects.push_back(url.url);
  pages.push_back(url);
  SetTopSites(pages);

  // Query again.
  TopSitesQuerier querier5;
  querier5.QueryTopSites(top_sites(), true);

  EXPECT_EQ(1, querier5.number_of_callbacks());

  ASSERT_EQ(3u + GetPrepopulatePages().size(), querier5.urls().size());
  EXPECT_EQ("http://1.com/", querier5.urls()[0].url.spec());
  EXPECT_EQ("http://2.com/", querier5.urls()[1].url.spec());
  EXPECT_EQ("http://3.com/", querier5.urls()[2].url.spec());
  ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier5, 3));
}

// Makes sure canceled requests are not notified.
TEST_F(TopSitesImplTest, CancelingRequestsForTopSites) {
  // Recreate top sites. It won't be loaded now.
  profile()->CreateTopSites();

  EXPECT_FALSE(IsTopSitesLoaded());

  TopSitesQuerier querier1;
  TopSitesQuerier querier2;

  // Starts the queries.
  querier1.QueryTopSites(top_sites(), false);
  querier2.QueryTopSites(top_sites(), false);

  // We shouldn't have gotten a callback.
  EXPECT_EQ(0, querier1.number_of_callbacks());
  EXPECT_EQ(0, querier2.number_of_callbacks());

  querier2.CancelRequest();

  // Wait for loading to complete.
  profile()->BlockUntilTopSitesLoaded();

  // The first callback should succeed.
  EXPECT_EQ(1, querier1.number_of_callbacks());
  EXPECT_EQ(GetPrepopulatePages().size(), querier1.urls().size());

  // And the canceled callback should not be notified.
  EXPECT_EQ(0, querier2.number_of_callbacks());
}

// Makes sure temporary thumbnails are copied over correctly.
TEST_F(TopSitesImplTest, AddTemporaryThumbnail) {
  GURL unknown_url("http://news.google.com/");
  GURL invalid_url("chrome://thumb/http://google.com/");
  GURL url1a("http://google.com/");
  GURL url1b("http://www.google.com/");

  // Create a dummy thumbnail.
  gfx::Image thumbnail(CreateBitmap(SK_ColorRED));

  ThumbnailScore medium_score(0.5, true, true, base::Time::Now());

  // Don't store thumbnails for Javascript URLs.
  EXPECT_FALSE(top_sites()->SetPageThumbnail(invalid_url,
                                             thumbnail,
                                             medium_score));
  // Store thumbnails for unknown (but valid) URLs temporarily - calls
  // AddTemporaryThumbnail.
  EXPECT_TRUE(top_sites()->SetPageThumbnail(unknown_url,
                                            thumbnail,
                                            medium_score));

  // We shouldn't get the thumnail back though (the url isn't in to sites yet).
  scoped_refptr<base::RefCountedMemory> out;
  EXPECT_FALSE(top_sites()->GetPageThumbnail(unknown_url, false, &out));
  // But we should be able to get the temporary page thumbnail score.
  ThumbnailScore out_score;
  EXPECT_TRUE(top_sites()->GetTemporaryPageThumbnailScore(unknown_url,
                                                          &out_score));
  EXPECT_TRUE(medium_score.Equals(out_score));

  std::vector<MostVisitedURL> list;

  MostVisitedURL mv;
  mv.url = unknown_url;
  mv.redirects.push_back(mv.url);
  mv.redirects.push_back(url1a);
  mv.redirects.push_back(url1b);
  list.push_back(mv);

  // Update URLs. This should result in using thumbnail.
  SetTopSites(list);

  ASSERT_TRUE(top_sites()->GetPageThumbnail(unknown_url, false, &out));
  EXPECT_TRUE(ThumbnailEqualsBytes(thumbnail, out.get()));
}

// Tests variations of blacklisting.
TEST_F(TopSitesImplTest, Blacklisting) {
  MostVisitedURLList pages;
  MostVisitedURL url, url1;
  url.url = GURL("http://bbc.com/");
  url.redirects.push_back(url.url);
  pages.push_back(url);
  url1.url = GURL("http://google.com/");
  url1.redirects.push_back(url1.url);
  pages.push_back(url1);

  SetTopSites(pages);
  EXPECT_FALSE(top_sites()->IsBlacklisted(GURL("http://bbc.com/")));

  // Blacklist google.com.
  top_sites()->AddBlacklistedURL(GURL("http://google.com/"));

  GURL prepopulate_url = GetPrepopulatePages()[0].url;

  EXPECT_TRUE(top_sites()->HasBlacklistedItems());
  EXPECT_TRUE(top_sites()->IsBlacklisted(GURL("http://google.com/")));
  EXPECT_FALSE(top_sites()->IsBlacklisted(GURL("http://bbc.com/")));
  EXPECT_FALSE(top_sites()->IsBlacklisted(prepopulate_url));

  // Make sure the blacklisted site isn't returned in the results.
  {
    TopSitesQuerier q;
    q.QueryTopSites(top_sites(), true);
    ASSERT_EQ(1u + GetPrepopulatePages().size(), q.urls().size());
    EXPECT_EQ("http://bbc.com/", q.urls()[0].url.spec());
    ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(q, 1));
  }

  // Recreate top sites and make sure blacklisted url was correctly read.
  RecreateTopSitesAndBlock();
  {
    TopSitesQuerier q;
    q.QueryTopSites(top_sites(), true);
    ASSERT_EQ(1u + GetPrepopulatePages().size(), q.urls().size());
    EXPECT_EQ("http://bbc.com/", q.urls()[0].url.spec());
    ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(q, 1));
  }

  // Blacklist one of the prepopulate urls.
  top_sites()->AddBlacklistedURL(prepopulate_url);
  EXPECT_TRUE(top_sites()->HasBlacklistedItems());

  // Make sure the blacklisted prepopulate url isn't returned.
  {
    TopSitesQuerier q;
    q.QueryTopSites(top_sites(), true);
    ASSERT_EQ(1u + GetPrepopulatePages().size() - 1, q.urls().size());
    EXPECT_EQ("http://bbc.com/", q.urls()[0].url.spec());
    for (size_t i = 1; i < q.urls().size(); ++i)
      EXPECT_NE(prepopulate_url.spec(), q.urls()[i].url.spec());
  }

  // Mark google as no longer blacklisted.
  top_sites()->RemoveBlacklistedURL(GURL("http://google.com/"));
  EXPECT_TRUE(top_sites()->HasBlacklistedItems());
  EXPECT_FALSE(top_sites()->IsBlacklisted(GURL("http://google.com/")));

  // Make sure google is returned now.
  {
    TopSitesQuerier q;
    q.QueryTopSites(top_sites(), true);
    ASSERT_EQ(2u + GetPrepopulatePages().size() - 1, q.urls().size());
    EXPECT_EQ("http://bbc.com/", q.urls()[0].url.spec());
    EXPECT_EQ("http://google.com/", q.urls()[1].url.spec());
    // Android has only one prepopulated page which has been blacklisted, so
    // only 2 urls are returned.
    if (q.urls().size() > 2)
      EXPECT_NE(prepopulate_url.spec(), q.urls()[2].url.spec());
    else
      EXPECT_EQ(1u, GetPrepopulatePages().size());
  }

  // Remove all blacklisted sites.
  top_sites()->ClearBlacklistedURLs();
  EXPECT_FALSE(top_sites()->HasBlacklistedItems());

  {
    TopSitesQuerier q;
    q.QueryTopSites(top_sites(), true);
    ASSERT_EQ(2u + GetPrepopulatePages().size(), q.urls().size());
    EXPECT_EQ("http://bbc.com/", q.urls()[0].url.spec());
    EXPECT_EQ("http://google.com/", q.urls()[1].url.spec());
    ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(q, 2));
  }
}

// Makes sure prepopulated pages exist.
TEST_F(TopSitesImplTest, AddPrepopulatedPages) {
  TopSitesQuerier q;
  q.QueryTopSites(top_sites(), true);
  EXPECT_EQ(GetPrepopulatePages().size(), q.urls().size());
  ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(q, 0));

  MostVisitedURLList pages = q.urls();
  EXPECT_FALSE(AddPrepopulatedPages(&pages));

  EXPECT_EQ(GetPrepopulatePages().size(), pages.size());
  q.set_urls(pages);
  ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(q, 0));
}

// Ensure calling SetTopSites with forced sites already in the DB works.
// This test both eviction and
TEST_F(TopSitesImplTest, SetForcedTopSites) {
  // Create forced elements in old URL list.
  MostVisitedURLList old_url_list;
  AppendForcedMostVisitedURL(&old_url_list, GURL("http://oldforced/0"), 1000);
  AppendForcedMostVisitedURL(&old_url_list, GURL("http://oldforced/1"), 4000);
  AppendForcedMostVisitedURL(&old_url_list, GURL("http://oldforced/2"), 7000);
  AppendForcedMostVisitedURL(&old_url_list, GURL("http://oldforced/3"), 10000);
  AppendForcedMostVisitedURL(&old_url_list, GURL("http://oldforced/4"), 11000);
  AppendForcedMostVisitedURL(&old_url_list, GURL("http://oldforced/5"), 12000);
  AppendForcedMostVisitedURL(&old_url_list, GURL("http://oldforced/6"), 13000);
  AppendForcedMostVisitedURL(&old_url_list, GURL("http://oldforced/7"), 18000);
  AppendForcedMostVisitedURL(&old_url_list, GURL("http://oldforced/8"), 21000);
  const size_t kNumOldForcedURLs = 9;

  // Create forced elements in new URL list.
  MostVisitedURLList new_url_list;
  AppendForcedMostVisitedURL(&new_url_list, GURL("http://newforced/0"), 2000);
  AppendForcedMostVisitedURL(&new_url_list, GURL("http://newforced/1"), 3000);
  AppendForcedMostVisitedURL(&new_url_list, GURL("http://newforced/2"), 5000);
  AppendForcedMostVisitedURL(&new_url_list, GURL("http://newforced/3"), 6000);
  AppendForcedMostVisitedURL(&new_url_list, GURL("http://newforced/4"), 8000);
  AppendForcedMostVisitedURL(&new_url_list, GURL("http://newforced/5"), 9000);
  AppendForcedMostVisitedURL(&new_url_list, GURL("http://newforced/6"), 14000);
  AppendForcedMostVisitedURL(&new_url_list, GURL("http://newforced/7"), 15000);
  AppendForcedMostVisitedURL(&new_url_list, GURL("http://newforced/8"), 16000);
  AppendForcedMostVisitedURL(&new_url_list, GURL("http://newforced/9"), 17000);
  AppendForcedMostVisitedURL(&new_url_list, GURL("http://newforced/10"), 19000);
  AppendForcedMostVisitedURL(&new_url_list, GURL("http://newforced/11"), 20000);
  AppendForcedMostVisitedURL(&new_url_list, GURL("http://newforced/12"), 22000);

  // Setup a number non-forced URLs in both old and new list.
  const size_t kNumNonForcedURLs = 20;  // Maximum number of non-forced URLs.
  for (size_t i = 0; i < kNumNonForcedURLs; ++i) {
    std::ostringstream url;
    url << "http://oldnonforced/" << i;
    AppendMostVisitedURL(&old_url_list, GURL(url.str()));
    url.str("");
    url << "http://newnonforced/" << i;
    AppendMostVisitedURL(&new_url_list, GURL(url.str()));
  }

  // Set the initial list of URLs.
  SetTopSites(old_url_list);
  EXPECT_EQ(kNumOldForcedURLs + kNumNonForcedURLs, last_num_urls_changed());

  TopSitesQuerier querier;
  // Query only non-forced URLs first.
  querier.QueryTopSites(top_sites(), false);
  ASSERT_EQ(kNumNonForcedURLs, querier.urls().size());

  // Check first URL.
  EXPECT_EQ("http://oldnonforced/0", querier.urls()[0].url.spec());

  // Query all URLs.
  querier.QueryAllTopSites(top_sites(), false, true);
  EXPECT_EQ(kNumOldForcedURLs + kNumNonForcedURLs, querier.urls().size());

  // Check first URLs.
  EXPECT_EQ("http://oldforced/0", querier.urls()[0].url.spec());
  EXPECT_EQ("http://oldnonforced/0",
            querier.urls()[kNumOldForcedURLs].url.spec());

  // Set the new list of URLs.
  SetTopSites(new_url_list);

  // Query all URLs.
  querier.QueryAllTopSites(top_sites(), false, true);

  // We should have reached the maximum of 20 forced URLs.
  ASSERT_EQ(20 + kNumNonForcedURLs, querier.urls().size());

  // Check forced URLs. They follow the order of timestamps above, smaller
  // timestamps since they were evicted.
  EXPECT_EQ("http://newforced/1", querier.urls()[0].url.spec());
  EXPECT_EQ(3000, querier.urls()[0].last_forced_time.ToJsTime());
  EXPECT_EQ("http://oldforced/1", querier.urls()[1].url.spec());
  EXPECT_EQ(4000, querier.urls()[1].last_forced_time.ToJsTime());
  EXPECT_EQ("http://newforced/2", querier.urls()[2].url.spec());
  EXPECT_EQ(5000, querier.urls()[2].last_forced_time.ToJsTime());
  EXPECT_EQ("http://newforced/3", querier.urls()[3].url.spec());
  EXPECT_EQ(6000, querier.urls()[3].last_forced_time.ToJsTime());
  EXPECT_EQ("http://oldforced/2", querier.urls()[4].url.spec());
  EXPECT_EQ(7000, querier.urls()[4].last_forced_time.ToJsTime());
  EXPECT_EQ("http://newforced/4", querier.urls()[5].url.spec());
  EXPECT_EQ(8000, querier.urls()[5].last_forced_time.ToJsTime());
  EXPECT_EQ("http://newforced/5", querier.urls()[6].url.spec());
  EXPECT_EQ(9000, querier.urls()[6].last_forced_time.ToJsTime());
  EXPECT_EQ("http://oldforced/3", querier.urls()[7].url.spec());
  EXPECT_EQ(10000, querier.urls()[7].last_forced_time.ToJsTime());
  EXPECT_EQ("http://oldforced/4", querier.urls()[8].url.spec());
  EXPECT_EQ(11000, querier.urls()[8].last_forced_time.ToJsTime());
  EXPECT_EQ("http://oldforced/5", querier.urls()[9].url.spec());
  EXPECT_EQ(12000, querier.urls()[9].last_forced_time.ToJsTime());
  EXPECT_EQ("http://oldforced/6", querier.urls()[10].url.spec());
  EXPECT_EQ(13000, querier.urls()[10].last_forced_time.ToJsTime());
  EXPECT_EQ("http://newforced/6", querier.urls()[11].url.spec());
  EXPECT_EQ(14000, querier.urls()[11].last_forced_time.ToJsTime());
  EXPECT_EQ("http://newforced/7", querier.urls()[12].url.spec());
  EXPECT_EQ(15000, querier.urls()[12].last_forced_time.ToJsTime());
  EXPECT_EQ("http://newforced/8", querier.urls()[13].url.spec());
  EXPECT_EQ(16000, querier.urls()[13].last_forced_time.ToJsTime());
  EXPECT_EQ("http://newforced/9", querier.urls()[14].url.spec());
  EXPECT_EQ(17000, querier.urls()[14].last_forced_time.ToJsTime());
  EXPECT_EQ("http://oldforced/7", querier.urls()[15].url.spec());
  EXPECT_EQ(18000, querier.urls()[15].last_forced_time.ToJsTime());
  EXPECT_EQ("http://newforced/10", querier.urls()[16].url.spec());
  EXPECT_EQ(19000, querier.urls()[16].last_forced_time.ToJsTime());
  EXPECT_EQ("http://newforced/11", querier.urls()[17].url.spec());
  EXPECT_EQ(20000, querier.urls()[17].last_forced_time.ToJsTime());
  EXPECT_EQ("http://oldforced/8", querier.urls()[18].url.spec());
  EXPECT_EQ(21000, querier.urls()[18].last_forced_time.ToJsTime());
  EXPECT_EQ("http://newforced/12", querier.urls()[19].url.spec());
  EXPECT_EQ(22000, querier.urls()[19].last_forced_time.ToJsTime());

  // Check first and last non-forced URLs.
  EXPECT_EQ("http://newnonforced/0", querier.urls()[20].url.spec());
  EXPECT_TRUE(querier.urls()[20].last_forced_time.is_null());
  EXPECT_EQ("http://newnonforced/19", querier.urls()[39].url.spec());
  EXPECT_TRUE(querier.urls()[39].last_forced_time.is_null());
}

TEST_F(TopSitesImplTest, SetForcedTopSitesWithCollisions) {

  // Setup an old URL list in order to generate some collisions.
  MostVisitedURLList old_url_list;
  AppendForcedMostVisitedURL(&old_url_list, GURL("http://url/0"), 1000);
  // The following three will be evicted.
  AppendForcedMostVisitedURL(&old_url_list, GURL("http://collision/0"), 4000);
  AppendForcedMostVisitedURL(&old_url_list, GURL("http://collision/1"), 6000);
  AppendForcedMostVisitedURL(&old_url_list, GURL("http://collision/2"), 7000);
  // The following is evicted since all non-forced URLs are, therefore it
  // doesn't cause a collision.
  AppendMostVisitedURL(&old_url_list, GURL("http://noncollision/0"));
  SetTopSites(old_url_list);

  // Setup a new URL list that will cause collisions.
  MostVisitedURLList new_url_list;
  AppendForcedMostVisitedURL(&new_url_list, GURL("http://collision/1"), 2000);
  AppendForcedMostVisitedURL(&new_url_list, GURL("http://url/2"), 3000);
  AppendForcedMostVisitedURL(&new_url_list, GURL("http://collision/0"), 5000);
  AppendForcedMostVisitedURL(&new_url_list, GURL("http://noncollision/0"),
                             9000);
  AppendMostVisitedURL(&new_url_list, GURL("http://collision/2"));
  AppendMostVisitedURL(&new_url_list, GURL("http://url/3"));
  SetTopSites(new_url_list);

  // Query all URLs.
  TopSitesQuerier querier;
  querier.QueryAllTopSites(top_sites(), false, true);

  // Check URLs. When collision occurs, the incoming one is always preferred.
  ASSERT_EQ(7u + GetPrepopulatePages().size(), querier.urls().size());
  EXPECT_EQ("http://url/0", querier.urls()[0].url.spec());
  EXPECT_EQ(1000u, querier.urls()[0].last_forced_time.ToJsTime());
  EXPECT_EQ("http://collision/1", querier.urls()[1].url.spec());
  EXPECT_EQ(2000u, querier.urls()[1].last_forced_time.ToJsTime());
  EXPECT_EQ("http://url/2", querier.urls()[2].url.spec());
  EXPECT_EQ(3000u, querier.urls()[2].last_forced_time.ToJsTime());
  EXPECT_EQ("http://collision/0", querier.urls()[3].url.spec());
  EXPECT_EQ(5000u, querier.urls()[3].last_forced_time.ToJsTime());
  EXPECT_EQ("http://noncollision/0", querier.urls()[4].url.spec());
  EXPECT_EQ(9000u, querier.urls()[4].last_forced_time.ToJsTime());
  EXPECT_EQ("http://collision/2", querier.urls()[5].url.spec());
  EXPECT_TRUE(querier.urls()[5].last_forced_time.is_null());
  EXPECT_EQ("http://url/3", querier.urls()[6].url.spec());
  EXPECT_TRUE(querier.urls()[6].last_forced_time.is_null());
  ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 7));
}

TEST_F(TopSitesImplTest, SetTopSitesIdentical) {
  // Set the initial list of URLs.
  MostVisitedURLList url_list;
  AppendForcedMostVisitedURL(&url_list, GURL("http://url/0"), 1000);
  AppendMostVisitedURL(&url_list, GURL("http://url/1"));
  AppendMostVisitedURL(&url_list, GURL("http://url/2"));
  SetTopSites(url_list);

  // Set the new list of URLs to be exactly the same.
  SetTopSites(MostVisitedURLList(url_list));

  // Query all URLs.
  TopSitesQuerier querier;
  querier.QueryAllTopSites(top_sites(), false, true);

  // Check URLs. When collision occurs, the incoming one is always preferred.
  ASSERT_EQ(3u + GetPrepopulatePages().size(), querier.urls().size());
  EXPECT_EQ("http://url/0", querier.urls()[0].url.spec());
  EXPECT_EQ(1000u, querier.urls()[0].last_forced_time.ToJsTime());
  EXPECT_EQ("http://url/1", querier.urls()[1].url.spec());
  EXPECT_EQ("http://url/2", querier.urls()[2].url.spec());
  ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 3));
}

TEST_F(TopSitesImplTest, AddForcedURL) {
  // Set the initial list of URLs.
  MostVisitedURLList url_list;
  AppendForcedMostVisitedURL(&url_list, GURL("http://forced/0"), 2000);
  AppendForcedMostVisitedURL(&url_list, GURL("http://forced/1"), 4000);
  AppendMostVisitedURL(&url_list, GURL("http://nonforced/0"));
  AppendMostVisitedURL(&url_list, GURL("http://nonforced/1"));
  AppendMostVisitedURL(&url_list, GURL("http://nonforced/2"));
  SetTopSites(url_list);

  // Add forced sites here and there to exercise a couple of cases.
  EXPECT_TRUE(AddForcedURL(GURL("http://forced/2"),
                           base::Time::FromJsTime(5000)));
  EXPECT_TRUE(AddForcedURL(GURL("http://forced/3"),
                           base::Time::FromJsTime(1000)));
  EXPECT_TRUE(AddForcedURL(GURL("http://forced/4"),
                           base::Time::FromJsTime(3000)));

  // Check URLs.
  TopSitesQuerier querier;
  querier.QueryAllTopSites(top_sites(), false, true);
  ASSERT_EQ(8u + GetPrepopulatePages().size(), querier.urls().size());
  EXPECT_EQ("http://forced/3", querier.urls()[0].url.spec());
  EXPECT_EQ(1000u, querier.urls()[0].last_forced_time.ToJsTime());
  EXPECT_EQ("http://forced/0", querier.urls()[1].url.spec());
  EXPECT_EQ(2000u, querier.urls()[1].last_forced_time.ToJsTime());
  EXPECT_EQ("http://forced/4", querier.urls()[2].url.spec());
  EXPECT_EQ(3000u, querier.urls()[2].last_forced_time.ToJsTime());
  EXPECT_EQ("http://forced/1", querier.urls()[3].url.spec());
  EXPECT_EQ(4000u, querier.urls()[3].last_forced_time.ToJsTime());
  EXPECT_EQ("http://forced/2", querier.urls()[4].url.spec());
  EXPECT_EQ(5000u, querier.urls()[4].last_forced_time.ToJsTime());
  EXPECT_EQ("http://nonforced/0", querier.urls()[5].url.spec());
  EXPECT_TRUE(querier.urls()[5].last_forced_time.is_null());
  EXPECT_EQ("http://nonforced/1", querier.urls()[6].url.spec());
  EXPECT_TRUE(querier.urls()[6].last_forced_time.is_null());
  EXPECT_EQ("http://nonforced/2", querier.urls()[7].url.spec());
  EXPECT_TRUE(querier.urls()[7].last_forced_time.is_null());
  ASSERT_NO_FATAL_FAILURE(ContainsPrepopulatePages(querier, 8));

  // Add some collisions with forced and non-forced. Non-forced URLs are never
  // expected to move.
  EXPECT_TRUE(AddForcedURL(GURL("http://forced/3"),
                           base::Time::FromJsTime(4000)));
  EXPECT_TRUE(AddForcedURL(GURL("http://forced/1"),
                            base::Time::FromJsTime(1000)));
  EXPECT_FALSE(AddForcedURL(GURL("http://nonforced/0"),
                            base::Time::FromJsTime(6000)));

  // Check relevant URLs.
  querier.QueryAllTopSites(top_sites(), false, true);
  ASSERT_EQ(8u + GetPrepopulatePages().size(), querier.urls().size());
  EXPECT_EQ("http://forced/1", querier.urls()[0].url.spec());
  EXPECT_EQ(1000u, querier.urls()[0].last_forced_time.ToJsTime());
  EXPECT_EQ("http://forced/3", querier.urls()[3].url.spec());
  EXPECT_EQ(4000u, querier.urls()[3].last_forced_time.ToJsTime());
  EXPECT_EQ("http://nonforced/0", querier.urls()[5].url.spec());
  EXPECT_TRUE(querier.urls()[5].last_forced_time.is_null());

  // Add a timestamp collision and make sure things don't break.
  EXPECT_TRUE(AddForcedURL(GURL("http://forced/5"),
                           base::Time::FromJsTime(4000)));
  querier.QueryAllTopSites(top_sites(), false, true);
  ASSERT_EQ(9u + GetPrepopulatePages().size(), querier.urls().size());
  EXPECT_EQ(4000u, querier.urls()[3].last_forced_time.ToJsTime());
  EXPECT_EQ(4000u, querier.urls()[4].last_forced_time.ToJsTime());
  // We don't care which order they get sorted in.
  if (querier.urls()[3].url.spec() == "http://forced/3") {
    EXPECT_EQ("http://forced/3", querier.urls()[3].url.spec());
    EXPECT_EQ("http://forced/5", querier.urls()[4].url.spec());
  } else {
    EXPECT_EQ("http://forced/5", querier.urls()[3].url.spec());
    EXPECT_EQ("http://forced/3", querier.urls()[4].url.spec());
  }

  // Make sure the thumbnail is not lost when the timestamp is updated.
  gfx::Image red_thumbnail(CreateBitmap(SK_ColorRED));
  ASSERT_TRUE(top_sites()->SetPageThumbnail(
                  GURL("http://forced/5"), red_thumbnail, ThumbnailScore()));

  // Get the original thumbnail for later comparison. Some compression can
  // happen in |top_sites| and we don't want to depend on that.
  SkBitmap orig_thumbnail = GetThumbnail(GURL("http://forced/5"));

  EXPECT_TRUE(AddForcedURL(GURL("http://forced/5"),
                           base::Time::FromJsTime(6000)));

  // Ensure the thumbnail is still there even if the timestamp changed.
  querier.QueryAllTopSites(top_sites(), false, true);
  EXPECT_EQ("http://forced/5", querier.urls()[5].url.spec());
  EXPECT_EQ(6000u, querier.urls()[5].last_forced_time.ToJsTime());
  SkBitmap thumbnail = GetThumbnail(GURL("http://forced/5"));
  ASSERT_EQ(orig_thumbnail.getSize(), thumbnail.getSize());
  orig_thumbnail.lockPixels();
  thumbnail.lockPixels();
  EXPECT_EQ(0, memcmp(orig_thumbnail.getPixels(), thumbnail.getPixels(),
                      orig_thumbnail.getSize()));
  thumbnail.unlockPixels();
  orig_thumbnail.unlockPixels();
}

}  // namespace history

/* [<][>][^][v][top][bottom][index][help] */