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