1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include <algorithm> 6 #include <vector> 7 8 #include "base/basictypes.h" 9 #include "base/files/file_enumerator.h" 10 #include "base/files/file_path.h" 11 #include "base/files/scoped_temp_dir.h" 12 #include "base/memory/ref_counted_memory.h" 13 #include "base/path_service.h" 14 #include "chrome/browser/history/thumbnail_database.h" 15 #include "chrome/common/chrome_paths.h" 16 #include "sql/connection.h" 17 #include "sql/recovery.h" 18 #include "sql/test/scoped_error_ignorer.h" 19 #include "sql/test/test_helpers.h" 20 #include "testing/gtest/include/gtest/gtest.h" 21 #include "third_party/sqlite/sqlite3.h" 22 #include "url/gurl.h" 23 24 namespace history { 25 26 namespace { 27 28 // Blobs for the bitmap tests. These aren't real bitmaps. Golden 29 // database files store the same blobs (see VersionN tests). 30 const unsigned char kBlob1[] = 31 "12346102356120394751634516591348710478123649165419234519234512349134"; 32 const unsigned char kBlob2[] = 33 "goiwuegrqrcomizqyzkjalitbahxfjytrqvpqeroicxmnlkhlzunacxaneviawrtxcywhgef"; 34 35 // Page and icon urls shared by tests. Present in golden database 36 // files (see VersionN tests). 37 const GURL kPageUrl1 = GURL("http://google.com/"); 38 const GURL kPageUrl2 = GURL("http://yahoo.com/"); 39 const GURL kPageUrl3 = GURL("http://www.google.com/"); 40 const GURL kPageUrl4 = GURL("http://www.google.com/blank.html"); 41 const GURL kPageUrl5 = GURL("http://www.bing.com/"); 42 43 const GURL kIconUrl1 = GURL("http://www.google.com/favicon.ico"); 44 const GURL kIconUrl2 = GURL("http://www.yahoo.com/favicon.ico"); 45 const GURL kIconUrl3 = GURL("http://www.google.com/touch.ico"); 46 const GURL kIconUrl5 = GURL("http://www.bing.com/favicon.ico"); 47 48 const gfx::Size kSmallSize = gfx::Size(16, 16); 49 const gfx::Size kLargeSize = gfx::Size(32, 32); 50 51 // Create the test database at |db_path| from the golden file at 52 // |ascii_path| in the "History/" subdir of the test data dir. 53 WARN_UNUSED_RESULT bool CreateDatabaseFromSQL(const base::FilePath &db_path, 54 const char* ascii_path) { 55 base::FilePath sql_path; 56 if (!PathService::Get(chrome::DIR_TEST_DATA, &sql_path)) 57 return false; 58 sql_path = sql_path.AppendASCII("History").AppendASCII(ascii_path); 59 return sql::test::CreateDatabaseFromSQL(db_path, sql_path); 60 } 61 62 // Verify that the up-to-date database has the expected tables and 63 // columns. Functional tests only check whether the things which 64 // should be there are, but do not check if extraneous items are 65 // present. Any extraneous items have the potential to interact 66 // negatively with future schema changes. 67 void VerifyTablesAndColumns(sql::Connection* db) { 68 // [meta], [favicons], [favicon_bitmaps], and [icon_mapping]. 69 EXPECT_EQ(4u, sql::test::CountSQLTables(db)); 70 71 // Implicit index on [meta], index on [favicons], index on 72 // [favicon_bitmaps], two indices on [icon_mapping]. 73 EXPECT_EQ(5u, sql::test::CountSQLIndices(db)); 74 75 // [key] and [value]. 76 EXPECT_EQ(2u, sql::test::CountTableColumns(db, "meta")); 77 78 // [id], [url], and [icon_type]. 79 EXPECT_EQ(3u, sql::test::CountTableColumns(db, "favicons")); 80 81 // [id], [icon_id], [last_updated], [image_data], [width], and [height]. 82 EXPECT_EQ(6u, sql::test::CountTableColumns(db, "favicon_bitmaps")); 83 84 // [id], [page_url], and [icon_id]. 85 EXPECT_EQ(3u, sql::test::CountTableColumns(db, "icon_mapping")); 86 } 87 88 void VerifyDatabaseEmpty(sql::Connection* db) { 89 size_t rows = 0; 90 EXPECT_TRUE(sql::test::CountTableRows(db, "favicons", &rows)); 91 EXPECT_EQ(0u, rows); 92 EXPECT_TRUE(sql::test::CountTableRows(db, "favicon_bitmaps", &rows)); 93 EXPECT_EQ(0u, rows); 94 EXPECT_TRUE(sql::test::CountTableRows(db, "icon_mapping", &rows)); 95 EXPECT_EQ(0u, rows); 96 } 97 98 // Helper to check that an expected mapping exists. 99 WARN_UNUSED_RESULT bool CheckPageHasIcon( 100 ThumbnailDatabase* db, 101 const GURL& page_url, 102 chrome::IconType expected_icon_type, 103 const GURL& expected_icon_url, 104 const gfx::Size& expected_icon_size, 105 size_t expected_icon_contents_size, 106 const unsigned char* expected_icon_contents) { 107 std::vector<IconMapping> icon_mappings; 108 if (!db->GetIconMappingsForPageURL(page_url, &icon_mappings)) { 109 ADD_FAILURE() << "failed GetIconMappingsForPageURL()"; 110 return false; 111 } 112 113 // Scan for the expected type. 114 std::vector<IconMapping>::const_iterator iter = icon_mappings.begin(); 115 for (; iter != icon_mappings.end(); ++iter) { 116 if (iter->icon_type == expected_icon_type) 117 break; 118 } 119 if (iter == icon_mappings.end()) { 120 ADD_FAILURE() << "failed to find |expected_icon_type|"; 121 return false; 122 } 123 124 if (expected_icon_url != iter->icon_url) { 125 EXPECT_EQ(expected_icon_url, iter->icon_url); 126 return false; 127 } 128 129 std::vector<FaviconBitmap> favicon_bitmaps; 130 if (!db->GetFaviconBitmaps(iter->icon_id, &favicon_bitmaps)) { 131 ADD_FAILURE() << "failed GetFaviconBitmaps()"; 132 return false; 133 } 134 135 if (1 != favicon_bitmaps.size()) { 136 EXPECT_EQ(1u, favicon_bitmaps.size()); 137 return false; 138 } 139 140 if (expected_icon_size != favicon_bitmaps[0].pixel_size) { 141 EXPECT_EQ(expected_icon_size, favicon_bitmaps[0].pixel_size); 142 return false; 143 } 144 145 if (expected_icon_contents_size != favicon_bitmaps[0].bitmap_data->size()) { 146 EXPECT_EQ(expected_icon_contents_size, 147 favicon_bitmaps[0].bitmap_data->size()); 148 return false; 149 } 150 151 if (memcmp(favicon_bitmaps[0].bitmap_data->front(), 152 expected_icon_contents, expected_icon_contents_size)) { 153 ADD_FAILURE() << "failed to match |expected_icon_contents|"; 154 return false; 155 } 156 return true; 157 } 158 159 } // namespace 160 161 class ThumbnailDatabaseTest : public testing::Test { 162 public: 163 ThumbnailDatabaseTest() { 164 } 165 virtual ~ThumbnailDatabaseTest() { 166 } 167 168 // Initialize a thumbnail database instance from the SQL file at 169 // |golden_path| in the "History/" subdirectory of test data. 170 scoped_ptr<ThumbnailDatabase> LoadFromGolden(const char* golden_path) { 171 if (!CreateDatabaseFromSQL(file_name_, golden_path)) { 172 ADD_FAILURE() << "Failed loading " << golden_path; 173 return scoped_ptr<ThumbnailDatabase>(); 174 } 175 176 scoped_ptr<ThumbnailDatabase> db(new ThumbnailDatabase()); 177 EXPECT_EQ(sql::INIT_OK, db->Init(file_name_)); 178 db->BeginTransaction(); 179 180 return db.Pass(); 181 } 182 183 protected: 184 virtual void SetUp() { 185 // Get a temporary directory for the test DB files. 186 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 187 188 file_name_ = temp_dir_.path().AppendASCII("TestFavicons.db"); 189 } 190 191 base::ScopedTempDir temp_dir_; 192 base::FilePath file_name_; 193 }; 194 195 TEST_F(ThumbnailDatabaseTest, AddIconMapping) { 196 ThumbnailDatabase db; 197 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 198 db.BeginTransaction(); 199 200 std::vector<unsigned char> data(kBlob1, kBlob1 + sizeof(kBlob1)); 201 scoped_refptr<base::RefCountedBytes> favicon(new base::RefCountedBytes(data)); 202 203 GURL url("http://google.com"); 204 base::Time time = base::Time::Now(); 205 chrome::FaviconID id = db.AddFavicon(url, 206 chrome::TOUCH_ICON, 207 favicon, 208 time, 209 gfx::Size()); 210 EXPECT_NE(0, id); 211 212 EXPECT_NE(0, db.AddIconMapping(url, id)); 213 std::vector<IconMapping> icon_mappings; 214 EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mappings)); 215 EXPECT_EQ(1u, icon_mappings.size()); 216 EXPECT_EQ(url, icon_mappings.front().page_url); 217 EXPECT_EQ(id, icon_mappings.front().icon_id); 218 } 219 220 TEST_F(ThumbnailDatabaseTest, UpdateIconMapping) { 221 ThumbnailDatabase db; 222 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 223 db.BeginTransaction(); 224 225 GURL url("http://google.com"); 226 chrome::FaviconID id = 227 db.AddFavicon(url, chrome::TOUCH_ICON); 228 229 EXPECT_LT(0, db.AddIconMapping(url, id)); 230 std::vector<IconMapping> icon_mapping; 231 EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mapping)); 232 ASSERT_EQ(1u, icon_mapping.size()); 233 EXPECT_EQ(url, icon_mapping.front().page_url); 234 EXPECT_EQ(id, icon_mapping.front().icon_id); 235 236 GURL url1("http://www.google.com/"); 237 chrome::FaviconID new_id = 238 db.AddFavicon(url1, chrome::TOUCH_ICON); 239 EXPECT_TRUE(db.UpdateIconMapping(icon_mapping.front().mapping_id, new_id)); 240 241 icon_mapping.clear(); 242 EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mapping)); 243 ASSERT_EQ(1u, icon_mapping.size()); 244 EXPECT_EQ(url, icon_mapping.front().page_url); 245 EXPECT_EQ(new_id, icon_mapping.front().icon_id); 246 EXPECT_NE(id, icon_mapping.front().icon_id); 247 } 248 249 TEST_F(ThumbnailDatabaseTest, DeleteIconMappings) { 250 ThumbnailDatabase db; 251 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 252 db.BeginTransaction(); 253 254 std::vector<unsigned char> data(kBlob1, kBlob1 + sizeof(kBlob1)); 255 scoped_refptr<base::RefCountedBytes> favicon(new base::RefCountedBytes(data)); 256 257 GURL url("http://google.com"); 258 chrome::FaviconID id = 259 db.AddFavicon(url, chrome::TOUCH_ICON); 260 base::Time time = base::Time::Now(); 261 db.AddFaviconBitmap(id, favicon, time, gfx::Size()); 262 EXPECT_LT(0, db.AddIconMapping(url, id)); 263 264 chrome::FaviconID id2 = 265 db.AddFavicon(url, chrome::FAVICON); 266 EXPECT_LT(0, db.AddIconMapping(url, id2)); 267 ASSERT_NE(id, id2); 268 269 std::vector<IconMapping> icon_mapping; 270 EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mapping)); 271 ASSERT_EQ(2u, icon_mapping.size()); 272 EXPECT_EQ(icon_mapping.front().icon_type, chrome::TOUCH_ICON); 273 EXPECT_TRUE(db.GetIconMappingsForPageURL(url, chrome::FAVICON, NULL)); 274 275 db.DeleteIconMappings(url); 276 277 EXPECT_FALSE(db.GetIconMappingsForPageURL(url, NULL)); 278 EXPECT_FALSE(db.GetIconMappingsForPageURL(url, chrome::FAVICON, NULL)); 279 } 280 281 TEST_F(ThumbnailDatabaseTest, GetIconMappingsForPageURL) { 282 ThumbnailDatabase db; 283 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 284 db.BeginTransaction(); 285 286 std::vector<unsigned char> data(kBlob1, kBlob1 + sizeof(kBlob1)); 287 scoped_refptr<base::RefCountedBytes> favicon(new base::RefCountedBytes(data)); 288 289 GURL url("http://google.com"); 290 291 chrome::FaviconID id1 = db.AddFavicon(url, chrome::TOUCH_ICON); 292 base::Time time = base::Time::Now(); 293 db.AddFaviconBitmap(id1, favicon, time, kSmallSize); 294 db.AddFaviconBitmap(id1, favicon, time, kLargeSize); 295 EXPECT_LT(0, db.AddIconMapping(url, id1)); 296 297 chrome::FaviconID id2 = db.AddFavicon(url, chrome::FAVICON); 298 EXPECT_NE(id1, id2); 299 db.AddFaviconBitmap(id2, favicon, time, kSmallSize); 300 EXPECT_LT(0, db.AddIconMapping(url, id2)); 301 302 std::vector<IconMapping> icon_mappings; 303 EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mappings)); 304 ASSERT_EQ(2u, icon_mappings.size()); 305 EXPECT_EQ(id1, icon_mappings[0].icon_id); 306 EXPECT_EQ(id2, icon_mappings[1].icon_id); 307 } 308 309 TEST_F(ThumbnailDatabaseTest, RetainDataForPageUrls) { 310 ThumbnailDatabase db; 311 312 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 313 314 db.BeginTransaction(); 315 316 // Build a database mapping kPageUrl1 -> kIconUrl1, kPageUrl2 -> 317 // kIconUrl2, kPageUrl3 -> kIconUrl1, and kPageUrl5 -> kIconUrl5. 318 // Then retain kPageUrl1, kPageUrl3, and kPageUrl5. kPageUrl2 319 // should go away, but the others should be retained correctly. 320 321 // TODO(shess): This would probably make sense as a golden file. 322 323 scoped_refptr<base::RefCountedStaticMemory> favicon1( 324 new base::RefCountedStaticMemory(kBlob1, sizeof(kBlob1))); 325 scoped_refptr<base::RefCountedStaticMemory> favicon2( 326 new base::RefCountedStaticMemory(kBlob2, sizeof(kBlob2))); 327 328 chrome::FaviconID kept_id1 = db.AddFavicon(kIconUrl1, chrome::FAVICON); 329 db.AddFaviconBitmap(kept_id1, favicon1, base::Time::Now(), kLargeSize); 330 db.AddIconMapping(kPageUrl1, kept_id1); 331 db.AddIconMapping(kPageUrl3, kept_id1); 332 333 chrome::FaviconID unkept_id = db.AddFavicon(kIconUrl2, chrome::FAVICON); 334 db.AddFaviconBitmap(unkept_id, favicon1, base::Time::Now(), kLargeSize); 335 db.AddIconMapping(kPageUrl2, unkept_id); 336 337 chrome::FaviconID kept_id2 = db.AddFavicon(kIconUrl5, chrome::FAVICON); 338 db.AddFaviconBitmap(kept_id2, favicon2, base::Time::Now(), kLargeSize); 339 db.AddIconMapping(kPageUrl5, kept_id2); 340 341 // RetainDataForPageUrls() uses schema manipulations for efficiency. 342 // Grab a copy of the schema to make sure the final schema matches. 343 const std::string original_schema = db.db_.GetSchema(); 344 345 std::vector<GURL> pages_to_keep; 346 pages_to_keep.push_back(kPageUrl1); 347 pages_to_keep.push_back(kPageUrl3); 348 pages_to_keep.push_back(kPageUrl5); 349 EXPECT_TRUE(db.RetainDataForPageUrls(pages_to_keep)); 350 351 // Mappings from the retained urls should be left. 352 EXPECT_TRUE(CheckPageHasIcon(&db, kPageUrl1, chrome::FAVICON, 353 kIconUrl1, kLargeSize, sizeof(kBlob1), kBlob1)); 354 EXPECT_TRUE(CheckPageHasIcon(&db, kPageUrl3, chrome::FAVICON, 355 kIconUrl1, kLargeSize, sizeof(kBlob1), kBlob1)); 356 EXPECT_TRUE(CheckPageHasIcon(&db, kPageUrl5, chrome::FAVICON, 357 kIconUrl5, kLargeSize, sizeof(kBlob2), kBlob2)); 358 359 // The one not retained should be missing. 360 EXPECT_FALSE(db.GetFaviconIDForFaviconURL(kPageUrl2, false, NULL)); 361 362 // Schema should be the same. 363 EXPECT_EQ(original_schema, db.db_.GetSchema()); 364 } 365 366 // Tests that deleting a favicon deletes the favicon row and favicon bitmap 367 // rows from the database. 368 TEST_F(ThumbnailDatabaseTest, DeleteFavicon) { 369 ThumbnailDatabase db; 370 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 371 db.BeginTransaction(); 372 373 std::vector<unsigned char> data1(kBlob1, kBlob1 + sizeof(kBlob1)); 374 scoped_refptr<base::RefCountedBytes> favicon1( 375 new base::RefCountedBytes(data1)); 376 std::vector<unsigned char> data2(kBlob2, kBlob2 + sizeof(kBlob2)); 377 scoped_refptr<base::RefCountedBytes> favicon2( 378 new base::RefCountedBytes(data2)); 379 380 GURL url("http://google.com"); 381 chrome::FaviconID id = db.AddFavicon(url, chrome::FAVICON); 382 base::Time last_updated = base::Time::Now(); 383 db.AddFaviconBitmap(id, favicon1, last_updated, kSmallSize); 384 db.AddFaviconBitmap(id, favicon2, last_updated, kLargeSize); 385 386 EXPECT_TRUE(db.GetFaviconBitmaps(id, NULL)); 387 388 EXPECT_TRUE(db.DeleteFavicon(id)); 389 EXPECT_FALSE(db.GetFaviconBitmaps(id, NULL)); 390 } 391 392 TEST_F(ThumbnailDatabaseTest, GetIconMappingsForPageURLForReturnOrder) { 393 ThumbnailDatabase db; 394 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 395 db.BeginTransaction(); 396 397 // Add a favicon 398 std::vector<unsigned char> data(kBlob1, kBlob1 + sizeof(kBlob1)); 399 scoped_refptr<base::RefCountedBytes> favicon(new base::RefCountedBytes(data)); 400 401 GURL page_url("http://google.com"); 402 GURL icon_url("http://google.com/favicon.ico"); 403 base::Time time = base::Time::Now(); 404 405 chrome::FaviconID id = db.AddFavicon(icon_url, 406 chrome::FAVICON, 407 favicon, 408 time, 409 gfx::Size()); 410 EXPECT_NE(0, db.AddIconMapping(page_url, id)); 411 std::vector<IconMapping> icon_mappings; 412 EXPECT_TRUE(db.GetIconMappingsForPageURL(page_url, &icon_mappings)); 413 414 EXPECT_EQ(page_url, icon_mappings.front().page_url); 415 EXPECT_EQ(id, icon_mappings.front().icon_id); 416 EXPECT_EQ(chrome::FAVICON, icon_mappings.front().icon_type); 417 EXPECT_EQ(icon_url, icon_mappings.front().icon_url); 418 419 // Add a touch icon 420 std::vector<unsigned char> data2(kBlob2, kBlob2 + sizeof(kBlob2)); 421 scoped_refptr<base::RefCountedBytes> favicon2 = 422 new base::RefCountedBytes(data); 423 424 chrome::FaviconID id2 = db.AddFavicon(icon_url, 425 chrome::TOUCH_ICON, 426 favicon2, 427 time, 428 gfx::Size()); 429 EXPECT_NE(0, db.AddIconMapping(page_url, id2)); 430 431 icon_mappings.clear(); 432 EXPECT_TRUE(db.GetIconMappingsForPageURL(page_url, &icon_mappings)); 433 434 EXPECT_EQ(page_url, icon_mappings.front().page_url); 435 EXPECT_EQ(id2, icon_mappings.front().icon_id); 436 EXPECT_EQ(chrome::TOUCH_ICON, icon_mappings.front().icon_type); 437 EXPECT_EQ(icon_url, icon_mappings.front().icon_url); 438 439 // Add a touch precomposed icon 440 scoped_refptr<base::RefCountedBytes> favicon3 = 441 new base::RefCountedBytes(data2); 442 443 chrome::FaviconID id3 = db.AddFavicon(icon_url, 444 chrome::TOUCH_PRECOMPOSED_ICON, 445 favicon3, 446 time, 447 gfx::Size()); 448 EXPECT_NE(0, db.AddIconMapping(page_url, id3)); 449 450 icon_mappings.clear(); 451 EXPECT_TRUE(db.GetIconMappingsForPageURL(page_url, &icon_mappings)); 452 453 EXPECT_EQ(page_url, icon_mappings.front().page_url); 454 EXPECT_EQ(id3, icon_mappings.front().icon_id); 455 EXPECT_EQ(chrome::TOUCH_PRECOMPOSED_ICON, icon_mappings.front().icon_type); 456 EXPECT_EQ(icon_url, icon_mappings.front().icon_url); 457 } 458 459 // Test result of GetIconMappingsForPageURL when an icon type is passed in. 460 TEST_F(ThumbnailDatabaseTest, GetIconMappingsForPageURLWithIconType) { 461 ThumbnailDatabase db; 462 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 463 db.BeginTransaction(); 464 465 GURL url("http://google.com"); 466 std::vector<unsigned char> data(kBlob1, kBlob1 + sizeof(kBlob1)); 467 scoped_refptr<base::RefCountedBytes> favicon(new base::RefCountedBytes(data)); 468 base::Time time = base::Time::Now(); 469 470 chrome::FaviconID id1 = db.AddFavicon(url, 471 chrome::FAVICON, 472 favicon, 473 time, 474 gfx::Size()); 475 EXPECT_NE(0, db.AddIconMapping(url, id1)); 476 477 chrome::FaviconID id2 = db.AddFavicon(url, 478 chrome::TOUCH_ICON, 479 favicon, 480 time, 481 gfx::Size()); 482 EXPECT_NE(0, db.AddIconMapping(url, id2)); 483 484 chrome::FaviconID id3 = db.AddFavicon(url, 485 chrome::TOUCH_ICON, 486 favicon, 487 time, 488 gfx::Size()); 489 EXPECT_NE(0, db.AddIconMapping(url, id3)); 490 491 // Only the mappings for favicons of type TOUCH_ICON should be returned as 492 // TOUCH_ICON is a larger icon type than FAVICON. 493 std::vector<IconMapping> icon_mappings; 494 EXPECT_TRUE(db.GetIconMappingsForPageURL( 495 url, 496 chrome::FAVICON | chrome::TOUCH_ICON | chrome::TOUCH_PRECOMPOSED_ICON, 497 &icon_mappings)); 498 499 EXPECT_EQ(2u, icon_mappings.size()); 500 if (id2 == icon_mappings[0].icon_id) { 501 EXPECT_EQ(id3, icon_mappings[1].icon_id); 502 } else { 503 EXPECT_EQ(id3, icon_mappings[0].icon_id); 504 EXPECT_EQ(id2, icon_mappings[1].icon_id); 505 } 506 507 icon_mappings.clear(); 508 EXPECT_TRUE( 509 db.GetIconMappingsForPageURL(url, chrome::TOUCH_ICON, &icon_mappings)); 510 if (id2 == icon_mappings[0].icon_id) { 511 EXPECT_EQ(id3, icon_mappings[1].icon_id); 512 } else { 513 EXPECT_EQ(id3, icon_mappings[0].icon_id); 514 EXPECT_EQ(id2, icon_mappings[1].icon_id); 515 } 516 517 icon_mappings.clear(); 518 EXPECT_TRUE( 519 db.GetIconMappingsForPageURL(url, chrome::FAVICON, &icon_mappings)); 520 EXPECT_EQ(1u, icon_mappings.size()); 521 EXPECT_EQ(id1, icon_mappings[0].icon_id); 522 } 523 524 TEST_F(ThumbnailDatabaseTest, HasMappingFor) { 525 ThumbnailDatabase db; 526 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 527 db.BeginTransaction(); 528 529 std::vector<unsigned char> data(kBlob1, kBlob1 + sizeof(kBlob1)); 530 scoped_refptr<base::RefCountedBytes> favicon(new base::RefCountedBytes(data)); 531 532 // Add a favicon which will have icon_mappings 533 base::Time time = base::Time::Now(); 534 chrome::FaviconID id1 = db.AddFavicon(GURL("http://google.com"), 535 chrome::FAVICON, 536 favicon, 537 time, 538 gfx::Size()); 539 EXPECT_NE(id1, 0); 540 541 // Add another type of favicon 542 time = base::Time::Now(); 543 chrome::FaviconID id2 = db.AddFavicon(GURL("http://www.google.com/icon"), 544 chrome::TOUCH_ICON, 545 favicon, 546 time, 547 gfx::Size()); 548 EXPECT_NE(id2, 0); 549 550 // Add 3rd favicon 551 time = base::Time::Now(); 552 chrome::FaviconID id3 = db.AddFavicon(GURL("http://www.google.com/icon"), 553 chrome::TOUCH_ICON, 554 favicon, 555 time, 556 gfx::Size()); 557 EXPECT_NE(id3, 0); 558 559 // Add 2 icon mapping 560 GURL page_url("http://www.google.com"); 561 EXPECT_TRUE(db.AddIconMapping(page_url, id1)); 562 EXPECT_TRUE(db.AddIconMapping(page_url, id2)); 563 564 EXPECT_TRUE(db.HasMappingFor(id1)); 565 EXPECT_TRUE(db.HasMappingFor(id2)); 566 EXPECT_FALSE(db.HasMappingFor(id3)); 567 568 // Remove all mappings 569 db.DeleteIconMappings(page_url); 570 EXPECT_FALSE(db.HasMappingFor(id1)); 571 EXPECT_FALSE(db.HasMappingFor(id2)); 572 EXPECT_FALSE(db.HasMappingFor(id3)); 573 } 574 575 TEST_F(ThumbnailDatabaseTest, CloneIconMappings) { 576 ThumbnailDatabase db; 577 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 578 db.BeginTransaction(); 579 580 std::vector<unsigned char> data(kBlob1, kBlob1 + sizeof(kBlob1)); 581 scoped_refptr<base::RefCountedBytes> favicon(new base::RefCountedBytes(data)); 582 583 // Add a favicon which will have icon_mappings 584 chrome::FaviconID id1 = db.AddFavicon( 585 GURL("http://google.com"), chrome::FAVICON); 586 EXPECT_NE(0, id1); 587 base::Time time = base::Time::Now(); 588 db.AddFaviconBitmap(id1, favicon, time, gfx::Size()); 589 590 // Add another type of favicon 591 chrome::FaviconID id2 = db.AddFavicon(GURL("http://www.google.com/icon"), 592 chrome::TOUCH_ICON); 593 EXPECT_NE(0, id2); 594 time = base::Time::Now(); 595 db.AddFaviconBitmap(id2, favicon, time, gfx::Size()); 596 597 // Add 3rd favicon 598 chrome::FaviconID id3 = db.AddFavicon(GURL("http://www.google.com/icon"), 599 chrome::TOUCH_ICON); 600 EXPECT_NE(0, id3); 601 time = base::Time::Now(); 602 db.AddFaviconBitmap(id3, favicon, time, gfx::Size()); 603 604 GURL page1_url("http://page1.com"); 605 EXPECT_TRUE(db.AddIconMapping(page1_url, id1)); 606 EXPECT_TRUE(db.AddIconMapping(page1_url, id2)); 607 608 GURL page2_url("http://page2.com"); 609 EXPECT_TRUE(db.AddIconMapping(page2_url, id3)); 610 611 // Test we do nothing with existing mappings. 612 std::vector<IconMapping> icon_mapping; 613 EXPECT_TRUE(db.GetIconMappingsForPageURL(page2_url, &icon_mapping)); 614 ASSERT_EQ(1U, icon_mapping.size()); 615 616 EXPECT_TRUE(db.CloneIconMappings(page1_url, page2_url)); 617 618 icon_mapping.clear(); 619 EXPECT_TRUE(db.GetIconMappingsForPageURL(page2_url, &icon_mapping)); 620 ASSERT_EQ(1U, icon_mapping.size()); 621 EXPECT_EQ(page2_url, icon_mapping[0].page_url); 622 EXPECT_EQ(id3, icon_mapping[0].icon_id); 623 624 // Test we clone if the new page has no mappings. 625 GURL page3_url("http://page3.com"); 626 EXPECT_TRUE(db.CloneIconMappings(page1_url, page3_url)); 627 628 icon_mapping.clear(); 629 EXPECT_TRUE(db.GetIconMappingsForPageURL(page3_url, &icon_mapping)); 630 631 ASSERT_EQ(2U, icon_mapping.size()); 632 if (icon_mapping[0].icon_id == id2) 633 std::swap(icon_mapping[0], icon_mapping[1]); 634 EXPECT_EQ(page3_url, icon_mapping[0].page_url); 635 EXPECT_EQ(id1, icon_mapping[0].icon_id); 636 EXPECT_EQ(page3_url, icon_mapping[1].page_url); 637 EXPECT_EQ(id2, icon_mapping[1].icon_id); 638 } 639 640 // Test loading version 3 database. 641 TEST_F(ThumbnailDatabaseTest, Version3) { 642 scoped_ptr<ThumbnailDatabase> db = LoadFromGolden("Favicons.v3.sql"); 643 ASSERT_TRUE(db.get() != NULL); 644 VerifyTablesAndColumns(&db->db_); 645 646 // Version 3 is deprecated, the data should all be gone. 647 VerifyDatabaseEmpty(&db->db_); 648 } 649 650 // Test loading version 4 database. 651 TEST_F(ThumbnailDatabaseTest, Version4) { 652 scoped_ptr<ThumbnailDatabase> db = LoadFromGolden("Favicons.v4.sql"); 653 ASSERT_TRUE(db.get() != NULL); 654 VerifyTablesAndColumns(&db->db_); 655 656 // Version 4 is deprecated, the data should all be gone. 657 VerifyDatabaseEmpty(&db->db_); 658 } 659 660 // Test loading version 5 database. 661 TEST_F(ThumbnailDatabaseTest, Version5) { 662 scoped_ptr<ThumbnailDatabase> db = LoadFromGolden("Favicons.v5.sql"); 663 ASSERT_TRUE(db.get() != NULL); 664 VerifyTablesAndColumns(&db->db_); 665 666 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl1, chrome::FAVICON, 667 kIconUrl1, gfx::Size(), sizeof(kBlob1), kBlob1)); 668 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl2, chrome::FAVICON, 669 kIconUrl2, gfx::Size(), sizeof(kBlob2), kBlob2)); 670 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl3, chrome::FAVICON, 671 kIconUrl1, gfx::Size(), sizeof(kBlob1), kBlob1)); 672 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl3, chrome::TOUCH_ICON, 673 kIconUrl3, gfx::Size(), sizeof(kBlob2), kBlob2)); 674 } 675 676 // Test loading version 6 database. 677 TEST_F(ThumbnailDatabaseTest, Version6) { 678 scoped_ptr<ThumbnailDatabase> db = LoadFromGolden("Favicons.v6.sql"); 679 ASSERT_TRUE(db.get() != NULL); 680 VerifyTablesAndColumns(&db->db_); 681 682 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl1, chrome::FAVICON, 683 kIconUrl1, kLargeSize, sizeof(kBlob1), kBlob1)); 684 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl2, chrome::FAVICON, 685 kIconUrl2, kLargeSize, sizeof(kBlob2), kBlob2)); 686 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl3, chrome::FAVICON, 687 kIconUrl1, kLargeSize, sizeof(kBlob1), kBlob1)); 688 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl3, chrome::TOUCH_ICON, 689 kIconUrl3, kLargeSize, sizeof(kBlob2), kBlob2)); 690 } 691 692 // Test loading version 7 database. 693 TEST_F(ThumbnailDatabaseTest, Version7) { 694 scoped_ptr<ThumbnailDatabase> db = LoadFromGolden("Favicons.v7.sql"); 695 ASSERT_TRUE(db.get() != NULL); 696 VerifyTablesAndColumns(&db->db_); 697 698 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl1, chrome::FAVICON, 699 kIconUrl1, kLargeSize, sizeof(kBlob1), kBlob1)); 700 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl2, chrome::FAVICON, 701 kIconUrl2, kLargeSize, sizeof(kBlob2), kBlob2)); 702 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl3, chrome::FAVICON, 703 kIconUrl1, kLargeSize, sizeof(kBlob1), kBlob1)); 704 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl3, chrome::TOUCH_ICON, 705 kIconUrl3, kLargeSize, sizeof(kBlob2), kBlob2)); 706 } 707 708 TEST_F(ThumbnailDatabaseTest, Recovery) { 709 // This code tests the recovery module in concert with Chromium's 710 // custom recover virtual table. Under USE_SYSTEM_SQLITE, this is 711 // not available. This is detected dynamically because corrupt 712 // databases still need to be handled, perhaps by Raze(), and the 713 // recovery module is an obvious layer to abstract that to. 714 // TODO(shess): Handle that case for real! 715 if (!sql::Recovery::FullRecoverySupported()) 716 return; 717 718 // Create an example database. 719 { 720 EXPECT_TRUE(CreateDatabaseFromSQL(file_name_, "Favicons.v7.sql")); 721 722 sql::Connection raw_db; 723 EXPECT_TRUE(raw_db.Open(file_name_)); 724 VerifyTablesAndColumns(&raw_db); 725 } 726 727 // Test that the contents make sense after clean open. 728 { 729 ThumbnailDatabase db; 730 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 731 732 EXPECT_TRUE( 733 CheckPageHasIcon(&db, kPageUrl1, chrome::FAVICON, 734 kIconUrl1, kLargeSize, sizeof(kBlob1), kBlob1)); 735 EXPECT_TRUE( 736 CheckPageHasIcon(&db, kPageUrl2, chrome::FAVICON, 737 kIconUrl2, kLargeSize, sizeof(kBlob2), kBlob2)); 738 } 739 740 // Corrupt the |icon_mapping.page_url| index by deleting an element 741 // from the backing table but not the index. 742 { 743 sql::Connection raw_db; 744 EXPECT_TRUE(raw_db.Open(file_name_)); 745 ASSERT_EQ("ok", sql::test::IntegrityCheck(&raw_db)); 746 } 747 const char kIndexName[] = "icon_mapping_page_url_idx"; 748 const char kDeleteSql[] = 749 "DELETE FROM icon_mapping WHERE page_url = 'http://yahoo.com/'"; 750 EXPECT_TRUE( 751 sql::test::CorruptTableOrIndex(file_name_, kIndexName, kDeleteSql)); 752 753 // Database should be corrupt at the SQLite level. 754 { 755 sql::Connection raw_db; 756 EXPECT_TRUE(raw_db.Open(file_name_)); 757 ASSERT_NE("ok", sql::test::IntegrityCheck(&raw_db)); 758 } 759 760 // Open the database and access the corrupt index. 761 { 762 sql::ScopedErrorIgnorer ignore_errors; 763 ignore_errors.IgnoreError(SQLITE_CORRUPT); 764 ThumbnailDatabase db; 765 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 766 767 // Data for kPageUrl2 was deleted, but the index entry remains, 768 // this will throw SQLITE_CORRUPT. The corruption handler will 769 // recover the database and poison the handle, so the outer call 770 // fails. 771 EXPECT_FALSE(db.GetIconMappingsForPageURL(kPageUrl2, NULL)); 772 773 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); 774 } 775 776 // Check that the database is recovered at the SQLite level. 777 { 778 sql::Connection raw_db; 779 EXPECT_TRUE(raw_db.Open(file_name_)); 780 ASSERT_EQ("ok", sql::test::IntegrityCheck(&raw_db)); 781 782 // Check that the expected tables exist. 783 VerifyTablesAndColumns(&raw_db); 784 } 785 786 // Database should also be recovered at higher levels. 787 { 788 ThumbnailDatabase db; 789 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 790 791 // Now this fails because there is no mapping. 792 EXPECT_FALSE(db.GetIconMappingsForPageURL(kPageUrl2, NULL)); 793 794 // Other data was retained by recovery. 795 EXPECT_TRUE( 796 CheckPageHasIcon(&db, kPageUrl1, chrome::FAVICON, 797 kIconUrl1, kLargeSize, sizeof(kBlob1), kBlob1)); 798 } 799 800 // Corrupt the database again by adjusting the header. 801 EXPECT_TRUE(sql::test::CorruptSizeInHeader(file_name_)); 802 803 // Database is unusable at the SQLite level. 804 { 805 sql::ScopedErrorIgnorer ignore_errors; 806 ignore_errors.IgnoreError(SQLITE_CORRUPT); 807 sql::Connection raw_db; 808 EXPECT_TRUE(raw_db.Open(file_name_)); 809 EXPECT_FALSE(raw_db.IsSQLValid("PRAGMA integrity_check")); 810 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); 811 } 812 813 // Database should be recovered during open. 814 { 815 sql::ScopedErrorIgnorer ignore_errors; 816 ignore_errors.IgnoreError(SQLITE_CORRUPT); 817 ThumbnailDatabase db; 818 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 819 820 EXPECT_FALSE(db.GetIconMappingsForPageURL(kPageUrl2, NULL)); 821 EXPECT_TRUE( 822 CheckPageHasIcon(&db, kPageUrl1, chrome::FAVICON, 823 kIconUrl1, kLargeSize, sizeof(kBlob1), kBlob1)); 824 825 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); 826 } 827 } 828 829 TEST_F(ThumbnailDatabaseTest, Recovery6) { 830 // TODO(shess): See comment at top of Recovery test. 831 if (!sql::Recovery::FullRecoverySupported()) 832 return; 833 834 // Create an example database without loading into ThumbnailDatabase 835 // (which would upgrade it). 836 EXPECT_TRUE(CreateDatabaseFromSQL(file_name_, "Favicons.v6.sql")); 837 838 // Corrupt the database again by adjusting the header. This form of 839 // corruption will cause immediate failures during Open(), before 840 // the migration code runs, so the version-6 recovery will occur. 841 EXPECT_TRUE(sql::test::CorruptSizeInHeader(file_name_)); 842 843 // Database is unusable at the SQLite level. 844 { 845 sql::ScopedErrorIgnorer ignore_errors; 846 ignore_errors.IgnoreError(SQLITE_CORRUPT); 847 sql::Connection raw_db; 848 EXPECT_TRUE(raw_db.Open(file_name_)); 849 EXPECT_FALSE(raw_db.IsSQLValid("PRAGMA integrity_check")); 850 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); 851 } 852 853 // Database should be recovered during open. 854 { 855 sql::ScopedErrorIgnorer ignore_errors; 856 ignore_errors.IgnoreError(SQLITE_CORRUPT); 857 ThumbnailDatabase db; 858 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 859 860 // Test that some data is present, copied from 861 // ThumbnailDatabaseTest.Version6 . 862 EXPECT_TRUE( 863 CheckPageHasIcon(&db, kPageUrl3, chrome::FAVICON, 864 kIconUrl1, kLargeSize, sizeof(kBlob1), kBlob1)); 865 EXPECT_TRUE( 866 CheckPageHasIcon(&db, kPageUrl3, chrome::TOUCH_ICON, 867 kIconUrl3, kLargeSize, sizeof(kBlob2), kBlob2)); 868 869 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); 870 } 871 872 // Check that the database is recovered at a SQLite level, and that 873 // the current schema is in place. 874 { 875 sql::Connection raw_db; 876 EXPECT_TRUE(raw_db.Open(file_name_)); 877 ASSERT_EQ("ok", sql::test::IntegrityCheck(&raw_db)); 878 879 // Check that the expected tables exist. 880 VerifyTablesAndColumns(&raw_db); 881 } 882 } 883 884 TEST_F(ThumbnailDatabaseTest, Recovery5) { 885 // Create an example database without loading into ThumbnailDatabase 886 // (which would upgrade it). 887 EXPECT_TRUE(CreateDatabaseFromSQL(file_name_, "Favicons.v5.sql")); 888 889 // Corrupt the database again by adjusting the header. This form of 890 // corruption will cause immediate failures during Open(), before 891 // the migration code runs, so the version-5 recovery will occur. 892 EXPECT_TRUE(sql::test::CorruptSizeInHeader(file_name_)); 893 894 // Database is unusable at the SQLite level. 895 { 896 sql::ScopedErrorIgnorer ignore_errors; 897 ignore_errors.IgnoreError(SQLITE_CORRUPT); 898 sql::Connection raw_db; 899 EXPECT_TRUE(raw_db.Open(file_name_)); 900 EXPECT_FALSE(raw_db.IsSQLValid("PRAGMA integrity_check")); 901 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); 902 } 903 904 // Database should be recovered during open. 905 { 906 sql::ScopedErrorIgnorer ignore_errors; 907 ignore_errors.IgnoreError(SQLITE_CORRUPT); 908 ThumbnailDatabase db; 909 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 910 911 // Test that some data is present, copied from 912 // ThumbnailDatabaseTest.Version5 . 913 EXPECT_TRUE( 914 CheckPageHasIcon(&db, kPageUrl3, chrome::FAVICON, 915 kIconUrl1, gfx::Size(), sizeof(kBlob1), kBlob1)); 916 EXPECT_TRUE( 917 CheckPageHasIcon(&db, kPageUrl3, chrome::TOUCH_ICON, 918 kIconUrl3, gfx::Size(), sizeof(kBlob2), kBlob2)); 919 920 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); 921 } 922 923 // Check that the database is recovered at a SQLite level, and that 924 // the current schema is in place. 925 { 926 sql::Connection raw_db; 927 EXPECT_TRUE(raw_db.Open(file_name_)); 928 ASSERT_EQ("ok", sql::test::IntegrityCheck(&raw_db)); 929 930 // Check that the expected tables exist. 931 VerifyTablesAndColumns(&raw_db); 932 } 933 } 934 935 // Test that various broken schema found in the wild can be opened 936 // successfully, and result in the correct schema. 937 TEST_F(ThumbnailDatabaseTest, WildSchema) { 938 base::FilePath sql_path; 939 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &sql_path)); 940 sql_path = sql_path.AppendASCII("History").AppendASCII("thumbnail_wild"); 941 942 base::FileEnumerator fe( 943 sql_path, false, base::FileEnumerator::FILES, FILE_PATH_LITERAL("*.sql")); 944 for (base::FilePath name = fe.Next(); !name.empty(); name = fe.Next()) { 945 SCOPED_TRACE(name.BaseName().AsUTF8Unsafe()); 946 // Generate a database path based on the golden's basename. 947 base::FilePath db_base_name = 948 name.BaseName().ReplaceExtension(FILE_PATH_LITERAL("db")); 949 base::FilePath db_path = file_name_.DirName().Append(db_base_name); 950 ASSERT_TRUE(sql::test::CreateDatabaseFromSQL(db_path, name)); 951 952 // All schema flaws should be cleaned up by Init(). 953 // TODO(shess): Differentiate between databases which need Raze() 954 // and those which can be salvaged. 955 ThumbnailDatabase db; 956 ASSERT_EQ(sql::INIT_OK, db.Init(db_path)); 957 958 // Verify that the resulting schema is correct, whether it 959 // involved razing the file or fixing things in place. 960 VerifyTablesAndColumns(&db.db_); 961 } 962 } 963 964 } // namespace history 965