Logo Search packages:      
Sourcecode: chromium-browser version File versions  Download package

history_backend_unittest.cc

// Copyright (c) 2010 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/file_path.h"
#include "base/file_util.h"
#include "base/path_service.h"
#include "base/ref_counted.h"
#include "base/scoped_ptr.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
#include "chrome/browser/history/history_backend.h"
#include "chrome/browser/history/history_notifications.h"
#include "chrome/browser/history/in_memory_history_backend.h"
#include "chrome/browser/history/in_memory_database.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/thumbnail_score.h"
#include "chrome/tools/profiles/thumbnail-inl.h"
#include "gfx/codec/jpeg_codec.h"
#include "googleurl/src/gurl.h"
#include "testing/gtest/include/gtest/gtest.h"

using base::Time;

// This file only tests functionality where it is most convenient to call the
// backend directly. Most of the history backend functions are tested by the
// history unit test. Because of the elaborate callbacks involved, this is no
// harder than calling it directly for many things.

namespace history {

class HistoryBackendTest;

// This must be a separate object since HistoryBackend manages its lifetime.
// This just forwards the messages we're interested in to the test object.
class HistoryBackendTestDelegate : public HistoryBackend::Delegate {
 public:
  explicit HistoryBackendTestDelegate(HistoryBackendTest* test) : test_(test) {}

  virtual void NotifyProfileError(int message_id) {}
  virtual void SetInMemoryBackend(InMemoryHistoryBackend* backend);
  virtual void BroadcastNotifications(NotificationType type,
                                      HistoryDetails* details);
  virtual void DBLoaded();
  virtual void StartTopSitesMigration();

 private:
  // Not owned by us.
  HistoryBackendTest* test_;

  DISALLOW_COPY_AND_ASSIGN(HistoryBackendTestDelegate);
};

class HistoryBackendTest : public testing::Test {
 public:
  HistoryBackendTest() : bookmark_model_(NULL), loaded_(false) {}
  virtual ~HistoryBackendTest() {
  }

 protected:
  scoped_refptr<HistoryBackend> backend_;  // Will be NULL on init failure.
  scoped_ptr<InMemoryHistoryBackend> mem_backend_;

  void AddRedirectChain(const char* sequence[], int page_id) {
    history::RedirectList redirects;
    for (int i = 0; sequence[i] != NULL; ++i)
      redirects.push_back(GURL(sequence[i]));

    int int_scope = 1;
    void* scope = 0;
    memcpy(&scope, &int_scope, sizeof(int_scope));
    scoped_refptr<history::HistoryAddPageArgs> request(
        new history::HistoryAddPageArgs(
            redirects.back(), Time::Now(), scope, page_id, GURL(),
            redirects, PageTransition::LINK, true));
    backend_->AddPage(request);
  }

  // Adds CLIENT_REDIRECT page transition.
  // |url1| is the source URL and |url2| is the destination.
  // |did_replace| is true if the transition is non-user initiated and the
  // navigation entry for |url2| has replaced that for |url1|. The possibly
  // updated transition code of the visit records for |url1| and |url2| is
  // returned by filling in |*transition1| and |*transition2|, respectively.
  void  AddClientRedirect(const GURL& url1, const GURL& url2, bool did_replace,
                          int* transition1, int* transition2) {
    void* const dummy_scope = reinterpret_cast<void*>(0x87654321);
    history::RedirectList redirects;
    if (url1.is_valid())
      redirects.push_back(url1);
    if (url2.is_valid())
      redirects.push_back(url2);
    scoped_refptr<HistoryAddPageArgs> request(
        new HistoryAddPageArgs(url2, base::Time(), dummy_scope, 0, url1,
            redirects, PageTransition::CLIENT_REDIRECT, did_replace));
    backend_->AddPage(request);

    *transition1 = getTransition(url1);
    *transition2 = getTransition(url2);
  }

  int getTransition(const GURL& url) {
    if (!url.is_valid())
      return 0;
    URLRow row;
    URLID id = backend_->db()->GetRowForURL(url, &row);
    VisitVector visits;
    EXPECT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
    return visits[0].transition;
  }

  BookmarkModel bookmark_model_;

