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
24namespace history {
25
26namespace {
27
28// Blobs for the bitmap tests.  These aren't real bitmaps.  Golden
29// database files store the same blobs (see VersionN tests).
30const unsigned char kBlob1[] =
31    "12346102356120394751634516591348710478123649165419234519234512349134";
32const unsigned char kBlob2[] =
33    "goiwuegrqrcomizqyzkjalitbahxfjytrqvpqeroicxmnlkhlzunacxaneviawrtxcywhgef";
34
35// Page and icon urls shared by tests.  Present in golden database
36// files (see VersionN tests).
37const GURL kPageUrl1 = GURL("http://google.com/");
38const GURL kPageUrl2 = GURL("http://yahoo.com/");
39const GURL kPageUrl3 = GURL("http://www.google.com/");
40const GURL kPageUrl4 = GURL("http://www.google.com/blank.html");
41const GURL kPageUrl5 = GURL("http://www.bing.com/");
42
43const GURL kIconUrl1 = GURL("http://www.google.com/favicon.ico");
44const GURL kIconUrl2 = GURL("http://www.yahoo.com/favicon.ico");
45const GURL kIconUrl3 = GURL("http://www.google.com/touch.ico");
46const GURL kIconUrl5 = GURL("http://www.bing.com/favicon.ico");
47
48const gfx::Size kSmallSize = gfx::Size(16, 16);
49const 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.
53WARN_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.
67void 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
88void 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.
99WARN_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
161class 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
195TEST_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
217TEST_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
245TEST_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
275TEST_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
303TEST_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.
380TEST_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
404TEST_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.
468TEST_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
524TEST_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
577TEST_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.
643TEST_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.
653TEST_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.
663TEST_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.
699TEST_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.
735TEST_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
770TEST_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
907TEST_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
955TEST_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.
1005TEST_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