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/top_sites_database.h" 12 #include "chrome/common/chrome_paths.h" 13 #include "chrome/tools/profiles/thumbnail-inl.h" 14 #include "components/history/core/browser/history_types.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, base::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, base::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, base::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