 protected:
  bool loaded_;

 private:
  friend class HistoryBackendTestDelegate;

  // testing::Test
  virtual void SetUp() {
    if (!file_util::CreateNewTempDirectory(FILE_PATH_LITERAL("BackendTest"),
                                           &test_dir_))
      return;
    backend_ = new HistoryBackend(test_dir_,
                                  new HistoryBackendTestDelegate(this),
                                  &bookmark_model_);
    backend_->Init(false);
  }
  virtual void TearDown() {
    backend_->Closing();
    backend_ = NULL;
    mem_backend_.reset();
    file_util::Delete(test_dir_, true);
  }

  void SetInMemoryBackend(InMemoryHistoryBackend* backend) {
    mem_backend_.reset(backend);
  }

  void BroadcastNotifications(NotificationType type,
                                      HistoryDetails* details) {
    // Send the notifications directly to the in-memory database.
    Details<HistoryDetails> det(details);
    mem_backend_->Observe(type, Source<HistoryBackendTest>(NULL), det);

    // The backend passes ownership of the details pointer to us.
    delete details;
  }

  MessageLoop message_loop_;
  FilePath test_dir_;
};

void HistoryBackendTestDelegate::SetInMemoryBackend(
    InMemoryHistoryBackend* backend) {
  test_->SetInMemoryBackend(backend);
}

void HistoryBackendTestDelegate::BroadcastNotifications(
    NotificationType type,
    HistoryDetails* details) {
  test_->BroadcastNotifications(type, details);
}

void HistoryBackendTestDelegate::DBLoaded() {
  test_->loaded_ = true;
}

void HistoryBackendTestDelegate::StartTopSitesMigration() {
  test_->backend_->MigrateThumbnailsDatabase();
}

TEST_F(HistoryBackendTest, Loaded) {
  ASSERT_TRUE(backend_.get());
  ASSERT_TRUE(loaded_);
}

TEST_F(HistoryBackendTest, DeleteAll) {
  ASSERT_TRUE(backend_.get());

  // Add two favicons, use the characters '1' and '2' for the image data. Note
  // that we do these in the opposite order. This is so the first one gets ID
  // 2 autoassigned to the database, which will change when the other one is
  // deleted. This way we can test that updating works properly.
  GURL favicon_url1("http://www.google.com/favicon.ico");
  GURL favicon_url2("http://news.google.com/favicon.ico");
  FavIconID favicon2 = backend_->thumbnail_db_->AddFavIcon(favicon_url2);
  FavIconID favicon1 = backend_->thumbnail_db_->AddFavIcon(favicon_url1);

  std::vector<unsigned char> data;
  data.push_back('1');
  EXPECT_TRUE(backend_->thumbnail_db_->SetFavIcon(favicon1,
      new RefCountedBytes(data), Time::Now()));

  data[0] = '2';
  EXPECT_TRUE(backend_->thumbnail_db_->SetFavIcon(
                  favicon2, new RefCountedBytes(data), Time::Now()));

  // First visit two URLs.
  URLRow row1(GURL("http://www.google.com/"));
  row1.set_visit_count(2);
  row1.set_typed_count(1);
  row1.set_last_visit(Time::Now());
  row1.set_favicon_id(favicon1);

  URLRow row2(GURL("http://news.google.com/"));
  row2.set_visit_count(1);
  row2.set_last_visit(Time::Now());
  row2.set_favicon_id(favicon2);

  std::vector<URLRow> rows;
  rows.push_back(row2);  // Reversed order for the same reason as favicons.
  rows.push_back(row1);
  backend_->AddPagesWithDetails(rows);

  URLID row1_id = backend_->db_->GetRowForURL(row1.url(), NULL);
  URLID row2_id = backend_->db_->GetRowForURL(row2.url(), NULL);

  // Get the two visits for the URLs we just added.
  VisitVector visits;
  backend_->db_->GetVisitsForURL(row1_id, &visits);
  ASSERT_EQ(1U, visits.size());
  VisitID visit1_id = visits[0].visit_id;

  visits.clear();
  backend_->db_->GetVisitsForURL(row2_id, &visits);
  ASSERT_EQ(1U, visits.size());
  VisitID visit2_id = visits[0].visit_id;

  // The in-memory backend should have been set and it should have gotten the
  // typed URL.
  ASSERT_TRUE(mem_backend_.get());
  URLRow outrow1;
  EXPECT_TRUE(mem_backend_->db_->GetRowForURL(row1.url(), NULL));

  // Add thumbnails for each page.
  ThumbnailScore score(0.25, true, true);
  scoped_ptr<SkBitmap> google_bitmap(
      gfx::JPEGCodec::Decode(kGoogleThumbnail, sizeof(kGoogleThumbnail)));

  Time time;
  GURL gurl;
  backend_->thumbnail_db_->SetPageThumbnail(gurl, row1_id, *google_bitmap,
                                            score, time);
  scoped_ptr<SkBitmap> weewar_bitmap(
     gfx::JPEGCodec::Decode(kWeewarThumbnail, sizeof(kWeewarThumbnail)));
  backend_->thumbnail_db_->SetPageThumbnail(gurl, row2_id, *weewar_bitmap,
                                            score, time);

  // Star row1.
  bookmark_model_.AddURL(
      bookmark_model_.GetBookmarkBarNode(), 0, std::wstring(), row1.url());

  // Set full text index for each one.
  backend_->text_database_->AddPageData(row1.url(), row1_id, visit1_id,
                                        row1.last_visit(),
                                        UTF8ToUTF16("Title 1"),
                                        UTF8ToUTF16("Body 1"));
  backend_->text_database_->AddPageData(row2.url(), row2_id, visit2_id,
                                        row2.last_visit(),
                                        UTF8ToUTF16("Title 2"),
                                        UTF8ToUTF16("Body 2"));

  // Now finally clear all history.
  backend_->DeleteAllHistory();

  // The first URL should be preserved but the time should be cleared.
  EXPECT_TRUE(backend_->db_->GetRowForURL(row1.url(), &outrow1));
  EXPECT_EQ(0, outrow1.visit_count());
  EXPECT_EQ(0, outrow1.typed_count());
  EXPECT_TRUE(Time() == outrow1.last_visit());

  // The second row should be deleted.
  URLRow outrow2;
  EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &outrow2));

  // All visits should be deleted for both URLs.
  VisitVector all_visits;
  backend_->db_->GetAllVisitsInRange(Time(), Time(), 0, &all_visits);
  ASSERT_EQ(0U, all_visits.size());

  // All thumbnails should be deleted.
  std::vector<unsigned char> out_data;
  EXPECT_FALSE(backend_->thumbnail_db_->GetPageThumbnail(outrow1.id(),
                                                         &out_data));
  EXPECT_FALSE(backend_->thumbnail_db_->GetPageThumbnail(row2_id, &out_data));

  // We should have a favicon for the first URL only. We look them up by favicon
  // URL since the IDs may hav changed.
  FavIconID out_favicon1 = backend_->thumbnail_db_->
      GetFavIconIDForFavIconURL(favicon_url1);
  EXPECT_TRUE(out_favicon1);
  FavIconID out_favicon2 = backend_->thumbnail_db_->
      GetFavIconIDForFavIconURL(favicon_url2);
  EXPECT_FALSE(out_favicon2) << "Favicon not deleted";

  // The remaining URL should still reference the same favicon, even if its
  // ID has changed.
  EXPECT_EQ(out_favicon1, outrow1.favicon_id());

  // The first URL should still be bookmarked.
  EXPECT_TRUE(bookmark_model_.IsBookmarked(row1.url()));

  // The full text database should have no data.
  std::vector<TextDatabase::Match> text_matches;
  Time first_time_searched;
  backend_->text_database_->GetTextMatches(UTF8ToUTF16("Body"),
                                           QueryOptions(),
                                           &text_matches,
                                           &first_time_searched);
  EXPECT_EQ(0U, text_matches.size());
}

