diff options
author | Torne (Richard Coles) <torne@google.com> | 2013-06-19 11:58:07 +0100 |
---|---|---|
committer | Torne (Richard Coles) <torne@google.com> | 2013-06-19 11:58:07 +0100 |
commit | 7d4cd473f85ac64c3747c96c277f9e506a0d2246 (patch) | |
tree | f5fecd524f5ac22cd38bcc6713b81f666730d5a1 /sql | |
parent | 84f2b2352908c30e40ae12ffe850dd8470f6c048 (diff) | |
download | chromium_org-7d4cd473f85ac64c3747c96c277f9e506a0d2246.tar.gz |
Merge from Chromium at DEPS revision r207203
This commit was generated by merge_to_master.py.
Change-Id: I5fbb6854d092096c4d39edc2865a48be1b53c418
Diffstat (limited to 'sql')
-rw-r--r-- | sql/connection.cc | 55 | ||||
-rw-r--r-- | sql/connection.h | 56 | ||||
-rw-r--r-- | sql/connection_unittest.cc | 14 | ||||
-rw-r--r-- | sql/sql.gyp | 37 | ||||
-rw-r--r-- | sql/sql.target.darwin-arm.mk | 3 | ||||
-rw-r--r-- | sql/sql.target.darwin-mips.mk | 3 | ||||
-rw-r--r-- | sql/sql.target.darwin-x86.mk | 3 | ||||
-rw-r--r-- | sql/sql.target.linux-arm.mk | 3 | ||||
-rw-r--r-- | sql/sql.target.linux-mips.mk | 3 | ||||
-rw-r--r-- | sql/sql.target.linux-x86.mk | 3 | ||||
-rw-r--r-- | sql/sqlite_features_unittest.cc | 32 | ||||
-rw-r--r-- | sql/statement_unittest.cc | 32 | ||||
-rw-r--r-- | sql/test/scoped_error_ignorer.cc | 60 | ||||
-rw-r--r-- | sql/test/scoped_error_ignorer.h | 64 |
14 files changed, 249 insertions, 119 deletions
diff --git a/sql/connection.cc b/sql/connection.cc index 4eb060b879..67cdf0886e 100644 --- a/sql/connection.cc +++ b/sql/connection.cc @@ -68,15 +68,34 @@ class ScopedWritableSchema { namespace sql { +// static +Connection::ErrorIgnorerCallback* Connection::current_ignorer_cb_ = NULL; + +// static +bool Connection::ShouldIgnore(int error) { + if (!current_ignorer_cb_) + return false; + return current_ignorer_cb_->Run(error); +} + +// static +void Connection::SetErrorIgnorer(Connection::ErrorIgnorerCallback* cb) { + CHECK(current_ignorer_cb_ == NULL); + current_ignorer_cb_ = cb; +} + +// static +void Connection::ResetErrorIgnorer() { + CHECK(current_ignorer_cb_); + current_ignorer_cb_ = NULL; +} + bool StatementID::operator<(const StatementID& other) const { if (number_ != other.number_) return number_ < other.number_; return strcmp(str_, other.str_) < 0; } -ErrorDelegate::~ErrorDelegate() { -} - Connection::StatementRef::StatementRef(Connection* connection, sqlite3_stmt* stmt, bool was_valid) @@ -122,8 +141,7 @@ Connection::Connection() transaction_nesting_(0), needs_rollback_(false), in_memory_(false), - poisoned_(false), - error_delegate_(NULL) { + poisoned_(false) { } Connection::~Connection() { @@ -439,7 +457,8 @@ bool Connection::Execute(const char* sql) { // This needs to be a FATAL log because the error case of arriving here is // that there's a malformed SQL statement. This can arise in development if - // a change alters the schema but not all queries adjust. + // a change alters the schema but not all queries adjust. This can happen + // in production if the schema is corrupted. if (error == SQLITE_ERROR) DLOG(FATAL) << "SQL Error in " << sql << ", " << GetErrorMessage(); return error == SQLITE_OK; @@ -664,11 +683,10 @@ bool Connection::OpenInternal(const std::string& file_name) { // assumptions about who might change things in the database. // http://crbug.com/56559 if (exclusive_locking_) { - // TODO(shess): This should probably be a full CHECK(). Code - // which requests exclusive locking but doesn't get it is almost - // certain to be ill-tested. - if (!Execute("PRAGMA locking_mode=EXCLUSIVE")) - DLOG(FATAL) << "Could not set locking mode: " << GetErrorMessage(); + // TODO(shess): This should probably be a failure. Code which + // requests exclusive locking but doesn't get it is almost certain + // to be ill-tested. + ignore_result(Execute("PRAGMA locking_mode=EXCLUSIVE")); } // http://www.sqlite.org/pragma.html#pragma_journal_mode @@ -693,19 +711,16 @@ bool Connection::OpenInternal(const std::string& file_name) { DCHECK_LE(page_size_, kSqliteMaxPageSize); const std::string sql = base::StringPrintf("PRAGMA page_size=%d", page_size_); - if (!ExecuteWithTimeout(sql.c_str(), kBusyTimeout)) - DLOG(FATAL) << "Could not set page size: " << GetErrorMessage(); + ignore_result(ExecuteWithTimeout(sql.c_str(), kBusyTimeout)); } if (cache_size_ != 0) { const std::string sql = base::StringPrintf("PRAGMA cache_size=%d", cache_size_); - if (!ExecuteWithTimeout(sql.c_str(), kBusyTimeout)) - DLOG(FATAL) << "Could not set cache size: " << GetErrorMessage(); + ignore_result(ExecuteWithTimeout(sql.c_str(), kBusyTimeout)); } if (!ExecuteWithTimeout("PRAGMA secure_delete=ON", kBusyTimeout)) { - DLOG(FATAL) << "Could not enable secure_delete: " << GetErrorMessage(); Close(); return false; } @@ -764,13 +779,9 @@ int Connection::OnSqliteError(int err, sql::Statement *stmt) { return err; } - // TODO(shess): Remove |error_delegate_| once everything is - // converted to |error_callback_|. - if (error_delegate_.get()) - return error_delegate_->OnError(err, this, stmt); - // The default handling is to assert on debug and to ignore on release. - DLOG(FATAL) << GetErrorMessage(); + if (!ShouldIgnore(err)) + DLOG(FATAL) << GetErrorMessage(); return err; } diff --git a/sql/connection.h b/sql/connection.h index 049d7cf5b4..91139cedc6 100644 --- a/sql/connection.h +++ b/sql/connection.h @@ -78,30 +78,6 @@ class StatementID { class Connection; -// ErrorDelegate defines the interface to implement error handling and recovery -// for sqlite operations. This allows the rest of the classes to return true or -// false while the actual error code and causing statement are delivered using -// the OnError() callback. -// The tipical usage is to centralize the code designed to handle database -// corruption, low-level IO errors or locking violations. -class SQL_EXPORT ErrorDelegate { - public: - virtual ~ErrorDelegate(); - - // |error| is an sqlite result code as seen in sqlite3.h. |connection| is the - // db connection where the error happened and |stmt| is our best guess at the - // statement that triggered the error. Do not store these pointers. - // - // |stmt| MAY BE NULL if there is no statement causing the problem (i.e. on - // initialization). - // - // If the error condition has been fixed and the original statement succesfuly - // re-tried then returning SQLITE_OK is appropriate; otherwise it is - // recommended that you return the original |error| or the appropriate error - // code. - virtual int OnError(int error, Connection* connection, Statement* stmt) = 0; -}; - class SQL_EXPORT Connection { private: class StatementRef; // Forward declaration, see real one below. @@ -145,14 +121,6 @@ class SQL_EXPORT Connection { // // If no callback is set, the default action is to crash in debug // mode or return failure in release mode. - // - // TODO(shess): ErrorDelegate allowed for returning a different - // error. Determine if this is necessary for the callback. In my - // experience, this is not well-tested and probably not safe, and - // current clients always return the same error passed. - // Additionally, most errors don't admit to a clean way to retry the - // failed operation, so converting an error to SQLITE_OK is probably - // not feasible. typedef base::Callback<void(int, Statement*)> ErrorCallback; void set_error_callback(const ErrorCallback& callback) { error_callback_ = callback; @@ -161,15 +129,6 @@ class SQL_EXPORT Connection { error_callback_.Reset(); } - // Sets the object that will handle errors. Recomended that it should be set - // before calling Open(). If not set, the default is to ignore errors on - // release and assert on debug builds. - // Takes ownership of |delegate|. - // NOTE(shess): Deprecated, use set_error_callback(). - void set_error_delegate(ErrorDelegate* delegate) { - error_delegate_.reset(delegate); - } - // Set this tag to enable additional connection-type histogramming // for SQLite error codes and database version numbers. void set_histogram_tag(const std::string& tag) { @@ -376,6 +335,9 @@ class SQL_EXPORT Connection { const char* GetErrorMessage() const; private: + // Allow test-support code to set/reset error ignorer. + friend class ScopedErrorIgnorer; + // Statement accesses StatementRef which we don't want to expose to everybody // (they should go through Statement). friend class Statement; @@ -400,6 +362,14 @@ class SQL_EXPORT Connection { // Internal helper for DoesTableExist and DoesIndexExist. bool DoesTableOrIndexExist(const char* name, const char* type) const; + // Accessors for global error-ignorer, for injecting behavior during tests. + // See test/scoped_error_ignorer.h. + typedef base::Callback<bool(int)> ErrorIgnorerCallback; + static ErrorIgnorerCallback* current_ignorer_cb_; + static bool ShouldIgnore(int error); + static void SetErrorIgnorer(ErrorIgnorerCallback* ignorer); + static void ResetErrorIgnorer(); + // A StatementRef is a refcounted wrapper around a sqlite statement pointer. // Refcounting allows us to give these statements out to sql::Statement // objects while also optionally maintaining a cache of compiled statements @@ -529,10 +499,6 @@ class SQL_EXPORT Connection { ErrorCallback error_callback_; - // This object handles errors resulting from all forms of executing sqlite - // commands or statements. It can be null which means default handling. - scoped_ptr<ErrorDelegate> error_delegate_; - // Tag for auxiliary histograms. std::string histogram_tag_; diff --git a/sql/connection_unittest.cc b/sql/connection_unittest.cc index 2aaeb27b33..b43e83cb48 100644 --- a/sql/connection_unittest.cc +++ b/sql/connection_unittest.cc @@ -8,6 +8,7 @@ #include "sql/connection.h" #include "sql/meta_table.h" #include "sql/statement.h" +#include "sql/test/scoped_error_ignorer.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/sqlite/sqlite3.h" @@ -136,6 +137,19 @@ TEST_F(SQLConnectionTest, Rollback) { EXPECT_TRUE(db().BeginTransaction()); } +// Test the scoped error ignorer by attempting to insert a duplicate +// value into an index. +TEST_F(SQLConnectionTest, ScopedIgnoreError) { + const char* kCreateSql = "CREATE TABLE foo (id INTEGER UNIQUE)"; + ASSERT_TRUE(db().Execute(kCreateSql)); + ASSERT_TRUE(db().Execute("INSERT INTO foo (id) VALUES (12)")); + + sql::ScopedErrorIgnorer ignore_errors; + ignore_errors.IgnoreError(SQLITE_CONSTRAINT); + ASSERT_FALSE(db().Execute("INSERT INTO foo (id) VALUES (12)")); + ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); +} + // Test that sql::Connection::Raze() results in a database without the // tables from the original database. TEST_F(SQLConnectionTest, Raze) { diff --git a/sql/sql.gyp b/sql/sql.gyp index 86cfbc9160..944defd6bc 100644 --- a/sql/sql.gyp +++ b/sql/sql.gyp @@ -14,6 +14,9 @@ '../base/base.gyp:base', '../third_party/sqlite/sqlite.gyp:sqlite', ], + 'export_dependent_settings': [ + '../base/base.gyp:base', + ], 'defines': [ 'SQL_IMPLEMENTATION' ], 'sources': [ 'connection.cc', @@ -28,14 +31,48 @@ 'transaction.cc', 'transaction.h', ], + 'include_dirs': [ + '..', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '..', + ], + }, # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. 'msvs_disabled_warnings': [4267, ], }, { + 'target_name': 'test_support_sql', + 'type': 'static_library', + 'dependencies': [ + 'sql', + '../base/base.gyp:base', + '../testing/gtest.gyp:gtest', + ], + 'export_dependent_settings': [ + 'sql', + '../base/base.gyp:base', + ], + 'sources': [ + 'test/scoped_error_ignorer.cc', + 'test/scoped_error_ignorer.h', + ], + 'include_dirs': [ + '..', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '..', + ], + }, + }, + { 'target_name': 'sql_unittests', 'type': '<(gtest_target_type)', 'dependencies': [ 'sql', + 'test_support_sql', '../base/base.gyp:test_support_base', '../testing/gtest.gyp:gtest', ], diff --git a/sql/sql.target.darwin-arm.mk b/sql/sql.target.darwin-arm.mk index a303e79d23..0be6cfd699 100644 --- a/sql/sql.target.darwin-arm.mk +++ b/sql/sql.target.darwin-arm.mk @@ -66,6 +66,7 @@ MY_CFLAGS := \ MY_CFLAGS_C := MY_DEFS := \ + '-DANGLE_DX11' \ '-D_FILE_OFFSET_BITS=64' \ '-DUSE_LINUX_BREAKPAD' \ '-DNO_TCMALLOC' \ @@ -93,10 +94,10 @@ LOCAL_CFLAGS := $(MY_CFLAGS_C) $(MY_CFLAGS) $(MY_DEFS) # Include paths placed before CFLAGS/CPPFLAGS LOCAL_C_INCLUDES := \ + $(LOCAL_PATH) \ $(gyp_shared_intermediate_dir)/shim_headers/ashmem/target \ $(gyp_shared_intermediate_dir)/shim_headers/icui18n/target \ $(gyp_shared_intermediate_dir)/shim_headers/icuuc/target \ - $(LOCAL_PATH) \ $(LOCAL_PATH)/third_party/sqlite \ $(PWD)/frameworks/wilhelm/include \ $(PWD)/bionic \ diff --git a/sql/sql.target.darwin-mips.mk b/sql/sql.target.darwin-mips.mk index 7b70ee34c5..9e80dbde5d 100644 --- a/sql/sql.target.darwin-mips.mk +++ b/sql/sql.target.darwin-mips.mk @@ -68,6 +68,7 @@ MY_CFLAGS := \ MY_CFLAGS_C := MY_DEFS := \ + '-DANGLE_DX11' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISABLE_NACL' \ @@ -94,10 +95,10 @@ LOCAL_CFLAGS := $(MY_CFLAGS_C) $(MY_CFLAGS) $(MY_DEFS) # Include paths placed before CFLAGS/CPPFLAGS LOCAL_C_INCLUDES := \ + $(LOCAL_PATH) \ $(gyp_shared_intermediate_dir)/shim_headers/ashmem/target \ $(gyp_shared_intermediate_dir)/shim_headers/icui18n/target \ $(gyp_shared_intermediate_dir)/shim_headers/icuuc/target \ - $(LOCAL_PATH) \ $(LOCAL_PATH)/third_party/sqlite \ $(PWD)/frameworks/wilhelm/include \ $(PWD)/bionic \ diff --git a/sql/sql.target.darwin-x86.mk b/sql/sql.target.darwin-x86.mk index 64df7c8372..7e001300af 100644 --- a/sql/sql.target.darwin-x86.mk +++ b/sql/sql.target.darwin-x86.mk @@ -68,6 +68,7 @@ MY_CFLAGS := \ MY_CFLAGS_C := MY_DEFS := \ + '-DANGLE_DX11' \ '-D_FILE_OFFSET_BITS=64' \ '-DUSE_LINUX_BREAKPAD' \ '-DNO_TCMALLOC' \ @@ -95,10 +96,10 @@ LOCAL_CFLAGS := $(MY_CFLAGS_C) $(MY_CFLAGS) $(MY_DEFS) # Include paths placed before CFLAGS/CPPFLAGS LOCAL_C_INCLUDES := \ + $(LOCAL_PATH) \ $(gyp_shared_intermediate_dir)/shim_headers/ashmem/target \ $(gyp_shared_intermediate_dir)/shim_headers/icui18n/target \ $(gyp_shared_intermediate_dir)/shim_headers/icuuc/target \ - $(LOCAL_PATH) \ $(LOCAL_PATH)/third_party/sqlite \ $(PWD)/frameworks/wilhelm/include \ $(PWD)/bionic \ diff --git a/sql/sql.target.linux-arm.mk b/sql/sql.target.linux-arm.mk index a303e79d23..0be6cfd699 100644 --- a/sql/sql.target.linux-arm.mk +++ b/sql/sql.target.linux-arm.mk @@ -66,6 +66,7 @@ MY_CFLAGS := \ MY_CFLAGS_C := MY_DEFS := \ + '-DANGLE_DX11' \ '-D_FILE_OFFSET_BITS=64' \ '-DUSE_LINUX_BREAKPAD' \ '-DNO_TCMALLOC' \ @@ -93,10 +94,10 @@ LOCAL_CFLAGS := $(MY_CFLAGS_C) $(MY_CFLAGS) $(MY_DEFS) # Include paths placed before CFLAGS/CPPFLAGS LOCAL_C_INCLUDES := \ + $(LOCAL_PATH) \ $(gyp_shared_intermediate_dir)/shim_headers/ashmem/target \ $(gyp_shared_intermediate_dir)/shim_headers/icui18n/target \ $(gyp_shared_intermediate_dir)/shim_headers/icuuc/target \ - $(LOCAL_PATH) \ $(LOCAL_PATH)/third_party/sqlite \ $(PWD)/frameworks/wilhelm/include \ $(PWD)/bionic \ diff --git a/sql/sql.target.linux-mips.mk b/sql/sql.target.linux-mips.mk index 7b70ee34c5..9e80dbde5d 100644 --- a/sql/sql.target.linux-mips.mk +++ b/sql/sql.target.linux-mips.mk @@ -68,6 +68,7 @@ MY_CFLAGS := \ MY_CFLAGS_C := MY_DEFS := \ + '-DANGLE_DX11' \ '-D_FILE_OFFSET_BITS=64' \ '-DNO_TCMALLOC' \ '-DDISABLE_NACL' \ @@ -94,10 +95,10 @@ LOCAL_CFLAGS := $(MY_CFLAGS_C) $(MY_CFLAGS) $(MY_DEFS) # Include paths placed before CFLAGS/CPPFLAGS LOCAL_C_INCLUDES := \ + $(LOCAL_PATH) \ $(gyp_shared_intermediate_dir)/shim_headers/ashmem/target \ $(gyp_shared_intermediate_dir)/shim_headers/icui18n/target \ $(gyp_shared_intermediate_dir)/shim_headers/icuuc/target \ - $(LOCAL_PATH) \ $(LOCAL_PATH)/third_party/sqlite \ $(PWD)/frameworks/wilhelm/include \ $(PWD)/bionic \ diff --git a/sql/sql.target.linux-x86.mk b/sql/sql.target.linux-x86.mk index 64df7c8372..7e001300af 100644 --- a/sql/sql.target.linux-x86.mk +++ b/sql/sql.target.linux-x86.mk @@ -68,6 +68,7 @@ MY_CFLAGS := \ MY_CFLAGS_C := MY_DEFS := \ + '-DANGLE_DX11' \ '-D_FILE_OFFSET_BITS=64' \ '-DUSE_LINUX_BREAKPAD' \ '-DNO_TCMALLOC' \ @@ -95,10 +96,10 @@ LOCAL_CFLAGS := $(MY_CFLAGS_C) $(MY_CFLAGS) $(MY_DEFS) # Include paths placed before CFLAGS/CPPFLAGS LOCAL_C_INCLUDES := \ + $(LOCAL_PATH) \ $(gyp_shared_intermediate_dir)/shim_headers/ashmem/target \ $(gyp_shared_intermediate_dir)/shim_headers/icui18n/target \ $(gyp_shared_intermediate_dir)/shim_headers/icuuc/target \ - $(LOCAL_PATH) \ $(LOCAL_PATH)/third_party/sqlite \ $(PWD)/frameworks/wilhelm/include \ $(PWD)/bionic \ diff --git a/sql/sqlite_features_unittest.cc b/sql/sqlite_features_unittest.cc index 2f6a49eb48..4fc730c2b8 100644 --- a/sql/sqlite_features_unittest.cc +++ b/sql/sqlite_features_unittest.cc @@ -4,6 +4,7 @@ #include <string> +#include "base/bind.h" #include "base/file_util.h" #include "base/files/scoped_temp_dir.h" #include "sql/connection.h" @@ -15,28 +16,12 @@ namespace { -class StatementErrorHandler : public sql::ErrorDelegate { - public: - StatementErrorHandler(int* error, std::string* sql_text) - : error_(error), - sql_text_(sql_text) {} - - virtual ~StatementErrorHandler() {} - - virtual int OnError(int error, sql::Connection* connection, - sql::Statement* stmt) OVERRIDE { - *error_ = error; - const char* sql_txt = stmt ? stmt->GetSQLStatement() : NULL; - *sql_text_ = sql_txt ? sql_txt : "no statement available"; - return error; - } - - private: - int* error_; - std::string* sql_text_; - - DISALLOW_COPY_AND_ASSIGN(StatementErrorHandler); -}; +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 SQLiteFeaturesTest : public testing::Test { public: @@ -48,7 +33,8 @@ class SQLiteFeaturesTest : public testing::Test { // The error delegate will set |error_| and |sql_text_| when any sqlite // statement operation returns an error code. - db_.set_error_delegate(new StatementErrorHandler(&error_, &sql_text_)); + db_.set_error_callback(base::Bind(&CaptureErrorCallback, + &error_, &sql_text_)); } virtual void TearDown() { diff --git a/sql/statement_unittest.cc b/sql/statement_unittest.cc index ffa8aef1f8..3d32862c79 100644 --- a/sql/statement_unittest.cc +++ b/sql/statement_unittest.cc @@ -4,6 +4,7 @@ #include <string> +#include "base/bind.h" #include "base/file_util.h" #include "base/files/scoped_temp_dir.h" #include "sql/connection.h" @@ -13,28 +14,12 @@ namespace { -class StatementErrorHandler : public sql::ErrorDelegate { - public: - StatementErrorHandler(int* error, std::string* sql_text) - : error_(error), - sql_text_(sql_text) {} - - virtual ~StatementErrorHandler() {} - - virtual int OnError(int error, sql::Connection* connection, - sql::Statement* stmt) OVERRIDE { - *error_ = error; - const char* sql_txt = stmt ? stmt->GetSQLStatement() : NULL; - *sql_text_ = sql_txt ? sql_txt : "no statement available"; - return error; - } - - private: - int* error_; - std::string* sql_text_; - - DISALLOW_COPY_AND_ASSIGN(StatementErrorHandler); -}; +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: @@ -45,7 +30,8 @@ class SQLStatementTest : public testing::Test { 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_delegate(new StatementErrorHandler(&error_, &sql_text_)); + db_.set_error_callback(base::Bind(&CaptureErrorCallback, + &error_, &sql_text_)); } virtual void TearDown() { diff --git a/sql/test/scoped_error_ignorer.cc b/sql/test/scoped_error_ignorer.cc new file mode 100644 index 0000000000..c5852295a1 --- /dev/null +++ b/sql/test/scoped_error_ignorer.cc @@ -0,0 +1,60 @@ +// 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/scoped_error_ignorer.h" + +#include "base/bind.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sql { + +ScopedErrorIgnorer::ScopedErrorIgnorer() + : checked_(false) { + callback_ = + base::Bind(&ScopedErrorIgnorer::ShouldIgnore, base::Unretained(this)); + Connection::SetErrorIgnorer(&callback_); +} + +ScopedErrorIgnorer::~ScopedErrorIgnorer() { + EXPECT_TRUE(checked_) << " Test must call CheckIgnoredErrors()"; + Connection::ResetErrorIgnorer(); +} + +void ScopedErrorIgnorer::IgnoreError(int err) { + EXPECT_EQ(0u, ignore_errors_.count(err)) + << " Error " << err << " is already ignored"; + ignore_errors_.insert(err); +} + +bool ScopedErrorIgnorer::CheckIgnoredErrors() { + checked_ = true; + return errors_ignored_ == ignore_errors_; +} + +bool ScopedErrorIgnorer::ShouldIgnore(int err) { + // Look for extended code. + if (ignore_errors_.count(err) > 0) { + // Record that the error was seen and ignore it. + errors_ignored_.insert(err); + return true; + } + + // Trim extended codes and check again. + int base_err = err & 0xff; + if (ignore_errors_.count(base_err) > 0) { + // Record that the error was seen and ignore it. + errors_ignored_.insert(base_err); + return true; + } + + // Unexpected error. + ADD_FAILURE() << " Unexpected SQLite error " << err; + + // TODO(shess): If it never makes sense to pass through an error + // under the test harness, then perhaps the ignore callback + // signature should be changed. + return true; +} + +} // namespace sql diff --git a/sql/test/scoped_error_ignorer.h b/sql/test/scoped_error_ignorer.h new file mode 100644 index 0000000000..e77717999d --- /dev/null +++ b/sql/test/scoped_error_ignorer.h @@ -0,0 +1,64 @@ +// 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_SCOPED_ERROR_IGNORER_H_ +#define SQL_TEST_SCOPED_ERROR_IGNORER_H_ + +#include <set> + +#include "base/basictypes.h" +#include "sql/connection.h" + +namespace sql { + +// sql::Connection and sql::Statement treat most SQLite errors as +// fatal in debug mode. The intention is to catch inappropriate uses +// of SQL before the code is shipped to production. This makes it +// challenging to write tests for things like recovery from +// corruption. This scoper can be used to ignore selected errors +// during a test. Errors are ignored globally (on all connections). +// +// Since errors can be very context-dependent, the class is pedantic - +// specific errors must be ignored, and every error ignored must be +// seen. +// +// NOTE(shess): There are still fatal error cases this does not +// address. If your test is handling database errors and you're +// hitting a case not handled, contact me. +class ScopedErrorIgnorer { + public: + ScopedErrorIgnorer(); + ~ScopedErrorIgnorer(); + + // Add an error to ignore. Extended error codes can be ignored + // specifically, or the base code can ignore an entire group + // (SQLITE_IOERR_* versus SQLITE_IOERR). + void IgnoreError(int err); + + // Allow containing test to check if the errors were encountered. + // Failure to call results in ADD_FAILURE() in destructor. + bool CheckIgnoredErrors(); + + // Record an error and check if it should be ignored. + bool ShouldIgnore(int err); + + private: + // Storage for callback passed to Connection::SetErrorIgnorer(). + Connection::ErrorIgnorerCallback callback_; + + // Record whether CheckIgnoredErrors() has been called. + bool checked_; + + // Errors to ignore. + std::set<int> ignore_errors_; + + // Errors which have been ignored. + std::set<int> errors_ignored_; + + DISALLOW_COPY_AND_ASSIGN(ScopedErrorIgnorer); +}; + +} // namespace sql + +#endif // SQL_TEST_SCOPED_ERROR_IGNORER_H_ |