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.
8 #include "base/basictypes.h"
9 #include "base/file_util.h"
10 #include "base/files/file_enumerator.h"
11 #include "base/files/file_path.h"
12 #include "base/files/scoped_temp_dir.h"
13 #include "base/memory/ref_counted_memory.h"
14 #include "base/path_service.h"
15 #include "chrome/browser/history/history_database.h"
16 #include "chrome/browser/history/thumbnail_database.h"
17 #include "chrome/common/chrome_constants.h"
18 #include "chrome/common/chrome_paths.h"
19 #include "chrome/test/base/testing_profile.h"
20 #include "sql/connection.h"
21 #include "sql/recovery.h" // For FullRecoverySupported().
22 #include "sql/statement.h"
23 #include "sql/test/scoped_error_ignorer.h"
24 #include "sql/test/test_helpers.h"
25 #include "testing/gtest/include/gtest/gtest.h"
26 #include "third_party/sqlite/sqlite3.h"
33 // Blobs for the bitmap tests. These aren't real bitmaps. Golden
34 // database files store the same blobs (see VersionN tests).
35 const unsigned char kBlob1[] =
36 "12346102356120394751634516591348710478123649165419234519234512349134";
37 const unsigned char kBlob2[] =
38 "goiwuegrqrcomizqyzkjalitbahxfjytrqvpqeroicxmnlkhlzunacxaneviawrtxcywhgef";
40 // Page and icon urls shared by tests. Present in golden database
41 // files (see VersionN tests).
42 const GURL kPageUrl1 = GURL("http://google.com/");
43 const GURL kPageUrl2 = GURL("http://yahoo.com/");
44 const GURL kPageUrl3 = GURL("http://www.google.com/");
45 const GURL kPageUrl4 = GURL("http://www.google.com/blank.html");
46 const GURL kPageUrl5 = GURL("http://www.bing.com/");
48 const GURL kIconUrl1 = GURL("http://www.google.com/favicon.ico");
49 const GURL kIconUrl2 = GURL("http://www.yahoo.com/favicon.ico");
50 const GURL kIconUrl3 = GURL("http://www.google.com/touch.ico");
51 const GURL kIconUrl5 = GURL("http://www.bing.com/favicon.ico");
53 const gfx::Size kSmallSize = gfx::Size(16, 16);
54 const gfx::Size kLargeSize = gfx::Size(32, 32);
56 // Create the test database at |db_path| from the golden file at
57 // |ascii_path| in the "History/" subdir of the test data dir.
58 WARN_UNUSED_RESULT bool CreateDatabaseFromSQL(const base::FilePath &db_path,
59 const char* ascii_path) {
60 base::FilePath sql_path;
61 if (!PathService::Get(chrome::DIR_TEST_DATA, &sql_path))
63 sql_path = sql_path.AppendASCII("History").AppendASCII(ascii_path);
64 return sql::test::CreateDatabaseFromSQL(db_path, sql_path);
67 int GetPageSize(sql::Connection* db) {
68 sql::Statement s(db->GetUniqueStatement("PRAGMA page_size"));
69 EXPECT_TRUE(s.Step());
70 return s.ColumnInt(0);
73 // Get |name|'s root page number in the database.
74 int GetRootPage(sql::Connection* db, const char* name) {
75 const char kPageSql[] = "SELECT rootpage FROM sqlite_master WHERE name = ?";
76 sql::Statement s(db->GetUniqueStatement(kPageSql));
77 s.BindString(0, name);
78 EXPECT_TRUE(s.Step());
79 return s.ColumnInt(0);
82 // Helper to read a SQLite page into a buffer. |page_no| is 1-based
84 bool ReadPage(const base::FilePath& path, size_t page_no,
85 char* buf, size_t page_size) {
86 file_util::ScopedFILE file(file_util::OpenFile(path, "rb"));
89 if (0 != fseek(file.get(), (page_no - 1) * page_size, SEEK_SET))
91 if (1u != fread(buf, page_size, 1, file.get()))
96 // Helper to write a SQLite page into a buffer. |page_no| is 1-based
98 bool WritePage(const base::FilePath& path, size_t page_no,
99 const char* buf, size_t page_size) {
100 file_util::ScopedFILE file(file_util::OpenFile(path, "rb+"));
103 if (0 != fseek(file.get(), (page_no - 1) * page_size, SEEK_SET))
105 if (1u != fwrite(buf, page_size, 1, file.get()))
110 // Verify that the up-to-date database has the expected tables and
111 // columns. Functional tests only check whether the things which
112 // should be there are, but do not check if extraneous items are
113 // present. Any extraneous items have the potential to interact
114 // negatively with future schema changes.
115 void VerifyTablesAndColumns(sql::Connection* db) {
116 // [meta], [favicons], [favicon_bitmaps], and [icon_mapping].
117 EXPECT_EQ(4u, sql::test::CountSQLTables(db));
119 // Implicit index on [meta], index on [favicons], index on
120 // [favicon_bitmaps], two indices on [icon_mapping].
121 EXPECT_EQ(5u, sql::test::CountSQLIndices(db));
123 // [key] and [value].
124 EXPECT_EQ(2u, sql::test::CountTableColumns(db, "meta"));
126 // [id], [url], and [icon_type].
127 EXPECT_EQ(3u, sql::test::CountTableColumns(db, "favicons"));
129 // [id], [icon_id], [last_updated], [image_data], [width], and [height].
130 EXPECT_EQ(6u, sql::test::CountTableColumns(db, "favicon_bitmaps"));
132 // [id], [page_url], and [icon_id].
133 EXPECT_EQ(3u, sql::test::CountTableColumns(db, "icon_mapping"));
136 void VerifyDatabaseEmpty(sql::Connection* db) {
138 EXPECT_TRUE(sql::test::CountTableRows(db, "favicons", &rows));
140 EXPECT_TRUE(sql::test::CountTableRows(db, "favicon_bitmaps", &rows));
142 EXPECT_TRUE(sql::test::CountTableRows(db, "icon_mapping", &rows));
146 // Helper to check that an expected mapping exists.
147 WARN_UNUSED_RESULT bool CheckPageHasIcon(
148 ThumbnailDatabase* db,
149 const GURL& page_url,
150 chrome::IconType expected_icon_type,
151 const GURL& expected_icon_url,
152 const gfx::Size& expected_icon_size,
153 size_t expected_icon_contents_size,
154 const unsigned char* expected_icon_contents) {
155 std::vector<IconMapping> icon_mappings;
156 if (!db->GetIconMappingsForPageURL(page_url, &icon_mappings)) {
157 ADD_FAILURE() << "failed GetIconMappingsForPageURL()";
161 // Scan for the expected type.
162 std::vector<IconMapping>::const_iterator iter = icon_mappings.begin();
163 for (; iter != icon_mappings.end(); ++iter) {
164 if (iter->icon_type == expected_icon_type)
167 if (iter == icon_mappings.end()) {
168 ADD_FAILURE() << "failed to find |expected_icon_type|";
172 if (expected_icon_url != iter->icon_url) {
173 EXPECT_EQ(expected_icon_url, iter->icon_url);
177 std::vector<FaviconBitmap> favicon_bitmaps;
178 if (!db->GetFaviconBitmaps(iter->icon_id, &favicon_bitmaps)) {
179 ADD_FAILURE() << "failed GetFaviconBitmaps()";
183 if (1 != favicon_bitmaps.size()) {
184 EXPECT_EQ(1u, favicon_bitmaps.size());
188 if (expected_icon_size != favicon_bitmaps[0].pixel_size) {
189 EXPECT_EQ(expected_icon_size, favicon_bitmaps[0].pixel_size);
193 if (expected_icon_contents_size != favicon_bitmaps[0].bitmap_data->size()) {
194 EXPECT_EQ(expected_icon_contents_size,
195 favicon_bitmaps[0].bitmap_data->size());
199 if (memcmp(favicon_bitmaps[0].bitmap_data->front(),
200 expected_icon_contents, expected_icon_contents_size)) {
201 ADD_FAILURE() << "failed to match |expected_icon_contents|";
209 class ThumbnailDatabaseTest : public testing::Test {
211 ThumbnailDatabaseTest() {
213 virtual ~ThumbnailDatabaseTest() {
216 // Initialize a thumbnail database instance from the SQL file at
217 // |golden_path| in the "History/" subdirectory of test data.
218 scoped_ptr<ThumbnailDatabase> LoadFromGolden(const char* golden_path) {
219 if (!CreateDatabaseFromSQL(file_name_, golden_path)) {
220 ADD_FAILURE() << "Failed loading " << golden_path;
221 return scoped_ptr<ThumbnailDatabase>();
224 scoped_ptr<ThumbnailDatabase> db(new ThumbnailDatabase());
225 EXPECT_EQ(sql::INIT_OK, db->Init(file_name_));
226 db->BeginTransaction();
232 virtual void SetUp() {
233 // Get a temporary directory for the test DB files.
234 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
236 file_name_ = temp_dir_.path().AppendASCII("TestFavicons.db");
239 base::ScopedTempDir temp_dir_;
240 base::FilePath file_name_;
243 TEST_F(ThumbnailDatabaseTest, AddIconMapping) {
244 ThumbnailDatabase db;
245 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_));
246 db.BeginTransaction();
248 std::vector<unsigned char> data(kBlob1, kBlob1 + sizeof(kBlob1));
249 scoped_refptr<base::RefCountedBytes> favicon(new base::RefCountedBytes(data));
251 GURL url("http://google.com");
252 base::Time time = base::Time::Now();
253 chrome::FaviconID id = db.AddFavicon(url,
260 EXPECT_NE(0, db.AddIconMapping(url, id));
261 std::vector<IconMapping> icon_mappings;
262 EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mappings));
263 EXPECT_EQ(1u, icon_mappings.size());
264 EXPECT_EQ(url, icon_mappings.front().page_url);
265 EXPECT_EQ(id, icon_mappings.front().icon_id);
268 TEST_F(ThumbnailDatabaseTest, UpdateIconMapping) {
269 ThumbnailDatabase db;
270 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_));
271 db.BeginTransaction();
273 GURL url("http://google.com");
274 chrome::FaviconID id =
275 db.AddFavicon(url, chrome::TOUCH_ICON);
277 EXPECT_LT(0, db.AddIconMapping(url, id));
278 std::vector<IconMapping> icon_mapping;
279 EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mapping));
280 ASSERT_EQ(1u, icon_mapping.size());
281 EXPECT_EQ(url, icon_mapping.front().page_url);
282 EXPECT_EQ(id, icon_mapping.front().icon_id);
284 GURL url1("http://www.google.com/");
285 chrome::FaviconID new_id =
286 db.AddFavicon(url1, chrome::TOUCH_ICON);
287 EXPECT_TRUE(db.UpdateIconMapping(icon_mapping.front().mapping_id, new_id));
289 icon_mapping.clear();
290 EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mapping));
291 ASSERT_EQ(1u, icon_mapping.size());
292 EXPECT_EQ(url, icon_mapping.front().page_url);
293 EXPECT_EQ(new_id, icon_mapping.front().icon_id);
294 EXPECT_NE(id, icon_mapping.front().icon_id);
297 TEST_F(ThumbnailDatabaseTest, DeleteIconMappings) {
298 ThumbnailDatabase db;
299 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_));
300 db.BeginTransaction();
302 std::vector<unsigned char> data(kBlob1, kBlob1 + sizeof(kBlob1));
303 scoped_refptr<base::RefCountedBytes> favicon(new base::RefCountedBytes(data));
305 GURL url("http://google.com");
306 chrome::FaviconID id =
307 db.AddFavicon(url, chrome::TOUCH_ICON);
308 base::Time time = base::Time::Now();
309 db.AddFaviconBitmap(id, favicon, time, gfx::Size());
310 EXPECT_LT(0, db.AddIconMapping(url, id));
312 chrome::FaviconID id2 =
313 db.AddFavicon(url, chrome::FAVICON);
314 EXPECT_LT(0, db.AddIconMapping(url, id2));
317 std::vector<IconMapping> icon_mapping;
318 EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mapping));
319 ASSERT_EQ(2u, icon_mapping.size());
320 EXPECT_EQ(icon_mapping.front().icon_type, chrome::TOUCH_ICON);
321 EXPECT_TRUE(db.GetIconMappingsForPageURL(url, chrome::FAVICON, NULL));
323 db.DeleteIconMappings(url);
325 EXPECT_FALSE(db.GetIconMappingsForPageURL(url, NULL));
326 EXPECT_FALSE(db.GetIconMappingsForPageURL(url, chrome::FAVICON, NULL));
329 TEST_F(ThumbnailDatabaseTest, GetIconMappingsForPageURL) {
330 ThumbnailDatabase db;
331 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_));
332 db.BeginTransaction();
334 std::vector<unsigned char> data(kBlob1, kBlob1 + sizeof(kBlob1));
335 scoped_refptr<base::RefCountedBytes> favicon(new base::RefCountedBytes(data));
337 GURL url("http://google.com");
339 chrome::FaviconID id1 = db.AddFavicon(url, chrome::TOUCH_ICON);
340 base::Time time = base::Time::Now();
341 db.AddFaviconBitmap(id1, favicon, time, kSmallSize);
342 db.AddFaviconBitmap(id1, favicon, time, kLargeSize);
343 EXPECT_LT(0, db.AddIconMapping(url, id1));
345 chrome::FaviconID id2 = db.AddFavicon(url, chrome::FAVICON);
347 db.AddFaviconBitmap(id2, favicon, time, kSmallSize);
348 EXPECT_LT(0, db.AddIconMapping(url, id2));
350 std::vector<IconMapping> icon_mappings;
351 EXPECT_TRUE(db.GetIconMappingsForPageURL(url, &icon_mappings));
352 ASSERT_EQ(2u, icon_mappings.size());
353 EXPECT_EQ(id1, icon_mappings[0].icon_id);
354 EXPECT_EQ(id2, icon_mappings[1].icon_id);
357 TEST_F(ThumbnailDatabaseTest, RetainDataForPageUrls) {
358 ThumbnailDatabase db;
360 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_));
362 db.BeginTransaction();
364 // Build a database mapping kPageUrl1 -> kIconUrl1, kPageUrl2 ->
365 // kIconUrl2, kPageUrl3 -> kIconUrl1, and kPageUrl5 -> kIconUrl5.
366 // Then retain kPageUrl1, kPageUrl3, and kPageUrl5. kPageUrl2
367 // should go away, but the others should be retained correctly.
369 // TODO(shess): This would probably make sense as a golden file.
371 scoped_refptr<base::RefCountedStaticMemory> favicon1(
372 new base::RefCountedStaticMemory(kBlob1, sizeof(kBlob1)));
373 scoped_refptr<base::RefCountedStaticMemory> favicon2(
374 new base::RefCountedStaticMemory(kBlob2, sizeof(kBlob2)));
376 chrome::FaviconID kept_id1 = db.AddFavicon(kIconUrl1, chrome::FAVICON);
377 db.AddFaviconBitmap(kept_id1, favicon1, base::Time::Now(), kLargeSize);
378 db.AddIconMapping(kPageUrl1, kept_id1);
379 db.AddIconMapping(kPageUrl3, kept_id1);
381 chrome::FaviconID unkept_id = db.AddFavicon(kIconUrl2, chrome::FAVICON);
382 db.AddFaviconBitmap(unkept_id, favicon1, base::Time::Now(), kLargeSize);
383 db.AddIconMapping(kPageUrl2, unkept_id);
385 chrome::FaviconID kept_id2 = db.AddFavicon(kIconUrl5, chrome::FAVICON);
386 db.AddFaviconBitmap(kept_id2, favicon2, base::Time::Now(), kLargeSize);
387 db.AddIconMapping(kPageUrl5, kept_id2);
389 // RetainDataForPageUrls() uses schema manipulations for efficiency.
390 // Grab a copy of the schema to make sure the final schema matches.
391 const std::string original_schema = db.db_.GetSchema();
393 std::vector<GURL> pages_to_keep;
394 pages_to_keep.push_back(kPageUrl1);
395 pages_to_keep.push_back(kPageUrl3);
396 pages_to_keep.push_back(kPageUrl5);
397 EXPECT_TRUE(db.RetainDataForPageUrls(pages_to_keep));
399 // Mappings from the retained urls should be left.
400 EXPECT_TRUE(CheckPageHasIcon(&db, kPageUrl1, chrome::FAVICON,
401 kIconUrl1, kLargeSize, sizeof(kBlob1), kBlob1));
402 EXPECT_TRUE(CheckPageHasIcon(&db, kPageUrl3, chrome::FAVICON,
403 kIconUrl1, kLargeSize, sizeof(kBlob1), kBlob1));
404 EXPECT_TRUE(CheckPageHasIcon(&db, kPageUrl5, chrome::FAVICON,
405 kIconUrl5, kLargeSize, sizeof(kBlob2), kBlob2));
407 // The one not retained should be missing.
408 EXPECT_FALSE(db.GetFaviconIDForFaviconURL(kPageUrl2, false, NULL));
410 // Schema should be the same.
411 EXPECT_EQ(original_schema, db.db_.GetSchema());
414 // Tests that deleting a favicon deletes the favicon row and favicon bitmap
415 // rows from the database.
416 TEST_F(ThumbnailDatabaseTest, DeleteFavicon) {
417 ThumbnailDatabase db;
418 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_));
419 db.BeginTransaction();
421 std::vector<unsigned char> data1(kBlob1, kBlob1 + sizeof(kBlob1));
422 scoped_refptr<base::RefCountedBytes> favicon1(
423 new base::RefCountedBytes(data1));
424 std::vector<unsigned char> data2(kBlob2, kBlob2 + sizeof(kBlob2));
425 scoped_refptr<base::RefCountedBytes> favicon2(
426 new base::RefCountedBytes(data2));
428 GURL url("http://google.com");
429 chrome::FaviconID id = db.AddFavicon(url, chrome::FAVICON);
430 base::Time last_updated = base::Time::Now();
431 db.AddFaviconBitmap(id, favicon1, last_updated, kSmallSize);
432 db.AddFaviconBitmap(id, favicon2, last_updated, kLargeSize);
434 EXPECT_TRUE(db.GetFaviconBitmaps(id, NULL));
436 EXPECT_TRUE(db.DeleteFavicon(id));
437 EXPECT_FALSE(db.GetFaviconBitmaps(id, NULL));
440 TEST_F(ThumbnailDatabaseTest, GetIconMappingsForPageURLForReturnOrder) {
441 ThumbnailDatabase db;
442 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_));
443 db.BeginTransaction();
446 std::vector<unsigned char> data(kBlob1, kBlob1 + sizeof(kBlob1));
447 scoped_refptr<base::RefCountedBytes> favicon(new base::RefCountedBytes(data));
449 GURL page_url("http://google.com");
450 GURL icon_url("http://google.com/favicon.ico");
451 base::Time time = base::Time::Now();
453 chrome::FaviconID id = db.AddFavicon(icon_url,
458 EXPECT_NE(0, db.AddIconMapping(page_url, id));
459 std::vector<IconMapping> icon_mappings;
460 EXPECT_TRUE(db.GetIconMappingsForPageURL(page_url, &icon_mappings));
462 EXPECT_EQ(page_url, icon_mappings.front().page_url);
463 EXPECT_EQ(id, icon_mappings.front().icon_id);
464 EXPECT_EQ(chrome::FAVICON, icon_mappings.front().icon_type);
465 EXPECT_EQ(icon_url, icon_mappings.front().icon_url);
468 std::vector<unsigned char> data2(kBlob2, kBlob2 + sizeof(kBlob2));
469 scoped_refptr<base::RefCountedBytes> favicon2 =
470 new base::RefCountedBytes(data);
472 chrome::FaviconID id2 = db.AddFavicon(icon_url,
477 EXPECT_NE(0, db.AddIconMapping(page_url, id2));
479 icon_mappings.clear();
480 EXPECT_TRUE(db.GetIconMappingsForPageURL(page_url, &icon_mappings));
482 EXPECT_EQ(page_url, icon_mappings.front().page_url);
483 EXPECT_EQ(id2, icon_mappings.front().icon_id);
484 EXPECT_EQ(chrome::TOUCH_ICON, icon_mappings.front().icon_type);
485 EXPECT_EQ(icon_url, icon_mappings.front().icon_url);
487 // Add a touch precomposed icon
488 scoped_refptr<base::RefCountedBytes> favicon3 =
489 new base::RefCountedBytes(data2);
491 chrome::FaviconID id3 = db.AddFavicon(icon_url,
492 chrome::TOUCH_PRECOMPOSED_ICON,
496 EXPECT_NE(0, db.AddIconMapping(page_url, id3));
498 icon_mappings.clear();
499 EXPECT_TRUE(db.GetIconMappingsForPageURL(page_url, &icon_mappings));
501 EXPECT_EQ(page_url, icon_mappings.front().page_url);
502 EXPECT_EQ(id3, icon_mappings.front().icon_id);
503 EXPECT_EQ(chrome::TOUCH_PRECOMPOSED_ICON, icon_mappings.front().icon_type);
504 EXPECT_EQ(icon_url, icon_mappings.front().icon_url);
507 // Test result of GetIconMappingsForPageURL when an icon type is passed in.
508 TEST_F(ThumbnailDatabaseTest, GetIconMappingsForPageURLWithIconType) {
509 ThumbnailDatabase db;
510 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_));
511 db.BeginTransaction();
513 GURL url("http://google.com");
514 std::vector<unsigned char> data(kBlob1, kBlob1 + sizeof(kBlob1));
515 scoped_refptr<base::RefCountedBytes> favicon(new base::RefCountedBytes(data));
516 base::Time time = base::Time::Now();
518 chrome::FaviconID id1 = db.AddFavicon(url,
523 EXPECT_NE(0, db.AddIconMapping(url, id1));
525 chrome::FaviconID id2 = db.AddFavicon(url,
530 EXPECT_NE(0, db.AddIconMapping(url, id2));
532 chrome::FaviconID id3 = db.AddFavicon(url,
537 EXPECT_NE(0, db.AddIconMapping(url, id3));
539 // Only the mappings for favicons of type TOUCH_ICON should be returned as
540 // TOUCH_ICON is a larger icon type than FAVICON.
541 std::vector<IconMapping> icon_mappings;
542 EXPECT_TRUE(db.GetIconMappingsForPageURL(
544 chrome::FAVICON | chrome::TOUCH_ICON | chrome::TOUCH_PRECOMPOSED_ICON,
547 EXPECT_EQ(2u, icon_mappings.size());
548 if (id2 == icon_mappings[0].icon_id) {
549 EXPECT_EQ(id3, icon_mappings[1].icon_id);
551 EXPECT_EQ(id3, icon_mappings[0].icon_id);
552 EXPECT_EQ(id2, icon_mappings[1].icon_id);
555 icon_mappings.clear();
557 db.GetIconMappingsForPageURL(url, chrome::TOUCH_ICON, &icon_mappings));
558 if (id2 == icon_mappings[0].icon_id) {
559 EXPECT_EQ(id3, icon_mappings[1].icon_id);
561 EXPECT_EQ(id3, icon_mappings[0].icon_id);
562 EXPECT_EQ(id2, icon_mappings[1].icon_id);
565 icon_mappings.clear();
567 db.GetIconMappingsForPageURL(url, chrome::FAVICON, &icon_mappings));
568 EXPECT_EQ(1u, icon_mappings.size());
569 EXPECT_EQ(id1, icon_mappings[0].icon_id);
572 TEST_F(ThumbnailDatabaseTest, HasMappingFor) {
573 ThumbnailDatabase db;
574 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_));
575 db.BeginTransaction();
577 std::vector<unsigned char> data(kBlob1, kBlob1 + sizeof(kBlob1));
578 scoped_refptr<base::RefCountedBytes> favicon(new base::RefCountedBytes(data));
580 // Add a favicon which will have icon_mappings
581 base::Time time = base::Time::Now();
582 chrome::FaviconID id1 = db.AddFavicon(GURL("http://google.com"),
589 // Add another type of favicon
590 time = base::Time::Now();
591 chrome::FaviconID id2 = db.AddFavicon(GURL("http://www.google.com/icon"),
599 time = base::Time::Now();
600 chrome::FaviconID id3 = db.AddFavicon(GURL("http://www.google.com/icon"),
607 // Add 2 icon mapping
608 GURL page_url("http://www.google.com");
609 EXPECT_TRUE(db.AddIconMapping(page_url, id1));
610 EXPECT_TRUE(db.AddIconMapping(page_url, id2));
612 EXPECT_TRUE(db.HasMappingFor(id1));
613 EXPECT_TRUE(db.HasMappingFor(id2));
614 EXPECT_FALSE(db.HasMappingFor(id3));
616 // Remove all mappings
617 db.DeleteIconMappings(page_url);
618 EXPECT_FALSE(db.HasMappingFor(id1));
619 EXPECT_FALSE(db.HasMappingFor(id2));
620 EXPECT_FALSE(db.HasMappingFor(id3));
623 TEST_F(ThumbnailDatabaseTest, CloneIconMappings) {
624 ThumbnailDatabase db;
625 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_));
626 db.BeginTransaction();
628 std::vector<unsigned char> data(kBlob1, kBlob1 + sizeof(kBlob1));
629 scoped_refptr<base::RefCountedBytes> favicon(new base::RefCountedBytes(data));
631 // Add a favicon which will have icon_mappings
632 chrome::FaviconID id1 = db.AddFavicon(
633 GURL("http://google.com"), chrome::FAVICON);
635 base::Time time = base::Time::Now();
636 db.AddFaviconBitmap(id1, favicon, time, gfx::Size());
638 // Add another type of favicon
639 chrome::FaviconID id2 = db.AddFavicon(GURL("http://www.google.com/icon"),
642 time = base::Time::Now();
643 db.AddFaviconBitmap(id2, favicon, time, gfx::Size());
646 chrome::FaviconID id3 = db.AddFavicon(GURL("http://www.google.com/icon"),
649 time = base::Time::Now();
650 db.AddFaviconBitmap(id3, favicon, time, gfx::Size());
652 GURL page1_url("http://page1.com");
653 EXPECT_TRUE(db.AddIconMapping(page1_url, id1));
654 EXPECT_TRUE(db.AddIconMapping(page1_url, id2));
656 GURL page2_url("http://page2.com");
657 EXPECT_TRUE(db.AddIconMapping(page2_url, id3));
659 // Test we do nothing with existing mappings.
660 std::vector<IconMapping> icon_mapping;
661 EXPECT_TRUE(db.GetIconMappingsForPageURL(page2_url, &icon_mapping));
662 ASSERT_EQ(1U, icon_mapping.size());
664 EXPECT_TRUE(db.CloneIconMappings(page1_url, page2_url));
666 icon_mapping.clear();
667 EXPECT_TRUE(db.GetIconMappingsForPageURL(page2_url, &icon_mapping));
668 ASSERT_EQ(1U, icon_mapping.size());
669 EXPECT_EQ(page2_url, icon_mapping[0].page_url);
670 EXPECT_EQ(id3, icon_mapping[0].icon_id);
672 // Test we clone if the new page has no mappings.
673 GURL page3_url("http://page3.com");
674 EXPECT_TRUE(db.CloneIconMappings(page1_url, page3_url));
676 icon_mapping.clear();
677 EXPECT_TRUE(db.GetIconMappingsForPageURL(page3_url, &icon_mapping));
679 ASSERT_EQ(2U, icon_mapping.size());
680 if (icon_mapping[0].icon_id == id2)
681 std::swap(icon_mapping[0], icon_mapping[1]);
682 EXPECT_EQ(page3_url, icon_mapping[0].page_url);
683 EXPECT_EQ(id1, icon_mapping[0].icon_id);
684 EXPECT_EQ(page3_url, icon_mapping[1].page_url);
685 EXPECT_EQ(id2, icon_mapping[1].icon_id);
688 // Test loading version 3 database.
689 TEST_F(ThumbnailDatabaseTest, Version3) {
690 scoped_ptr<ThumbnailDatabase> db = LoadFromGolden("Favicons.v3.sql");
691 ASSERT_TRUE(db.get() != NULL);
692 VerifyTablesAndColumns(&db->db_);
694 // Version 3 is deprecated, the data should all be gone.
695 VerifyDatabaseEmpty(&db->db_);
698 // Test loading version 4 database.
699 TEST_F(ThumbnailDatabaseTest, Version4) {
700 scoped_ptr<ThumbnailDatabase> db = LoadFromGolden("Favicons.v4.sql");
701 ASSERT_TRUE(db.get() != NULL);
702 VerifyTablesAndColumns(&db->db_);
704 // Version 4 is deprecated, the data should all be gone.
705 VerifyDatabaseEmpty(&db->db_);
708 // Test loading version 5 database.
709 TEST_F(ThumbnailDatabaseTest, Version5) {
710 scoped_ptr<ThumbnailDatabase> db = LoadFromGolden("Favicons.v5.sql");
711 ASSERT_TRUE(db.get() != NULL);
712 VerifyTablesAndColumns(&db->db_);
714 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl1, chrome::FAVICON,
715 kIconUrl1, gfx::Size(), sizeof(kBlob1), kBlob1));
716 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl2, chrome::FAVICON,
717 kIconUrl2, gfx::Size(), sizeof(kBlob2), kBlob2));
718 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl3, chrome::FAVICON,
719 kIconUrl1, gfx::Size(), sizeof(kBlob1), kBlob1));
720 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl3, chrome::TOUCH_ICON,
721 kIconUrl3, gfx::Size(), sizeof(kBlob2), kBlob2));
724 // Test loading version 6 database.
725 TEST_F(ThumbnailDatabaseTest, Version6) {
726 scoped_ptr<ThumbnailDatabase> db = LoadFromGolden("Favicons.v6.sql");
727 ASSERT_TRUE(db.get() != NULL);
728 VerifyTablesAndColumns(&db->db_);
730 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl1, chrome::FAVICON,
731 kIconUrl1, kLargeSize, sizeof(kBlob1), kBlob1));
732 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl2, chrome::FAVICON,
733 kIconUrl2, kLargeSize, sizeof(kBlob2), kBlob2));
734 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl3, chrome::FAVICON,
735 kIconUrl1, kLargeSize, sizeof(kBlob1), kBlob1));
736 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl3, chrome::TOUCH_ICON,
737 kIconUrl3, kLargeSize, sizeof(kBlob2), kBlob2));
740 // Test loading version 7 database.
741 TEST_F(ThumbnailDatabaseTest, Version7) {
742 scoped_ptr<ThumbnailDatabase> db = LoadFromGolden("Favicons.v7.sql");
743 ASSERT_TRUE(db.get() != NULL);
744 VerifyTablesAndColumns(&db->db_);
746 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl1, chrome::FAVICON,
747 kIconUrl1, kLargeSize, sizeof(kBlob1), kBlob1));
748 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl2, chrome::FAVICON,
749 kIconUrl2, kLargeSize, sizeof(kBlob2), kBlob2));
750 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl3, chrome::FAVICON,
751 kIconUrl1, kLargeSize, sizeof(kBlob1), kBlob1));
752 EXPECT_TRUE(CheckPageHasIcon(db.get(), kPageUrl3, chrome::TOUCH_ICON,
753 kIconUrl3, kLargeSize, sizeof(kBlob2), kBlob2));
756 TEST_F(ThumbnailDatabaseTest, Recovery) {
757 // This code tests the recovery module in concert with Chromium's
758 // custom recover virtual table. Under USE_SYSTEM_SQLITE, this is
759 // not available. This is detected dynamically because corrupt
760 // databases still need to be handled, perhaps by Raze(), and the
761 // recovery module is an obvious layer to abstract that to.
762 // TODO(shess): Handle that case for real!
763 if (!sql::Recovery::FullRecoverySupported())
766 // Create an example database.
768 EXPECT_TRUE(CreateDatabaseFromSQL(file_name_, "Favicons.v7.sql"));
770 sql::Connection raw_db;
771 EXPECT_TRUE(raw_db.Open(file_name_));
772 VerifyTablesAndColumns(&raw_db);
775 // Test that the contents make sense after clean open.
777 ThumbnailDatabase db;
778 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_));
781 CheckPageHasIcon(&db, kPageUrl1, chrome::FAVICON,
782 kIconUrl1, kLargeSize, sizeof(kBlob1), kBlob1));
784 CheckPageHasIcon(&db, kPageUrl2, chrome::FAVICON,
785 kIconUrl2, kLargeSize, sizeof(kBlob2), kBlob2));
788 // Corrupt the |icon_mapping.page_url| index by deleting an element
789 // from the backing table but not the index.
791 sql::Connection raw_db;
792 EXPECT_TRUE(raw_db.Open(file_name_));
794 sql::Statement statement(
795 raw_db.GetUniqueStatement("PRAGMA integrity_check"));
796 EXPECT_TRUE(statement.Step());
797 ASSERT_EQ("ok", statement.ColumnString(0));
800 const char kIndexName[] = "icon_mapping_page_url_idx";
801 const int idx_root_page = GetRootPage(&raw_db, kIndexName);
802 const int page_size = GetPageSize(&raw_db);
803 scoped_ptr<char[]> buf(new char[page_size]);
804 EXPECT_TRUE(ReadPage(file_name_, idx_root_page, buf.get(), page_size));
807 const char kDeleteSql[] = "DELETE FROM icon_mapping WHERE page_url = ?";
808 sql::Statement statement(raw_db.GetUniqueStatement(kDeleteSql));
809 statement.BindString(0, URLDatabase::GURLToDatabaseURL(kPageUrl2));
810 EXPECT_TRUE(statement.Run());
814 EXPECT_TRUE(WritePage(file_name_, idx_root_page, buf.get(), page_size));
817 // Database should be corrupt at the SQLite level.
819 sql::Connection raw_db;
820 EXPECT_TRUE(raw_db.Open(file_name_));
821 sql::Statement statement(
822 raw_db.GetUniqueStatement("PRAGMA integrity_check"));
823 EXPECT_TRUE(statement.Step());
824 ASSERT_NE("ok", statement.ColumnString(0));
827 // Open the database and access the corrupt index.
829 sql::ScopedErrorIgnorer ignore_errors;
830 ignore_errors.IgnoreError(SQLITE_CORRUPT);
831 ThumbnailDatabase db;
832 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_));
834 // Data for kPageUrl2 was deleted, but the index entry remains,
835 // this will throw SQLITE_CORRUPT. The corruption handler will
836 // recover the database and poison the handle, so the outer call
838 EXPECT_FALSE(db.GetIconMappingsForPageURL(kPageUrl2, NULL));
840 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors());
843 // Check that the database is recovered at the SQLite level.
845 sql::Connection raw_db;
846 EXPECT_TRUE(raw_db.Open(file_name_));
847 sql::Statement statement(
848 raw_db.GetUniqueStatement("PRAGMA integrity_check"));
849 EXPECT_TRUE(statement.Step());
850 EXPECT_EQ("ok", statement.ColumnString(0));
852 // Check that the expected tables exist.
853 VerifyTablesAndColumns(&raw_db);
856 // Database should also be recovered at higher levels.
858 ThumbnailDatabase db;
859 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_));
861 // Now this fails because there is no mapping.
862 EXPECT_FALSE(db.GetIconMappingsForPageURL(kPageUrl2, NULL));
864 // Other data was retained by recovery.
866 CheckPageHasIcon(&db, kPageUrl1, chrome::FAVICON,
867 kIconUrl1, kLargeSize, sizeof(kBlob1), kBlob1));
870 // Corrupt the database again by making the actual file shorter than
871 // the header expects.
874 EXPECT_TRUE(file_util::GetFileSize(file_name_, &db_size));
876 sql::Connection raw_db;
877 EXPECT_TRUE(raw_db.Open(file_name_));
878 EXPECT_TRUE(raw_db.Execute("CREATE TABLE t(x)"));
880 file_util::ScopedFILE file(file_util::OpenFile(file_name_, "rb+"));
881 ASSERT_TRUE(file.get() != NULL);
882 EXPECT_EQ(0, fseek(file.get(), static_cast<long>(db_size), SEEK_SET));
883 EXPECT_TRUE(file_util::TruncateFile(file.get()));
886 // Database is unusable at the SQLite level.
888 sql::ScopedErrorIgnorer ignore_errors;
889 ignore_errors.IgnoreError(SQLITE_CORRUPT);
890 sql::Connection raw_db;
891 EXPECT_TRUE(raw_db.Open(file_name_));
892 EXPECT_FALSE(raw_db.IsSQLValid("PRAGMA integrity_check"));
893 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors());
896 // Database should be recovered during open.
898 sql::ScopedErrorIgnorer ignore_errors;
899 ignore_errors.IgnoreError(SQLITE_CORRUPT);
900 ThumbnailDatabase db;
901 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_));
903 EXPECT_FALSE(db.GetIconMappingsForPageURL(kPageUrl2, NULL));
905 CheckPageHasIcon(&db, kPageUrl1, chrome::FAVICON,
906 kIconUrl1, kLargeSize, sizeof(kBlob1), kBlob1));
908 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors());
912 TEST_F(ThumbnailDatabaseTest, Recovery6) {
913 // TODO(shess): See comment at top of Recovery test.
914 if (!sql::Recovery::FullRecoverySupported())
917 // Create an example database without loading into ThumbnailDatabase
918 // (which would upgrade it).
919 EXPECT_TRUE(CreateDatabaseFromSQL(file_name_, "Favicons.v6.sql"));
921 // Corrupt the database by making the actual file shorter than the
922 // SQLite header expects. This form of corruption will cause
923 // immediate failures during Open(), before the migration code runs,
924 // so the version-6 recovery will occur.
927 EXPECT_TRUE(file_util::GetFileSize(file_name_, &db_size));
929 sql::Connection raw_db;
930 EXPECT_TRUE(raw_db.Open(file_name_));
931 EXPECT_TRUE(raw_db.Execute("CREATE TABLE t(x)"));
933 file_util::ScopedFILE file(file_util::OpenFile(file_name_, "rb+"));
934 ASSERT_TRUE(file.get() != NULL);
935 EXPECT_EQ(0, fseek(file.get(), static_cast<long>(db_size), SEEK_SET));
936 EXPECT_TRUE(file_util::TruncateFile(file.get()));
939 // Database is unusable at the SQLite level.
941 sql::ScopedErrorIgnorer ignore_errors;
942 ignore_errors.IgnoreError(SQLITE_CORRUPT);
943 sql::Connection raw_db;
944 EXPECT_TRUE(raw_db.Open(file_name_));
945 EXPECT_FALSE(raw_db.IsSQLValid("PRAGMA integrity_check"));
946 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors());
949 // Database should be recovered during open.
951 sql::ScopedErrorIgnorer ignore_errors;
952 ignore_errors.IgnoreError(SQLITE_CORRUPT);
953 ThumbnailDatabase db;
954 ASSERT_EQ(sql::INIT_OK, db.Init(file_name_));
956 // Test that some data is present, copied from
957 // ThumbnailDatabaseTest.Version6 .
959 CheckPageHasIcon(&db, kPageUrl3, chrome::FAVICON,
960 kIconUrl1, kLargeSize, sizeof(kBlob1), kBlob1));
962 CheckPageHasIcon(&db, kPageUrl3, chrome::TOUCH_ICON,
963 kIconUrl3, kLargeSize, sizeof(kBlob2), kBlob2));
965 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors());
968 // Check that the database is recovered at a SQLite level, and that
969 // the current schema is in place.
971 sql::Connection raw_db;
972 EXPECT_TRUE(raw_db.Open(file_name_));
973 sql::Statement statement(
974 raw_db.GetUniqueStatement("PRAGMA integrity_check"));
975 EXPECT_TRUE(statement.Step());
976 EXPECT_EQ("ok", statement.ColumnString(0));
978 // Check that the expected tables exist.
979 VerifyTablesAndColumns(&raw_db);
983 // Test that various broken schema found in the wild can be opened
984 // successfully, and result in the correct schema.
985 TEST_F(ThumbnailDatabaseTest, WildSchema) {
986 base::FilePath sql_path;
987 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &sql_path));
988 sql_path = sql_path.AppendASCII("History").AppendASCII("thumbnail_wild");
990 base::FileEnumerator fe(
991 sql_path, false, base::FileEnumerator::FILES, FILE_PATH_LITERAL("*.sql"));
992 for (base::FilePath name = fe.Next(); !name.empty(); name = fe.Next()) {
993 SCOPED_TRACE(name.BaseName().AsUTF8Unsafe());
994 // Generate a database path based on the golden's basename.
995 base::FilePath db_base_name =
996 name.BaseName().ReplaceExtension(FILE_PATH_LITERAL("db"));
997 base::FilePath db_path = file_name_.DirName().Append(db_base_name);
998 ASSERT_TRUE(sql::test::CreateDatabaseFromSQL(db_path, name));
1000 // All schema flaws should be cleaned up by Init().
1001 // TODO(shess): Differentiate between databases which need Raze()
1002 // and those which can be salvaged.
1003 ThumbnailDatabase db;
1004 ASSERT_EQ(sql::INIT_OK, db.Init(db_path));
1006 // Verify that the resulting schema is correct, whether it
1007 // involved razing the file or fixing things in place.
1008 VerifyTablesAndColumns(&db.db_);
1012 } // namespace history