TEST_F(HistoryBackendTest, URLsNoLongerBookmarked) {
  GURL favicon_url1("http://www.google.com/favicon.ico");
  GURL favicon_url2("http://news.google.com/favicon.ico");
  FavIconID favicon2 = backend_->thumbnail_db_->AddFavIcon(favicon_url2);
  FavIconID favicon1 = backend_->thumbnail_db_->AddFavIcon(favicon_url1);

  std::vector<unsigned char> data;
  data.push_back('1');
  EXPECT_TRUE(backend_->thumbnail_db_->SetFavIcon(
                  favicon1, new RefCountedBytes(data), Time::Now()));

  data[0] = '2';
  EXPECT_TRUE(backend_->thumbnail_db_->SetFavIcon(
                  favicon2, new RefCountedBytes(data), Time::Now()));

  // First visit two URLs.
  URLRow row1(GURL("http://www.google.com/"));
  row1.set_visit_count(2);
  row1.set_typed_count(1);
  row1.set_last_visit(Time::Now());
  row1.set_favicon_id(favicon1);

  URLRow row2(GURL("http://news.google.com/"));
  row2.set_visit_count(1);
  row2.set_last_visit(Time::Now());
  row2.set_favicon_id(favicon2);

  std::vector<URLRow> rows;
  rows.push_back(row2);  // Reversed order for the same reason as favicons.
  rows.push_back(row1);
  backend_->AddPagesWithDetails(rows);

  URLID row1_id = backend_->db_->GetRowForURL(row1.url(), NULL);
  URLID row2_id = backend_->db_->GetRowForURL(row2.url(), NULL);

  // Star the two URLs.
  bookmark_model_.SetURLStarred(row1.url(), std::wstring(), true);
  bookmark_model_.SetURLStarred(row2.url(), std::wstring(), true);

  // Delete url 2. Because url 2 is starred this won't delete the URL, only
  // the visits.
  backend_->expirer_.DeleteURL(row2.url());

  // Make sure url 2 is still valid, but has no visits.
  URLRow tmp_url_row;
  EXPECT_EQ(row2_id, backend_->db_->GetRowForURL(row2.url(), NULL));
  VisitVector visits;
  backend_->db_->GetVisitsForURL(row2_id, &visits);
  EXPECT_EQ(0U, visits.size());
  // The favicon should still be valid.
  EXPECT_EQ(favicon2,
      backend_->thumbnail_db_->GetFavIconIDForFavIconURL(favicon_url2));

  // Unstar row2.
  bookmark_model_.SetURLStarred(row2.url(), std::wstring(), false);
  // Tell the backend it was unstarred. We have to explicitly do this as
  // BookmarkModel isn't wired up to the backend during testing.
  std::set<GURL> unstarred_urls;
  unstarred_urls.insert(row2.url());
  backend_->URLsNoLongerBookmarked(unstarred_urls);

  // The URL should no longer exist.
  EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &tmp_url_row));
  // And the favicon should be deleted.
  EXPECT_EQ(0,
      backend_->thumbnail_db_->GetFavIconIDForFavIconURL(favicon_url2));

  // Unstar row 1.
  bookmark_model_.SetURLStarred(row1.url(), std::wstring(), false);
  // Tell the backend it was unstarred. We have to explicitly do this as
  // BookmarkModel isn't wired up to the backend during testing.
  unstarred_urls.clear();
  unstarred_urls.insert(row1.url());
  backend_->URLsNoLongerBookmarked(unstarred_urls);

  // The URL should still exist (because there were visits).
  EXPECT_EQ(row1_id, backend_->db_->GetRowForURL(row1.url(), NULL));

  // There should still be visits.
  visits.clear();
  backend_->db_->GetVisitsForURL(row1_id, &visits);
  EXPECT_EQ(1U, visits.size());

  // The favicon should still be valid.
  EXPECT_EQ(favicon1,
      backend_->thumbnail_db_->GetFavIconIDForFavIconURL(favicon_url1));
}

