[M120 Migration][VD] Fix url crash in RequestCertificateConfirm
[platform/framework/web/chromium-efl.git] / sql / recovery_unittest.cc
1 // Copyright 2013 The Chromium Authors
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 "sql/recovery.h"
6
7 #include <stddef.h>
8
9 #include <memory>
10 #include <string>
11 #include <utility>
12
13 #include "base/feature_list.h"
14 #include "base/files/file_path.h"
15 #include "base/files/file_util.h"
16 #include "base/files/scoped_temp_dir.h"
17 #include "base/functional/bind.h"
18 #include "base/functional/callback_forward.h"
19 #include "base/functional/callback_helpers.h"
20 #include "base/path_service.h"
21 #include "base/ranges/algorithm.h"
22 #include "base/strings/strcat.h"
23 #include "base/strings/string_number_conversions.h"
24 #include "base/test/bind.h"
25 #include "base/test/gtest_util.h"
26 #include "base/test/metrics/histogram_tester.h"
27 #include "base/test/scoped_feature_list.h"
28 #include "sql/database.h"
29 #include "sql/meta_table.h"
30 #include "sql/sql_features.h"
31 #include "sql/sqlite_result_code.h"
32 #include "sql/sqlite_result_code_values.h"
33 #include "sql/statement.h"
34 #include "sql/test/paths.h"
35 #include "sql/test/scoped_error_expecter.h"
36 #include "sql/test/test_helpers.h"
37 #include "testing/gtest/include/gtest/gtest.h"
38 #include "third_party/sqlite/sqlite3.h"
39
40 namespace sql {
41
42 namespace {
43
44 using sql::test::ExecuteWithResult;
45 using sql::test::ExecuteWithResults;
46
47 constexpr char kRecoveryResultHistogramName[] = "Sql.Recovery.Result";
48 constexpr char kRecoveryResultCodeHistogramName[] = "Sql.Recovery.ResultCode";
49
50 // Dump consistent human-readable representation of the database
51 // schema.  For tables or indices, this will contain the sql command
52 // to create the table or index.  For certain automatic SQLite
53 // structures with no sql, the name is used.
54 std::string GetSchema(Database* db) {
55   static const char kSql[] =
56       "SELECT COALESCE(sql, name) FROM sqlite_schema ORDER BY 1";
57   return ExecuteWithResults(db, kSql, "|", "\n");
58 }
59
60 // Base class for all recovery-related tests. Each subclass must initialize
61 // `scoped_feature_list_`, as appropriate.
62 class SqlRecoveryTestBase : public testing::Test {
63  public:
64   void SetUp() override {
65     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
66     db_path_ = temp_dir_.GetPath().AppendASCII("recovery_test.sqlite");
67     ASSERT_TRUE(db_.Open(db_path_));
68   }
69
70   void TearDown() override {
71     if (db_.is_open()) {
72       db_.Close();
73     }
74     // Ensure the database, along with any recovery files, are cleaned up.
75     ASSERT_TRUE(base::DeleteFile(db_path_));
76     ASSERT_TRUE(base::DeleteFile(db_path_.AddExtensionASCII(".backup")));
77     ASSERT_TRUE(temp_dir_.Delete());
78   }
79
80   bool Reopen() {
81     db_.Close();
82     return db_.Open(db_path_);
83   }
84
85   bool OverwriteDatabaseHeader() {
86     base::File file(db_path_,
87                     base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
88     static constexpr char kText[] = "Now is the winter of our discontent.";
89     constexpr int kTextBytes = sizeof(kText) - 1;
90     return file.Write(0, kText, kTextBytes) == kTextBytes;
91   }
92
93  protected:
94   base::test::ScopedFeatureList scoped_feature_list_;
95   base::ScopedTempDir temp_dir_;
96   base::FilePath db_path_;
97   Database db_;
98   base::HistogramTester histogram_tester_;
99 };
100
101 // Tests both the legacy `sql::Recovery` interface and the newer
102 // `sql::BuiltInRecovery` interface, if it's supported.
103 class SqlRecoveryTest : public SqlRecoveryTestBase,
104                         public testing::WithParamInterface<bool> {
105  public:
106   SqlRecoveryTest() {
107     scoped_feature_list_.InitWithFeatureState(
108         features::kUseBuiltInRecoveryIfSupported, GetParam());
109   }
110
111   bool UseBuiltIn() { return GetParam() && BuiltInRecovery::IsSupported(); }
112 };
113
114 // Tests specific to the newer `sql::BuiltInRecovery` interface.
115 //
116 // Creating a new `SqlRecoveryTest` should be preferred, if possible.
117 // These tests should include a comment indicating why it is not relevant to
118 // the legacy `sql::Recovery` module.
119 class SqlBuiltInRecoveryTest : public SqlRecoveryTestBase {
120  public:
121   SqlBuiltInRecoveryTest() {
122     scoped_feature_list_.InitAndEnableFeature(
123         features::kUseBuiltInRecoveryIfSupported);
124   }
125 };
126
127 // Tests specific to the legacy `sql::Recovery` interface.
128 //
129 // Creating a new `SqlRecoveryTest` should be preferred, if possible.
130 // These tests should include a comment indicating why it is not relevant to
131 // the new `sql::BuiltInRecovery` module.
132 class SqlLegacyRecoveryTest : public SqlRecoveryTestBase {
133  public:
134   SqlLegacyRecoveryTest() {
135     scoped_feature_list_.InitAndDisableFeature(
136         features::kUseBuiltInRecoveryIfSupported);
137   }
138 };
139
140 TEST_F(SqlBuiltInRecoveryTest, ShouldAttemptRecovery) {
141 #if BUILDFLAG(IS_FUCHSIA)
142   // TODO(https://crbug.com/1385500): `BuiltInRecovery` is not yet supported on
143   // Fuchsia.
144   ASSERT_FALSE(BuiltInRecovery::ShouldAttemptRecovery(&db_, SQLITE_CORRUPT));
145 #else
146   // Attempt to recover from corruption.
147   ASSERT_TRUE(BuiltInRecovery::ShouldAttemptRecovery(&db_, SQLITE_CORRUPT));
148
149   // Do not attempt to recover from transient errors.
150   EXPECT_FALSE(BuiltInRecovery::ShouldAttemptRecovery(&db_, SQLITE_BUSY));
151
152   // Do not attempt to recover null databases.
153   EXPECT_FALSE(BuiltInRecovery::ShouldAttemptRecovery(nullptr, SQLITE_CORRUPT));
154
155   // Do not attempt to recover closed databases.
156   Database invalid_db;
157   EXPECT_FALSE(
158       BuiltInRecovery::ShouldAttemptRecovery(&invalid_db, SQLITE_CORRUPT));
159
160   // Do not attempt to recover in-memory databases.
161   ASSERT_TRUE(invalid_db.OpenInMemory());
162   EXPECT_FALSE(
163       BuiltInRecovery::ShouldAttemptRecovery(&invalid_db, SQLITE_CORRUPT));
164
165   // Return true for databases which have an error callback set, even though
166   // the error callback should be reset before recovery is attempted.
167   db_.set_error_callback(base::DoNothing());
168   EXPECT_TRUE(BuiltInRecovery::ShouldAttemptRecovery(&db_, SQLITE_CORRUPT));
169 #endif  // BUILDFLAG(IS_FUCHSIA)
170 }
171
172 // Baseline Recovery test covering the different ways to dispose of the scoped
173 // pointer received from Recovery::Begin().
174 //
175 // This tests behavior of the legacy corruption recovery module which is not
176 // needed in the new API. Specifically, this tests what happens to the Recovery
177 // object when it goes out of scope. The new API does not publicly expose such
178 // an object.
179 TEST_F(SqlLegacyRecoveryTest, RecoverBasic) {
180   static const char kCreateSql[] = "CREATE TABLE x (t TEXT)";
181   static const char kInsertSql[] = "INSERT INTO x VALUES ('This is a test')";
182   static const char kAltInsertSql[] =
183       "INSERT INTO x VALUES ('That was a test')";
184   ASSERT_TRUE(db_.Execute(kCreateSql));
185   ASSERT_TRUE(db_.Execute(kInsertSql));
186   ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
187
188   // If the Recovery handle goes out of scope without being
189   // Recovered(), the database is razed.
190   {
191     std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
192     ASSERT_TRUE(recovery.get());
193   }
194   EXPECT_FALSE(db_.is_open());
195   ASSERT_TRUE(Reopen());
196   EXPECT_TRUE(db_.is_open());
197   ASSERT_EQ("", GetSchema(&db_));
198
199   // Recreate the database.
200   ASSERT_TRUE(db_.Execute(kCreateSql));
201   ASSERT_TRUE(db_.Execute(kInsertSql));
202   ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
203
204   // Unrecoverable() also razes.
205   {
206     std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
207     ASSERT_TRUE(recovery.get());
208     Recovery::Unrecoverable(std::move(recovery));
209
210     // TODO(shess): Test that calls to recover.db_ start failing.
211   }
212   EXPECT_FALSE(db_.is_open());
213   ASSERT_TRUE(Reopen());
214   EXPECT_TRUE(db_.is_open());
215   ASSERT_EQ("", GetSchema(&db_));
216
217   // Attempting to recover a previously-recovered handle fails early.
218   {
219     std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
220     ASSERT_TRUE(recovery.get());
221     recovery.reset();
222
223     recovery = Recovery::Begin(&db_, db_path_);
224     ASSERT_FALSE(recovery.get());
225   }
226   ASSERT_TRUE(Reopen());
227
228   // Recreate the database.
229   ASSERT_TRUE(db_.Execute(kCreateSql));
230   ASSERT_TRUE(db_.Execute(kInsertSql));
231   ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
232
233   // Unrecovered table to distinguish from recovered database.
234   ASSERT_TRUE(db_.Execute("CREATE TABLE y (c INTEGER)"));
235   ASSERT_NE("CREATE TABLE x (t TEXT)", GetSchema(&db_));
236
237   // Recovered() replaces the original with the "recovered" version.
238   {
239     std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
240     ASSERT_TRUE(recovery.get());
241
242     // Create the new version of the table.
243     ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
244
245     // Insert different data to distinguish from original database.
246     ASSERT_TRUE(recovery->db()->Execute(kAltInsertSql));
247
248     // Successfully recovered.
249     ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
250   }
251   EXPECT_FALSE(db_.is_open());
252   ASSERT_TRUE(Reopen());
253   EXPECT_TRUE(db_.is_open());
254   ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
255
256   const char* kXSql = "SELECT * FROM x ORDER BY 1";
257   ASSERT_EQ("That was a test", ExecuteWithResult(&db_, kXSql));
258
259   // Reset the database contents.
260   ASSERT_TRUE(db_.Execute("DELETE FROM x"));
261   ASSERT_TRUE(db_.Execute(kInsertSql));
262
263   // Rollback() discards recovery progress and leaves the database as it was.
264   {
265     std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
266     ASSERT_TRUE(recovery.get());
267
268     ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
269     ASSERT_TRUE(recovery->db()->Execute(kAltInsertSql));
270
271     Recovery::Rollback(std::move(recovery));
272   }
273   EXPECT_FALSE(db_.is_open());
274   ASSERT_TRUE(Reopen());
275   EXPECT_TRUE(db_.is_open());
276   ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
277
278   ASSERT_EQ("This is a test", ExecuteWithResult(&db_, kXSql));
279 }
280
281 // Test operation of the virtual table used by Recovery.
282 //
283 // This tests behavior of the legacy corruption recovery module which is not
284 // needed in the new API. Specifically, this tests what happens to virtual
285 // tables added to the recovery database. The new API does not manually
286 // create the recovery database. Virtual table support is required to use
287 // the built-in module, but many of the other tests in this file would fail
288 // if virtual tables were not supported.
289 TEST_F(SqlLegacyRecoveryTest, VirtualTable) {
290   static const char kCreateSql[] = "CREATE TABLE x (t TEXT)";
291   ASSERT_TRUE(db_.Execute(kCreateSql));
292   ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES ('This is a test')"));
293   ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES ('That was a test')"));
294
295   // Successfully recover the database.
296   {
297     std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
298
299     // Tables to recover original DB, now at [corrupt].
300     static const char kRecoveryCreateSql[] =
301         "CREATE VIRTUAL TABLE temp.recover_x using recover("
302         "  corrupt.x,"
303         "  t TEXT STRICT"
304         ")";
305     ASSERT_TRUE(recovery->db()->Execute(kRecoveryCreateSql));
306
307     // Re-create the original schema.
308     ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
309
310     // Copy the data from the recovery tables to the new database.
311     static const char kRecoveryCopySql[] =
312         "INSERT INTO x SELECT t FROM recover_x";
313     ASSERT_TRUE(recovery->db()->Execute(kRecoveryCopySql));
314
315     // Successfully recovered.
316     ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
317   }
318
319   // Since the database was not corrupt, the entire schema and all
320   // data should be recovered.
321   ASSERT_TRUE(Reopen());
322   ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
323
324   static const char* kXSql = "SELECT * FROM x ORDER BY 1";
325   ASSERT_EQ("That was a test\nThis is a test",
326             ExecuteWithResults(&db_, kXSql, "|", "\n"));
327 }
328
329 // Our corruption handling assumes that a corrupt index doesn't impact
330 // SQL statements that only operate on the associated table. This test verifies
331 // the assumption.
332 //
333 // This tests an assumption of the legacy corruption recovery module which is
334 // irrelevant to the new API. Specifically, that a corrupt index doesn't
335 // impact SQL statements that only operate on the associated table.
336 TEST_F(SqlLegacyRecoveryTest, TableIndependentFromCorruptIndex) {
337   static const char kCreateTable[] =
338       "CREATE TABLE rows(indexed INTEGER NOT NULL, unindexed INTEGER NOT NULL)";
339   ASSERT_TRUE(db_.Execute(kCreateTable));
340   ASSERT_TRUE(db_.Execute("CREATE UNIQUE INDEX rows_index ON rows(indexed)"));
341
342   // Populate the table with powers of two. These numbers make it easy to see if
343   // SUM() missed a row.
344   ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(1, 1)"));
345   ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(2, 2)"));
346   ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(4, 4)"));
347   ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(8, 8)"));
348
349   // SQL statement that performs a table scan. SUM(unindexed) heavily nudges
350   // SQLite to use the table instead of the index.
351   static const char kUnindexedCountSql[] = "SELECT SUM(unindexed) FROM rows";
352   EXPECT_EQ("15", ExecuteWithResult(&db_, kUnindexedCountSql))
353       << "No SQL statement should fail before corruption";
354
355   // SQL statement that performs an index scan.
356   static const char kIndexedCountSql[] =
357       "SELECT SUM(indexed) FROM rows INDEXED BY rows_index";
358   EXPECT_EQ("15", ExecuteWithResult(&db_, kIndexedCountSql))
359       << "Table scan should not fail due to corrupt index";
360
361   db_.Close();
362   ASSERT_TRUE(sql::test::CorruptIndexRootPage(db_path_, "rows_index"));
363   ASSERT_TRUE(Reopen());
364
365   {
366     sql::test::ScopedErrorExpecter expecter;
367     expecter.ExpectError(SQLITE_CORRUPT);
368     EXPECT_FALSE(db_.Execute(kIndexedCountSql))
369         << "Index scan on corrupt index should fail";
370     EXPECT_TRUE(expecter.SawExpectedErrors())
371         << "Index scan on corrupt index should fail";
372   }
373
374   EXPECT_EQ("15", ExecuteWithResult(&db_, kUnindexedCountSql))
375       << "Table scan should not fail due to corrupt index";
376 }
377
378 TEST_P(SqlRecoveryTest, RecoverCorruptIndex) {
379   static const char kCreateTable[] =
380       "CREATE TABLE rows(indexed INTEGER NOT NULL, unindexed INTEGER NOT NULL)";
381   ASSERT_TRUE(db_.Execute(kCreateTable));
382
383   static const char kCreateIndex[] =
384       "CREATE UNIQUE INDEX rows_index ON rows(indexed)";
385   ASSERT_TRUE(db_.Execute(kCreateIndex));
386
387   // Populate the table with powers of two. These numbers make it easy to see if
388   // SUM() missed a row.
389   ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(1, 1)"));
390   ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(2, 2)"));
391   ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(4, 4)"));
392   ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(8, 8)"));
393
394   db_.Close();
395   ASSERT_TRUE(sql::test::CorruptIndexRootPage(db_path_, "rows_index"));
396   ASSERT_TRUE(Reopen());
397
398   int error = SQLITE_OK;
399   db_.set_error_callback(
400       base::BindLambdaForTesting([&](int sqlite_error, Statement* statement) {
401         error = sqlite_error;
402
403         // Recovery::Begin() does not support a pre-existing error callback.
404         db_.reset_error_callback();
405
406         if (UseBuiltIn()) {
407           EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
408                         &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
409                     SqliteResultCode::kOk);
410           histogram_tester_.ExpectUniqueSample(
411               kRecoveryResultHistogramName, BuiltInRecovery::Result::kSuccess,
412               /*expected_bucket_count=*/1);
413           histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
414                                                SqliteLoggedResultCode::kNoError,
415                                                /*expected_bucket_count=*/1);
416           return;
417         }
418
419         std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
420         ASSERT_TRUE(recovery.get());
421
422         ASSERT_TRUE(recovery->db()->Execute(kCreateTable));
423         ASSERT_TRUE(recovery->db()->Execute(kCreateIndex));
424
425         size_t rows = 0;
426         ASSERT_TRUE(recovery->AutoRecoverTable("rows", &rows));
427         ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
428       }));
429
430   // SUM(unindexed) heavily nudges SQLite to use the table instead of the index.
431   static const char kUnindexedCountSql[] = "SELECT SUM(unindexed) FROM rows";
432   EXPECT_EQ("15", ExecuteWithResult(&db_, kUnindexedCountSql))
433       << "Table scan should not fail due to corrupt index";
434   EXPECT_EQ(SQLITE_OK, error)
435       << "Successful statement execution should not invoke the error callback";
436
437   static const char kIndexedCountSql[] =
438       "SELECT SUM(indexed) FROM rows INDEXED BY rows_index";
439   EXPECT_EQ("", ExecuteWithResult(&db_, kIndexedCountSql))
440       << "Index scan on corrupt index should fail";
441   EXPECT_EQ(SQLITE_CORRUPT, error)
442       << "Error callback should be called during scan on corrupt index";
443
444   EXPECT_EQ("", ExecuteWithResult(&db_, kUnindexedCountSql))
445       << "Table scan should not succeed anymore on a poisoned database";
446
447   ASSERT_TRUE(Reopen());
448
449   // The recovered table has consistency between the index and the table.
450   EXPECT_EQ("15", ExecuteWithResult(&db_, kUnindexedCountSql))
451       << "Table should survive database recovery";
452   EXPECT_EQ("15", ExecuteWithResult(&db_, kIndexedCountSql))
453       << "Index should be reconstructed during database recovery";
454 }
455
456 TEST_P(SqlRecoveryTest, RecoverCorruptTable) {
457   // The `filler` column is used to cause a record to overflow multiple pages.
458   static const char kCreateTable[] =
459       // clang-format off
460       "CREATE TABLE rows(indexed INTEGER NOT NULL, unindexed INTEGER NOT NULL,"
461       "filler BLOB NOT NULL)";
462   // clang-format on
463   ASSERT_TRUE(db_.Execute(kCreateTable));
464
465   static const char kCreateIndex[] =
466       "CREATE UNIQUE INDEX rows_index ON rows(indexed)";
467   ASSERT_TRUE(db_.Execute(kCreateIndex));
468
469   // Populate the table with powers of two. These numbers make it easy to see if
470   // SUM() missed a row.
471   ASSERT_TRUE(db_.Execute(
472       "INSERT INTO rows(indexed, unindexed, filler) VALUES(1, 1, x'31')"));
473   ASSERT_TRUE(db_.Execute(
474       "INSERT INTO rows(indexed, unindexed, filler) VALUES(2, 2, x'32')"));
475   ASSERT_TRUE(db_.Execute(
476       "INSERT INTO rows(indexed, unindexed, filler) VALUES(4, 4, x'34')"));
477
478   constexpr int kDbPageSize = 4096;
479   {
480     // Insert a record that will overflow the page.
481     std::vector<uint8_t> large_buffer;
482     ASSERT_EQ(db_.page_size(), kDbPageSize)
483         << "Page overflow relies on specific size";
484     large_buffer.resize(kDbPageSize * 2);
485     base::ranges::fill(large_buffer, '8');
486     sql::Statement insert(db_.GetUniqueStatement(
487         "INSERT INTO rows(indexed,unindexed,filler) VALUES(8,8,?)"));
488     insert.BindBlob(0, large_buffer);
489     ASSERT_TRUE(insert.Run());
490   }
491
492   db_.Close();
493   {
494     // Zero out the last page of the database. This should be the overflow page
495     // allocated for the last inserted row. So, deleting it should corrupt the
496     // rows table.
497     base::File db_file(db_path_, base::File::FLAG_OPEN | base::File::FLAG_READ |
498                                      base::File::FLAG_WRITE);
499     ASSERT_TRUE(db_file.IsValid());
500     int64_t db_size = db_file.GetLength();
501     ASSERT_GT(db_size, kDbPageSize)
502         << "The database should have multiple pages";
503     ASSERT_TRUE(db_file.SetLength(db_size - kDbPageSize));
504   }
505
506   {
507     sql::test::ScopedErrorExpecter expecter;
508     expecter.ExpectError(SQLITE_CORRUPT);
509     ASSERT_TRUE(Reopen());
510     EXPECT_TRUE(expecter.SawExpectedErrors());
511     // PRAGMAs executed inside Database::Open() will error out.
512   }
513
514   int error = SQLITE_OK;
515   db_.set_error_callback(
516       base::BindLambdaForTesting([&](int sqlite_error, Statement* statement) {
517         error = sqlite_error;
518
519         // Recovery::Begin() does not support a pre-existing error callback.
520         db_.reset_error_callback();
521
522         if (UseBuiltIn()) {
523           EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
524                         &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
525                     SqliteResultCode::kOk);
526           return;
527         }
528
529         std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
530         ASSERT_TRUE(recovery.get());
531
532         ASSERT_TRUE(recovery->db()->Execute(kCreateTable));
533         ASSERT_TRUE(recovery->db()->Execute(kCreateIndex));
534
535         size_t rows = 0;
536         ASSERT_TRUE(recovery->AutoRecoverTable("rows", &rows));
537         ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
538       }));
539
540   // SUM(unindexed) heavily nudges SQLite to use the table instead of the index.
541   static const char kUnindexedCountSql[] = "SELECT SUM(unindexed) FROM rows";
542   EXPECT_FALSE(db_.Execute(kUnindexedCountSql))
543       << "Table scan on corrupt table should fail";
544   EXPECT_EQ(SQLITE_CORRUPT, error)
545       << "Error callback should be called during scan on corrupt index";
546
547   ASSERT_TRUE(Reopen());
548
549   // All rows should be recovered. Only the BLOB in the last row was damaged.
550   EXPECT_EQ("15", ExecuteWithResult(&db_, kUnindexedCountSql))
551       << "Table should survive database recovery";
552   static const char kIndexedCountSql[] =
553       "SELECT SUM(indexed) FROM rows INDEXED BY rows_index";
554   EXPECT_EQ("15", ExecuteWithResult(&db_, kIndexedCountSql))
555       << "Index should be reconstructed during database recovery";
556 }
557
558 TEST_P(SqlRecoveryTest, Meta) {
559   const int kVersion = 3;
560   const int kCompatibleVersion = 2;
561
562   {
563     MetaTable meta;
564     EXPECT_TRUE(meta.Init(&db_, kVersion, kCompatibleVersion));
565     EXPECT_EQ(kVersion, meta.GetVersionNumber());
566   }
567
568   // Test expected case where everything works.
569   if (UseBuiltIn()) {
570     EXPECT_EQ(
571         BuiltInRecovery::RecoverDatabase(
572             &db_, BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze),
573         SqliteResultCode::kOk);
574     histogram_tester_.ExpectUniqueSample(kRecoveryResultHistogramName,
575                                          BuiltInRecovery::Result::kSuccess,
576                                          /*expected_bucket_count=*/1);
577     histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
578                                          SqliteLoggedResultCode::kNoError,
579                                          /*expected_bucket_count=*/1);
580   } else {
581     std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
582     EXPECT_TRUE(recovery->SetupMeta());
583     int version = 0;
584     EXPECT_TRUE(recovery->GetMetaVersionNumber(&version));
585     EXPECT_EQ(kVersion, version);
586
587     Recovery::Rollback(std::move(recovery));
588   }
589
590   ASSERT_TRUE(Reopen());  // Handle was poisoned.
591
592   ASSERT_TRUE(db_.DoesTableExist("meta"));
593
594   // Test version row missing.
595   EXPECT_TRUE(db_.Execute("DELETE FROM meta WHERE key = 'version'"));
596
597   if (UseBuiltIn()) {
598     EXPECT_EQ(
599         BuiltInRecovery::RecoverDatabase(
600             &db_, BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze),
601         SqliteResultCode::kError);
602     histogram_tester_.ExpectBucketCount(
603         kRecoveryResultHistogramName,
604         BuiltInRecovery::Result::kFailedMetaTableVersionWasInvalid,
605         /*expected_count=*/1);
606     histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
607                                          SqliteLoggedResultCode::kNoError,
608                                          /*expected_bucket_count=*/2);
609   } else {
610     std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
611     EXPECT_TRUE(recovery->SetupMeta());
612     int version = 0;
613     EXPECT_FALSE(recovery->GetMetaVersionNumber(&version));
614     EXPECT_EQ(0, version);
615
616     Recovery::Rollback(std::move(recovery));
617   }
618   ASSERT_TRUE(Reopen());  // Handle was poisoned.
619
620   // Test meta table missing.
621   if (UseBuiltIn()) {
622     ASSERT_FALSE(db_.DoesTableExist("meta"));
623
624     EXPECT_EQ(
625         BuiltInRecovery::RecoverDatabase(
626             &db_, BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze),
627         SqliteResultCode::kError);
628     histogram_tester_.ExpectBucketCount(
629         kRecoveryResultHistogramName,
630         BuiltInRecovery::Result::kFailedMetaTableDoesNotExist,
631         /*expected_count=*/1);
632     histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
633                                          SqliteLoggedResultCode::kNoError,
634                                          /*expected_bucket_count=*/3);
635   } else {
636     // The table was rolled back after the recovery failure. Manually drop the
637     // table.
638     ASSERT_TRUE(db_.DoesTableExist("meta"));
639     EXPECT_TRUE(db_.Execute("DROP TABLE meta"));
640
641     sql::test::ScopedErrorExpecter expecter;
642     expecter.ExpectError(SQLITE_CORRUPT);  // From virtual table.
643     std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
644     EXPECT_FALSE(recovery->SetupMeta());
645     ASSERT_TRUE(expecter.SawExpectedErrors());
646   }
647 }
648
649 // Baseline AutoRecoverTable() test.
650 TEST_P(SqlRecoveryTest, AutoRecoverTable) {
651   // BIGINT and VARCHAR to test type affinity.
652   static const char kCreateSql[] =
653       "CREATE TABLE x (id BIGINT, t TEXT, v VARCHAR)";
654   ASSERT_TRUE(db_.Execute(kCreateSql));
655   ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (11, 'This is', 'a test')"));
656   ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (5, 'That was', 'a test')"));
657
658   // Save aside a copy of the original schema and data.
659   const std::string orig_schema(GetSchema(&db_));
660   static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
661   const std::string orig_data(ExecuteWithResults(&db_, kXSql, "|", "\n"));
662
663   if (UseBuiltIn()) {
664     EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
665                   &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
666               SqliteResultCode::kOk);
667   } else {
668     // Create a lame-duck table which will not be propagated by recovery to
669     // detect that the recovery code actually ran.
670     ASSERT_TRUE(db_.Execute("CREATE TABLE y (c TEXT)"));
671     ASSERT_NE(orig_schema, GetSchema(&db_));
672
673     std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
674     ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
675
676     // Save a copy of the temp db's schema before recovering the table.
677     static const char kTempSchemaSql[] =
678         "SELECT name, sql FROM sqlite_temp_schema";
679     const std::string temp_schema(
680         ExecuteWithResults(recovery->db(), kTempSchemaSql, "|", "\n"));
681
682     size_t rows = 0;
683     EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
684     EXPECT_EQ(2u, rows);
685
686     // Test that any additional temp tables were cleaned up.
687     EXPECT_EQ(temp_schema,
688               ExecuteWithResults(recovery->db(), kTempSchemaSql, "|", "\n"));
689
690     ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
691   }
692
693   // Since the database was not corrupt, the entire schema and all
694   // data should be recovered.
695   ASSERT_TRUE(Reopen());
696   ASSERT_EQ(orig_schema, GetSchema(&db_));
697   ASSERT_EQ(orig_data, ExecuteWithResults(&db_, kXSql, "|", "\n"));
698
699   // Recovery fails if the target table doesn't exist.
700   if (UseBuiltIn()) {
701     // ... or it can succeed silently, since there's nothing to do.
702     EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
703                   &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
704               SqliteResultCode::kOk);
705   } else {
706     std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
707     ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
708
709     // TODO(shess): Should this failure implicitly lead to Raze()?
710     size_t rows = 0;
711     EXPECT_FALSE(recovery->AutoRecoverTable("y", &rows));
712
713     Recovery::Unrecoverable(std::move(recovery));
714   }
715 }
716
717 // Test that default values correctly replace nulls.  The recovery
718 // virtual table reads directly from the database, so DEFAULT is not
719 // interpreted at that level.
720 TEST_P(SqlRecoveryTest, AutoRecoverTableWithDefault) {
721   ASSERT_TRUE(db_.Execute("CREATE TABLE x (id INTEGER)"));
722   ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (5)"));
723   ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (15)"));
724
725   // ALTER effectively leaves the new columns NULL in the first two
726   // rows.  The row with 17 will get the default injected at insert
727   // time, while the row with 42 will get the actual value provided.
728   // Embedded "'" to make sure default-handling continues to be quoted
729   // correctly.
730   ASSERT_TRUE(db_.Execute("ALTER TABLE x ADD COLUMN t TEXT DEFAULT 'a''a'"));
731   ASSERT_TRUE(db_.Execute("ALTER TABLE x ADD COLUMN b BLOB DEFAULT x'AA55'"));
732   ASSERT_TRUE(db_.Execute("ALTER TABLE x ADD COLUMN i INT DEFAULT 93"));
733   ASSERT_TRUE(db_.Execute("INSERT INTO x (id) VALUES (17)"));
734   ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (42, 'b', x'1234', 12)"));
735
736   // Save aside a copy of the original schema and data.
737   const std::string orig_schema(GetSchema(&db_));
738   static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
739   const std::string orig_data(ExecuteWithResults(&db_, kXSql, "|", "\n"));
740
741   std::string final_schema(orig_schema);
742   std::string final_data(orig_data);
743   if (UseBuiltIn()) {
744     EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
745                   &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
746               SqliteResultCode::kOk);
747   } else {
748     // Create a lame-duck table which will not be propagated by recovery to
749     // detect that the recovery code actually ran.
750     ASSERT_TRUE(db_.Execute("CREATE TABLE y (c TEXT)"));
751     ASSERT_NE(orig_schema, GetSchema(&db_));
752
753     // Mechanically adjust the stored schema and data to allow detecting
754     // where the default value is coming from.  The target table is just
755     // like the original with the default for [t] changed, to signal
756     // defaults coming from the recovery system.  The two %5 rows should
757     // get the target-table default for [t], while the others should get
758     // the source-table default.
759     size_t pos;
760     while ((pos = final_schema.find("'a''a'")) != std::string::npos) {
761       final_schema.replace(pos, 6, "'c''c'");
762     }
763     while ((pos = final_data.find("5|a'a")) != std::string::npos) {
764       final_data.replace(pos, 5, "5|c'c");
765     }
766     std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
767     // Different default to detect which table provides the default.
768     ASSERT_TRUE(recovery->db()->Execute(final_schema.c_str()));
769
770     size_t rows = 0;
771     EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
772     EXPECT_EQ(4u, rows);
773
774     ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
775   }
776
777   // Since the database was not corrupt, the entire schema and all
778   // data should be recovered.
779   ASSERT_TRUE(Reopen());
780   ASSERT_EQ(final_schema, GetSchema(&db_));
781   ASSERT_EQ(final_data, ExecuteWithResults(&db_, kXSql, "|", "\n"));
782 }
783
784 // Test that rows with NULL in a NOT NULL column are filtered
785 // correctly.  In the wild, this would probably happen due to
786 // corruption, but here it is simulated by recovering a table which
787 // allowed nulls into a table which does not.
788 //
789 // This tests behavior of the legacy corruption recovery module which is not
790 // needed in the new API. Specifically, this tests what happens to tables
791 // with NULL values in non-nullable columns. This test is not easily
792 // replicated, since SQLite does not support ALTER COLUMN. We'll trust that
793 // it's handled properly upstream.
794 TEST_F(SqlLegacyRecoveryTest, AutoRecoverTableNullFilter) {
795   static const char kOrigSchema[] = "CREATE TABLE x (id INTEGER, t TEXT)";
796   static const char kFinalSchema[] =
797       "CREATE TABLE x (id INTEGER, t TEXT NOT NULL)";
798
799   ASSERT_TRUE(db_.Execute(kOrigSchema));
800   ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (5, NULL)"));
801   ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (15, 'this is a test')"));
802
803   // Create a lame-duck table which will not be propagated by recovery to
804   // detect that the recovery code actually ran.
805   ASSERT_EQ(kOrigSchema, GetSchema(&db_));
806   ASSERT_TRUE(db_.Execute("CREATE TABLE y (c TEXT)"));
807   ASSERT_NE(kOrigSchema, GetSchema(&db_));
808
809   {
810     std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
811     ASSERT_TRUE(recovery->db()->Execute(kFinalSchema));
812
813     size_t rows = 0;
814     EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
815     EXPECT_EQ(1u, rows);
816
817     ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
818   }
819
820   // The schema should be the same, but only one row of data should
821   // have been recovered.
822   ASSERT_TRUE(Reopen());
823   ASSERT_EQ(kFinalSchema, GetSchema(&db_));
824   static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
825   ASSERT_EQ("15|this is a test", ExecuteWithResults(&db_, kXSql, "|", "\n"));
826 }
827
828 // Test AutoRecoverTable with a ROWID alias.
829 TEST_P(SqlRecoveryTest, AutoRecoverTableWithRowid) {
830   // The rowid alias is almost always the first column, intentionally
831   // put it later.
832   static const char kCreateSql[] =
833       "CREATE TABLE x (t TEXT, id INTEGER PRIMARY KEY NOT NULL)";
834   ASSERT_TRUE(db_.Execute(kCreateSql));
835   ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES ('This is a test', NULL)"));
836   ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES ('That was a test', NULL)"));
837
838   // Save aside a copy of the original schema and data.
839   const std::string orig_schema(GetSchema(&db_));
840   static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
841   const std::string orig_data(ExecuteWithResults(&db_, kXSql, "|", "\n"));
842
843   if (UseBuiltIn()) {
844     EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
845                   &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
846               SqliteResultCode::kOk);
847   } else {
848     // Create a lame-duck table which will not be propagated by recovery to
849     // detect that the recovery code actually ran.
850     ASSERT_TRUE(db_.Execute("CREATE TABLE y (c TEXT)"));
851     ASSERT_NE(orig_schema, GetSchema(&db_));
852
853     std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
854     ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
855
856     size_t rows = 0;
857     EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
858     EXPECT_EQ(2u, rows);
859
860     ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
861   }
862
863   // Since the database was not corrupt, the entire schema and all
864   // data should be recovered.
865   ASSERT_TRUE(Reopen());
866   ASSERT_EQ(orig_schema, GetSchema(&db_));
867   ASSERT_EQ(orig_data, ExecuteWithResults(&db_, kXSql, "|", "\n"));
868 }
869
870 // Test that a compound primary key doesn't fire the ROWID code.
871 //
872 // This tests behavior of the legacy corruption recovery module which is not
873 // needed in the new API. Specifically, this tests how ROWID tables are
874 // handled.
875 TEST_F(SqlLegacyRecoveryTest, AutoRecoverTableWithCompoundKey) {
876   static const char kCreateSql[] =
877       "CREATE TABLE x ("
878       "id INTEGER NOT NULL,"
879       "id2 TEXT NOT NULL,"
880       "t TEXT,"
881       "PRIMARY KEY (id, id2)"
882       ")";
883   ASSERT_TRUE(db_.Execute(kCreateSql));
884
885   // NOTE(shess): Do not accidentally use [id] 1, 2, 3, as those will
886   // be the ROWID values.
887   ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (1, 'a', 'This is a test')"));
888   ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (1, 'b', 'That was a test')"));
889   ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (2, 'a', 'Another test')"));
890
891   // Save aside a copy of the original schema and data.
892   const std::string orig_schema(GetSchema(&db_));
893   static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
894   const std::string orig_data(ExecuteWithResults(&db_, kXSql, "|", "\n"));
895
896   // Create a lame-duck table which will not be propagated by recovery to
897   // detect that the recovery code actually ran.
898   ASSERT_TRUE(db_.Execute("CREATE TABLE y (c TEXT)"));
899   ASSERT_NE(orig_schema, GetSchema(&db_));
900
901   {
902     std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
903     ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
904
905     size_t rows = 0;
906     EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
907     EXPECT_EQ(3u, rows);
908
909     ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
910   }
911
912   // Since the database was not corrupt, the entire schema and all
913   // data should be recovered.
914   ASSERT_TRUE(Reopen());
915   ASSERT_EQ(orig_schema, GetSchema(&db_));
916   ASSERT_EQ(orig_data, ExecuteWithResults(&db_, kXSql, "|", "\n"));
917 }
918
919 // Test recovering from a table with fewer columns than the target.
920 //
921 // This tests behavior of the legacy corruption recovery module which is not
922 // needed in the new API. Specifically, this tests how tables with missing
923 // columns are handled.
924 TEST_F(SqlLegacyRecoveryTest, AutoRecoverTableMissingColumns) {
925   static const char kCreateSql[] =
926       "CREATE TABLE x (id INTEGER PRIMARY KEY, t0 TEXT)";
927   static const char kAlterSql[] =
928       "ALTER TABLE x ADD COLUMN t1 TEXT DEFAULT 't'";
929   ASSERT_TRUE(db_.Execute(kCreateSql));
930   ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (1, 'This is')"));
931   ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (2, 'That was')"));
932
933   // Generate the expected info by faking a table to match what recovery will
934   // create.
935   const std::string orig_schema(GetSchema(&db_));
936   static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
937   std::string expected_schema;
938   std::string expected_data;
939   {
940     ASSERT_TRUE(db_.BeginTransaction());
941     ASSERT_TRUE(db_.Execute(kAlterSql));
942
943     expected_schema = GetSchema(&db_);
944     expected_data = ExecuteWithResults(&db_, kXSql, "|", "\n");
945
946     db_.RollbackTransaction();
947   }
948
949   // Following tests are pointless if the rollback didn't work.
950   ASSERT_EQ(orig_schema, GetSchema(&db_));
951
952   // Recover the previous version of the table into the altered version.
953   {
954     std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
955     ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
956     ASSERT_TRUE(recovery->db()->Execute(kAlterSql));
957     size_t rows = 0;
958     EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
959     EXPECT_EQ(2u, rows);
960     ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
961   }
962
963   // Since the database was not corrupt, the entire schema and all
964   // data should be recovered.
965   ASSERT_TRUE(Reopen());
966   ASSERT_EQ(expected_schema, GetSchema(&db_));
967   ASSERT_EQ(expected_data, ExecuteWithResults(&db_, kXSql, "|", "\n"));
968 }
969
970 // Recover a golden file where an interior page has been manually modified so
971 // that the number of cells is greater than will fit on a single page.  This
972 // case happened in <http://crbug.com/387868>.
973 TEST_P(SqlRecoveryTest, Bug387868) {
974   base::FilePath golden_path;
975   ASSERT_TRUE(base::PathService::Get(sql::test::DIR_TEST_DATA, &golden_path));
976   golden_path = golden_path.AppendASCII("recovery_387868");
977   db_.Close();
978   ASSERT_TRUE(base::CopyFile(golden_path, db_path_));
979   ASSERT_TRUE(Reopen());
980
981   if (UseBuiltIn()) {
982     EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
983                   &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
984               SqliteResultCode::kOk);
985   } else {
986     std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
987     ASSERT_TRUE(recovery.get());
988
989     // Create the new version of the table.
990     static const char kCreateSql[] =
991         "CREATE TABLE x (id INTEGER PRIMARY KEY, t0 TEXT)";
992     ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
993
994     size_t rows = 0;
995     EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
996     EXPECT_EQ(43u, rows);
997
998     // Successfully recovered.
999     EXPECT_TRUE(Recovery::Recovered(std::move(recovery)));
1000   }
1001 }
1002
1003 // Memory-mapped I/O interacts poorly with I/O errors.  Make sure the recovery
1004 // database doesn't accidentally enable it.
1005 //
1006 // This tests behavior of the legacy corruption recovery module which is not
1007 // needed in the new API. Specifically, that MMAPing is disabled.
1008 TEST_F(SqlLegacyRecoveryTest, NoMmap) {
1009   std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
1010   ASSERT_TRUE(recovery.get());
1011
1012   // In the current implementation, the PRAGMA successfully runs with no result
1013   // rows.  Running with a single result of |0| is also acceptable.
1014   Statement s(recovery->db()->GetUniqueStatement("PRAGMA mmap_size"));
1015   EXPECT_TRUE(!s.Step() || !s.ColumnInt64(0));
1016 }
1017
1018 void TestRecoverDatabase(Database& db,
1019                          const base::FilePath& db_path,
1020                          bool with_meta,
1021                          base::OnceClosure run_recovery) {
1022   const int kVersion = 3;
1023   const int kCompatibleVersion = 2;
1024
1025   if (with_meta) {
1026     MetaTable meta;
1027     EXPECT_TRUE(meta.Init(&db, kVersion, kCompatibleVersion));
1028     EXPECT_EQ(kVersion, meta.GetVersionNumber());
1029     EXPECT_EQ(kCompatibleVersion, meta.GetCompatibleVersionNumber());
1030   }
1031
1032   // As a side effect, AUTOINCREMENT creates the sqlite_sequence table for
1033   // RecoverDatabase() to handle.
1034   ASSERT_TRUE(db.Execute(
1035       "CREATE TABLE table1(id INTEGER PRIMARY KEY AUTOINCREMENT, value TEXT)"));
1036   EXPECT_TRUE(db.Execute("INSERT INTO table1(value) VALUES('turtle')"));
1037   EXPECT_TRUE(db.Execute("INSERT INTO table1(value) VALUES('truck')"));
1038   EXPECT_TRUE(db.Execute("INSERT INTO table1(value) VALUES('trailer')"));
1039
1040   // This table needs index and a unique index to work.
1041   ASSERT_TRUE(db.Execute("CREATE TABLE table2(name TEXT, value TEXT)"));
1042   ASSERT_TRUE(db.Execute("CREATE UNIQUE INDEX table2_name ON table2(name)"));
1043   ASSERT_TRUE(db.Execute("CREATE INDEX table2_value ON table2(value)"));
1044   EXPECT_TRUE(
1045       db.Execute("INSERT INTO table2(name, value) VALUES('jim', 'telephone')"));
1046   EXPECT_TRUE(
1047       db.Execute("INSERT INTO table2(name, value) VALUES('bob', 'truck')"));
1048   EXPECT_TRUE(
1049       db.Execute("INSERT INTO table2(name, value) VALUES('dean', 'trailer')"));
1050
1051   // Save aside a copy of the original schema, verifying that it has the created
1052   // items plus the sqlite_sequence table.
1053   const std::string original_schema = GetSchema(&db);
1054   ASSERT_EQ(with_meta ? 6 : 4, base::ranges::count(original_schema, '\n'))
1055       << original_schema;
1056
1057   static constexpr char kTable1Sql[] = "SELECT * FROM table1 ORDER BY 1";
1058   static constexpr char kTable2Sql[] = "SELECT * FROM table2 ORDER BY 1";
1059   EXPECT_EQ("1|turtle\n2|truck\n3|trailer",
1060             ExecuteWithResults(&db, kTable1Sql, "|", "\n"));
1061   EXPECT_EQ("bob|truck\ndean|trailer\njim|telephone",
1062             ExecuteWithResults(&db, kTable2Sql, "|", "\n"));
1063
1064   // Database handle is valid before recovery, poisoned after.
1065   static constexpr char kTrivialSql[] = "SELECT COUNT(*) FROM sqlite_schema";
1066   EXPECT_TRUE(db.IsSQLValid(kTrivialSql));
1067
1068   std::move(run_recovery).Run();
1069
1070   EXPECT_FALSE(db.is_open());
1071
1072   // Since the database was not corrupt, the entire schema and all data should
1073   // be recovered. Re-open the database.
1074   db.Close();
1075   ASSERT_TRUE(db.Open(db_path));
1076   ASSERT_EQ(original_schema, GetSchema(&db));
1077   EXPECT_EQ("1|turtle\n2|truck\n3|trailer",
1078             ExecuteWithResults(&db, kTable1Sql, "|", "\n"));
1079   EXPECT_EQ("bob|truck\ndean|trailer\njim|telephone",
1080             ExecuteWithResults(&db, kTable2Sql, "|", "\n"));
1081
1082   if (with_meta) {
1083     MetaTable meta;
1084     EXPECT_TRUE(meta.Init(&db, kVersion, kCompatibleVersion));
1085     EXPECT_EQ(kVersion, meta.GetVersionNumber());
1086     EXPECT_EQ(kCompatibleVersion, meta.GetCompatibleVersionNumber());
1087   }
1088 }
1089
1090 TEST_P(SqlRecoveryTest, RecoverDatabase) {
1091   auto run_recovery = base::BindLambdaForTesting([&]() {
1092     if (UseBuiltIn()) {
1093       EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
1094                     &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
1095                 SqliteResultCode::kOk);
1096     } else {
1097       Recovery::RecoverDatabase(&db_, db_path_);
1098     }
1099   });
1100
1101   TestRecoverDatabase(db_, db_path_, /*with_meta=*/false,
1102                       std::move(run_recovery));
1103 }
1104
1105 TEST_P(SqlRecoveryTest, RecoverDatabaseMeta) {
1106   auto run_recovery = base::BindLambdaForTesting([&]() {
1107     if (UseBuiltIn()) {
1108       EXPECT_EQ(
1109           BuiltInRecovery::RecoverDatabase(
1110               &db_, BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze),
1111           SqliteResultCode::kOk);
1112     } else {
1113       Recovery::RecoverDatabaseWithMetaVersion(&db_, db_path_);
1114     }
1115   });
1116
1117   TestRecoverDatabase(db_, db_path_, /*with_meta=*/true,
1118                       std::move(run_recovery));
1119 }
1120
1121 TEST_P(SqlRecoveryTest, RecoverIfPossible) {
1122   auto run_recovery = base::BindLambdaForTesting([&]() {
1123     EXPECT_TRUE(BuiltInRecovery::RecoverIfPossible(
1124         &db_, SQLITE_CORRUPT, BuiltInRecovery::Strategy::kRecoverOrRaze));
1125   });
1126
1127   TestRecoverDatabase(db_, db_path_, /*with_meta=*/false,
1128                       std::move(run_recovery));
1129 }
1130
1131 TEST_P(SqlRecoveryTest, RecoverIfPossibleMeta) {
1132   auto run_recovery = base::BindLambdaForTesting([&]() {
1133     EXPECT_TRUE(BuiltInRecovery::RecoverIfPossible(
1134         &db_, SQLITE_CORRUPT,
1135         BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze));
1136   });
1137
1138   TestRecoverDatabase(db_, db_path_, /*with_meta=*/true,
1139                       std::move(run_recovery));
1140 }
1141
1142 TEST_P(SqlRecoveryTest, RecoverIfPossibleWithErrorCallback) {
1143   auto run_recovery = base::BindLambdaForTesting([&]() {
1144     db_.set_error_callback(base::DoNothing());
1145     // The error callback should be reset during `RecoverIfPossible()` if
1146     // recovery was attempted.
1147     bool recovery_was_attempted = BuiltInRecovery::RecoverIfPossible(
1148         &db_, SQLITE_CORRUPT,
1149         BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze);
1150     EXPECT_TRUE(recovery_was_attempted);
1151     EXPECT_NE(db_.has_error_callback(), recovery_was_attempted);
1152   });
1153
1154   TestRecoverDatabase(db_, db_path_, /*with_meta=*/true,
1155                       std::move(run_recovery));
1156 }
1157
1158 TEST_P(SqlRecoveryTest, RecoverIfPossibleWithClosedDatabase) {
1159   auto run_recovery = base::BindLambdaForTesting([&]() {
1160     // Recovery should not be attempted on a closed database.
1161     db_.Close();
1162
1163     EXPECT_FALSE(BuiltInRecovery::RecoverIfPossible(
1164         &db_, SQLITE_CORRUPT, BuiltInRecovery::Strategy::kRecoverOrRaze));
1165   });
1166
1167   TestRecoverDatabase(db_, db_path_, /*with_meta=*/false,
1168                       std::move(run_recovery));
1169 }
1170
1171 TEST_P(SqlRecoveryTest, RecoverIfPossibleWithPerDatabaseUma) {
1172   auto run_recovery = base::BindLambdaForTesting([&]() {
1173     EXPECT_TRUE(BuiltInRecovery::RecoverIfPossible(
1174         &db_, SQLITE_CORRUPT, BuiltInRecovery::Strategy::kRecoverOrRaze,
1175         &features::kUseBuiltInRecoveryIfSupported, "MyFeatureDatabase"));
1176   });
1177
1178   TestRecoverDatabase(db_, db_path_, /*with_meta=*/false,
1179                       std::move(run_recovery));
1180
1181   if (UseBuiltIn()) {
1182     // Log to the overall histograms.
1183     histogram_tester_.ExpectUniqueSample(kRecoveryResultHistogramName,
1184                                          BuiltInRecovery::Result::kSuccess,
1185                                          /*expected_bucket_count=*/1);
1186     histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
1187                                          SqliteLoggedResultCode::kNoError,
1188                                          /*expected_bucket_count=*/1);
1189     // And the histograms for this specific feature.
1190     histogram_tester_.ExpectUniqueSample(
1191         base::StrCat({kRecoveryResultHistogramName, ".MyFeatureDatabase"}),
1192         BuiltInRecovery::Result::kSuccess,
1193         /*expected_bucket_count=*/1);
1194     histogram_tester_.ExpectUniqueSample(
1195         base::StrCat({kRecoveryResultCodeHistogramName, ".MyFeatureDatabase"}),
1196         SqliteLoggedResultCode::kNoError,
1197         /*expected_bucket_count=*/1);
1198   }
1199 }
1200
1201 TEST_P(SqlRecoveryTest, RecoverDatabaseWithView) {
1202   db_.Close();
1203   sql::Database db({.enable_views_discouraged = true});
1204   ASSERT_TRUE(db.Open(db_path_));
1205
1206   ASSERT_TRUE(db.Execute(
1207       "CREATE TABLE table1(id INTEGER PRIMARY KEY AUTOINCREMENT, value TEXT)"));
1208   EXPECT_TRUE(db.Execute("INSERT INTO table1(value) VALUES('turtle')"));
1209   EXPECT_TRUE(db.Execute("INSERT INTO table1(value) VALUES('truck')"));
1210   EXPECT_TRUE(db.Execute("INSERT INTO table1(value) VALUES('trailer')"));
1211
1212   ASSERT_TRUE(db.Execute("CREATE TABLE table2(name TEXT, value TEXT)"));
1213   ASSERT_TRUE(db.Execute("CREATE UNIQUE INDEX table2_name ON table2(name)"));
1214   EXPECT_TRUE(
1215       db.Execute("INSERT INTO table2(name, value) VALUES('jim', 'telephone')"));
1216   EXPECT_TRUE(
1217       db.Execute("INSERT INTO table2(name, value) VALUES('bob', 'truck')"));
1218   EXPECT_TRUE(
1219       db.Execute("INSERT INTO table2(name, value) VALUES('dean', 'trailer')"));
1220
1221   // View which is the intersection of [table1.value] and [table2.value].
1222   ASSERT_TRUE(db.Execute(
1223       "CREATE VIEW view_table12 AS SELECT table1.value FROM table1, table2 "
1224       "WHERE table1.value = table2.value"));
1225
1226   static constexpr char kViewSql[] = "SELECT * FROM view_table12 ORDER BY 1";
1227   EXPECT_EQ("trailer\ntruck", ExecuteWithResults(&db, kViewSql, "|", "\n"));
1228
1229   // Save aside a copy of the original schema, verifying that it has the created
1230   // items plus the sqlite_sequence table.
1231   const std::string original_schema = GetSchema(&db);
1232   ASSERT_EQ(4, base::ranges::count(original_schema, '\n')) << original_schema;
1233
1234   // Database handle is valid before recovery, poisoned after.
1235   static constexpr char kTrivialSql[] = "SELECT COUNT(*) FROM sqlite_schema";
1236   EXPECT_TRUE(db.IsSQLValid(kTrivialSql));
1237   if (UseBuiltIn()) {
1238     EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
1239                   &db, BuiltInRecovery::Strategy::kRecoverOrRaze),
1240               SqliteResultCode::kOk);
1241   } else {
1242     Recovery::RecoverDatabase(&db, db_path_);
1243   }
1244   EXPECT_FALSE(db.IsSQLValid(kTrivialSql));
1245
1246   // Since the database was not corrupt, the entire schema and all data should
1247   // be recovered.
1248   db.Close();
1249   ASSERT_TRUE(db.Open(db_path_));
1250   EXPECT_EQ("trailer\ntruck", ExecuteWithResults(&db, kViewSql, "|", "\n"));
1251 }
1252
1253 // When RecoverDatabase() encounters SQLITE_NOTADB, the database is deleted.
1254 TEST_P(SqlRecoveryTest, RecoverDatabaseDelete) {
1255   // Create a valid database, then write junk over the header.  This should lead
1256   // to SQLITE_NOTADB, which will cause ATTACH to fail.
1257   ASSERT_TRUE(db_.Execute("CREATE TABLE x (t TEXT)"));
1258   ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES ('This is a test')"));
1259   db_.Close();
1260   ASSERT_TRUE(OverwriteDatabaseHeader());
1261
1262   {
1263     sql::test::ScopedErrorExpecter expecter;
1264     expecter.ExpectError(SQLITE_NOTADB);
1265
1266     // Reopen() here because it will see SQLITE_NOTADB.
1267     ASSERT_TRUE(Reopen());
1268
1269     // This should "recover" the database by making it valid, but empty.
1270     if (UseBuiltIn()) {
1271       EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
1272                     &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
1273                 SqliteResultCode::kNotADatabase);
1274       histogram_tester_.ExpectUniqueSample(
1275           kRecoveryResultHistogramName,
1276           BuiltInRecovery::Result::kFailedRecoveryRun,
1277           /*expected_bucket_count=*/1);
1278       histogram_tester_.ExpectUniqueSample(
1279           kRecoveryResultCodeHistogramName,
1280           SqliteLoggedResultCode::kNotADatabase,
1281           /*expected_bucket_count=*/1);
1282     } else {
1283       Recovery::RecoverDatabase(&db_, db_path_);
1284     }
1285     ASSERT_TRUE(expecter.SawExpectedErrors());
1286   }
1287
1288   // Recovery poisoned the handle, must re-open.
1289   db_.Close();
1290   ASSERT_TRUE(Reopen());
1291
1292   EXPECT_EQ("", GetSchema(&db_));
1293 }
1294
1295 // Allow callers to validate the database between recovery and commit.
1296 TEST_P(SqlRecoveryTest, BeginRecoverDatabase) {
1297   static const char kCreateTable[] =
1298       "CREATE TABLE rows(indexed INTEGER NOT NULL, unindexed INTEGER NOT NULL)";
1299   ASSERT_TRUE(db_.Execute(kCreateTable));
1300
1301   ASSERT_TRUE(db_.Execute("CREATE UNIQUE INDEX rows_index ON rows(indexed)"));
1302
1303   // Populate the table with powers of two. These numbers make it easy to see if
1304   // SUM() missed a row.
1305   ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(1, 1)"));
1306   ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(2, 2)"));
1307   ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(4, 4)"));
1308   ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(8, 8)"));
1309
1310   db_.Close();
1311   ASSERT_TRUE(sql::test::CorruptIndexRootPage(db_path_, "rows_index"));
1312   ASSERT_TRUE(Reopen());
1313
1314   // Run recovery code, then rollback.  Database remains the same.
1315   {
1316     std::unique_ptr<Recovery> recovery =
1317         Recovery::BeginRecoverDatabase(&db_, db_path_);
1318     ASSERT_TRUE(recovery);
1319     Recovery::Rollback(std::move(recovery));
1320   }
1321   db_.Close();
1322   ASSERT_TRUE(Reopen());
1323
1324   static const char kIndexedCountSql[] =
1325       "SELECT SUM(indexed) FROM rows INDEXED BY rows_index";
1326   {
1327     sql::test::ScopedErrorExpecter expecter;
1328     expecter.ExpectError(SQLITE_CORRUPT);
1329     EXPECT_EQ("", ExecuteWithResult(&db_, kIndexedCountSql))
1330         << "Index should still be corrupted after recovery rollback";
1331     EXPECT_TRUE(expecter.SawExpectedErrors())
1332         << "Index should still be corrupted after recovery rollback";
1333   }
1334
1335   // Run recovery code, then commit.  The index is recovered.
1336   if (UseBuiltIn()) {
1337     EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
1338                   &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
1339               SqliteResultCode::kOk);
1340   } else {
1341     std::unique_ptr<Recovery> recovery =
1342         Recovery::BeginRecoverDatabase(&db_, db_path_);
1343     ASSERT_TRUE(recovery);
1344     ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
1345   }
1346   db_.Close();
1347   ASSERT_TRUE(Reopen());
1348
1349   EXPECT_EQ("15", ExecuteWithResult(&db_, kIndexedCountSql))
1350       << "Index should be reconstructed after database recovery";
1351 }
1352
1353 TEST_P(SqlRecoveryTest, AttachFailure) {
1354   // Create a valid database, then write junk over the header.  This should lead
1355   // to SQLITE_NOTADB, which will cause ATTACH to fail.
1356   ASSERT_TRUE(db_.Execute("CREATE TABLE x (t TEXT)"));
1357   ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES ('This is a test')"));
1358   db_.Close();
1359   ASSERT_TRUE(OverwriteDatabaseHeader());
1360
1361   {
1362     sql::test::ScopedErrorExpecter expecter;
1363     expecter.ExpectError(SQLITE_NOTADB);
1364
1365     // Reopen() here because it will see SQLITE_NOTADB.
1366     ASSERT_TRUE(Reopen());
1367
1368     // Begin() should fail.
1369     if (UseBuiltIn()) {
1370       EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
1371                     &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
1372                 SqliteResultCode::kNotADatabase);
1373       histogram_tester_.ExpectUniqueSample(
1374           kRecoveryResultHistogramName,
1375           BuiltInRecovery::Result::kFailedRecoveryRun,
1376           /*expected_bucket_count=*/1);
1377       histogram_tester_.ExpectUniqueSample(
1378           kRecoveryResultCodeHistogramName,
1379           SqliteLoggedResultCode::kNotADatabase,
1380           /*expected_bucket_count=*/1);
1381     } else {
1382       std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
1383       EXPECT_FALSE(recovery.get());
1384     }
1385     ASSERT_TRUE(expecter.SawExpectedErrors());
1386   }
1387 }
1388
1389 // Helper for SqlRecoveryTest.PageSize.  Creates a fresh db based on db_prefix,
1390 // with the given initial page size, and verifies it against the expected size.
1391 // Then changes to the final page size and recovers, verifying that the
1392 // recovered database ends up with the expected final page size.
1393 void TestPageSize(const base::FilePath& db_prefix,
1394                   int initial_page_size,
1395                   const std::string& expected_initial_page_size,
1396                   int final_page_size,
1397                   const std::string& expected_final_page_size,
1398                   bool use_built_in) {
1399   static const char kCreateSql[] = "CREATE TABLE x (t TEXT)";
1400   static const char kInsertSql1[] = "INSERT INTO x VALUES ('This is a test')";
1401   static const char kInsertSql2[] = "INSERT INTO x VALUES ('That was a test')";
1402   static const char kSelectSql[] = "SELECT * FROM x ORDER BY t";
1403
1404   const base::FilePath db_path = db_prefix.InsertBeforeExtensionASCII(
1405       base::NumberToString(initial_page_size));
1406   Database::Delete(db_path);
1407   Database db({.page_size = initial_page_size});
1408   ASSERT_TRUE(db.Open(db_path));
1409   ASSERT_TRUE(db.Execute(kCreateSql));
1410   ASSERT_TRUE(db.Execute(kInsertSql1));
1411   ASSERT_TRUE(db.Execute(kInsertSql2));
1412   ASSERT_EQ(expected_initial_page_size,
1413             ExecuteWithResult(&db, "PRAGMA page_size"));
1414   db.Close();
1415
1416   // Re-open the database while setting a new |options.page_size| in the object.
1417   Database recover_db({.page_size = final_page_size});
1418   ASSERT_TRUE(recover_db.Open(db_path));
1419   // Recovery will use the page size set in the database object, which may not
1420   // match the file's page size.
1421   if (use_built_in) {
1422     EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
1423                   &recover_db, BuiltInRecovery::Strategy::kRecoverOrRaze),
1424               SqliteResultCode::kOk);
1425   } else {
1426     Recovery::RecoverDatabase(&recover_db, db_path);
1427   }
1428
1429   // Recovery poisoned the handle, must re-open.
1430   recover_db.Close();
1431
1432   // Make sure the page size is read from the file.
1433   Database recovered_db({.page_size = DatabaseOptions::kDefaultPageSize});
1434   ASSERT_TRUE(recovered_db.Open(db_path));
1435   ASSERT_EQ(expected_final_page_size,
1436             ExecuteWithResult(&recovered_db, "PRAGMA page_size"));
1437   EXPECT_EQ("That was a test\nThis is a test",
1438             ExecuteWithResults(&recovered_db, kSelectSql, "|", "\n"));
1439 }
1440
1441 // Verify that Recovery maintains the page size, and the virtual table
1442 // works with page sizes other than SQLite's default.  Also verify the case
1443 // where the default page size has changed.
1444 TEST_P(SqlRecoveryTest, PageSize) {
1445   const std::string default_page_size =
1446       ExecuteWithResult(&db_, "PRAGMA page_size");
1447
1448   // Check the default page size first.
1449   EXPECT_NO_FATAL_FAILURE(TestPageSize(
1450       db_path_, DatabaseOptions::kDefaultPageSize, default_page_size,
1451       DatabaseOptions::kDefaultPageSize, default_page_size, UseBuiltIn()));
1452
1453   // Sync uses 32k pages.
1454   EXPECT_NO_FATAL_FAILURE(
1455       TestPageSize(db_path_, 32768, "32768", 32768, "32768", UseBuiltIn()));
1456
1457   // Many clients use 4k pages.  This is the SQLite default after 3.12.0.
1458   EXPECT_NO_FATAL_FAILURE(
1459       TestPageSize(db_path_, 4096, "4096", 4096, "4096", UseBuiltIn()));
1460
1461   // 1k is the default page size before 3.12.0.
1462   EXPECT_NO_FATAL_FAILURE(
1463       TestPageSize(db_path_, 1024, "1024", 1024, "1024", UseBuiltIn()));
1464
1465   ASSERT_NE("2048", default_page_size);
1466   if (UseBuiltIn()) {
1467     // Databases with no page size specified should recover to the page size of
1468     // the source database.
1469     EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path_, 2048, "2048",
1470                                          DatabaseOptions::kDefaultPageSize,
1471                                          "2048", UseBuiltIn()));
1472   } else {
1473     // Databases with no page size specified should recover with the new default
1474     // page size.  2k has never been the default page size.
1475     EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path_, 2048, "2048",
1476                                          DatabaseOptions::kDefaultPageSize,
1477                                          default_page_size, UseBuiltIn()));
1478   }
1479 }
1480
1481 TEST_P(SqlRecoveryTest, CannotRecoverClosedDb) {
1482   db_.Close();
1483
1484   if (UseBuiltIn()) {
1485     EXPECT_CHECK_DEATH(std::ignore = BuiltInRecovery::RecoverDatabase(
1486                            &db_, BuiltInRecovery::Strategy::kRecoverOrRaze));
1487   } else {
1488     EXPECT_DCHECK_DEATH(Recovery::RecoverDatabase(&db_, db_path_));
1489   }
1490 }
1491
1492 TEST_P(SqlRecoveryTest, CannotRecoverDbWithErrorCallback) {
1493   db_.set_error_callback(base::DoNothing());
1494
1495   if (UseBuiltIn()) {
1496     EXPECT_CHECK_DEATH(std::ignore = BuiltInRecovery::RecoverDatabase(
1497                            &db_, BuiltInRecovery::Strategy::kRecoverOrRaze));
1498   } else {
1499     EXPECT_DCHECK_DEATH(Recovery::RecoverDatabase(&db_, db_path_));
1500   }
1501 }
1502
1503 #if !BUILDFLAG(IS_FUCHSIA)
1504 // TODO(https://crbug.com/1255316): Ideally this would be a
1505 // `SqlRecoveryTest`, but `Recovery::RecoverDatabase()` does not DCHECK
1506 // that it is passed a non-null database pointer and will instead likely result
1507 // in unexpected behavior or crashes.
1508 TEST_F(SqlBuiltInRecoveryTest, CannotRecoverNullDb) {
1509   EXPECT_CHECK_DEATH(std::ignore = BuiltInRecovery::RecoverDatabase(
1510                          nullptr, BuiltInRecovery::Strategy::kRecoverOrRaze));
1511 }
1512
1513 // TODO(https://crbug.com/1255316): Ideally this would be a
1514 // `SqlRecoveryTest`, but `Recovery::RecoverDatabase()` does not DCHECK
1515 // whether the database is in-memory and will instead likely result in
1516 // unexpected behavior or crashes.
1517 TEST_F(SqlBuiltInRecoveryTest, CannotRecoverInMemoryDb) {
1518   Database in_memory_db;
1519   ASSERT_TRUE(in_memory_db.OpenInMemory());
1520
1521   EXPECT_CHECK_DEATH(
1522       std::ignore = BuiltInRecovery::RecoverDatabase(
1523           &in_memory_db, BuiltInRecovery::Strategy::kRecoverOrRaze));
1524 }
1525
1526 TEST_P(SqlRecoveryTest, BuiltInRecoveryNotAttempedIfNotEnabled) {
1527   // `BuiltInRecovery` will return early if the kill switch is disabled.
1528   EXPECT_EQ(
1529       base::FeatureList::IsEnabled(features::kUseBuiltInRecoveryIfSupported),
1530       IsSqliteSuccessCode(BuiltInRecovery::RecoverDatabase(
1531           &db_, BuiltInRecovery::Strategy::kRecoverOrRaze)));
1532 }
1533 #endif  // !BUILDFLAG(IS_FUCHSIA)
1534
1535 INSTANTIATE_TEST_SUITE_P(All, SqlRecoveryTest, testing::Bool());
1536
1537 }  // namespace
1538
1539 }  // namespace sql