diff options
author | Ben Murdoch <benm@google.com> | 2013-07-17 14:55:54 +0100 |
---|---|---|
committer | Ben Murdoch <benm@google.com> | 2013-07-17 14:55:54 +0100 |
commit | 7dbb3d5cf0c15f500944d211057644d6a2f37371 (patch) | |
tree | 701119ba0596f51b0ab466d6472b0f98211359c5 /sql | |
parent | b2ecf4836a0eb284ddac7746b1f7ba613777a739 (diff) | |
download | chromium_org-7dbb3d5cf0c15f500944d211057644d6a2f37371.tar.gz |
Merge from Chromium at DEPS revision r212014
This commit was generated by merge_to_master.py.
Change-Id: Ie0f261e9682cd8abea1eea1e51beab83d5eea21a
Diffstat (limited to 'sql')
-rw-r--r-- | sql/connection.cc | 125 | ||||
-rw-r--r-- | sql/connection.h | 12 | ||||
-rw-r--r-- | sql/connection_unittest.cc | 294 | ||||
-rw-r--r-- | sql/sql.gyp | 2 | ||||
-rw-r--r-- | sql/sql.target.darwin-arm.mk | 6 | ||||
-rw-r--r-- | sql/sql.target.darwin-mips.mk | 4 | ||||
-rw-r--r-- | sql/sql.target.darwin-x86.mk | 6 | ||||
-rw-r--r-- | sql/sql.target.linux-arm.mk | 6 | ||||
-rw-r--r-- | sql/sql.target.linux-mips.mk | 4 | ||||
-rw-r--r-- | sql/sql.target.linux-x86.mk | 6 | ||||
-rw-r--r-- | sql/statement_unittest.cc | 56 | ||||
-rw-r--r-- | sql/test/error_callback_support.cc | 28 | ||||
-rw-r--r-- | sql/test/error_callback_support.h | 36 |
13 files changed, 492 insertions, 93 deletions
diff --git a/sql/connection.cc b/sql/connection.cc index a11bc72893..95d09c24ff 100644 --- a/sql/connection.cc +++ b/sql/connection.cc @@ -18,6 +18,10 @@ #include "sql/statement.h" #include "third_party/sqlite/sqlite3.h" +#if defined(OS_IOS) && defined(USE_SYSTEM_SQLITE) +#include "third_party/sqlite/src/ext/icu/sqliteicu.h" +#endif + namespace { // Spin for up to a second waiting for the lock to clear when setting @@ -64,6 +68,32 @@ class ScopedWritableSchema { sqlite3* db_; }; +// Helper to wrap the sqlite3_backup_*() step of Raze(). Return +// SQLite error code from running the backup step. +int BackupDatabase(sqlite3* src, sqlite3* dst, const char* db_name) { + DCHECK_NE(src, dst); + sqlite3_backup* backup = sqlite3_backup_init(dst, db_name, src, db_name); + if (!backup) { + // Since this call only sets things up, this indicates a gross + // error in SQLite. + DLOG(FATAL) << "Unable to start sqlite3_backup(): " << sqlite3_errmsg(dst); + return sqlite3_errcode(dst); + } + + // -1 backs up the entire database. + int rc = sqlite3_backup_step(backup, -1); + int pages = sqlite3_backup_pagecount(backup); + sqlite3_backup_finish(backup); + + // If successful, exactly one page should have been backed up. If + // this breaks, check this function to make sure assumptions aren't + // being broken. + if (rc == SQLITE_DONE) + DCHECK_EQ(pages, 1); + + return rc; +} + } // namespace namespace sql { @@ -164,15 +194,15 @@ bool Connection::Open(const base::FilePath& path) { } #if defined(OS_WIN) - return OpenInternal(WideToUTF8(path.value())); + return OpenInternal(WideToUTF8(path.value()), RETRY_ON_POISON); #elif defined(OS_POSIX) - return OpenInternal(path.value()); + return OpenInternal(path.value(), RETRY_ON_POISON); #endif } bool Connection::OpenInMemory() { in_memory_ = true; - return OpenInternal(":memory:"); + return OpenInternal(":memory:", NO_RETRY); } void Connection::CloseInternal(bool forced) { @@ -208,8 +238,8 @@ void Connection::CloseInternal(bool forced) { AssertIOAllowed(); // TODO(shess): Histogram for failure. sqlite3_close(db_); - db_ = NULL; } + db_ = NULL; } void Connection::Close() { @@ -313,33 +343,54 @@ bool Connection::Raze() { // page_size" can be used to query such a database. ScopedWritableSchema writable_schema(db_); - sqlite3_backup* backup = sqlite3_backup_init(db_, "main", - null_db.db_, "main"); - if (!backup) { - DLOG(FATAL) << "Unable to start sqlite3_backup()."; - return false; - } - - // -1 backs up the entire database. - int rc = sqlite3_backup_step(backup, -1); - int pages = sqlite3_backup_pagecount(backup); - sqlite3_backup_finish(backup); + const char* kMain = "main"; + int rc = BackupDatabase(null_db.db_, db_, kMain); + UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RazeDatabase",rc); // The destination database was locked. if (rc == SQLITE_BUSY) { return false; } + // SQLITE_NOTADB can happen if page 1 of db_ exists, but is not + // formatted correctly. SQLITE_IOERR_SHORT_READ can happen if db_ + // isn't even big enough for one page. Either way, reach in and + // truncate it before trying again. + // TODO(shess): Maybe it would be worthwhile to just truncate from + // the get-go? + if (rc == SQLITE_NOTADB || rc == SQLITE_IOERR_SHORT_READ) { + sqlite3_file* file = NULL; + rc = sqlite3_file_control(db_, "main", SQLITE_FCNTL_FILE_POINTER, &file); + if (rc != SQLITE_OK) { + DLOG(FATAL) << "Failure getting file handle."; + return false; + } else if (!file) { + DLOG(FATAL) << "File handle is empty."; + return false; + } + + rc = file->pMethods->xTruncate(file, 0); + if (rc != SQLITE_OK) { + UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RazeDatabaseTruncate",rc); + DLOG(FATAL) << "Failed to truncate file."; + return false; + } + + rc = BackupDatabase(null_db.db_, db_, kMain); + UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RazeDatabase2",rc); + + if (rc != SQLITE_DONE) { + DLOG(FATAL) << "Failed retrying Raze()."; + } + } + // The entire database should have been backed up. if (rc != SQLITE_DONE) { + // TODO(shess): Figure out which other cases can happen. DLOG(FATAL) << "Unable to copy entire null database."; return false; } - // Exactly one page should have been backed up. If this breaks, - // check this function to make sure assumptions aren't being broken. - DCHECK_EQ(pages, 1); - return true; } @@ -394,13 +445,13 @@ bool Connection::Delete(const base::FilePath& path) { base::FilePath journal_path(path.value() + FILE_PATH_LITERAL("-journal")); base::FilePath wal_path(path.value() + FILE_PATH_LITERAL("-wal")); - base::Delete(journal_path, false); - base::Delete(wal_path, false); - base::Delete(path, false); + base::DeleteFile(journal_path, false); + base::DeleteFile(wal_path, false); + base::DeleteFile(path, false); - return !file_util::PathExists(journal_path) && - !file_util::PathExists(wal_path) && - !file_util::PathExists(path); + return !base::PathExists(journal_path) && + !base::PathExists(wal_path) && + !base::PathExists(path); } bool Connection::BeginTransaction() { @@ -648,7 +699,8 @@ const char* Connection::GetErrorMessage() const { return sqlite3_errmsg(db_); } -bool Connection::OpenInternal(const std::string& file_name) { +bool Connection::OpenInternal(const std::string& file_name, + Connection::Retry retry_flag) { AssertIOAllowed(); if (db_) { @@ -663,6 +715,7 @@ bool Connection::OpenInternal(const std::string& file_name) { // TODO(shess): Revise is_open() to consider poisoned_, and review // to see if any non-testing code even depends on it. DLOG_IF(FATAL, poisoned_) << "sql::Connection is already open."; + poisoned_ = false; int err = sqlite3_open(file_name.c_str(), &db_); if (err != SQLITE_OK) { @@ -671,8 +724,11 @@ bool Connection::OpenInternal(const std::string& file_name) { UMA_HISTOGRAM_ENUMERATION("Sqlite.OpenFailure", err & 0xff, 50); OnSqliteError(err, NULL); + bool was_poisoned = poisoned_; Close(); - db_ = NULL; + + if (was_poisoned && retry_flag == RETRY_ON_POISON) + return OpenInternal(file_name, NO_RETRY); return false; } @@ -701,6 +757,13 @@ bool Connection::OpenInternal(const std::string& file_name) { err = sqlite3_extended_result_codes(db_, 1); DCHECK_EQ(err, SQLITE_OK) << "Could not enable extended result codes"; +#if defined(OS_IOS) && defined(USE_SYSTEM_SQLITE) + // The version of SQLite shipped with iOS doesn't enable ICU, which includes + // REGEXP support. Add it in dynamically. + err = sqlite3IcuInit(db_); + DCHECK_EQ(err, SQLITE_OK) << "Could not enable ICU support"; +#endif // OS_IOS && USE_SYSTEM_SQLITE + // If indicated, lock up the database before doing anything else, so // that the following code doesn't have to deal with locking. // TODO(shess): This code is brittle. Find the cases where code @@ -747,7 +810,10 @@ bool Connection::OpenInternal(const std::string& file_name) { } if (!ExecuteWithTimeout("PRAGMA secure_delete=ON", kBusyTimeout)) { + bool was_poisoned = poisoned_; Close(); + if (was_poisoned && retry_flag == RETRY_ON_POISON) + return OpenInternal(file_name, NO_RETRY); return false; } @@ -801,7 +867,10 @@ int Connection::OnSqliteError(int err, sql::Statement *stmt) { << ": " << GetErrorMessage(); if (!error_callback_.is_null()) { - error_callback_.Run(err, stmt); + // Fire from a copy of the callback in case of reentry into + // re/set_error_callback(). + // TODO(shess): <http://crbug.com/254584> + ErrorCallback(error_callback_).Run(err, stmt); return err; } diff --git a/sql/connection.h b/sql/connection.h index a2e8f67f94..1c4d72cfff 100644 --- a/sql/connection.h +++ b/sql/connection.h @@ -125,6 +125,9 @@ class SQL_EXPORT Connection { void set_error_callback(const ErrorCallback& callback) { error_callback_ = callback; } + bool has_error_callback() const { + return !error_callback_.is_null(); + } void reset_error_callback() { error_callback_.Reset(); } @@ -357,7 +360,14 @@ class SQL_EXPORT Connection { // Internal initialize function used by both Init and InitInMemory. The file // name is always 8 bits since we want to use the 8-bit version of // sqlite3_open. The string can also be sqlite's special ":memory:" string. - bool OpenInternal(const std::string& file_name); + // + // |retry_flag| controls retrying the open if the error callback + // addressed errors using RazeAndClose(). + enum Retry { + NO_RETRY = 0, + RETRY_ON_POISON + }; + bool OpenInternal(const std::string& file_name, Retry retry_flag); // Internal close function used by Close() and RazeAndClose(). // |forced| indicates that orderly-shutdown checks should not apply. diff --git a/sql/connection_unittest.cc b/sql/connection_unittest.cc index 1afd2dded3..8cf32fb3f8 100644 --- a/sql/connection_unittest.cc +++ b/sql/connection_unittest.cc @@ -2,16 +2,75 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/bind.h" #include "base/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/logging.h" #include "sql/connection.h" #include "sql/meta_table.h" #include "sql/statement.h" +#include "sql/test/error_callback_support.h" #include "sql/test/scoped_error_ignorer.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/sqlite/sqlite3.h" +namespace { + +// Helper to return the count of items in sqlite_master. Return -1 in +// case of error. +int SqliteMasterCount(sql::Connection* db) { + const char* kMasterCount = "SELECT COUNT(*) FROM sqlite_master"; + sql::Statement s(db->GetUniqueStatement(kMasterCount)); + return s.Step() ? s.ColumnInt(0) : -1; +} + +// Track the number of valid references which share the same pointer. +// This is used to allow testing an implicitly use-after-free case by +// explicitly having the ref count live longer than the object. +class RefCounter { + public: + RefCounter(size_t* counter) + : counter_(counter) { + (*counter_)++; + } + RefCounter(const RefCounter& other) + : counter_(other.counter_) { + (*counter_)++; + } + ~RefCounter() { + (*counter_)--; + } + + private: + size_t* counter_; + + DISALLOW_ASSIGN(RefCounter); +}; + +// Empty callback for implementation of ErrorCallbackSetHelper(). +void IgnoreErrorCallback(int error, sql::Statement* stmt) { +} + +void ErrorCallbackSetHelper(sql::Connection* db, + size_t* counter, + const RefCounter& r, + int error, sql::Statement* stmt) { + // The ref count should not go to zero when changing the callback. + EXPECT_GT(*counter, 0u); + db->set_error_callback(base::Bind(&IgnoreErrorCallback)); + EXPECT_GT(*counter, 0u); +} + +void ErrorCallbackResetHelper(sql::Connection* db, + size_t* counter, + const RefCounter& r, + int error, sql::Statement* stmt) { + // The ref count should not go to zero when clearing the callback. + EXPECT_GT(*counter, 0u); + db->reset_error_callback(); + EXPECT_GT(*counter, 0u); +} + class SQLConnectionTest : public testing::Test { public: SQLConnectionTest() {} @@ -31,6 +90,12 @@ class SQLConnectionTest : public testing::Test { return temp_dir_.path().AppendASCII("SQLConnectionTest.db"); } + // Handle errors by blowing away the database. + void RazeErrorCallback(int expected_error, int error, sql::Statement* stmt) { + EXPECT_EQ(expected_error, error); + db_.RazeAndClose(); + } + private: base::ScopedTempDir temp_dir_; sql::Connection db_; @@ -150,6 +215,61 @@ TEST_F(SQLConnectionTest, ScopedIgnoreError) { ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); } +TEST_F(SQLConnectionTest, 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)")); + + int error = SQLITE_OK; + { + sql::ScopedErrorCallback sec( + &db(), base::Bind(&sql::CaptureErrorCallback, &error)); + + // Inserting something other than a number into the primary key + // should result in the callback seeing SQLITE_MISMATCH. + EXPECT_FALSE(db().Execute("INSERT INTO foo (id) VALUES (12)")); + EXPECT_EQ(SQLITE_CONSTRAINT, error); + } + + // Callback is no longer in force due to reset. + { + error = SQLITE_OK; + sql::ScopedErrorIgnorer ignore_errors; + ignore_errors.IgnoreError(SQLITE_CONSTRAINT); + ASSERT_FALSE(db().Execute("INSERT INTO foo (id) VALUES (12)")); + ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); + EXPECT_EQ(SQLITE_OK, error); + } + + // base::Bind() can curry arguments to be passed by const reference + // to the callback function. If the callback function causes + // re/set_error_callback() to be called, the storage for those + // arguments can be deleted. + // + // RefCounter() counts how many objects are live using an external + // count. The same counter is passed to the callback, so that it + // can check directly even if the RefCounter object is no longer + // live. + { + size_t count = 0; + sql::ScopedErrorCallback sec( + &db(), base::Bind(&ErrorCallbackSetHelper, + &db(), &count, RefCounter(&count))); + + EXPECT_FALSE(db().Execute("INSERT INTO foo (id) VALUES (12)")); + } + + // Same test, but reset_error_callback() case. + { + size_t count = 0; + sql::ScopedErrorCallback sec( + &db(), base::Bind(&ErrorCallbackResetHelper, + &db(), &count, RefCounter(&count))); + + EXPECT_FALSE(db().Execute("INSERT INTO foo (id) VALUES (12)")); + } +} + // Test that sql::Connection::Raze() results in a database without the // tables from the original database. TEST_F(SQLConnectionTest, Raze) { @@ -193,10 +313,7 @@ TEST_F(SQLConnectionTest, Raze) { EXPECT_EQ(1, s.ColumnInt(0)); } - { - sql::Statement s(db().GetUniqueStatement("SELECT * FROM sqlite_master")); - ASSERT_FALSE(s.Step()); - } + ASSERT_EQ(0, SqliteMasterCount(&db())); { sql::Statement s(db().GetUniqueStatement("PRAGMA auto_vacuum")); @@ -245,18 +362,12 @@ TEST_F(SQLConnectionTest, RazeMultiple) { ASSERT_TRUE(other_db.Open(db_path())); // Check that the second connection sees the table. - const char *kTablesQuery = "SELECT COUNT(*) FROM sqlite_master"; - sql::Statement s(other_db.GetUniqueStatement(kTablesQuery)); - ASSERT_TRUE(s.Step()); - ASSERT_EQ(1, s.ColumnInt(0)); - ASSERT_FALSE(s.Step()); // Releases the shared lock. + ASSERT_EQ(1, SqliteMasterCount(&other_db)); ASSERT_TRUE(db().Raze()); // The second connection sees the updated database. - s.Reset(true); - ASSERT_TRUE(s.Step()); - ASSERT_EQ(0, s.ColumnInt(0)); + ASSERT_EQ(0, SqliteMasterCount(&other_db)); } TEST_F(SQLConnectionTest, RazeLocked) { @@ -294,6 +405,145 @@ TEST_F(SQLConnectionTest, RazeLocked) { ASSERT_TRUE(db().Raze()); } +// Verify that Raze() can handle an empty file. SQLite should treat +// this as an empty database. +TEST_F(SQLConnectionTest, RazeEmptyDB) { + const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"; + ASSERT_TRUE(db().Execute(kCreateSql)); + db().Close(); + + { + file_util::ScopedFILE file(file_util::OpenFile(db_path(), "r+")); + ASSERT_TRUE(file.get() != NULL); + ASSERT_EQ(0, fseek(file.get(), 0, SEEK_SET)); + ASSERT_TRUE(file_util::TruncateFile(file.get())); + } + + ASSERT_TRUE(db().Open(db_path())); + ASSERT_TRUE(db().Raze()); + EXPECT_EQ(0, SqliteMasterCount(&db())); +} + +// Verify that Raze() can handle a file of junk. +TEST_F(SQLConnectionTest, RazeNOTADB) { + db().Close(); + sql::Connection::Delete(db_path()); + ASSERT_FALSE(base::PathExists(db_path())); + + { + file_util::ScopedFILE file(file_util::OpenFile(db_path(), "w")); + ASSERT_TRUE(file.get() != NULL); + + const char* kJunk = "This is the hour of our discontent."; + fputs(kJunk, file.get()); + } + ASSERT_TRUE(base::PathExists(db_path())); + + // SQLite will successfully open the handle, but will fail with + // SQLITE_IOERR_SHORT_READ on pragma statemenets which read the + // header. + { + sql::ScopedErrorIgnorer ignore_errors; + ignore_errors.IgnoreError(SQLITE_IOERR_SHORT_READ); + EXPECT_TRUE(db().Open(db_path())); + ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); + } + EXPECT_TRUE(db().Raze()); + db().Close(); + + // Now empty, the open should open an empty database. + EXPECT_TRUE(db().Open(db_path())); + EXPECT_EQ(0, SqliteMasterCount(&db())); +} + +// Verify that Raze() can handle a database overwritten with garbage. +TEST_F(SQLConnectionTest, RazeNOTADB2) { + const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"; + ASSERT_TRUE(db().Execute(kCreateSql)); + ASSERT_EQ(1, SqliteMasterCount(&db())); + db().Close(); + + { + file_util::ScopedFILE file(file_util::OpenFile(db_path(), "r+")); + ASSERT_TRUE(file.get() != NULL); + ASSERT_EQ(0, fseek(file.get(), 0, SEEK_SET)); + + const char* kJunk = "This is the hour of our discontent."; + fputs(kJunk, file.get()); + } + + // SQLite will successfully open the handle, but will fail with + // SQLITE_NOTADB on pragma statemenets which attempt to read the + // corrupted header. + { + sql::ScopedErrorIgnorer ignore_errors; + ignore_errors.IgnoreError(SQLITE_NOTADB); + EXPECT_TRUE(db().Open(db_path())); + ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); + } + EXPECT_TRUE(db().Raze()); + db().Close(); + + // Now empty, the open should succeed with an empty database. + EXPECT_TRUE(db().Open(db_path())); + EXPECT_EQ(0, SqliteMasterCount(&db())); +} + +// Test that a callback from Open() can raze the database. This is +// 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. +// +// Most corruptions seen in the wild seem to happen when two pages in +// the database were not written transactionally (the transaction +// changed both, but one wasn't successfully written for some reason). +// A special case of that is when the header indicates that the +// database contains more pages than are in the file. This breaks +// things at a very basic level, verify that Raze() can handle it. +TEST_F(SQLConnectionTest, RazeCallbackReopen) { + const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"; + ASSERT_TRUE(db().Execute(kCreateSql)); + ASSERT_EQ(1, SqliteMasterCount(&db())); + int page_size = 0; + { + sql::Statement s(db().GetUniqueStatement("PRAGMA page_size")); + ASSERT_TRUE(s.Step()); + page_size = s.ColumnInt(0); + } + db().Close(); + + // Trim a single page from the end of the file. + { + file_util::ScopedFILE file(file_util::OpenFile(db_path(), "r+")); + ASSERT_TRUE(file.get() != NULL); + ASSERT_EQ(0, fseek(file.get(), -page_size, SEEK_END)); + ASSERT_TRUE(file_util::TruncateFile(file.get())); + } + + // Open() will succeed, even though the PRAGMA calls within will + // fail with SQLITE_CORRUPT, as will this PRAGMA. + { + sql::ScopedErrorIgnorer ignore_errors; + ignore_errors.IgnoreError(SQLITE_CORRUPT); + ASSERT_TRUE(db().Open(db_path())); + ASSERT_FALSE(db().Execute("PRAGMA auto_vacuum")); + db().Close(); + ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); + } + + db().set_error_callback(base::Bind(&SQLConnectionTest::RazeErrorCallback, + base::Unretained(this), + SQLITE_CORRUPT)); + + // When the PRAGMA calls in Open() raise SQLITE_CORRUPT, the error + // callback will call RazeAndClose(). Open() will then fail and be + // retried. The second Open() on the empty database will succeed + // cleanly. + ASSERT_TRUE(db().Open(db_path())); + ASSERT_TRUE(db().Execute("PRAGMA auto_vacuum")); + EXPECT_EQ(0, SqliteMasterCount(&db())); +} + // Basic test of RazeAndClose() operation. TEST_F(SQLConnectionTest, RazeAndClose) { const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"; @@ -307,10 +557,7 @@ TEST_F(SQLConnectionTest, RazeAndClose) { ASSERT_FALSE(db().is_open()); db().Close(); ASSERT_TRUE(db().Open(db_path())); - { - sql::Statement s(db().GetUniqueStatement("SELECT * FROM sqlite_master")); - ASSERT_FALSE(s.Step()); - } + ASSERT_EQ(0, SqliteMasterCount(&db())); // Test that RazeAndClose() can break transactions. ASSERT_TRUE(db().Execute(kCreateSql)); @@ -321,10 +568,7 @@ TEST_F(SQLConnectionTest, RazeAndClose) { ASSERT_FALSE(db().CommitTransaction()); db().Close(); ASSERT_TRUE(db().Open(db_path())); - { - sql::Statement s(db().GetUniqueStatement("SELECT * FROM sqlite_master")); - ASSERT_FALSE(s.Step()); - } + ASSERT_EQ(0, SqliteMasterCount(&db())); } // Test that various operations fail without crashing after @@ -419,10 +663,12 @@ TEST_F(SQLConnectionTest, Delete) { // Should have both a main database file and a journal file because // of journal_mode PERSIST. base::FilePath journal(db_path().value() + FILE_PATH_LITERAL("-journal")); - ASSERT_TRUE(file_util::PathExists(db_path())); - ASSERT_TRUE(file_util::PathExists(journal)); + ASSERT_TRUE(base::PathExists(db_path())); + ASSERT_TRUE(base::PathExists(journal)); sql::Connection::Delete(db_path()); - EXPECT_FALSE(file_util::PathExists(db_path())); - EXPECT_FALSE(file_util::PathExists(journal)); + EXPECT_FALSE(base::PathExists(db_path())); + EXPECT_FALSE(base::PathExists(journal)); } + +} // namespace diff --git a/sql/sql.gyp b/sql/sql.gyp index 944defd6bc..2e8fc184db 100644 --- a/sql/sql.gyp +++ b/sql/sql.gyp @@ -55,6 +55,8 @@ '../base/base.gyp:base', ], 'sources': [ + 'test/error_callback_support.cc', + 'test/error_callback_support.h', 'test/scoped_error_ignorer.cc', 'test/scoped_error_ignorer.h', ], diff --git a/sql/sql.target.darwin-arm.mk b/sql/sql.target.darwin-arm.mk index 0eb909fb4a..214df24e9e 100644 --- a/sql/sql.target.darwin-arm.mk +++ b/sql/sql.target.darwin-arm.mk @@ -66,8 +66,9 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ '-D_FILE_OFFSET_BITS=64' \ - '-DUSE_LINUX_BREAKPAD' \ '-DNO_TCMALLOC' \ + '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ + '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \ '-DDISABLE_NACL' \ '-DCHROMIUM_BUILD' \ '-DUSE_LIBJPEG_TURBO=1' \ @@ -149,8 +150,9 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ '-D_FILE_OFFSET_BITS=64' \ - '-DUSE_LINUX_BREAKPAD' \ '-DNO_TCMALLOC' \ + '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ + '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \ '-DDISABLE_NACL' \ '-DCHROMIUM_BUILD' \ '-DUSE_LIBJPEG_TURBO=1' \ diff --git a/sql/sql.target.darwin-mips.mk b/sql/sql.target.darwin-mips.mk index 66b9d20103..1a63837383 100644 --- a/sql/sql.target.darwin-mips.mk +++ b/sql/sql.target.darwin-mips.mk @@ -66,6 +66,8 @@ MY_DEFS_Debug := \ '-DANGLE_DX11' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ + '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ + '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \ '-DDISABLE_NACL' \ '-DCHROMIUM_BUILD' \ '-DUSE_LIBJPEG_TURBO=1' \ @@ -147,6 +149,8 @@ MY_DEFS_Release := \ '-DANGLE_DX11' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ + '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ + '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \ '-DDISABLE_NACL' \ '-DCHROMIUM_BUILD' \ '-DUSE_LIBJPEG_TURBO=1' \ diff --git a/sql/sql.target.darwin-x86.mk b/sql/sql.target.darwin-x86.mk index ada2ff0848..5625a1c768 100644 --- a/sql/sql.target.darwin-x86.mk +++ b/sql/sql.target.darwin-x86.mk @@ -68,8 +68,9 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ '-D_FILE_OFFSET_BITS=64' \ - '-DUSE_LINUX_BREAKPAD' \ '-DNO_TCMALLOC' \ + '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ + '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \ '-DDISABLE_NACL' \ '-DCHROMIUM_BUILD' \ '-DUSE_LIBJPEG_TURBO=1' \ @@ -154,8 +155,9 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ '-D_FILE_OFFSET_BITS=64' \ - '-DUSE_LINUX_BREAKPAD' \ '-DNO_TCMALLOC' \ + '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ + '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \ '-DDISABLE_NACL' \ '-DCHROMIUM_BUILD' \ '-DUSE_LIBJPEG_TURBO=1' \ diff --git a/sql/sql.target.linux-arm.mk b/sql/sql.target.linux-arm.mk index 0eb909fb4a..214df24e9e 100644 --- a/sql/sql.target.linux-arm.mk +++ b/sql/sql.target.linux-arm.mk @@ -66,8 +66,9 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ '-D_FILE_OFFSET_BITS=64' \ - '-DUSE_LINUX_BREAKPAD' \ '-DNO_TCMALLOC' \ + '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ + '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \ '-DDISABLE_NACL' \ '-DCHROMIUM_BUILD' \ '-DUSE_LIBJPEG_TURBO=1' \ @@ -149,8 +150,9 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ '-D_FILE_OFFSET_BITS=64' \ - '-DUSE_LINUX_BREAKPAD' \ '-DNO_TCMALLOC' \ + '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ + '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \ '-DDISABLE_NACL' \ '-DCHROMIUM_BUILD' \ '-DUSE_LIBJPEG_TURBO=1' \ diff --git a/sql/sql.target.linux-mips.mk b/sql/sql.target.linux-mips.mk index 66b9d20103..1a63837383 100644 --- a/sql/sql.target.linux-mips.mk +++ b/sql/sql.target.linux-mips.mk @@ -66,6 +66,8 @@ MY_DEFS_Debug := \ '-DANGLE_DX11' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ + '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ + '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \ '-DDISABLE_NACL' \ '-DCHROMIUM_BUILD' \ '-DUSE_LIBJPEG_TURBO=1' \ @@ -147,6 +149,8 @@ MY_DEFS_Release := \ '-DANGLE_DX11' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ + '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ + '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \ '-DDISABLE_NACL' \ '-DCHROMIUM_BUILD' \ '-DUSE_LIBJPEG_TURBO=1' \ diff --git a/sql/sql.target.linux-x86.mk b/sql/sql.target.linux-x86.mk index ada2ff0848..5625a1c768 100644 --- a/sql/sql.target.linux-x86.mk +++ b/sql/sql.target.linux-x86.mk @@ -68,8 +68,9 @@ MY_CFLAGS_Debug := \ MY_DEFS_Debug := \ '-DANGLE_DX11' \ '-D_FILE_OFFSET_BITS=64' \ - '-DUSE_LINUX_BREAKPAD' \ '-DNO_TCMALLOC' \ + '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ + '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \ '-DDISABLE_NACL' \ '-DCHROMIUM_BUILD' \ '-DUSE_LIBJPEG_TURBO=1' \ @@ -154,8 +155,9 @@ MY_CFLAGS_Release := \ MY_DEFS_Release := \ '-DANGLE_DX11' \ '-D_FILE_OFFSET_BITS=64' \ - '-DUSE_LINUX_BREAKPAD' \ '-DNO_TCMALLOC' \ + '-DDISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY' \ + '-DSYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE' \ '-DDISABLE_NACL' \ '-DCHROMIUM_BUILD' \ '-DUSE_LIBJPEG_TURBO=1' \ diff --git a/sql/statement_unittest.cc b/sql/statement_unittest.cc index 3d32862c79..c5217aa6c9 100644 --- a/sql/statement_unittest.cc +++ b/sql/statement_unittest.cc @@ -9,55 +9,29 @@ #include "base/files/scoped_temp_dir.h" #include "sql/connection.h" #include "sql/statement.h" +#include "sql/test/error_callback_support.h" +#include "sql/test/scoped_error_ignorer.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/sqlite/sqlite3.h" namespace { -void CaptureErrorCallback(int* error_pointer, std::string* sql_text, - int error, sql::Statement* stmt) { - *error_pointer = error; - const char* text = stmt ? stmt->GetSQLStatement() : NULL; - *sql_text = text ? text : "no statement available"; -} - class SQLStatementTest : public testing::Test { public: - SQLStatementTest() : error_(SQLITE_OK) {} - virtual void SetUp() { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); ASSERT_TRUE(db_.Open(temp_dir_.path().AppendASCII("SQLStatementTest.db"))); - // The error delegate will set |error_| and |sql_text_| when any sqlite - // statement operation returns an error code. - db_.set_error_callback(base::Bind(&CaptureErrorCallback, - &error_, &sql_text_)); } virtual void TearDown() { - // If any error happened the original sql statement can be found in - // |sql_text_|. - EXPECT_EQ(SQLITE_OK, error_); db_.Close(); } sql::Connection& db() { return db_; } - int sqlite_error() const { return error_; } - - void ResetError() { - error_ = SQLITE_OK; - sql_text_.clear(); - } - private: base::ScopedTempDir temp_dir_; sql::Connection db_; - - // The error code of the most recent error. - int error_; - // Original statement which caused the error. - std::string sql_text_; }; } // namespace @@ -101,9 +75,14 @@ TEST_F(SQLStatementTest, Run) { EXPECT_TRUE(s.Succeeded()); } -TEST_F(SQLStatementTest, BasicErrorCallback) { +// Error callback called for error running a statement. +TEST_F(SQLStatementTest, ErrorCallback) { ASSERT_TRUE(db().Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)")); - EXPECT_EQ(SQLITE_OK, sqlite_error()); + + int error = SQLITE_OK; + sql::ScopedErrorCallback sec( + &db(), base::Bind(&sql::CaptureErrorCallback, &error)); + // Insert in the foo table the primary key. It is an error to insert // something other than an number. This error causes the error callback // handler to be called with SQLITE_MISMATCH as error code. @@ -111,8 +90,21 @@ TEST_F(SQLStatementTest, BasicErrorCallback) { EXPECT_TRUE(s.is_valid()); s.BindCString(0, "bad bad"); EXPECT_FALSE(s.Run()); - EXPECT_EQ(SQLITE_MISMATCH, sqlite_error()); - ResetError(); + EXPECT_EQ(SQLITE_MISMATCH, error); +} + +// Error ignorer works for error running a statement. +TEST_F(SQLStatementTest, ScopedIgnoreError) { + ASSERT_TRUE(db().Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)")); + + sql::Statement s(db().GetUniqueStatement("INSERT INTO foo (a) VALUES (?)")); + EXPECT_TRUE(s.is_valid()); + + sql::ScopedErrorIgnorer ignore_errors; + ignore_errors.IgnoreError(SQLITE_MISMATCH); + s.BindCString(0, "bad bad"); + ASSERT_FALSE(s.Run()); + ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); } TEST_F(SQLStatementTest, Reset) { diff --git a/sql/test/error_callback_support.cc b/sql/test/error_callback_support.cc new file mode 100644 index 0000000000..9f0e22f381 --- /dev/null +++ b/sql/test/error_callback_support.cc @@ -0,0 +1,28 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sql/test/error_callback_support.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace sql { + +void CaptureErrorCallback(int* error_pointer, int error, sql::Statement* stmt) { + *error_pointer = error; +} + +ScopedErrorCallback::ScopedErrorCallback( + sql::Connection* db, + const sql::Connection::ErrorCallback& cb) + : db_(db) { + // Make sure someone isn't trying to nest things. + EXPECT_FALSE(db_->has_error_callback()); + db_->set_error_callback(cb); +} + +ScopedErrorCallback::~ScopedErrorCallback() { + db_->reset_error_callback(); +} + +} // namespace diff --git a/sql/test/error_callback_support.h b/sql/test/error_callback_support.h new file mode 100644 index 0000000000..30268570f8 --- /dev/null +++ b/sql/test/error_callback_support.h @@ -0,0 +1,36 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SQL_TEST_ERROR_CALLBACK_SUPPORT_H_ +#define SQL_TEST_ERROR_CALLBACK_SUPPORT_H_ + +#include "sql/connection.h" + +namespace sql { + +// Helper to capture any errors into a local variable for testing. +// For instance: +// int error = SQLITE_OK; +// ScopedErrorCallback sec(db, base::Bind(&CaptureErrorCallback, &error)); +// // Provoke SQLITE_CONSTRAINT on db. +// EXPECT_EQ(SQLITE_CONSTRAINT, error); +void CaptureErrorCallback(int* error_pointer, int error, sql::Statement* stmt); + +// Helper to set db's error callback and then reset it when it goes +// out of scope. +class ScopedErrorCallback { + public: + ScopedErrorCallback(sql::Connection* db, + const sql::Connection::ErrorCallback& cb); + ~ScopedErrorCallback(); + + private: + sql::Connection* db_; + + DISALLOW_COPY_AND_ASSIGN(ScopedErrorCallback); +}; + +} // namespace sql + +#endif // SQL_TEST_ERROR_CALLBACK_SUPPORT_H_ |