TEST_F(HistoryBackendTest, GetPageThumbnailAfterRedirects) {
  ASSERT_TRUE(backend_.get());

  const char* base_url = "http://mail";
  const char* thumbnail_url = "http://mail.google.com";
  const char* first_chain[] = {
    base_url,
    thumbnail_url,
    NULL
  };
  AddRedirectChain(first_chain, 0);

  // Add a thumbnail for the end of that redirect chain.
  scoped_ptr<SkBitmap> thumbnail(
      gfx::JPEGCodec::Decode(kGoogleThumbnail, sizeof(kGoogleThumbnail)));
  backend_->SetPageThumbnail(GURL(thumbnail_url), *thumbnail,
                             ThumbnailScore(0.25, true, true));

  // Write a second URL chain so that if you were to simply check what
  // "http://mail" redirects to, you wouldn't see the URL that has
  // contains the thumbnail.
  const char* second_chain[] = {
    base_url,
    "http://mail.google.com/somewhere/else",
    NULL
  };
  AddRedirectChain(second_chain, 1);

  // Now try to get the thumbnail for the base url. It shouldn't be
  // distracted by the second chain and should return the thumbnail
  // attached to thumbnail_url_.
  scoped_refptr<RefCountedBytes> data;
  backend_->GetPageThumbnailDirectly(GURL(base_url), &data);

  EXPECT_TRUE(data.get());
}

