Add test coverage for WAL mode in database_unittest
This change makes it so that all tests in database_unittest.cc now run both with WAL mode on and off. Better test coverage will help us transition to using WAL mode by default in the future. Bug: 78507 Change-Id: I116a8151c1ab4919a78657e46df98b26eddb7f11 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2490664 Commit-Queue: Shubham Aggarwal <shuagga@microsoft.com> Reviewed-by: Victor Costan <pwnall@chromium.org> Cr-Commit-Position: refs/heads/master@{#821790}
This commit is contained in:

committed by
Commit Bot

parent
1bfcd597da
commit
00370898ce
@ -114,9 +114,31 @@ class ScopedUmaskSetter {
|
||||
|
||||
} // namespace
|
||||
|
||||
using SQLDatabaseTest = sql::SQLTestBase;
|
||||
// We use the parameter to run all tests with WAL mode on and off.
|
||||
class SQLDatabaseTest : public SQLTestBase,
|
||||
public testing::WithParamInterface<bool> {
|
||||
public:
|
||||
SQLDatabaseTest() : SQLTestBase(GetDBOptions()) {}
|
||||
explicit SQLDatabaseTest(DatabaseOptions options) : SQLTestBase(options) {}
|
||||
|
||||
TEST_F(SQLDatabaseTest, Execute) {
|
||||
sql::DatabaseOptions GetDBOptions() {
|
||||
sql::DatabaseOptions options;
|
||||
options.wal_mode = IsWALEnabled();
|
||||
// TODO(crbug.com/1120969): Remove after switching to exclusive mode on by
|
||||
// default.
|
||||
options.exclusive_locking = false;
|
||||
#if defined(OS_FUCHSIA) // Exclusive mode needs to be enabled to enter WAL mode
|
||||
// on Fuchsia
|
||||
if (IsWALEnabled()) {
|
||||
options.exclusive_locking = true;
|
||||
}
|
||||
#endif // defined(OS_FUCHSIA)
|
||||
return options;
|
||||
}
|
||||
bool IsWALEnabled() { return GetParam(); }
|
||||
};
|
||||
|
||||
TEST_P(SQLDatabaseTest, Execute) {
|
||||
// Valid statement should return true.
|
||||
ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
|
||||
EXPECT_EQ(SQLITE_OK, db().GetErrorCode());
|
||||
@ -127,7 +149,7 @@ TEST_F(SQLDatabaseTest, Execute) {
|
||||
EXPECT_EQ(SQLITE_ERROR, db().GetErrorCode());
|
||||
}
|
||||
|
||||
TEST_F(SQLDatabaseTest, ExecuteWithErrorCode) {
|
||||
TEST_P(SQLDatabaseTest, ExecuteWithErrorCode) {
|
||||
ASSERT_EQ(SQLITE_OK,
|
||||
db().ExecuteAndReturnErrorCode("CREATE TABLE foo (a, b)"));
|
||||
ASSERT_EQ(SQLITE_ERROR, db().ExecuteAndReturnErrorCode("CREATE TABLE TABLE"));
|
||||
@ -135,7 +157,7 @@ TEST_F(SQLDatabaseTest, ExecuteWithErrorCode) {
|
||||
"INSERT INTO foo(a, b) VALUES (1, 2, 3, 4)"));
|
||||
}
|
||||
|
||||
TEST_F(SQLDatabaseTest, CachedStatement) {
|
||||
TEST_P(SQLDatabaseTest, CachedStatement) {
|
||||
sql::StatementID id1 = SQL_FROM_HERE;
|
||||
sql::StatementID id2 = SQL_FROM_HERE;
|
||||
static const char kId1Sql[] = "SELECT a FROM foo";
|
||||
@ -195,13 +217,13 @@ TEST_F(SQLDatabaseTest, CachedStatement) {
|
||||
<< "Using a different SQL with the same statement ID should DCHECK";
|
||||
}
|
||||
|
||||
TEST_F(SQLDatabaseTest, IsSQLValidTest) {
|
||||
TEST_P(SQLDatabaseTest, IsSQLValidTest) {
|
||||
ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
|
||||
ASSERT_TRUE(db().IsSQLValid("SELECT a FROM foo"));
|
||||
ASSERT_FALSE(db().IsSQLValid("SELECT no_exist FROM foo"));
|
||||
}
|
||||
|
||||
TEST_F(SQLDatabaseTest, DoesTableExist) {
|
||||
TEST_P(SQLDatabaseTest, DoesTableExist) {
|
||||
EXPECT_FALSE(db().DoesTableExist("foo"));
|
||||
EXPECT_FALSE(db().DoesTableExist("foo_index"));
|
||||
|
||||
@ -215,7 +237,7 @@ TEST_F(SQLDatabaseTest, DoesTableExist) {
|
||||
EXPECT_FALSE(db().DoesTableExist("FOO"));
|
||||
}
|
||||
|
||||
TEST_F(SQLDatabaseTest, DoesIndexExist) {
|
||||
TEST_P(SQLDatabaseTest, DoesIndexExist) {
|
||||
ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
|
||||
EXPECT_FALSE(db().DoesIndexExist("foo"));
|
||||
EXPECT_FALSE(db().DoesIndexExist("foo_ubdex"));
|
||||
@ -230,7 +252,7 @@ TEST_F(SQLDatabaseTest, DoesIndexExist) {
|
||||
EXPECT_FALSE(db().DoesIndexExist("FOO_INDEX"));
|
||||
}
|
||||
|
||||
TEST_F(SQLDatabaseTest, DoesViewExist) {
|
||||
TEST_P(SQLDatabaseTest, DoesViewExist) {
|
||||
EXPECT_FALSE(db().DoesViewExist("voo"));
|
||||
ASSERT_TRUE(db().Execute("CREATE VIEW voo (a) AS SELECT 1"));
|
||||
EXPECT_FALSE(db().DoesIndexExist("voo"));
|
||||
@ -242,7 +264,7 @@ TEST_F(SQLDatabaseTest, DoesViewExist) {
|
||||
EXPECT_FALSE(db().DoesViewExist("VOO"));
|
||||
}
|
||||
|
||||
TEST_F(SQLDatabaseTest, DoesColumnExist) {
|
||||
TEST_P(SQLDatabaseTest, DoesColumnExist) {
|
||||
ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
|
||||
|
||||
EXPECT_FALSE(db().DoesColumnExist("foo", "bar"));
|
||||
@ -257,7 +279,7 @@ TEST_F(SQLDatabaseTest, DoesColumnExist) {
|
||||
EXPECT_TRUE(db().DoesColumnExist("foo", "A"));
|
||||
}
|
||||
|
||||
TEST_F(SQLDatabaseTest, GetLastInsertRowId) {
|
||||
TEST_P(SQLDatabaseTest, GetLastInsertRowId) {
|
||||
ASSERT_TRUE(db().Execute("CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"));
|
||||
|
||||
ASSERT_TRUE(db().Execute("INSERT INTO foo (value) VALUES (12)"));
|
||||
@ -273,7 +295,7 @@ TEST_F(SQLDatabaseTest, GetLastInsertRowId) {
|
||||
EXPECT_EQ(12, s.ColumnInt(0));
|
||||
}
|
||||
|
||||
TEST_F(SQLDatabaseTest, Rollback) {
|
||||
TEST_P(SQLDatabaseTest, Rollback) {
|
||||
ASSERT_TRUE(db().BeginTransaction());
|
||||
ASSERT_TRUE(db().BeginTransaction());
|
||||
EXPECT_EQ(2, db().transaction_nesting());
|
||||
@ -284,7 +306,7 @@ TEST_F(SQLDatabaseTest, Rollback) {
|
||||
|
||||
// Test the scoped error expecter by attempting to insert a duplicate
|
||||
// value into an index.
|
||||
TEST_F(SQLDatabaseTest, ScopedErrorExpecter) {
|
||||
TEST_P(SQLDatabaseTest, ScopedErrorExpecter) {
|
||||
const char* kCreateSql = "CREATE TABLE foo (id INTEGER UNIQUE)";
|
||||
ASSERT_TRUE(db().Execute(kCreateSql));
|
||||
ASSERT_TRUE(db().Execute("INSERT INTO foo (id) VALUES (12)"));
|
||||
@ -299,7 +321,7 @@ TEST_F(SQLDatabaseTest, ScopedErrorExpecter) {
|
||||
|
||||
// Test that clients of GetUntrackedStatement() can test corruption-handling
|
||||
// with ScopedErrorExpecter.
|
||||
TEST_F(SQLDatabaseTest, ScopedIgnoreUntracked) {
|
||||
TEST_P(SQLDatabaseTest, ScopedIgnoreUntracked) {
|
||||
const char* kCreateSql = "CREATE TABLE foo (id INTEGER UNIQUE)";
|
||||
ASSERT_TRUE(db().Execute(kCreateSql));
|
||||
ASSERT_FALSE(db().DoesTableExist("bar"));
|
||||
@ -321,7 +343,7 @@ TEST_F(SQLDatabaseTest, ScopedIgnoreUntracked) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SQLDatabaseTest, ErrorCallback) {
|
||||
TEST_P(SQLDatabaseTest, ErrorCallback) {
|
||||
const char* kCreateSql = "CREATE TABLE foo (id INTEGER UNIQUE)";
|
||||
ASSERT_TRUE(db().Execute(kCreateSql));
|
||||
ASSERT_TRUE(db().Execute("INSERT INTO foo (id) VALUES (12)"));
|
||||
@ -378,7 +400,7 @@ TEST_F(SQLDatabaseTest, ErrorCallback) {
|
||||
|
||||
// Test that sql::Database::Raze() results in a database without the
|
||||
// tables from the original database.
|
||||
TEST_F(SQLDatabaseTest, Raze) {
|
||||
TEST_P(SQLDatabaseTest, Raze) {
|
||||
const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
|
||||
ASSERT_TRUE(db().Execute(kCreateSql));
|
||||
ASSERT_TRUE(db().Execute("INSERT INTO foo (value) VALUES (12)"));
|
||||
@ -476,7 +498,7 @@ void TestPageSize(const base::FilePath& db_prefix,
|
||||
// Verify that sql::Recovery maintains the page size, and the virtual table
|
||||
// works with page sizes other than SQLite's default. Also verify the case
|
||||
// where the default page size has changed.
|
||||
TEST_F(SQLDatabaseTest, RazePageSize) {
|
||||
TEST_P(SQLDatabaseTest, RazePageSize) {
|
||||
const std::string default_page_size =
|
||||
ExecuteWithResult(&db(), "PRAGMA page_size");
|
||||
|
||||
@ -501,11 +523,11 @@ TEST_F(SQLDatabaseTest, RazePageSize) {
|
||||
}
|
||||
|
||||
// Test that Raze() results are seen in other connections.
|
||||
TEST_F(SQLDatabaseTest, RazeMultiple) {
|
||||
TEST_P(SQLDatabaseTest, RazeMultiple) {
|
||||
const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
|
||||
ASSERT_TRUE(db().Execute(kCreateSql));
|
||||
|
||||
sql::Database other_db;
|
||||
sql::Database other_db(GetDBOptions());
|
||||
ASSERT_TRUE(other_db.Open(db_path()));
|
||||
|
||||
// Check that the second connection sees the table.
|
||||
@ -517,14 +539,14 @@ TEST_F(SQLDatabaseTest, RazeMultiple) {
|
||||
ASSERT_EQ(0, SqliteMasterCount(&other_db));
|
||||
}
|
||||
|
||||
TEST_F(SQLDatabaseTest, RazeLocked) {
|
||||
TEST_P(SQLDatabaseTest, RazeLocked) {
|
||||
const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
|
||||
ASSERT_TRUE(db().Execute(kCreateSql));
|
||||
|
||||
// Open a transaction and write some data in a second connection.
|
||||
// This will acquire a PENDING or EXCLUSIVE transaction, which will
|
||||
// cause the raze to fail.
|
||||
sql::Database other_db;
|
||||
sql::Database other_db(GetDBOptions());
|
||||
ASSERT_TRUE(other_db.Open(db_path()));
|
||||
ASSERT_TRUE(other_db.BeginTransaction());
|
||||
const char* kInsertSql = "INSERT INTO foo VALUES (1, 'data')";
|
||||
@ -544,7 +566,7 @@ TEST_F(SQLDatabaseTest, RazeLocked) {
|
||||
// blocks raze.
|
||||
// This doesn't happen in WAL mode because reads are no longer blocked by
|
||||
// write operations when using a WAL.
|
||||
if (!base::FeatureList::IsEnabled(sql::features::kEnableWALModeByDefault)) {
|
||||
if (!IsWALEnabled()) {
|
||||
const char* kQuery = "SELECT COUNT(*) FROM foo";
|
||||
sql::Statement s(other_db.GetUniqueStatement(kQuery));
|
||||
ASSERT_TRUE(s.Step());
|
||||
@ -558,7 +580,7 @@ TEST_F(SQLDatabaseTest, RazeLocked) {
|
||||
|
||||
// Verify that Raze() can handle an empty file. SQLite should treat
|
||||
// this as an empty database.
|
||||
TEST_F(SQLDatabaseTest, RazeEmptyDB) {
|
||||
TEST_P(SQLDatabaseTest, RazeEmptyDB) {
|
||||
const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
|
||||
ASSERT_TRUE(db().Execute(kCreateSql));
|
||||
db().Close();
|
||||
@ -573,7 +595,7 @@ TEST_F(SQLDatabaseTest, RazeEmptyDB) {
|
||||
// Verify that Raze() can handle a file of junk.
|
||||
// Need exclusive mode off here as there are some subtleties (by design) around
|
||||
// how the cache is used with it on which causes the test to fail.
|
||||
TEST_F(SQLDatabaseTest, RazeNOTADB) {
|
||||
TEST_P(SQLDatabaseTest, RazeNOTADB) {
|
||||
db().Close();
|
||||
sql::Database::Delete(db_path());
|
||||
ASSERT_FALSE(GetPathExists(db_path()));
|
||||
@ -599,7 +621,7 @@ TEST_F(SQLDatabaseTest, RazeNOTADB) {
|
||||
}
|
||||
|
||||
// Verify that Raze() can handle a database overwritten with garbage.
|
||||
TEST_F(SQLDatabaseTest, RazeNOTADB2) {
|
||||
TEST_P(SQLDatabaseTest, RazeNOTADB2) {
|
||||
const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
|
||||
ASSERT_TRUE(db().Execute(kCreateSql));
|
||||
ASSERT_EQ(1, SqliteMasterCount(&db()));
|
||||
@ -628,7 +650,7 @@ TEST_F(SQLDatabaseTest, RazeNOTADB2) {
|
||||
// essential for cases where the Open() can fail entirely, so the
|
||||
// Raze() cannot happen later. Additionally test that when the
|
||||
// callback does this during Open(), the open is retried and succeeds.
|
||||
TEST_F(SQLDatabaseTest, RazeCallbackReopen) {
|
||||
TEST_P(SQLDatabaseTest, RazeCallbackReopen) {
|
||||
const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
|
||||
ASSERT_TRUE(db().Execute(kCreateSql));
|
||||
ASSERT_EQ(1, SqliteMasterCount(&db()));
|
||||
@ -661,7 +683,7 @@ TEST_F(SQLDatabaseTest, RazeCallbackReopen) {
|
||||
}
|
||||
|
||||
// Basic test of RazeAndClose() operation.
|
||||
TEST_F(SQLDatabaseTest, RazeAndClose) {
|
||||
TEST_P(SQLDatabaseTest, RazeAndClose) {
|
||||
const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
|
||||
const char* kPopulateSql = "INSERT INTO foo (value) VALUES (12)";
|
||||
|
||||
@ -689,7 +711,7 @@ TEST_F(SQLDatabaseTest, RazeAndClose) {
|
||||
|
||||
// Test that various operations fail without crashing after
|
||||
// RazeAndClose().
|
||||
TEST_F(SQLDatabaseTest, RazeAndCloseDiagnostics) {
|
||||
TEST_P(SQLDatabaseTest, RazeAndCloseDiagnostics) {
|
||||
const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
|
||||
const char* kPopulateSql = "INSERT INTO foo (value) VALUES (12)";
|
||||
const char* kSimpleSql = "SELECT 1";
|
||||
@ -763,7 +785,7 @@ TEST_F(SQLDatabaseTest, RazeAndCloseDiagnostics) {
|
||||
// Test that Raze() turns off memory mapping so that the file is truncated.
|
||||
// [This would not cover the case of multiple connections where one of the other
|
||||
// connections is memory-mapped. That is infrequent in Chromium.]
|
||||
TEST_F(SQLDatabaseTest, RazeTruncate) {
|
||||
TEST_P(SQLDatabaseTest, RazeTruncate) {
|
||||
// The empty database has 0 or 1 pages. Raze() should leave it with exactly 1
|
||||
// page. Not checking directly because auto_vacuum on Android adds a freelist
|
||||
// page.
|
||||
@ -800,7 +822,7 @@ TEST_F(SQLDatabaseTest, RazeTruncate) {
|
||||
}
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
TEST_F(SQLDatabaseTest, SetTempDirForSQL) {
|
||||
TEST_P(SQLDatabaseTest, SetTempDirForSQL) {
|
||||
sql::MetaTable meta_table;
|
||||
// Below call needs a temporary directory in sqlite3
|
||||
// On Android, it can pass only when the temporary directory is set.
|
||||
@ -811,27 +833,7 @@ TEST_F(SQLDatabaseTest, SetTempDirForSQL) {
|
||||
}
|
||||
#endif // defined(OS_ANDROID)
|
||||
|
||||
// Contained param indicates whether WAL mode is switched on or not.
|
||||
class JournalModeTest : public SQLDatabaseTest,
|
||||
public testing::WithParamInterface<bool> {
|
||||
public:
|
||||
JournalModeTest() : SQLDatabaseTest(GetDBOptions()) {}
|
||||
|
||||
sql::DatabaseOptions GetDBOptions() {
|
||||
sql::DatabaseOptions options;
|
||||
options.wal_mode = IsWALEnabled();
|
||||
#if defined(OS_FUCHSIA) // Exclusive mode needs to be enabled to enter WAL mode
|
||||
// on Fuchsia
|
||||
if (IsWALEnabled()) {
|
||||
options.exclusive_locking = true;
|
||||
}
|
||||
#endif // defined(OS_FUCHSIA)
|
||||
return options;
|
||||
}
|
||||
bool IsWALEnabled() { return GetParam(); }
|
||||
};
|
||||
|
||||
TEST_P(JournalModeTest, Delete) {
|
||||
TEST_P(SQLDatabaseTest, Delete) {
|
||||
EXPECT_TRUE(db().Execute("CREATE TABLE x (x)"));
|
||||
db().Close();
|
||||
|
||||
@ -853,15 +855,13 @@ TEST_P(JournalModeTest, Delete) {
|
||||
|
||||
// WAL mode is currently not supported on Fuchsia
|
||||
#if !defined(OS_FUCHSIA)
|
||||
INSTANTIATE_TEST_SUITE_P(SQLDatabaseTest, JournalModeTest, testing::Bool());
|
||||
INSTANTIATE_TEST_SUITE_P(JournalMode, SQLDatabaseTest, testing::Bool());
|
||||
#else
|
||||
INSTANTIATE_TEST_SUITE_P(SQLDatabaseTest,
|
||||
JournalModeTest,
|
||||
testing::Values(false));
|
||||
INSTANTIATE_TEST_SUITE_P(JournalMode, SQLDatabaseTest, testing::Values(false));
|
||||
#endif
|
||||
|
||||
#if defined(OS_POSIX) // This test operates on POSIX file permissions.
|
||||
TEST_P(JournalModeTest, PosixFilePermissions) {
|
||||
TEST_P(SQLDatabaseTest, PosixFilePermissions) {
|
||||
db().Close();
|
||||
sql::Database::Delete(db_path());
|
||||
ASSERT_FALSE(GetPathExists(db_path()));
|
||||
@ -910,7 +910,7 @@ TEST_P(JournalModeTest, PosixFilePermissions) {
|
||||
#endif // defined(OS_POSIX)
|
||||
|
||||
// Test that errors start happening once Poison() is called.
|
||||
TEST_F(SQLDatabaseTest, Poison) {
|
||||
TEST_P(SQLDatabaseTest, Poison) {
|
||||
EXPECT_TRUE(db().Execute("CREATE TABLE x (x)"));
|
||||
|
||||
// Before the Poison() call, things generally work.
|
||||
@ -957,7 +957,7 @@ TEST_F(SQLDatabaseTest, Poison) {
|
||||
EXPECT_FALSE(db().CommitTransaction());
|
||||
}
|
||||
|
||||
TEST_F(SQLDatabaseTest, AttachDatabase) {
|
||||
TEST_P(SQLDatabaseTest, AttachDatabase) {
|
||||
EXPECT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
|
||||
|
||||
// Create a database to attach to.
|
||||
@ -990,7 +990,7 @@ TEST_F(SQLDatabaseTest, AttachDatabase) {
|
||||
EXPECT_FALSE(db().IsSQLValid("SELECT count(*) from other.bar"));
|
||||
}
|
||||
|
||||
TEST_F(SQLDatabaseTest, AttachDatabaseWithOpenTransaction) {
|
||||
TEST_P(SQLDatabaseTest, AttachDatabaseWithOpenTransaction) {
|
||||
EXPECT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
|
||||
|
||||
// Create a database to attach to.
|
||||
@ -1036,7 +1036,7 @@ TEST_F(SQLDatabaseTest, AttachDatabaseWithOpenTransaction) {
|
||||
EXPECT_FALSE(db().IsSQLValid("SELECT count(*) from other.bar"));
|
||||
}
|
||||
|
||||
TEST_F(SQLDatabaseTest, Basic_QuickIntegrityCheck) {
|
||||
TEST_P(SQLDatabaseTest, Basic_QuickIntegrityCheck) {
|
||||
const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
|
||||
ASSERT_TRUE(db().Execute(kCreateSql));
|
||||
EXPECT_TRUE(db().QuickIntegrityCheck());
|
||||
@ -1053,7 +1053,7 @@ TEST_F(SQLDatabaseTest, Basic_QuickIntegrityCheck) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SQLDatabaseTest, Basic_FullIntegrityCheck) {
|
||||
TEST_P(SQLDatabaseTest, Basic_FullIntegrityCheck) {
|
||||
const std::string kOk("ok");
|
||||
std::vector<std::string> messages;
|
||||
|
||||
@ -1080,7 +1080,7 @@ TEST_F(SQLDatabaseTest, Basic_FullIntegrityCheck) {
|
||||
// file that would pass the quick check and fail the full check.
|
||||
}
|
||||
|
||||
TEST_F(SQLDatabaseTest, OnMemoryDump) {
|
||||
TEST_P(SQLDatabaseTest, OnMemoryDump) {
|
||||
base::trace_event::MemoryDumpArgs args = {
|
||||
base::trace_event::MemoryDumpLevelOfDetail::DETAILED};
|
||||
base::trace_event::ProcessMemoryDump pmd(args);
|
||||
@ -1090,7 +1090,7 @@ TEST_F(SQLDatabaseTest, OnMemoryDump) {
|
||||
|
||||
// Test that the functions to collect diagnostic data run to completion, without
|
||||
// worrying too much about what they generate (since that will change).
|
||||
TEST_F(SQLDatabaseTest, CollectDiagnosticInfo) {
|
||||
TEST_P(SQLDatabaseTest, CollectDiagnosticInfo) {
|
||||
const std::string corruption_info = db().CollectCorruptionInfo();
|
||||
EXPECT_NE(std::string::npos, corruption_info.find("SQLITE_CORRUPT"));
|
||||
EXPECT_NE(std::string::npos, corruption_info.find("integrity_check"));
|
||||
@ -1123,7 +1123,7 @@ TEST_F(SQLDatabaseTest, CollectDiagnosticInfo) {
|
||||
|
||||
// Test that a fresh database has mmap enabled by default, if mmap'ed I/O is
|
||||
// enabled by SQLite.
|
||||
TEST_F(SQLDatabaseTest, MmapInitiallyEnabled) {
|
||||
TEST_P(SQLDatabaseTest, MmapInitiallyEnabled) {
|
||||
{
|
||||
sql::Statement s(db().GetUniqueStatement("PRAGMA mmap_size"));
|
||||
ASSERT_TRUE(s.Step())
|
||||
@ -1151,7 +1151,7 @@ TEST_F(SQLDatabaseTest, MmapInitiallyEnabled) {
|
||||
|
||||
// Test whether a fresh database gets mmap enabled when using alternate status
|
||||
// storage.
|
||||
TEST_F(SQLDatabaseTest, MmapInitiallyEnabledAltStatus) {
|
||||
TEST_P(SQLDatabaseTest, MmapInitiallyEnabledAltStatus) {
|
||||
// Re-open fresh database with alt-status flag set.
|
||||
db().Close();
|
||||
sql::Database::Delete(db_path());
|
||||
@ -1183,7 +1183,7 @@ TEST_F(SQLDatabaseTest, MmapInitiallyEnabledAltStatus) {
|
||||
EXPECT_EQ("0", ExecuteWithResult(&db(), "PRAGMA mmap_size"));
|
||||
}
|
||||
|
||||
TEST_F(SQLDatabaseTest, GetAppropriateMmapSize) {
|
||||
TEST_P(SQLDatabaseTest, GetAppropriateMmapSize) {
|
||||
const size_t kMmapAlot = 25 * 1024 * 1024;
|
||||
int64_t mmap_status = MetaTable::kMmapFailure;
|
||||
|
||||
@ -1226,7 +1226,7 @@ TEST_F(SQLDatabaseTest, GetAppropriateMmapSize) {
|
||||
ASSERT_EQ(MetaTable::kMmapSuccess, mmap_status);
|
||||
}
|
||||
|
||||
TEST_F(SQLDatabaseTest, GetAppropriateMmapSizeAltStatus) {
|
||||
TEST_P(SQLDatabaseTest, GetAppropriateMmapSizeAltStatus) {
|
||||
const size_t kMmapAlot = 25 * 1024 * 1024;
|
||||
|
||||
// At this point, Database still expects a future [meta] table.
|
||||
@ -1262,7 +1262,7 @@ TEST_F(SQLDatabaseTest, GetAppropriateMmapSizeAltStatus) {
|
||||
ExecuteWithResult(&db(), "SELECT * FROM MmapStatus"));
|
||||
}
|
||||
|
||||
TEST_F(SQLDatabaseTest, GetMemoryUsage) {
|
||||
TEST_P(SQLDatabaseTest, GetMemoryUsage) {
|
||||
// Databases with mmap enabled may not follow the assumptions below.
|
||||
db().Close();
|
||||
db().set_mmap_disabled();
|
||||
@ -1287,24 +1287,29 @@ TEST_F(SQLDatabaseTest, GetMemoryUsage) {
|
||||
|
||||
class SQLDatabaseTestExclusiveMode : public SQLDatabaseTest {
|
||||
public:
|
||||
SQLDatabaseTestExclusiveMode()
|
||||
: SQLDatabaseTest({.exclusive_locking = true}) {}
|
||||
SQLDatabaseTestExclusiveMode() : SQLDatabaseTest(GetDBOptions()) {}
|
||||
|
||||
DatabaseOptions GetDBOptions() {
|
||||
DatabaseOptions options = SQLDatabaseTest::GetDBOptions();
|
||||
options.exclusive_locking = true;
|
||||
return options;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(SQLDatabaseTestExclusiveMode, LockingModeExclusive) {
|
||||
TEST_P(SQLDatabaseTestExclusiveMode, LockingModeExclusive) {
|
||||
EXPECT_EQ(ExecuteWithResult(&db(), "PRAGMA locking_mode"), "exclusive");
|
||||
}
|
||||
|
||||
TEST_F(SQLDatabaseTest, LockingModeNormal) {
|
||||
TEST_P(SQLDatabaseTest, LockingModeNormal) {
|
||||
EXPECT_EQ(ExecuteWithResult(&db(), "PRAGMA locking_mode"), "normal");
|
||||
}
|
||||
|
||||
TEST_P(JournalModeTest, OpenedInCorrectMode) {
|
||||
TEST_P(SQLDatabaseTest, OpenedInCorrectMode) {
|
||||
std::string expected_mode = IsWALEnabled() ? "wal" : "truncate";
|
||||
EXPECT_EQ(ExecuteWithResult(&db(), "PRAGMA journal_mode"), expected_mode);
|
||||
}
|
||||
|
||||
TEST_P(JournalModeTest, CheckpointDatabase) {
|
||||
TEST_P(SQLDatabaseTest, CheckpointDatabase) {
|
||||
if (!IsWALEnabled())
|
||||
return;
|
||||
|
||||
@ -1338,14 +1343,16 @@ TEST_P(JournalModeTest, CheckpointDatabase) {
|
||||
EXPECT_EQ(ExecuteWithResult(&db(), "SELECT value FROM foo where id=2"), "2");
|
||||
}
|
||||
|
||||
TEST_F(SQLDatabaseTest, CorruptSizeInHeaderTest) {
|
||||
TEST_P(SQLDatabaseTest, CorruptSizeInHeaderTest) {
|
||||
ASSERT_TRUE(db().Execute("CREATE TABLE foo (x)"));
|
||||
ASSERT_TRUE(db().Execute("CREATE TABLE bar (x)"));
|
||||
db().Close();
|
||||
|
||||
ASSERT_TRUE(CorruptSizeInHeaderOfDB());
|
||||
{
|
||||
sql::test::ScopedErrorExpecter expecter;
|
||||
expecter.ExpectError(SQLITE_CORRUPT);
|
||||
ASSERT_TRUE(db().Open(db_path()));
|
||||
EXPECT_FALSE(db().Execute("INSERT INTO foo values (1)"));
|
||||
EXPECT_FALSE(db().DoesTableExist("foo"));
|
||||
EXPECT_FALSE(db().DoesTableExist("bar"));
|
||||
@ -1357,7 +1364,7 @@ TEST_F(SQLDatabaseTest, CorruptSizeInHeaderTest) {
|
||||
// To prevent invalid SQL from accidentally shipping to production, prepared
|
||||
// statements which fail to compile with SQLITE_ERROR call DLOG(DCHECK). This
|
||||
// case cannot be suppressed with an error callback.
|
||||
TEST_F(SQLDatabaseTest, CompileError) {
|
||||
TEST_P(SQLDatabaseTest, CompileError) {
|
||||
// DEATH tests not supported on Android, iOS, or Fuchsia.
|
||||
#if !defined(OS_ANDROID) && !defined(OS_IOS) && !defined(OS_FUCHSIA)
|
||||
if (DLOG_IS_ON(FATAL)) {
|
||||
|
@ -9,6 +9,7 @@ namespace sql {
|
||||
|
||||
namespace test {
|
||||
struct ColumnInfo;
|
||||
bool CorruptSizeInHeader(const base::FilePath&);
|
||||
} // namespace test
|
||||
|
||||
// Restricts access to APIs internal to the //sql package.
|
||||
@ -23,6 +24,7 @@ class InternalApiToken {
|
||||
friend class DatabaseTestPeer;
|
||||
friend class Recovery;
|
||||
friend struct test::ColumnInfo;
|
||||
friend bool test::CorruptSizeInHeader(const base::FilePath&);
|
||||
};
|
||||
|
||||
} // namespace sql
|
||||
|
@ -10,12 +10,12 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "base/check.h"
|
||||
#include "base/check_op.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/files/scoped_file.h"
|
||||
#include "base/threading/thread_restrictions.h"
|
||||
#include "sql/database.h"
|
||||
#include "sql/sql_features.h"
|
||||
#include "sql/statement.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "third_party/sqlite/sqlite3.h"
|
||||
@ -71,21 +71,49 @@ void WriteBigEndian(unsigned val, unsigned char* buf, size_t bytes) {
|
||||
}
|
||||
}
|
||||
|
||||
bool IsWalDatabase(const base::FilePath& db_path) {
|
||||
// The SQLite header is documented at:
|
||||
// https://www.sqlite.org/fileformat.html#the_database_header
|
||||
//
|
||||
// Read the entire header.
|
||||
constexpr int kHeaderSize = 100;
|
||||
uint8_t header[kHeaderSize];
|
||||
base::ReadFile(db_path, reinterpret_cast<char*>(header), sizeof(header));
|
||||
constexpr int kWriteVersionHeaderOffset = 18;
|
||||
constexpr int kReadVersionHeaderOffset = 19;
|
||||
// If the read version is unsupported, we can't rely on our ability to
|
||||
// interpret anything else in the header.
|
||||
DCHECK_LE(header[kReadVersionHeaderOffset], 2)
|
||||
<< "Unsupported SQLite file format";
|
||||
return header[kWriteVersionHeaderOffset] == 2;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace sql {
|
||||
namespace test {
|
||||
|
||||
bool CorruptSizeInHeader(const base::FilePath& db_path) {
|
||||
// Checkpoint the WAL file in Truncate mode before corrupting to ensure that
|
||||
// any future transaction always touches the DB file and not just the WAL
|
||||
// file.
|
||||
if (base::FeatureList::IsEnabled(sql::features::kEnableWALModeByDefault)) {
|
||||
if (IsWalDatabase(db_path)) {
|
||||
// Checkpoint the WAL file in Truncate mode before corrupting to ensure that
|
||||
// any future transaction always touches the DB file and not just the WAL
|
||||
// file.
|
||||
base::ScopedAllowBlockingForTesting allow_blocking;
|
||||
sql::Database db;
|
||||
// TODO: This function doesn't reliably work if connections to the DB are
|
||||
// still open. Change any uses to ensure that we close all database
|
||||
// connections before calling this function.
|
||||
sql::Database db({.exclusive_locking = false, .wal_mode = true});
|
||||
if (!db.Open(db_path))
|
||||
return false;
|
||||
if (!db.Execute("PRAGMA wal_checkpoint(TRUNCATE)"))
|
||||
int wal_log_size = 0;
|
||||
int checkpointed_frame_count = 0;
|
||||
int truncate_result = sqlite3_wal_checkpoint_v2(
|
||||
db.db(InternalApiToken()), /*zDb=*/nullptr, SQLITE_CHECKPOINT_TRUNCATE,
|
||||
&wal_log_size, &checkpointed_frame_count);
|
||||
// A successful checkpoint in truncate mode sets these to zero.
|
||||
DCHECK(wal_log_size == 0);
|
||||
DCHECK(checkpointed_frame_count == 0);
|
||||
if (truncate_result != SQLITE_OK)
|
||||
return false;
|
||||
db.Close();
|
||||
}
|
||||
|
Reference in New Issue
Block a user