1 // Copyright (c) 2011 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 <stddef.h>
6 
7 #include <map>
8 
9 #include "base/files/file_path.h"
10 #include "base/files/scoped_temp_dir.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "components/history/core/browser/history_types.h"
13 #include "components/history/core/browser/top_sites_database.h"
14 #include "components/history/core/test/database_test_utils.h"
15 #include "components/history/core/test/thumbnail-inl.h"
16 #include "sql/database.h"
17 #include "sql/recovery.h"
18 #include "sql/test/scoped_error_expecter.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 {
25 
26 // URL with url_rank 0 in golden files.
27 // TODO(https://crbug.com/1042727): Fix test GURL scoping and remove this getter
28 // function.
Url0()29 GURL Url0() {
30   return GURL("http://www.google.com/");
31 }
32 
33 // URL with url_rank 1 in golden files.
Url1()34 GURL Url1() {
35   return GURL("http://www.google.com/chrome/intl/en/welcome.html");
36 }
37 
38 // URL with url_rank 2 in golden files.
Url2()39 GURL Url2() {
40   return GURL("https://chrome.google.com/webstore?hl=en");
41 }
42 
43 // Verify that the up-to-date database has the expected tables and
44 // columns.  Functional tests only check whether the things which
45 // should be there are, but do not check if extraneous items are
46 // present.  Any extraneous items have the potential to interact
47 // negatively with future schema changes.
VerifyTablesAndColumns(sql::Database * db)48 void VerifyTablesAndColumns(sql::Database* db) {
49   // [meta] and [top_sites].
50   EXPECT_EQ(2u, sql::test::CountSQLTables(db));
51 
52   // Implicit index on [meta], index on [top_sites].
53   EXPECT_EQ(2u, sql::test::CountSQLIndices(db));
54 
55   // [key] and [value].
56   EXPECT_EQ(2u, sql::test::CountTableColumns(db, "meta"));
57 
58   // [url], [url_rank], [title], [redirects]
59   EXPECT_EQ(4u, sql::test::CountTableColumns(db, "top_sites"));
60 }
61 
VerifyDatabaseEmpty(sql::Database * db)62 void VerifyDatabaseEmpty(sql::Database* db) {
63   size_t rows = 0;
64   EXPECT_TRUE(sql::test::CountTableRows(db, "top_sites", &rows));
65   EXPECT_EQ(0u, rows);
66 }
67 
VerifyURLsEqual(const std::vector<GURL> & expected,const history::MostVisitedURLList & actual)68 void VerifyURLsEqual(const std::vector<GURL>& expected,
69                      const history::MostVisitedURLList& actual) {
70   EXPECT_EQ(expected.size(), actual.size());
71   for (size_t i = 0; i < expected.size(); i++)
72     EXPECT_EQ(expected[i], actual[i].url) << " for i = " << i;
73 }
74 
75 }  // namespace
76 
77 namespace history {
78 
79 class TopSitesDatabaseTest : public testing::Test {
80  protected:
SetUp()81   void SetUp() override {
82     // Get a temporary directory for the test DB files.
83     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
84     file_name_ = temp_dir_.GetPath().AppendASCII("TestTopSites.db");
85   }
86 
87   base::ScopedTempDir temp_dir_;
88   base::FilePath file_name_;
89 };
90 
91 // Version 1 is deprecated, the resulting schema should be current,
92 // with no data.
TEST_F(TopSitesDatabaseTest,Version1)93 TEST_F(TopSitesDatabaseTest, Version1) {
94   ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v1.sql"));
95 
96   TopSitesDatabase db;
97   ASSERT_TRUE(db.Init(file_name_));
98   VerifyTablesAndColumns(db.db_.get());
99   VerifyDatabaseEmpty(db.db_.get());
100 }
101 
102 // Version 2 is deprecated, the resulting schema should be current,
103 // with no data.
TEST_F(TopSitesDatabaseTest,Version2)104 TEST_F(TopSitesDatabaseTest, Version2) {
105   ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v2.sql"));
106 
107   TopSitesDatabase db;
108   ASSERT_TRUE(db.Init(file_name_));
109   VerifyTablesAndColumns(db.db_.get());
110   VerifyDatabaseEmpty(db.db_.get());
111 }
112 
TEST_F(TopSitesDatabaseTest,Version3)113 TEST_F(TopSitesDatabaseTest, Version3) {
114   ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v3.sql"));
115 
116   TopSitesDatabase db;
117   ASSERT_TRUE(db.Init(file_name_));
118 
119   VerifyTablesAndColumns(db.db_.get());
120 
121   // Basic operational check.
122   MostVisitedURLList urls;
123   db.GetSites(&urls);
124   ASSERT_EQ(3u, urls.size());
125   EXPECT_EQ(Url0(), urls[0].url);  // [0] because of url_rank.
126 
127   sql::Transaction transaction(db.db_.get());
128   transaction.Begin();
129   ASSERT_TRUE(db.RemoveURLNoTransaction(urls[1]));
130   transaction.Commit();
131 
132   db.GetSites(&urls);
133   ASSERT_EQ(2u, urls.size());
134 }
135 
TEST_F(TopSitesDatabaseTest,Version4)136 TEST_F(TopSitesDatabaseTest, Version4) {
137   ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v4.sql"));
138 
139   TopSitesDatabase db;
140   ASSERT_TRUE(db.Init(file_name_));
141 
142   VerifyTablesAndColumns(db.db_.get());
143 
144   // Basic operational check.
145   MostVisitedURLList urls;
146   db.GetSites(&urls);
147   ASSERT_EQ(3u, urls.size());
148   EXPECT_EQ(Url0(), urls[0].url);  // [0] because of url_rank.
149 
150   sql::Transaction transaction(db.db_.get());
151   transaction.Begin();
152   ASSERT_TRUE(db.RemoveURLNoTransaction(urls[1]));
153   transaction.Commit();
154 
155   db.GetSites(&urls);
156   ASSERT_EQ(2u, urls.size());
157 }
158 
159 // Version 1 is deprecated, the resulting schema should be current, with no
160 // data.
TEST_F(TopSitesDatabaseTest,Recovery1)161 TEST_F(TopSitesDatabaseTest, Recovery1) {
162   // Create an example database.
163   EXPECT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v1.sql"));
164 
165   // Corrupt the database by adjusting the header size.
166   EXPECT_TRUE(sql::test::CorruptSizeInHeader(file_name_));
167 
168   // Database is unusable at the SQLite level.
169   {
170     sql::test::ScopedErrorExpecter expecter;
171     expecter.ExpectError(SQLITE_CORRUPT);
172     sql::Database raw_db;
173     EXPECT_TRUE(raw_db.Open(file_name_));
174     EXPECT_FALSE(raw_db.IsSQLValid("PRAGMA integrity_check"));
175     ASSERT_TRUE(expecter.SawExpectedErrors());
176   }
177 
178   // Corruption should be detected and recovered during Init().
179   {
180     sql::test::ScopedErrorExpecter expecter;
181     expecter.ExpectError(SQLITE_CORRUPT);
182 
183     TopSitesDatabase db;
184     ASSERT_TRUE(db.Init(file_name_));
185     VerifyTablesAndColumns(db.db_.get());
186     VerifyDatabaseEmpty(db.db_.get());
187 
188     ASSERT_TRUE(expecter.SawExpectedErrors());
189   }
190 }
191 
192 // Version 2 is deprecated, the resulting schema should be current, with no
193 // data.
TEST_F(TopSitesDatabaseTest,Recovery2)194 TEST_F(TopSitesDatabaseTest, Recovery2) {
195   // Create an example database.
196   EXPECT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v2.sql"));
197 
198   // Corrupt the database by adjusting the header.
199   EXPECT_TRUE(sql::test::CorruptSizeInHeader(file_name_));
200 
201   // Database is unusable at the SQLite level.
202   {
203     sql::test::ScopedErrorExpecter expecter;
204     expecter.ExpectError(SQLITE_CORRUPT);
205     sql::Database raw_db;
206     EXPECT_TRUE(raw_db.Open(file_name_));
207     EXPECT_FALSE(raw_db.IsSQLValid("PRAGMA integrity_check"));
208     ASSERT_TRUE(expecter.SawExpectedErrors());
209   }
210 
211   // Corruption should be detected and recovered during Init().
212   {
213     sql::test::ScopedErrorExpecter expecter;
214     expecter.ExpectError(SQLITE_CORRUPT);
215 
216     TopSitesDatabase db;
217     ASSERT_TRUE(db.Init(file_name_));
218     VerifyTablesAndColumns(db.db_.get());
219     VerifyDatabaseEmpty(db.db_.get());
220 
221     ASSERT_TRUE(expecter.SawExpectedErrors());
222   }
223 }
224 
TEST_F(TopSitesDatabaseTest,Recovery3)225 TEST_F(TopSitesDatabaseTest, Recovery3) {
226   // Create an example database.
227   EXPECT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v3.sql"));
228 
229   // Corrupt the database by adjusting the header.
230   EXPECT_TRUE(sql::test::CorruptSizeInHeader(file_name_));
231 
232   // Database is unusable at the SQLite level.
233   {
234     sql::test::ScopedErrorExpecter expecter;
235     expecter.ExpectError(SQLITE_CORRUPT);
236     sql::Database raw_db;
237     EXPECT_TRUE(raw_db.Open(file_name_));
238     EXPECT_FALSE(raw_db.IsSQLValid("PRAGMA integrity_check"));
239     ASSERT_TRUE(expecter.SawExpectedErrors());
240   }
241 
242   // Corruption should be detected and recovered during Init().
243   {
244     sql::test::ScopedErrorExpecter expecter;
245     expecter.ExpectError(SQLITE_CORRUPT);
246 
247     TopSitesDatabase db;
248     ASSERT_TRUE(db.Init(file_name_));
249 
250     MostVisitedURLList urls;
251     db.GetSites(&urls);
252     ASSERT_EQ(3u, urls.size());
253     EXPECT_EQ(Url0(), urls[0].url);  // [0] because of url_rank.
254 
255     ASSERT_TRUE(expecter.SawExpectedErrors());
256   }
257 
258   // Double-check database integrity.
259   {
260     sql::Database raw_db;
261     EXPECT_TRUE(raw_db.Open(file_name_));
262     ASSERT_EQ("ok", sql::test::IntegrityCheck(&raw_db));
263   }
264 
265   // Corrupt the thumnails.url auto-index by deleting an element from the table
266   // but leaving it in the index.
267   static const char kIndexName[] = "sqlite_autoindex_top_sites_1";
268   // TODO(shess): Refactor CorruptTableOrIndex() to make parameterized
269   // statements easy.
270   static const char kDeleteSql[] =
271       "DELETE FROM top_sites WHERE url = "
272       "'http://www.google.com/chrome/intl/en/welcome.html'";
273   EXPECT_TRUE(
274       sql::test::CorruptTableOrIndex(file_name_, kIndexName, kDeleteSql));
275 
276   // SQLite can operate on the database, but notices the corruption in integrity
277   // check.
278   {
279     sql::Database raw_db;
280     EXPECT_TRUE(raw_db.Open(file_name_));
281     ASSERT_NE("ok", sql::test::IntegrityCheck(&raw_db));
282   }
283 
284   // Open the database and access the corrupt index.
285   {
286     TopSitesDatabase db;
287     ASSERT_TRUE(db.Init(file_name_));
288 
289     {
290       sql::test::ScopedErrorExpecter expecter;
291       expecter.ExpectError(SQLITE_CORRUPT);
292 
293       // Data for Url1() was deleted, but the index entry remains, this will
294       // throw SQLITE_CORRUPT.  The corruption handler will recover the database
295       // and poison the handle, so the outer call fails.
296       EXPECT_EQ(TopSitesDatabase::kRankOfNonExistingURL,
297                 db.GetURLRank(MostVisitedURL(Url1(), base::string16())));
298 
299       ASSERT_TRUE(expecter.SawExpectedErrors());
300     }
301   }
302 
303   // Check that the database is recovered at the SQLite level.
304   {
305     sql::Database raw_db;
306     EXPECT_TRUE(raw_db.Open(file_name_));
307     ASSERT_EQ("ok", sql::test::IntegrityCheck(&raw_db));
308   }
309 
310   // After recovery, the database accesses won't throw errors.  The top-ranked
311   // item is removed, but the ranking was revised in post-processing.
312   {
313     TopSitesDatabase db;
314     ASSERT_TRUE(db.Init(file_name_));
315     VerifyTablesAndColumns(db.db_.get());
316 
317     EXPECT_EQ(TopSitesDatabase::kRankOfNonExistingURL,
318               db.GetURLRank(MostVisitedURL(Url1(), base::string16())));
319 
320     MostVisitedURLList urls;
321     db.GetSites(&urls);
322     ASSERT_EQ(2u, urls.size());
323     EXPECT_EQ(Url0(), urls[0].url);  // [0] because of url_rank.
324     EXPECT_EQ(Url2(), urls[1].url);  // [1] because of url_rank.
325   }
326 }
327 
TEST_F(TopSitesDatabaseTest,Recovery4)328 TEST_F(TopSitesDatabaseTest, Recovery4) {
329   // Create an example database.
330   EXPECT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v4.sql"));
331 
332   // Corrupt the database by adjusting the header.
333   EXPECT_TRUE(sql::test::CorruptSizeInHeader(file_name_));
334 
335   // Database is unusable at the SQLite level.
336   {
337     sql::test::ScopedErrorExpecter expecter;
338     expecter.ExpectError(SQLITE_CORRUPT);
339     sql::Database raw_db;
340     EXPECT_TRUE(raw_db.Open(file_name_));
341     EXPECT_FALSE(raw_db.IsSQLValid("PRAGMA integrity_check"));
342     ASSERT_TRUE(expecter.SawExpectedErrors());
343   }
344 
345   // Corruption should be detected and recovered during Init().
346   {
347     sql::test::ScopedErrorExpecter expecter;
348     expecter.ExpectError(SQLITE_CORRUPT);
349 
350     TopSitesDatabase db;
351     ASSERT_TRUE(db.Init(file_name_));
352 
353     MostVisitedURLList urls;
354     db.GetSites(&urls);
355     ASSERT_EQ(3u, urls.size());
356     EXPECT_EQ(Url0(), urls[0].url);  // [0] because of url_rank.
357 
358     ASSERT_TRUE(expecter.SawExpectedErrors());
359   }
360 
361   // Double-check database integrity.
362   {
363     sql::Database raw_db;
364     EXPECT_TRUE(raw_db.Open(file_name_));
365     ASSERT_EQ("ok", sql::test::IntegrityCheck(&raw_db));
366   }
367 
368   // Corrupt the tops_sites.url auto-index by deleting an element from the table
369   // but leaving it in the index.
370   static const char kIndexName[] = "sqlite_autoindex_top_sites_1";
371   // TODO(shess): Refactor CorruptTableOrIndex() to make parameterized
372   // statements easy.
373   static const char kDeleteSql[] =
374       "DELETE FROM top_sites WHERE url = "
375       "'http://www.google.com/chrome/intl/en/welcome.html'";
376   EXPECT_TRUE(
377       sql::test::CorruptTableOrIndex(file_name_, kIndexName, kDeleteSql));
378 
379   // SQLite can operate on the database, but notices the corruption in integrity
380   // check.
381   {
382     sql::Database raw_db;
383     EXPECT_TRUE(raw_db.Open(file_name_));
384     ASSERT_NE("ok", sql::test::IntegrityCheck(&raw_db));
385   }
386 
387   // Open the database and access the corrupt index.
388   {
389     TopSitesDatabase db;
390     ASSERT_TRUE(db.Init(file_name_));
391 
392     {
393       sql::test::ScopedErrorExpecter expecter;
394       expecter.ExpectError(SQLITE_CORRUPT);
395 
396       // Data for Url1() was deleted, but the index entry remains, this will
397       // throw SQLITE_CORRUPT.  The corruption handler will recover the database
398       // and poison the handle, so the outer call fails.
399       EXPECT_EQ(TopSitesDatabase::kRankOfNonExistingURL,
400                 db.GetURLRank(MostVisitedURL(Url1(), base::string16())));
401 
402       ASSERT_TRUE(expecter.SawExpectedErrors());
403     }
404   }
405 
406   // Check that the database is recovered at the SQLite level.
407   {
408     sql::Database raw_db;
409     EXPECT_TRUE(raw_db.Open(file_name_));
410     ASSERT_EQ("ok", sql::test::IntegrityCheck(&raw_db));
411   }
412 
413   // After recovery, the database accesses won't throw errors.  The top-ranked
414   // item is removed, but the ranking was revised in post-processing.
415   {
416     TopSitesDatabase db;
417     ASSERT_TRUE(db.Init(file_name_));
418     VerifyTablesAndColumns(db.db_.get());
419 
420     EXPECT_EQ(TopSitesDatabase::kRankOfNonExistingURL,
421               db.GetURLRank(MostVisitedURL(Url1(), base::string16())));
422 
423     MostVisitedURLList urls;
424     db.GetSites(&urls);
425     ASSERT_EQ(2u, urls.size());
426     EXPECT_EQ(Url0(), urls[0].url);  // [0] because of url_rank.
427     EXPECT_EQ(Url2(), urls[1].url);  // [1] because of url_rank.
428   }
429 }
430 
TEST_F(TopSitesDatabaseTest,ApplyDelta_Delete)431 TEST_F(TopSitesDatabaseTest, ApplyDelta_Delete) {
432   ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v4.sql"));
433 
434   TopSitesDatabase db;
435   ASSERT_TRUE(db.Init(file_name_));
436 
437   TopSitesDelta delta;
438   // Delete Url0(). Now db has Url1() and Url2().
439   MostVisitedURL url_to_delete(Url0(), base::ASCIIToUTF16("Google"));
440   delta.deleted.push_back(url_to_delete);
441 
442   // Update db.
443   db.ApplyDelta(delta);
444 
445   // Read db and verify.
446   MostVisitedURLList urls;
447   db.GetSites(&urls);
448   VerifyURLsEqual(std::vector<GURL>({Url1(), Url2()}), urls);
449 }
450 
TEST_F(TopSitesDatabaseTest,ApplyDelta_Add)451 TEST_F(TopSitesDatabaseTest, ApplyDelta_Add) {
452   ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v4.sql"));
453 
454   TopSitesDatabase db;
455   ASSERT_TRUE(db.Init(file_name_));
456 
457   GURL mapsUrl = GURL("http://maps.google.com/");
458 
459   // Add a new URL, rank = 0. Now db has mapsUrl, Url0(), Url1(), and Url2().
460   TopSitesDelta delta;
461   MostVisitedURLWithRank url_to_add;
462   url_to_add.url = MostVisitedURL(mapsUrl, base::ASCIIToUTF16("Google Maps"));
463   url_to_add.rank = 0;
464   delta.added.push_back(url_to_add);
465 
466   // Update db.
467   db.ApplyDelta(delta);
468 
469   // Read db and verify.
470   MostVisitedURLList urls;
471   db.GetSites(&urls);
472   VerifyURLsEqual(std::vector<GURL>({mapsUrl, Url0(), Url1(), Url2()}), urls);
473 }
474 
TEST_F(TopSitesDatabaseTest,ApplyDelta_Move)475 TEST_F(TopSitesDatabaseTest, ApplyDelta_Move) {
476   ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v4.sql"));
477 
478   TopSitesDatabase db;
479   ASSERT_TRUE(db.Init(file_name_));
480 
481   // Move Url1() by updating its rank to 2. Now db has Url0(), Url2(), and
482   // Url1().
483   TopSitesDelta delta;
484   MostVisitedURLWithRank url_to_move;
485   url_to_move.url = MostVisitedURL(Url1(), base::ASCIIToUTF16("Google Chrome"));
486   url_to_move.rank = 2;
487   delta.moved.push_back(url_to_move);
488 
489   // Update db.
490   db.ApplyDelta(delta);
491 
492   // Read db and verify.
493   MostVisitedURLList urls;
494   db.GetSites(&urls);
495   VerifyURLsEqual(std::vector<GURL>({Url0(), Url2(), Url1()}), urls);
496 }
497 
TEST_F(TopSitesDatabaseTest,ApplyDelta_All)498 TEST_F(TopSitesDatabaseTest, ApplyDelta_All) {
499   ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v4.sql"));
500 
501   TopSitesDatabase db;
502   ASSERT_TRUE(db.Init(file_name_));
503 
504   GURL mapsUrl = GURL("http://maps.google.com/");
505 
506   TopSitesDelta delta;
507   // Delete Url0(). Now db has Url1() and Url2().
508   MostVisitedURL url_to_delete(Url0(), base::ASCIIToUTF16("Google"));
509   delta.deleted.push_back(url_to_delete);
510 
511   // Add a new URL, not forced, rank = 0. Now db has mapsUrl, Url1() and Url2().
512   MostVisitedURLWithRank url_to_add;
513   url_to_add.url = MostVisitedURL(mapsUrl, base::ASCIIToUTF16("Google Maps"));
514   url_to_add.rank = 0;
515   delta.added.push_back(url_to_add);
516 
517   // Move Url1() by updating its rank to 2. Now db has mapsUrl, Url2() and
518   // Url1().
519   MostVisitedURLWithRank url_to_move;
520   url_to_move.url = MostVisitedURL(Url1(), base::ASCIIToUTF16("Google Chrome"));
521   url_to_move.rank = 2;
522   delta.moved.push_back(url_to_move);
523 
524   // Update db.
525   db.ApplyDelta(delta);
526 
527   // Read db and verify.
528   MostVisitedURLList urls;
529   db.GetSites(&urls);
530   VerifyURLsEqual(std::vector<GURL>({mapsUrl, Url2(), Url1()}), urls);
531 }
532 
533 }  // namespace history
534