// Tests a handful of assertions for a navigation with a type of
// KEYWORD_GENERATED.
TEST_F(HistoryBackendTest, KeywordGenerated) {
  ASSERT_TRUE(backend_.get());

  GURL url("http://google.com");

  Time visit_time = Time::Now() - base::TimeDelta::FromDays(1);
  scoped_refptr<HistoryAddPageArgs> request(
      new HistoryAddPageArgs(url, visit_time, NULL, 0, GURL(),
                             history::RedirectList(),
                             PageTransition::KEYWORD_GENERATED, false));
  backend_->AddPage(request);

  // A row should have been added for the url.
  URLRow row;
  URLID url_id = backend_->db()->GetRowForURL(url, &row);
  ASSERT_NE(0, url_id);

  // The typed count should be 1.
  ASSERT_EQ(1, row.typed_count());

  // KEYWORD_GENERATED urls should not be added to the segment db.
  std::string segment_name = VisitSegmentDatabase::ComputeSegmentName(url);
  EXPECT_EQ(0, backend_->db()->GetSegmentNamed(segment_name));

  // One visit should be added.
  VisitVector visits;
  EXPECT_TRUE(backend_->db()->GetVisitsForURL(url_id, &visits));
  EXPECT_EQ(1U, visits.size());

  // But no visible visits.
  visits.clear();
  backend_->db()->GetVisibleVisitsInRange(base::Time(), base::Time(), 1,
                                          &visits);
  EXPECT_TRUE(visits.empty());

  // Expire the visits.
  std::set<GURL> restrict_urls;
  backend_->expire_backend()->ExpireHistoryBetween(restrict_urls,
                                                   visit_time, Time::Now());

  // The visit should have been nuked.
  visits.clear();
  EXPECT_TRUE(backend_->db()->GetVisitsForURL(url_id, &visits));
  EXPECT_TRUE(visits.empty());

  // As well as the url.
  ASSERT_EQ(0, backend_->db()->GetRowForURL(url, &row));
}

TEST_F(HistoryBackendTest, ClientRedirect) {
  ASSERT_TRUE(backend_.get());

  int transition1;
  int transition2;

  // Initial transition to page A.
  GURL url_a("http://google.com/a");
  AddClientRedirect(GURL(), url_a, false, &transition1, &transition2);
  EXPECT_TRUE(transition2 & PageTransition::CHAIN_END);

  // User initiated redirect to page B.
  GURL url_b("http://google.com/b");
  AddClientRedirect(url_a, url_b, false, &transition1, &transition2);
  EXPECT_TRUE(transition1 & PageTransition::CHAIN_END);
  EXPECT_TRUE(transition2 & PageTransition::CHAIN_END);

  // Non-user initiated redirect to page C.
  GURL url_c("http://google.com/c");
  AddClientRedirect(url_b, url_c, true, &transition1, &transition2);
  EXPECT_FALSE(transition1 & PageTransition::CHAIN_END);
  EXPECT_TRUE(transition2 & PageTransition::CHAIN_END);
}

