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 favicon_base::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(NULL)); 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(NULL); 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 favicon_base::FaviconID id = 206 db.AddFavicon(url, favicon_base::TOUCH_ICON, favicon, time, gfx::Size()); 207 EXPECT_NE(0, id); 208 209 EXPECT_NE(0, db.AddIconMapping(url, id)); 210 std::vector<IconMapping> icon_mappings; 211 EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mappings)); 212 EXPECT_EQ(1u, icon_mappings.size()); 213 EXPECT_EQ(url, icon_mappings.front().page_url); 214 EXPECT_EQ(id, icon_mappings.front().icon_id); 215 } 216 217 TEST_F(ThumbnailDatabaseTest, UpdateIconMapping) { 218 ThumbnailDatabase db(NULL); 219 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 220 db.BeginTransaction(); 221 222 GURL url("http://google.com"); 223 favicon_base::FaviconID id = db.AddFavicon(url, favicon_base::TOUCH_ICON); 224 225 EXPECT_LT(0, db.AddIconMapping(url, id)); 226 std::vector<IconMapping> icon_mapping; 227 EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mapping)); 228 ASSERT_EQ(1u, icon_mapping.size()); 229 EXPECT_EQ(url, icon_mapping.front().page_url); 230 EXPECT_EQ(id, icon_mapping.front().icon_id); 231 232 GURL url1("http://www.google.com/"); 233 favicon_base::FaviconID new_id = 234 db.AddFavicon(url1, favicon_base::TOUCH_ICON); 235 EXPECT_TRUE(db.UpdateIconMapping(icon_mapping.front().mapping_id, new_id)); 236 237 icon_mapping.clear(); 238 EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mapping)); 239 ASSERT_EQ(1u, icon_mapping.size()); 240 EXPECT_EQ(url, icon_mapping.front().page_url); 241 EXPECT_EQ(new_id, icon_mapping.front().icon_id); 242 EXPECT_NE(id, icon_mapping.front().icon_id); 243 } 244 245 TEST_F(ThumbnailDatabaseTest, DeleteIconMappings) { 246 ThumbnailDatabase db(NULL); 247 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 248 db.BeginTransaction(); 249 250 std::vector<unsigned char> data(kBlob1, kBlob1 + sizeof(kBlob1)); 251 scoped_refptr<base::RefCountedBytes> favicon(new base::RefCountedBytes(data)); 252 253 GURL url("http://google.com"); 254 favicon_base::FaviconID id = db.AddFavicon(url, favicon_base::TOUCH_ICON); 255 base::Time time = base::Time::Now(); 256 db.AddFaviconBitmap(id, favicon, time, gfx::Size()); 257 EXPECT_LT(0, db.AddIconMapping(url, id)); 258 259 favicon_base::FaviconID id2 = db.AddFavicon(url, favicon_base::FAVICON); 260 EXPECT_LT(0, db.AddIconMapping(url, id2)); 261 ASSERT_NE(id, id2); 262 263 std::vector<IconMapping> icon_mapping; 264 EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mapping)); 265 ASSERT_EQ(2u, icon_mapping.size()); 266 EXPECT_EQ(icon_mapping.front().icon_type, favicon_base::TOUCH_ICON); 267 EXPECT_TRUE(db.GetIconMappingsForPageURL(url, favicon_base::FAVICON, NULL)); 268 269 db.DeleteIconMappings(url); 270 271 EXPECT_FALSE(db.GetIconMappingsForPageURL(url, NULL)); 272 EXPECT_FALSE(db.GetIconMappingsForPageURL(url, favicon_base::FAVICON, NULL)); 273 } 274 275 TEST_F(ThumbnailDatabaseTest, GetIconMappingsForPageURL) { 276 ThumbnailDatabase db(NULL); 277 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 278 db.BeginTransaction(); 279 280 std::vector<unsigned char> data(kBlob1, kBlob1 + sizeof(kBlob1)); 281 scoped_refptr<base::RefCountedBytes> favicon(new base::RefCountedBytes(data)); 282 283 GURL url("http://google.com"); 284 285 favicon_base::FaviconID id1 = db.AddFavicon(url, favicon_base::TOUCH_ICON); 286 base::Time time = base::Time::Now(); 287 db.AddFaviconBitmap(id1, favicon, time, kSmallSize); 288 db.AddFaviconBitmap(id1, favicon, time, kLargeSize); 289 EXPECT_LT(0, db.AddIconMapping(url, id1)); 290 291 favicon_base::FaviconID id2 = db.AddFavicon(url, favicon_base::FAVICON); 292 EXPECT_NE(id1, id2); 293 db.AddFaviconBitmap(id2, favicon, time, kSmallSize); 294 EXPECT_LT(0, db.AddIconMapping(url, id2)); 295 296 std::vector<IconMapping> icon_mappings; 297 EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mappings)); 298 ASSERT_EQ(2u, icon_mappings.size()); 299 EXPECT_EQ(id1, icon_mappings[0].icon_id); 300 EXPECT_EQ(id2, icon_mappings[1].icon_id); 301 } 302 303 TEST_F(ThumbnailDatabaseTest, RetainDataForPageUrls) { 304 ThumbnailDatabase db(NULL); 305 306 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 307 308 db.BeginTransaction(); 309 310 // Build a database mapping kPageUrl1 -> kIconUrl1, kPageUrl2 -> 311 // kIconUrl2, kPageUrl3 -> kIconUrl1, and kPageUrl5 -> kIconUrl5. 312 // Then retain kPageUrl1, kPageUrl3, and kPageUrl5. kPageUrl2 313 // should go away, but the others should be retained correctly. 314 315 // TODO(shess): This would probably make sense as a golden file. 316 317 scoped_refptr<base::RefCountedStaticMemory> favicon1( 318 new base::RefCountedStaticMemory(kBlob1, sizeof(kBlob1))); 319 scoped_refptr<base::RefCountedStaticMemory> favicon2( 320 new base::RefCountedStaticMemory(kBlob2, sizeof(kBlob2))); 321 322 favicon_base::FaviconID kept_id1 = 323 db.AddFavicon(kIconUrl1, favicon_base::FAVICON); 324 db.AddFaviconBitmap(kept_id1, favicon1, base::Time::Now(), kLargeSize); 325 db.AddIconMapping(kPageUrl1, kept_id1); 326 db.AddIconMapping(kPageUrl3, kept_id1); 327 328 favicon_base::FaviconID unkept_id = 329 db.AddFavicon(kIconUrl2, favicon_base::FAVICON); 330 db.AddFaviconBitmap(unkept_id, favicon1, base::Time::Now(), kLargeSize); 331 db.AddIconMapping(kPageUrl2, unkept_id); 332 333 favicon_base::FaviconID kept_id2 = 334 db.AddFavicon(kIconUrl5, favicon_base::FAVICON); 335 db.AddFaviconBitmap(kept_id2, favicon2, base::Time::Now(), kLargeSize); 336 db.AddIconMapping(kPageUrl5, kept_id2); 337 338 // RetainDataForPageUrls() uses schema manipulations for efficiency. 339 // Grab a copy of the schema to make sure the final schema matches. 340 const std::string original_schema = db.db_.GetSchema(); 341 342 std::vector<GURL> pages_to_keep; 343 pages_to_keep.push_back(kPageUrl1); 344 pages_to_keep.push_back(kPageUrl3); 345 pages_to_keep.push_back(kPageUrl5); 346 EXPECT_TRUE(db.RetainDataForPageUrls(pages_to_keep)); 347 348 // Mappings from the retained urls should be left. 349 EXPECT_TRUE(CheckPageHasIcon(&db, 350 kPageUrl1, 351 favicon_base::FAVICON, 352 kIconUrl1, 353 kLargeSize, 354 sizeof(kBlob1), 355 kBlob1)); 356 EXPECT_TRUE(CheckPageHasIcon(&db, 357 kPageUrl3, 358 favicon_base::FAVICON, 359 kIconUrl1, 360 kLargeSize, 361 sizeof(kBlob1), 362 kBlob1)); 363 EXPECT_TRUE(CheckPageHasIcon(&db, 364 kPageUrl5, 365 favicon_base::FAVICON, 366 kIconUrl5, 367 kLargeSize, 368 sizeof(kBlob2), 369 kBlob2)); 370 371 // The one not retained should be missing. 372 EXPECT_FALSE(db.GetFaviconIDForFaviconURL(kPageUrl2, false, NULL)); 373 374 // Schema should be the same. 375 EXPECT_EQ(original_schema, db.db_.GetSchema()); 376 } 377 378 // Tests that deleting a favicon deletes the favicon row and favicon bitmap 379 // rows from the database. 380 TEST_F(ThumbnailDatabaseTest, DeleteFavicon) { 381 ThumbnailDatabase db(NULL); 382 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 383 db.BeginTransaction(); 384 385 std::vector<unsigned char> data1(kBlob1, kBlob1 + sizeof(kBlob1)); 386 scoped_refptr<base::RefCountedBytes> favicon1( 387 new base::RefCountedBytes(data1)); 388 std::vector<unsigned char> data2(kBlob2, kBlob2 + sizeof(kBlob2)); 389 scoped_refptr<base::RefCountedBytes> favicon2( 390 new base::RefCountedBytes(data2)); 391 392 GURL url("http://google.com"); 393 favicon_base::FaviconID id = db.AddFavicon(url, favicon_base::FAVICON); 394 base::Time last_updated = base::Time::Now(); 395 db.AddFaviconBitmap(id, favicon1, last_updated, kSmallSize); 396 db.AddFaviconBitmap(id, favicon2, last_updated, kLargeSize); 397 398 EXPECT_TRUE(db.GetFaviconBitmaps(id, NULL)); 399 400 EXPECT_TRUE(db.DeleteFavicon(id)); 401 EXPECT_FALSE(db.GetFaviconBitmaps(id, NULL)); 402 } 403 404 TEST_F(ThumbnailDatabaseTest, GetIconMappingsForPageURLForReturnOrder) { 405 ThumbnailDatabase db(NULL); 406 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 407 db.BeginTransaction(); 408 409 // Add a favicon 410 std::vector<unsigned char> data(kBlob1, kBlob1 + sizeof(kBlob1)); 411 scoped_refptr<base::RefCountedBytes> favicon(new base::RefCountedBytes(data)); 412 413 GURL page_url("http://google.com"); 414 GURL icon_url("http://google.com/favicon.ico"); 415 base::Time time = base::Time::Now(); 416 417 favicon_base::FaviconID id = db.AddFavicon( 418 icon_url, favicon_base::FAVICON, favicon, time, gfx::Size()); 419 EXPECT_NE(0, db.AddIconMapping(page_url, id)); 420 std::vector<IconMapping> icon_mappings; 421 EXPECT_TRUE(db.GetIconMappingsForPageURL(page_url, &icon_mappings)); 422 423 EXPECT_EQ(page_url, icon_mappings.front().page_url); 424 EXPECT_EQ(id, icon_mappings.front().icon_id); 425 EXPECT_EQ(favicon_base::FAVICON, icon_mappings.front().icon_type); 426 EXPECT_EQ(icon_url, icon_mappings.front().icon_url); 427 428 // Add a touch icon 429 std::vector<unsigned char> data2(kBlob2, kBlob2 + sizeof(kBlob2)); 430 scoped_refptr<base::RefCountedBytes> favicon2 = 431 new base::RefCountedBytes(data); 432 433 favicon_base::FaviconID id2 = db.AddFavicon( 434 icon_url, favicon_base::TOUCH_ICON, favicon2, time, gfx::Size()); 435 EXPECT_NE(0, db.AddIconMapping(page_url, id2)); 436 437 icon_mappings.clear(); 438 EXPECT_TRUE(db.GetIconMappingsForPageURL(page_url, &icon_mappings)); 439 440 EXPECT_EQ(page_url, icon_mappings.front().page_url); 441 EXPECT_EQ(id2, icon_mappings.front().icon_id); 442 EXPECT_EQ(favicon_base::TOUCH_ICON, icon_mappings.front().icon_type); 443 EXPECT_EQ(icon_url, icon_mappings.front().icon_url); 444 445 // Add a touch precomposed icon 446 scoped_refptr<base::RefCountedBytes> favicon3 = 447 new base::RefCountedBytes(data2); 448 449 favicon_base::FaviconID id3 = 450 db.AddFavicon(icon_url, 451 favicon_base::TOUCH_PRECOMPOSED_ICON, 452 favicon3, 453 time, 454 gfx::Size()); 455 EXPECT_NE(0, db.AddIconMapping(page_url, id3)); 456 457 icon_mappings.clear(); 458 EXPECT_TRUE(db.GetIconMappingsForPageURL(page_url, &icon_mappings)); 459 460 EXPECT_EQ(page_url, icon_mappings.front().page_url); 461 EXPECT_EQ(id3, icon_mappings.front().icon_id); 462 EXPECT_EQ(favicon_base::TOUCH_PRECOMPOSED_ICON, 463 icon_mappings.front().icon_type); 464 EXPECT_EQ(icon_url, icon_mappings.front().icon_url); 465 } 466 467 // Test result of GetIconMappingsForPageURL when an icon type is passed in. 468 TEST_F(ThumbnailDatabaseTest, GetIconMappingsForPageURLWithIconType) { 469 ThumbnailDatabase db(NULL); 470 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 471 db.BeginTransaction(); 472 473 GURL url("http://google.com"); 474 std::vector<unsigned char> data(kBlob1, kBlob1 + sizeof(kBlob1)); 475 scoped_refptr<base::RefCountedBytes> favicon(new base::RefCountedBytes(data)); 476 base::Time time = base::Time::Now(); 477 478 favicon_base::FaviconID id1 = 479 db.AddFavicon(url, favicon_base::FAVICON, favicon, time, gfx::Size()); 480 EXPECT_NE(0, db.AddIconMapping(url, id1)); 481 482 favicon_base::FaviconID id2 = 483 db.AddFavicon(url, favicon_base::TOUCH_ICON, favicon, time, gfx::Size()); 484 EXPECT_NE(0, db.AddIconMapping(url, id2)); 485 486 favicon_base::FaviconID id3 = 487 db.AddFavicon(url, favicon_base::TOUCH_ICON, favicon, time, gfx::Size()); 488 EXPECT_NE(0, db.AddIconMapping(url, id3)); 489 490 // Only the mappings for favicons of type TOUCH_ICON should be returned as 491 // TOUCH_ICON is a larger icon type than FAVICON. 492 std::vector<IconMapping> icon_mappings; 493 EXPECT_TRUE(db.GetIconMappingsForPageURL( 494 url, 495 favicon_base::FAVICON | favicon_base::TOUCH_ICON | 496 favicon_base::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(db.GetIconMappingsForPageURL( 509 url, favicon_base::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, favicon_base::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(NULL); 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 favicon_base::FaviconID id1 = db.AddFavicon(GURL("http://google.com"), 535 favicon_base::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 favicon_base::FaviconID id2 = 544 db.AddFavicon(GURL("http://www.google.com/icon"), 545 favicon_base::TOUCH_ICON, 546 favicon, 547 time, 548 gfx::Size()); 549 EXPECT_NE(id2, 0); 550 551 // Add 3rd favicon 552 time = base::Time::Now(); 553 favicon_base::FaviconID id3 = 554 db.AddFavicon(GURL("http://www.google.com/icon"), 555 favicon_base::TOUCH_ICON, 556 favicon, 557 time, 558 gfx::Size()); 559 EXPECT_NE(id3, 0); 560 561 // Add 2 icon mapping 562 GURL page_url("http://www.google.com"); 563 EXPECT_TRUE(db.AddIconMapping(page_url, id1)); 564 EXPECT_TRUE(db.AddIconMapping(page_url, id2)); 565 566 EXPECT_TRUE(db.HasMappingFor(id1)); 567 EXPECT_TRUE(db.HasMappingFor(id2)); 568 EXPECT_FALSE(db.HasMappingFor(id3)); 569 570 // Remove all mappings 571 db.DeleteIconMappings(page_url); 572 EXPECT_FALSE(db.HasMappingFor(id1)); 573 EXPECT_FALSE(db.HasMappingFor(id2)); 574 EXPECT_FALSE(db.HasMappingFor(id3)); 575 } 576 577 TEST_F(ThumbnailDatabaseTest, CloneIconMappings) { 578 ThumbnailDatabase db(NULL); 579 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 580 db.BeginTransaction(); 581 582 std::vector<unsigned char> data(kBlob1, kBlob1 + sizeof(kBlob1)); 583 scoped_refptr<base::RefCountedBytes> favicon(new base::RefCountedBytes(data)); 584 585 // Add a favicon which will have icon_mappings 586 favicon_base::FaviconID id1 = 587 db.AddFavicon(GURL("http://google.com"), favicon_base::FAVICON); 588 EXPECT_NE(0, id1); 589 base::Time time = base::Time::Now(); 590 db.AddFaviconBitmap(id1, favicon, time, gfx::Size()); 591 592 // Add another type of favicon 593 favicon_base::FaviconID id2 = db.AddFavicon( 594 GURL("http://www.google.com/icon"), favicon_base::TOUCH_ICON); 595 EXPECT_NE(0, id2); 596 time = base::Time::Now(); 597 db.AddFaviconBitmap(id2, favicon, time, gfx::Size()); 598 599 // Add 3rd favicon 600 favicon_base::FaviconID id3 = db.AddFavicon( 601 GURL("http://www.google.com/icon"), favicon_base::TOUCH_ICON); 602 EXPECT_NE(0, id3); 603 time = base::Time::Now(); 604 db.AddFaviconBitmap(id3, favicon, time, gfx::Size()); 605 606 GURL page1_url("http://page1.com"); 607 EXPECT_TRUE(db.AddIconMapping(page1_url, id1)); 608 EXPECT_TRUE(db.AddIconMapping(page1_url, id2)); 609 610 GURL page2_url("http://page2.com"); 611 EXPECT_TRUE(db.AddIconMapping(page2_url, id3)); 612 613 // Test we do nothing with existing mappings. 614 std::vector<IconMapping> icon_mapping; 615 EXPECT_TRUE(db.GetIconMappingsForPageURL(page2_url, &icon_mapping)); 616 ASSERT_EQ(1U, icon_mapping.size()); 617 618 EXPECT_TRUE(db.CloneIconMappings(page1_url, page2_url)); 619 620 icon_mapping.clear(); 621 EXPECT_TRUE(db.GetIconMappingsForPageURL(page2_url, &icon_mapping)); 622 ASSERT_EQ(1U, icon_mapping.size()); 623 EXPECT_EQ(page2_url, icon_mapping[0].page_url); 624 EXPECT_EQ(id3, icon_mapping[0].icon_id); 625 626 // Test we clone if the new page has no mappings. 627 GURL page3_url("http://page3.com"); 628 EXPECT_TRUE(db.CloneIconMappings(page1_url, page3_url)); 629 630 icon_mapping.clear(); 631 EXPECT_TRUE(db.GetIconMappingsForPageURL(page3_url, &icon_mapping)); 632 633 ASSERT_EQ(2U, icon_mapping.size()); 634 if (icon_mapping[0].icon_id == id2) 635 std::swap(icon_mapping[0], icon_mapping[1]); 636 EXPECT_EQ(page3_url, icon_mapping[0].page_url); 637 EXPECT_EQ(id1, icon_mapping[0].icon_id); 638 EXPECT_EQ(page3_url, icon_mapping[1].page_url); 639 EXPECT_EQ(id2, icon_mapping[1].icon_id); 640 } 641 642 // Test loading version 3 database. 643 TEST_F(ThumbnailDatabaseTest, Version3) { 644 scoped_ptr<ThumbnailDatabase> db = LoadFromGolden("Favicons.v3.sql"); 645 ASSERT_TRUE(db.get() != NULL); 646 VerifyTablesAndColumns(&db->db_); 647 648 // Version 3 is deprecated, the data should all be gone. 649 VerifyDatabaseEmpty(&db->db_); 650 } 651 652 // Test loading version 4 database. 653 TEST_F(ThumbnailDatabaseTest, Version4) { 654 scoped_ptr<ThumbnailDatabase> db = LoadFromGolden("Favicons.v4.sql"); 655 ASSERT_TRUE(db.get() != NULL); 656 VerifyTablesAndColumns(&db->db_); 657 658 // Version 4 is deprecated, the data should all be gone. 659 VerifyDatabaseEmpty(&db->db_); 660 } 661 662 // Test loading version 5 database. 663 TEST_F(ThumbnailDatabaseTest, Version5) { 664 scoped_ptr<ThumbnailDatabase> db = LoadFromGolden("Favicons.v5.sql"); 665 ASSERT_TRUE(db.get() != NULL); 666 VerifyTablesAndColumns(&db->db_); 667 668 EXPECT_TRUE(CheckPageHasIcon(db.get(), 669 kPageUrl1, 670 favicon_base::FAVICON, 671 kIconUrl1, 672 gfx::Size(), 673 sizeof(kBlob1), 674 kBlob1)); 675 EXPECT_TRUE(CheckPageHasIcon(db.get(), 676 kPageUrl2, 677 favicon_base::FAVICON, 678 kIconUrl2, 679 gfx::Size(), 680 sizeof(kBlob2), 681 kBlob2)); 682 EXPECT_TRUE(CheckPageHasIcon(db.get(), 683 kPageUrl3, 684 favicon_base::FAVICON, 685 kIconUrl1, 686 gfx::Size(), 687 sizeof(kBlob1), 688 kBlob1)); 689 EXPECT_TRUE(CheckPageHasIcon(db.get(), 690 kPageUrl3, 691 favicon_base::TOUCH_ICON, 692 kIconUrl3, 693 gfx::Size(), 694 sizeof(kBlob2), 695 kBlob2)); 696 } 697 698 // Test loading version 6 database. 699 TEST_F(ThumbnailDatabaseTest, Version6) { 700 scoped_ptr<ThumbnailDatabase> db = LoadFromGolden("Favicons.v6.sql"); 701 ASSERT_TRUE(db.get() != NULL); 702 VerifyTablesAndColumns(&db->db_); 703 704 EXPECT_TRUE(CheckPageHasIcon(db.get(), 705 kPageUrl1, 706 favicon_base::FAVICON, 707 kIconUrl1, 708 kLargeSize, 709 sizeof(kBlob1), 710 kBlob1)); 711 EXPECT_TRUE(CheckPageHasIcon(db.get(), 712 kPageUrl2, 713 favicon_base::FAVICON, 714 kIconUrl2, 715 kLargeSize, 716 sizeof(kBlob2), 717 kBlob2)); 718 EXPECT_TRUE(CheckPageHasIcon(db.get(), 719 kPageUrl3, 720 favicon_base::FAVICON, 721 kIconUrl1, 722 kLargeSize, 723 sizeof(kBlob1), 724 kBlob1)); 725 EXPECT_TRUE(CheckPageHasIcon(db.get(), 726 kPageUrl3, 727 favicon_base::TOUCH_ICON, 728 kIconUrl3, 729 kLargeSize, 730 sizeof(kBlob2), 731 kBlob2)); 732 } 733 734 // Test loading version 7 database. 735 TEST_F(ThumbnailDatabaseTest, Version7) { 736 scoped_ptr<ThumbnailDatabase> db = LoadFromGolden("Favicons.v7.sql"); 737 ASSERT_TRUE(db.get() != NULL); 738 VerifyTablesAndColumns(&db->db_); 739 740 EXPECT_TRUE(CheckPageHasIcon(db.get(), 741 kPageUrl1, 742 favicon_base::FAVICON, 743 kIconUrl1, 744 kLargeSize, 745 sizeof(kBlob1), 746 kBlob1)); 747 EXPECT_TRUE(CheckPageHasIcon(db.get(), 748 kPageUrl2, 749 favicon_base::FAVICON, 750 kIconUrl2, 751 kLargeSize, 752 sizeof(kBlob2), 753 kBlob2)); 754 EXPECT_TRUE(CheckPageHasIcon(db.get(), 755 kPageUrl3, 756 favicon_base::FAVICON, 757 kIconUrl1, 758 kLargeSize, 759 sizeof(kBlob1), 760 kBlob1)); 761 EXPECT_TRUE(CheckPageHasIcon(db.get(), 762 kPageUrl3, 763 favicon_base::TOUCH_ICON, 764 kIconUrl3, 765 kLargeSize, 766 sizeof(kBlob2), 767 kBlob2)); 768 } 769 770 TEST_F(ThumbnailDatabaseTest, Recovery) { 771 // This code tests the recovery module in concert with Chromium's 772 // custom recover virtual table. Under USE_SYSTEM_SQLITE, this is 773 // not available. This is detected dynamically because corrupt 774 // databases still need to be handled, perhaps by Raze(), and the 775 // recovery module is an obvious layer to abstract that to. 776 // TODO(shess): Handle that case for real! 777 if (!sql::Recovery::FullRecoverySupported()) 778 return; 779 780 // Create an example database. 781 { 782 EXPECT_TRUE(CreateDatabaseFromSQL(file_name_, "Favicons.v7.sql")); 783 784 sql::Connection raw_db; 785 EXPECT_TRUE(raw_db.Open(file_name_)); 786 VerifyTablesAndColumns(&raw_db); 787 } 788 789 // Test that the contents make sense after clean open. 790 { 791 ThumbnailDatabase db(NULL); 792 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 793 794 EXPECT_TRUE(CheckPageHasIcon(&db, 795 kPageUrl1, 796 favicon_base::FAVICON, 797 kIconUrl1, 798 kLargeSize, 799 sizeof(kBlob1), 800 kBlob1)); 801 EXPECT_TRUE(CheckPageHasIcon(&db, 802 kPageUrl2, 803 favicon_base::FAVICON, 804 kIconUrl2, 805 kLargeSize, 806 sizeof(kBlob2), 807 kBlob2)); 808 } 809 810 // Corrupt the |icon_mapping.page_url| index by deleting an element 811 // from the backing table but not the index. 812 { 813 sql::Connection raw_db; 814 EXPECT_TRUE(raw_db.Open(file_name_)); 815 ASSERT_EQ("ok", sql::test::IntegrityCheck(&raw_db)); 816 } 817 const char kIndexName[] = "icon_mapping_page_url_idx"; 818 const char kDeleteSql[] = 819 "DELETE FROM icon_mapping WHERE page_url = 'http://yahoo.com/'"; 820 EXPECT_TRUE( 821 sql::test::CorruptTableOrIndex(file_name_, kIndexName, kDeleteSql)); 822 823 // Database should be corrupt at the SQLite level. 824 { 825 sql::Connection raw_db; 826 EXPECT_TRUE(raw_db.Open(file_name_)); 827 ASSERT_NE("ok", sql::test::IntegrityCheck(&raw_db)); 828 } 829 830 // Open the database and access the corrupt index. 831 { 832 sql::ScopedErrorIgnorer ignore_errors; 833 ignore_errors.IgnoreError(SQLITE_CORRUPT); 834 ThumbnailDatabase db(NULL); 835 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 836 837 // Data for kPageUrl2 was deleted, but the index entry remains, 838 // this will throw SQLITE_CORRUPT. The corruption handler will 839 // recover the database and poison the handle, so the outer call 840 // fails. 841 EXPECT_FALSE(db.GetIconMappingsForPageURL(kPageUrl2, NULL)); 842 843 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); 844 } 845 846 // Check that the database is recovered at the SQLite level. 847 { 848 sql::Connection raw_db; 849 EXPECT_TRUE(raw_db.Open(file_name_)); 850 ASSERT_EQ("ok", sql::test::IntegrityCheck(&raw_db)); 851 852 // Check that the expected tables exist. 853 VerifyTablesAndColumns(&raw_db); 854 } 855 856 // Database should also be recovered at higher levels. 857 { 858 ThumbnailDatabase db(NULL); 859 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 860 861 // Now this fails because there is no mapping. 862 EXPECT_FALSE(db.GetIconMappingsForPageURL(kPageUrl2, NULL)); 863 864 // Other data was retained by recovery. 865 EXPECT_TRUE(CheckPageHasIcon(&db, 866 kPageUrl1, 867 favicon_base::FAVICON, 868 kIconUrl1, 869 kLargeSize, 870 sizeof(kBlob1), 871 kBlob1)); 872 } 873 874 // Corrupt the database again by adjusting the header. 875 EXPECT_TRUE(sql::test::CorruptSizeInHeader(file_name_)); 876 877 // Database is unusable at the SQLite level. 878 { 879 sql::ScopedErrorIgnorer ignore_errors; 880 ignore_errors.IgnoreError(SQLITE_CORRUPT); 881 sql::Connection raw_db; 882 EXPECT_TRUE(raw_db.Open(file_name_)); 883 EXPECT_FALSE(raw_db.IsSQLValid("PRAGMA integrity_check")); 884 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); 885 } 886 887 // Database should be recovered during open. 888 { 889 sql::ScopedErrorIgnorer ignore_errors; 890 ignore_errors.IgnoreError(SQLITE_CORRUPT); 891 ThumbnailDatabase db(NULL); 892 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 893 894 EXPECT_FALSE(db.GetIconMappingsForPageURL(kPageUrl2, NULL)); 895 EXPECT_TRUE(CheckPageHasIcon(&db, 896 kPageUrl1, 897 favicon_base::FAVICON, 898 kIconUrl1, 899 kLargeSize, 900 sizeof(kBlob1), 901 kBlob1)); 902 903 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); 904 } 905 } 906 907 TEST_F(ThumbnailDatabaseTest, Recovery6) { 908 // TODO(shess): See comment at top of Recovery test. 909 if (!sql::Recovery::FullRecoverySupported()) 910 return; 911 912 // Create an example database without loading into ThumbnailDatabase 913 // (which would upgrade it). 914 EXPECT_TRUE(CreateDatabaseFromSQL(file_name_, "Favicons.v6.sql")); 915 916 // Corrupt the database by adjusting the header. This form of corruption will 917 // cause immediate failures during Open(), before the migration code runs, so 918 // the recovery code will run. 919 EXPECT_TRUE(sql::test::CorruptSizeInHeader(file_name_)); 920 921 // Database is unusable at the SQLite level. 922 { 923 sql::ScopedErrorIgnorer ignore_errors; 924 ignore_errors.IgnoreError(SQLITE_CORRUPT); 925 sql::Connection raw_db; 926 EXPECT_TRUE(raw_db.Open(file_name_)); 927 EXPECT_FALSE(raw_db.IsSQLValid("PRAGMA integrity_check")); 928 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); 929 } 930 931 // Database open should succeed. 932 { 933 sql::ScopedErrorIgnorer ignore_errors; 934 ignore_errors.IgnoreError(SQLITE_CORRUPT); 935 ThumbnailDatabase db(NULL); 936 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 937 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); 938 } 939 940 // The database should be usable at the SQLite level, with a current schema 941 // and no data. 942 { 943 sql::Connection raw_db; 944 EXPECT_TRUE(raw_db.Open(file_name_)); 945 ASSERT_EQ("ok", sql::test::IntegrityCheck(&raw_db)); 946 947 // Check that the expected tables exist. 948 VerifyTablesAndColumns(&raw_db); 949 950 // Version 6 recovery is deprecated, the data should all be gone. 951 VerifyDatabaseEmpty(&raw_db); 952 } 953 } 954 955 TEST_F(ThumbnailDatabaseTest, Recovery5) { 956 // TODO(shess): See comment at top of Recovery test. 957 if (!sql::Recovery::FullRecoverySupported()) 958 return; 959 960 // Create an example database without loading into ThumbnailDatabase 961 // (which would upgrade it). 962 EXPECT_TRUE(CreateDatabaseFromSQL(file_name_, "Favicons.v5.sql")); 963 964 // Corrupt the database by adjusting the header. This form of corruption will 965 // cause immediate failures during Open(), before the migration code runs, so 966 // the recovery code will run. 967 EXPECT_TRUE(sql::test::CorruptSizeInHeader(file_name_)); 968 969 // Database is unusable at the SQLite level. 970 { 971 sql::ScopedErrorIgnorer ignore_errors; 972 ignore_errors.IgnoreError(SQLITE_CORRUPT); 973 sql::Connection raw_db; 974 EXPECT_TRUE(raw_db.Open(file_name_)); 975 EXPECT_FALSE(raw_db.IsSQLValid("PRAGMA integrity_check")); 976 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); 977 } 978 979 // Database open should succeed. 980 { 981 sql::ScopedErrorIgnorer ignore_errors; 982 ignore_errors.IgnoreError(SQLITE_CORRUPT); 983 ThumbnailDatabase db(NULL); 984 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_)); 985 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); 986 } 987 988 // The database should be usable at the SQLite level, with a current schema 989 // and no data. 990 { 991 sql::Connection raw_db; 992 EXPECT_TRUE(raw_db.Open(file_name_)); 993 ASSERT_EQ("ok", sql::test::IntegrityCheck(&raw_db)); 994 995 // Check that the expected tables exist. 996 VerifyTablesAndColumns(&raw_db); 997 998 // Version 5 recovery is deprecated, the data should all be gone. 999 VerifyDatabaseEmpty(&raw_db); 1000 } 1001 } 1002 1003 // Test that various broken schema found in the wild can be opened 1004 // successfully, and result in the correct schema. 1005 TEST_F(ThumbnailDatabaseTest, WildSchema) { 1006 base::FilePath sql_path; 1007 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &sql_path)); 1008 sql_path = sql_path.AppendASCII("History").AppendASCII("thumbnail_wild"); 1009 1010 base::FileEnumerator fe( 1011 sql_path, false, base::FileEnumerator::FILES, FILE_PATH_LITERAL("*.sql")); 1012 for (base::FilePath name = fe.Next(); !name.empty(); name = fe.Next()) { 1013 SCOPED_TRACE(name.BaseName().AsUTF8Unsafe()); 1014 // Generate a database path based on the golden's basename. 1015 base::FilePath db_base_name = 1016 name.BaseName().ReplaceExtension(FILE_PATH_LITERAL("db")); 1017 base::FilePath db_path = file_name_.DirName().Append(db_base_name); 1018 ASSERT_TRUE(sql::test::CreateDatabaseFromSQL(db_path, name)); 1019 1020 // All schema flaws should be cleaned up by Init(). 1021 // TODO(shess): Differentiate between databases which need Raze() 1022 // and those which can be salvaged. 1023 ThumbnailDatabase db(NULL); 1024 ASSERT_EQ(sql::INIT_OK, db.Init(db_path)); 1025 1026 // Verify that the resulting schema is correct, whether it 1027 // involved razing the file or fixing things in place. 1028 VerifyTablesAndColumns(&db.db_); 1029 } 1030 } 1031 1032 } // namespace history 1033