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.
CreateDatabaseFromSQL(const base::FilePath & db_path,const char * ascii_path)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.
VerifyTablesAndColumns(sql::Connection * db)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
VerifyDatabaseEmpty(sql::Connection * db)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.
CheckPageHasIcon(ThumbnailDatabase * db,const GURL & page_url,favicon_base::IconType expected_icon_type,const GURL & expected_icon_url,const gfx::Size & expected_icon_size,size_t expected_icon_contents_size,const unsigned char * expected_icon_contents)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:
ThumbnailDatabaseTest()163 ThumbnailDatabaseTest() {
164 }
~ThumbnailDatabaseTest()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.
LoadFromGolden(const char * golden_path)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:
SetUp()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
TEST_F(ThumbnailDatabaseTest,AddIconMapping)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
TEST_F(ThumbnailDatabaseTest,UpdateIconMapping)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
TEST_F(ThumbnailDatabaseTest,DeleteIconMappings)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
TEST_F(ThumbnailDatabaseTest,GetIconMappingsForPageURL)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
TEST_F(ThumbnailDatabaseTest,RetainDataForPageUrls)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.
TEST_F(ThumbnailDatabaseTest,DeleteFavicon)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
TEST_F(ThumbnailDatabaseTest,GetIconMappingsForPageURLForReturnOrder)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.
TEST_F(ThumbnailDatabaseTest,GetIconMappingsForPageURLWithIconType)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
TEST_F(ThumbnailDatabaseTest,HasMappingFor)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
TEST_F(ThumbnailDatabaseTest,CloneIconMappings)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.
TEST_F(ThumbnailDatabaseTest,Version3)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.
TEST_F(ThumbnailDatabaseTest,Version4)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.
TEST_F(ThumbnailDatabaseTest,Version5)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.
TEST_F(ThumbnailDatabaseTest,Version6)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.
TEST_F(ThumbnailDatabaseTest,Version7)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
TEST_F(ThumbnailDatabaseTest,Recovery)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
TEST_F(ThumbnailDatabaseTest,Recovery6)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
TEST_F(ThumbnailDatabaseTest,Recovery5)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.
TEST_F(ThumbnailDatabaseTest,WildSchema)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