TEST_F(HistoryBackendTest, ImportedFaviconsTest) {
  // Setup test data - two Urls in the history, one with favicon assigned and
  // one without.
  GURL favicon_url1("http://www.google.com/favicon.ico");
  FavIconID favicon1 = backend_->thumbnail_db_->AddFavIcon(favicon_url1);
  std::vector<unsigned char> data;
  data.push_back('1');
  EXPECT_TRUE(backend_->thumbnail_db_->SetFavIcon(favicon1,
      RefCountedBytes::TakeVector(&data), Time::Now()));
  URLRow row1(GURL("http://www.google.com/"));
  row1.set_favicon_id(favicon1);
  row1.set_visit_count(1);
  row1.set_last_visit(Time::Now());
  URLRow row2(GURL("http://news.google.com/"));
  row2.set_visit_count(1);
  row2.set_last_visit(Time::Now());
  std::vector<URLRow> rows;
  rows.push_back(row1);
  rows.push_back(row2);
  backend_->AddPagesWithDetails(rows);
  URLRow url_row1, url_row2;
  EXPECT_FALSE(backend_->db_->GetRowForURL(row1.url(), &url_row1) == 0);
  EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &url_row2) == 0);
  EXPECT_FALSE(url_row1.favicon_id() == 0);
  EXPECT_TRUE(url_row2.favicon_id() == 0);

  // Now provide one imported favicon for both URLs already in the registry.
  // The new favicon should only be used with the URL that doesn't already have
  // a favicon.
  std::vector<history::ImportedFavIconUsage> favicons;
  history::ImportedFavIconUsage favicon;
  favicon.favicon_url = GURL("http://news.google.com/favicon.ico");
  favicon.png_data.push_back('2');
  favicon.urls.insert(row1.url());
  favicon.urls.insert(row2.url());
  favicons.push_back(favicon);
  backend_->SetImportedFavicons(favicons);
  EXPECT_FALSE(backend_->db_->GetRowForURL(row1.url(), &url_row1) == 0);
  EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &url_row2) == 0);
  EXPECT_FALSE(url_row1.favicon_id() == 0);
  EXPECT_FALSE(url_row2.favicon_id() == 0);
  EXPECT_FALSE(url_row1.favicon_id() == url_row2.favicon_id());

  // A URL should not be added to history (to store favicon), if
  // the URL is not bookmarked.
  GURL url3("http://mail.google.com");
  favicons.clear();
  favicon.favicon_url = GURL("http://mail.google.com/favicon.ico");
  favicon.png_data.push_back('3');
  favicon.urls.insert(url3);
  favicons.push_back(favicon);
  backend_->SetImportedFavicons(favicons);
  URLRow url_row3;
  EXPECT_TRUE(backend_->db_->GetRowForURL(url3, &url_row3) == 0);

  // If the URL is bookmarked, it should get added to history with 0 visits.
  bookmark_model_.AddURL(bookmark_model_.GetBookmarkBarNode(), 0,
                         std::wstring(), url3);
  backend_->SetImportedFavicons(favicons);
  EXPECT_FALSE(backend_->db_->GetRowForURL(url3, &url_row3) == 0);
  EXPECT_TRUE(url_row3.visit_count() == 0);
}

TEST_F(HistoryBackendTest, StripUsernamePasswordTest) {
  ASSERT_TRUE(backend_.get());

  GURL url("http://anyuser:anypass@www.google.com");
  GURL stripped_url("http://www.google.com");

  // Clear all history.
  backend_->DeleteAllHistory();

  // Visit the url with username, password.
  backend_->AddPageVisit(url, base::Time::Now(), 0,
    PageTransition::GetQualifier(PageTransition::TYPED));

  // Fetch the row information about stripped url from history db.
  VisitVector visits;
  URLID row_id = backend_->db_->GetRowForURL(stripped_url, NULL);
  backend_->db_->GetVisitsForURL(row_id, &visits);

  // Check if stripped url is stored in database.
  ASSERT_EQ(1U, visits.size());
}

TEST_F(HistoryBackendTest, DeleteThumbnailsDatabaseTest) {
  EXPECT_TRUE(backend_->thumbnail_db_->NeedsMigrationToTopSites());
  backend_->delegate_->StartTopSitesMigration();
  EXPECT_FALSE(backend_->thumbnail_db_->NeedsMigrationToTopSites());
}

}  // namespace history

Generated by  Doxygen 1.6.0   Back to index