Home | History | Annotate | Download | only in history
      1 // Copyright (c) 2011 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 <map>
      6 
      7 #include "base/files/file_path.h"
      8 #include "base/files/scoped_temp_dir.h"
      9 #include "base/path_service.h"
     10 #include "base/strings/utf_string_conversions.h"
     11 #include "chrome/browser/history/history_types.h"
     12 #include "chrome/browser/history/top_sites_database.h"
     13 #include "chrome/common/chrome_paths.h"
     14 #include "chrome/tools/profiles/thumbnail-inl.h"
     15 #include "sql/connection.h"
     16 #include "sql/recovery.h"
     17 #include "sql/test/scoped_error_ignorer.h"
     18 #include "sql/test/test_helpers.h"
     19 #include "testing/gtest/include/gtest/gtest.h"
     20 #include "third_party/sqlite/sqlite3.h"
     21 #include "url/gurl.h"
     22 
     23 namespace {
     24 
     25 // URL with url_rank 0 in golden files.
     26 const GURL kUrl0 = GURL("http://www.google.com/");
     27 
     28 // URL with url_rank 1 in golden files.
     29 const GURL kUrl1 = GURL("http://www.google.com/chrome/intl/en/welcome.html");
     30 
     31 // URL with url_rank 2 in golden files.
     32 const GURL kUrl2 = GURL("https://chrome.google.com/webstore?hl=en");
     33 
     34 // Create the test database at |db_path| from the golden file at
     35 // |ascii_path| in the "History/" subdir of the test data dir.
     36 WARN_UNUSED_RESULT bool CreateDatabaseFromSQL(const base::FilePath &db_path,
     37                                                 const char* ascii_path) {
     38   base::FilePath sql_path;
     39   if (!PathService::Get(chrome::DIR_TEST_DATA, &sql_path))
     40     return false;
     41   sql_path = sql_path.AppendASCII("History").AppendASCII(ascii_path);
     42   return sql::test::CreateDatabaseFromSQL(db_path, sql_path);
     43 }
     44 
     45 // Verify that the up-to-date database has the expected tables and
     46 // columns.  Functional tests only check whether the things which
     47 // should be there are, but do not check if extraneous items are
     48 // present.  Any extraneous items have the potential to interact
     49 // negatively with future schema changes.
     50 void VerifyTablesAndColumns(sql::Connection* db) {
     51   // [meta] and [thumbnails].
     52   EXPECT_EQ(2u, sql::test::CountSQLTables(db));
     53 
     54   // Implicit index on [meta], index on [thumbnails].
     55   EXPECT_EQ(2u, sql::test::CountSQLIndices(db));
     56 
     57   // [key] and [value].
     58   EXPECT_EQ(2u, sql::test::CountTableColumns(db, "meta"));
     59 
     60   // [url], [url_rank], [title], [thumbnail], [redirects],
     61   // [boring_score], [good_clipping], [at_top], [last_updated], and
     62   // [load_completed], [last_forced]
     63   EXPECT_EQ(11u, sql::test::CountTableColumns(db, "thumbnails"));
     64 }
     65 
     66 void VerifyDatabaseEmpty(sql::Connection* db) {
     67   size_t rows = 0;
     68   EXPECT_TRUE(sql::test::CountTableRows(db, "thumbnails", &rows));
     69   EXPECT_EQ(0u, rows);
     70 }
     71 
     72 }  // namespace
     73 
     74 namespace history {
     75 
     76 class TopSitesDatabaseTest : public testing::Test {
     77  protected:
     78   virtual void SetUp() {
     79     // Get a temporary directory for the test DB files.
     80     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
     81     file_name_ = temp_dir_.path().AppendASCII("TestTopSites.db");
     82   }
     83 
     84   base::ScopedTempDir temp_dir_;
     85   base::FilePath file_name_;
     86 };
     87 
     88 // Version 1 is deprecated, the resulting schema should be current,
     89 // with no data.
     90 TEST_F(TopSitesDatabaseTest, Version1) {
     91   ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v1.sql"));
     92 
     93   TopSitesDatabase db;
     94   ASSERT_TRUE(db.Init(file_name_));
     95   VerifyTablesAndColumns(db.db_.get());
     96   VerifyDatabaseEmpty(db.db_.get());
     97 }
     98 
     99 TEST_F(TopSitesDatabaseTest, Version2) {
    100   ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v2.sql"));
    101 
    102   TopSitesDatabase db;
    103   ASSERT_TRUE(db.Init(file_name_));
    104 
    105   VerifyTablesAndColumns(db.db_.get());
    106 
    107   // Basic operational check.
    108   MostVisitedURLList urls;
    109   std::map<GURL, Images> thumbnails;
    110   db.GetPageThumbnails(&urls, &thumbnails);
    111   ASSERT_EQ(3u, urls.size());
    112   ASSERT_EQ(3u, thumbnails.size());
    113   EXPECT_EQ(kUrl0, urls[0].url);  // [0] because of url_rank.
    114   // kGoogleThumbnail includes nul terminator.
    115   ASSERT_EQ(sizeof(kGoogleThumbnail) - 1,
    116             thumbnails[urls[0].url].thumbnail->size());
    117   EXPECT_TRUE(!memcmp(thumbnails[urls[0].url].thumbnail->front(),
    118                       kGoogleThumbnail, sizeof(kGoogleThumbnail) - 1));
    119 
    120   ASSERT_TRUE(db.RemoveURL(urls[1]));
    121   db.GetPageThumbnails(&urls, &thumbnails);
    122   ASSERT_EQ(2u, urls.size());
    123   ASSERT_EQ(2u, thumbnails.size());
    124 }
    125 
    126 TEST_F(TopSitesDatabaseTest, Version3) {
    127   ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v3.sql"));
    128 
    129   TopSitesDatabase db;
    130   ASSERT_TRUE(db.Init(file_name_));
    131 
    132   VerifyTablesAndColumns(db.db_.get());
    133 
    134   // Basic operational check.
    135   MostVisitedURLList urls;
    136   std::map<GURL, Images> thumbnails;
    137   db.GetPageThumbnails(&urls, &thumbnails);
    138   ASSERT_EQ(3u, urls.size());
    139   ASSERT_EQ(3u, thumbnails.size());
    140   EXPECT_EQ(kUrl0, urls[0].url);  // [0] because of url_rank.
    141   // kGoogleThumbnail includes nul terminator.
    142   ASSERT_EQ(sizeof(kGoogleThumbnail) - 1,
    143             thumbnails[urls[0].url].thumbnail->size());
    144   EXPECT_TRUE(!memcmp(thumbnails[urls[0].url].thumbnail->front(),
    145                       kGoogleThumbnail, sizeof(kGoogleThumbnail) - 1));
    146 
    147   ASSERT_TRUE(db.RemoveURL(urls[1]));
    148   db.GetPageThumbnails(&urls, &thumbnails);
    149   ASSERT_EQ(2u, urls.size());
    150   ASSERT_EQ(2u, thumbnails.size());
    151 }
    152 
    153 // Version 1 is deprecated, the resulting schema should be current,
    154 // with no data.
    155 TEST_F(TopSitesDatabaseTest, Recovery1) {
    156   // Recovery module only supports some platforms at this time.
    157   if (!sql::Recovery::FullRecoverySupported())
    158     return;
    159 
    160   // Create an example database.
    161   EXPECT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v1.sql"));
    162 
    163   // Corrupt the database by adjusting the header size.
    164   EXPECT_TRUE(sql::test::CorruptSizeInHeader(file_name_));
    165 
    166   // Database is unusable at the SQLite level.
    167   {
    168     sql::ScopedErrorIgnorer ignore_errors;
    169     ignore_errors.IgnoreError(SQLITE_CORRUPT);
    170     sql::Connection raw_db;
    171     EXPECT_TRUE(raw_db.Open(file_name_));
    172     EXPECT_FALSE(raw_db.IsSQLValid("PRAGMA integrity_check"));
    173     ASSERT_TRUE(ignore_errors.CheckIgnoredErrors());
    174   }
    175 
    176   // Corruption should be detected and recovered during Init().
    177   {
    178     sql::ScopedErrorIgnorer ignore_errors;
    179     ignore_errors.IgnoreError(SQLITE_CORRUPT);
    180 
    181     TopSitesDatabase db;
    182     ASSERT_TRUE(db.Init(file_name_));
    183     VerifyTablesAndColumns(db.db_.get());
    184     VerifyDatabaseEmpty(db.db_.get());
    185 
    186     ASSERT_TRUE(ignore_errors.CheckIgnoredErrors());
    187   }
    188 }
    189 
    190 TEST_F(TopSitesDatabaseTest, Recovery2) {
    191   // Recovery module only supports some platforms at this time.
    192   if (!sql::Recovery::FullRecoverySupported())
    193     return;
    194 
    195   // Create an example database.
    196   EXPECT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v2.sql"));
    197 
    198   // Corrupt the database by adjusting the header.
    199   EXPECT_TRUE(sql::test::CorruptSizeInHeader(file_name_));
    200 
    201   // Database is unusable at the SQLite level.
    202   {
    203     sql::ScopedErrorIgnorer ignore_errors;
    204     ignore_errors.IgnoreError(SQLITE_CORRUPT);
    205     sql::Connection raw_db;
    206     EXPECT_TRUE(raw_db.Open(file_name_));
    207     EXPECT_FALSE(raw_db.IsSQLValid("PRAGMA integrity_check"));
    208     ASSERT_TRUE(ignore_errors.CheckIgnoredErrors());
    209   }
    210 
    211   // Corruption should be detected and recovered during Init().  After recovery,
    212   // the Version2 checks should work.
    213   {
    214     sql::ScopedErrorIgnorer ignore_errors;
    215     ignore_errors.IgnoreError(SQLITE_CORRUPT);
    216 
    217     TopSitesDatabase db;
    218     ASSERT_TRUE(db.Init(file_name_));
    219 
    220     VerifyTablesAndColumns(db.db_.get());
    221 
    222     // Basic operational check.
    223     MostVisitedURLList urls;
    224     std::map<GURL, Images> thumbnails;
    225     db.GetPageThumbnails(&urls, &thumbnails);
    226     ASSERT_EQ(3u, urls.size());
    227     ASSERT_EQ(3u, thumbnails.size());
    228     EXPECT_EQ(kUrl0, urls[0].url);  // [0] because of url_rank.
    229     // kGoogleThumbnail includes nul terminator.
    230     ASSERT_EQ(sizeof(kGoogleThumbnail) - 1,
    231               thumbnails[urls[0].url].thumbnail->size());
    232     EXPECT_TRUE(!memcmp(thumbnails[urls[0].url].thumbnail->front(),
    233                         kGoogleThumbnail, sizeof(kGoogleThumbnail) - 1));
    234 
    235     ASSERT_TRUE(ignore_errors.CheckIgnoredErrors());
    236   }
    237 }
    238 
    239 TEST_F(TopSitesDatabaseTest, Recovery3) {
    240   // Recovery module only supports some platforms at this time.
    241   if (!sql::Recovery::FullRecoverySupported())
    242     return;
    243 
    244   // Create an example database.
    245   EXPECT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v3.sql"));
    246 
    247   // Corrupt the database by adjusting the header.
    248   EXPECT_TRUE(sql::test::CorruptSizeInHeader(file_name_));
    249 
    250   // Database is unusable at the SQLite level.
    251   {
    252     sql::ScopedErrorIgnorer ignore_errors;
    253     ignore_errors.IgnoreError(SQLITE_CORRUPT);
    254     sql::Connection raw_db;
    255     EXPECT_TRUE(raw_db.Open(file_name_));
    256     EXPECT_FALSE(raw_db.IsSQLValid("PRAGMA integrity_check"));
    257     ASSERT_TRUE(ignore_errors.CheckIgnoredErrors());
    258   }
    259 
    260   // Corruption should be detected and recovered during Init().
    261   {
    262     sql::ScopedErrorIgnorer ignore_errors;
    263     ignore_errors.IgnoreError(SQLITE_CORRUPT);
    264 
    265     TopSitesDatabase db;
    266     ASSERT_TRUE(db.Init(file_name_));
    267 
    268     MostVisitedURLList urls;
    269     std::map<GURL, Images> thumbnails;
    270     db.GetPageThumbnails(&urls, &thumbnails);
    271     ASSERT_EQ(3u, urls.size());
    272     ASSERT_EQ(3u, thumbnails.size());
    273     EXPECT_EQ(kUrl0, urls[0].url);  // [0] because of url_rank.
    274     // kGoogleThumbnail includes nul terminator.
    275     ASSERT_EQ(sizeof(kGoogleThumbnail) - 1,
    276               thumbnails[urls[0].url].thumbnail->size());
    277     EXPECT_TRUE(!memcmp(thumbnails[urls[0].url].thumbnail->front(),
    278                         kGoogleThumbnail, sizeof(kGoogleThumbnail) - 1));
    279 
    280     ASSERT_TRUE(ignore_errors.CheckIgnoredErrors());
    281   }
    282 
    283   // Double-check database integrity.
    284   {
    285     sql::Connection raw_db;
    286     EXPECT_TRUE(raw_db.Open(file_name_));
    287     ASSERT_EQ("ok", sql::test::IntegrityCheck(&raw_db));
    288   }
    289 
    290   // Corrupt the thumnails.url auto-index by deleting an element from the table
    291   // but leaving it in the index.
    292   const char kIndexName[] = "sqlite_autoindex_thumbnails_1";
    293   // TODO(shess): Refactor CorruptTableOrIndex() to make parameterized
    294   // statements easy.
    295   const char kDeleteSql[] =
    296       "DELETE FROM thumbnails WHERE url = "
    297       "'http://www.google.com/chrome/intl/en/welcome.html'";
    298   EXPECT_TRUE(
    299       sql::test::CorruptTableOrIndex(file_name_, kIndexName, kDeleteSql));
    300 
    301   // SQLite can operate on the database, but notices the corruption in integrity
    302   // check.
    303   {
    304     sql::Connection raw_db;
    305     EXPECT_TRUE(raw_db.Open(file_name_));
    306     ASSERT_NE("ok", sql::test::IntegrityCheck(&raw_db));
    307   }
    308 
    309   // Open the database and access the corrupt index.
    310   {
    311     TopSitesDatabase db;
    312     ASSERT_TRUE(db.Init(file_name_));
    313 
    314     {
    315       sql::ScopedErrorIgnorer ignore_errors;
    316       ignore_errors.IgnoreError(SQLITE_CORRUPT);
    317 
    318       // Data for kUrl1 was deleted, but the index entry remains, this will
    319       // throw SQLITE_CORRUPT.  The corruption handler will recover the database
    320       // and poison the handle, so the outer call fails.
    321       EXPECT_EQ(TopSitesDatabase::kRankOfNonExistingURL,
    322                 db.GetURLRank(MostVisitedURL(kUrl1, base::string16())));
    323 
    324       ASSERT_TRUE(ignore_errors.CheckIgnoredErrors());
    325     }
    326   }
    327 
    328   // Check that the database is recovered at the SQLite level.
    329   {
    330     sql::Connection raw_db;
    331     EXPECT_TRUE(raw_db.Open(file_name_));
    332     ASSERT_EQ("ok", sql::test::IntegrityCheck(&raw_db));
    333   }
    334 
    335   // After recovery, the database accesses won't throw errors.  The top-ranked
    336   // item is removed, but the ranking was revised in post-processing.
    337   {
    338     TopSitesDatabase db;
    339     ASSERT_TRUE(db.Init(file_name_));
    340     VerifyTablesAndColumns(db.db_.get());
    341 
    342     EXPECT_EQ(TopSitesDatabase::kRankOfNonExistingURL,
    343               db.GetURLRank(MostVisitedURL(kUrl1, base::string16())));
    344 
    345     MostVisitedURLList urls;
    346     std::map<GURL, Images> thumbnails;
    347     db.GetPageThumbnails(&urls, &thumbnails);
    348     ASSERT_EQ(2u, urls.size());
    349     ASSERT_EQ(2u, thumbnails.size());
    350     EXPECT_EQ(kUrl0, urls[0].url);  // [0] because of url_rank.
    351     EXPECT_EQ(kUrl2, urls[1].url);  // [1] because of url_rank.
    352   }
    353 }
    354 
    355 TEST_F(TopSitesDatabaseTest, AddRemoveEditThumbnails) {
    356   ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v3.sql"));
    357 
    358   TopSitesDatabase db;
    359   ASSERT_TRUE(db.Init(file_name_));
    360 
    361   // Add a new URL, not forced, rank = 1.
    362   GURL mapsUrl = GURL("http://maps.google.com/");
    363   MostVisitedURL url1(mapsUrl, ASCIIToUTF16("Google Maps"));
    364   db.SetPageThumbnail(url1, 1, Images());
    365 
    366   MostVisitedURLList urls;
    367   std::map<GURL, Images> thumbnails;
    368   db.GetPageThumbnails(&urls, &thumbnails);
    369   ASSERT_EQ(4u, urls.size());
    370   ASSERT_EQ(4u, thumbnails.size());
    371   EXPECT_EQ(kUrl0, urls[0].url);
    372   EXPECT_EQ(mapsUrl, urls[1].url);
    373 
    374   // Add a new URL, forced.
    375   GURL driveUrl = GURL("http://drive.google.com/");
    376   MostVisitedURL url2(driveUrl, ASCIIToUTF16("Google Drive"));
    377   url2.last_forced_time = base::Time::FromJsTime(789714000000);  // 10/1/1995
    378   db.SetPageThumbnail(url2, TopSitesDatabase::kRankOfForcedURL, Images());
    379 
    380   db.GetPageThumbnails(&urls, &thumbnails);
    381   ASSERT_EQ(5u, urls.size());
    382   ASSERT_EQ(5u, thumbnails.size());
    383   EXPECT_EQ(driveUrl, urls[0].url);  // Forced URLs always appear first.
    384   EXPECT_EQ(kUrl0, urls[1].url);
    385   EXPECT_EQ(mapsUrl, urls[2].url);
    386 
    387   // Add a new URL, forced (earlier).
    388   GURL plusUrl = GURL("http://plus.google.com/");
    389   MostVisitedURL url3(plusUrl, ASCIIToUTF16("Google Plus"));
    390   url3.last_forced_time = base::Time::FromJsTime(787035600000);  // 10/12/1994
    391   db.SetPageThumbnail(url3, TopSitesDatabase::kRankOfForcedURL, Images());
    392 
    393   db.GetPageThumbnails(&urls, &thumbnails);
    394   ASSERT_EQ(6u, urls.size());
    395   ASSERT_EQ(6u, thumbnails.size());
    396   EXPECT_EQ(plusUrl, urls[0].url);  // New forced URL should appear first.
    397   EXPECT_EQ(driveUrl, urls[1].url);
    398   EXPECT_EQ(kUrl0, urls[2].url);
    399   EXPECT_EQ(mapsUrl, urls[3].url);
    400 
    401   // Change the last_forced_time of a forced URL.
    402   url3.last_forced_time = base::Time::FromJsTime(792392400000);  // 10/2/1995
    403   db.SetPageThumbnail(url3, TopSitesDatabase::kRankOfForcedURL, Images());
    404 
    405   db.GetPageThumbnails(&urls, &thumbnails);
    406   ASSERT_EQ(6u, urls.size());
    407   ASSERT_EQ(6u, thumbnails.size());
    408   EXPECT_EQ(driveUrl, urls[0].url);
    409   EXPECT_EQ(plusUrl, urls[1].url);  // Forced URL should have moved second.
    410   EXPECT_EQ(kUrl0, urls[2].url);
    411   EXPECT_EQ(mapsUrl, urls[3].url);
    412 
    413   // Change a non-forced URL to forced using UpdatePageRank.
    414   url1.last_forced_time = base::Time::FromJsTime(792219600000);  // 8/2/1995
    415   db.UpdatePageRank(url1, TopSitesDatabase::kRankOfForcedURL);
    416 
    417   db.GetPageThumbnails(&urls, &thumbnails);
    418   ASSERT_EQ(6u, urls.size());
    419   ASSERT_EQ(6u, thumbnails.size());
    420   EXPECT_EQ(driveUrl, urls[0].url);
    421   EXPECT_EQ(mapsUrl, urls[1].url);  // Maps moves to second forced URL.
    422   EXPECT_EQ(plusUrl, urls[2].url);
    423   EXPECT_EQ(kUrl0, urls[3].url);
    424 
    425   // Change a forced URL to non-forced using SetPageThumbnail.
    426   url3.last_forced_time = base::Time();
    427   db.SetPageThumbnail(url3, 1, Images());
    428 
    429   db.GetPageThumbnails(&urls, &thumbnails);
    430   ASSERT_EQ(6u, urls.size());
    431   ASSERT_EQ(6u, thumbnails.size());
    432   EXPECT_EQ(driveUrl, urls[0].url);
    433   EXPECT_EQ(mapsUrl, urls[1].url);
    434   EXPECT_EQ(kUrl0, urls[2].url);
    435   EXPECT_EQ(plusUrl, urls[3].url);  // Plus moves to second non-forced URL.
    436 
    437   // Change a non-forced URL to earlier non-forced using UpdatePageRank.
    438   db.UpdatePageRank(url3, 0);
    439 
    440   db.GetPageThumbnails(&urls, &thumbnails);
    441   ASSERT_EQ(6u, urls.size());
    442   ASSERT_EQ(6u, thumbnails.size());
    443   EXPECT_EQ(driveUrl, urls[0].url);
    444   EXPECT_EQ(mapsUrl, urls[1].url);
    445   EXPECT_EQ(plusUrl, urls[2].url);  // Plus moves to first non-forced URL.
    446   EXPECT_EQ(kUrl0, urls[3].url);
    447 
    448   // Change a non-forced URL to later non-forced using SetPageThumbnail.
    449   db.SetPageThumbnail(url3, 2, Images());
    450 
    451   db.GetPageThumbnails(&urls, &thumbnails);
    452   ASSERT_EQ(6u, urls.size());
    453   ASSERT_EQ(6u, thumbnails.size());
    454   EXPECT_EQ(driveUrl, urls[0].url);
    455   EXPECT_EQ(mapsUrl, urls[1].url);
    456   EXPECT_EQ(kUrl0, urls[2].url);
    457   EXPECT_EQ(plusUrl, urls[4].url);  // Plus moves to third non-forced URL.
    458 
    459   // Remove a non-forced URL.
    460   db.RemoveURL(url3);
    461 
    462   db.GetPageThumbnails(&urls, &thumbnails);
    463   ASSERT_EQ(5u, urls.size());
    464   ASSERT_EQ(5u, thumbnails.size());
    465   EXPECT_EQ(driveUrl, urls[0].url);
    466   EXPECT_EQ(mapsUrl, urls[1].url);
    467   EXPECT_EQ(kUrl0, urls[2].url);
    468 
    469   // Remove a forced URL.
    470   db.RemoveURL(url2);
    471 
    472   db.GetPageThumbnails(&urls, &thumbnails);
    473   ASSERT_EQ(4u, urls.size());
    474   ASSERT_EQ(4u, thumbnails.size());
    475   EXPECT_EQ(mapsUrl, urls[0].url);
    476   EXPECT_EQ(kUrl0, urls[1].url);
    477 }
    478 
    479 }  // namespace history
    480