diff options
author | Allan Sandfeld Jensen <allan.jensen@theqtcompany.com> | 2015-08-14 11:38:45 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@theqtcompany.com> | 2015-08-14 17:16:47 +0000 |
commit | 3a97ca8dd9b96b599ae2d33e40df0dd2f7ea5859 (patch) | |
tree | 43cc572ba067417c7341db81f71ae7cc6e0fcc3e /chromium/sql | |
parent | f61ab1ac7f855cd281809255c0aedbb1895e1823 (diff) | |
download | qtwebengine-chromium-3a97ca8dd9b96b599ae2d33e40df0dd2f7ea5859.tar.gz |
BASELINE: Update chromium to 45.0.2454.40
Change-Id: Id2121d9f11a8fc633677236c65a3e41feef589e4
Reviewed-by: Andras Becsi <andras.becsi@theqtcompany.com>
Diffstat (limited to 'chromium/sql')
-rw-r--r-- | chromium/sql/BUILD.gn | 20 | ||||
-rw-r--r-- | chromium/sql/connection.cc | 247 | ||||
-rw-r--r-- | chromium/sql/connection.h | 120 | ||||
-rw-r--r-- | chromium/sql/connection_unittest.cc | 506 | ||||
-rw-r--r-- | chromium/sql/correct_sql_test_base.h | 27 | ||||
-rw-r--r-- | chromium/sql/meta_table_unittest.cc | 17 | ||||
-rw-r--r-- | chromium/sql/mojo/BUILD.gn | 62 | ||||
-rw-r--r-- | chromium/sql/mojo/DEPS | 6 | ||||
-rw-r--r-- | chromium/sql/mojo/mojo_vfs.cc | 413 | ||||
-rw-r--r-- | chromium/sql/mojo/mojo_vfs.h | 45 | ||||
-rw-r--r-- | chromium/sql/mojo/sql_test_base.cc | 156 | ||||
-rw-r--r-- | chromium/sql/mojo/sql_test_base.h | 85 | ||||
-rw-r--r-- | chromium/sql/mojo/vfs_unittest.cc | 317 | ||||
-rw-r--r-- | chromium/sql/proxy.cc | 28 | ||||
-rw-r--r-- | chromium/sql/proxy.h | 39 | ||||
-rw-r--r-- | chromium/sql/recovery_unittest.cc | 28 | ||||
-rw-r--r-- | chromium/sql/sql.gyp | 5 | ||||
-rw-r--r-- | chromium/sql/sql_unittests.isolate | 19 | ||||
-rw-r--r-- | chromium/sql/sqlite_features_unittest.cc | 17 | ||||
-rw-r--r-- | chromium/sql/statement.cc | 57 | ||||
-rw-r--r-- | chromium/sql/statement.h | 12 | ||||
-rw-r--r-- | chromium/sql/statement_unittest.cc | 17 | ||||
-rw-r--r-- | chromium/sql/transaction_unittest.cc | 15 |
23 files changed, 2083 insertions, 175 deletions
diff --git a/chromium/sql/BUILD.gn b/chromium/sql/BUILD.gn index 8b75d800936..fdf8c7be96e 100644 --- a/chromium/sql/BUILD.gn +++ b/chromium/sql/BUILD.gn @@ -13,6 +13,8 @@ component("sql") { "init_status.h", "meta_table.cc", "meta_table.h", + "proxy.cc", + "proxy.h", "recovery.cc", "recovery.h", "statement.cc", @@ -52,6 +54,17 @@ source_set("test_support") { ] } +source_set("redirection_header") { + # This target exists because we need a way to switch between + # "test/sql_test_base.h" and "mojo/sql_test_base.h" at compile time, to allow + # us to switch out the gtest vs mojo:apptest frameworks. + check_includes = false + + sources = [ + "correct_sql_test_base.h", + ] +} + test("sql_unittests") { sources = [ "connection_unittest.cc", @@ -62,6 +75,8 @@ test("sql_unittests") { "test/paths.cc", "test/paths.h", "test/run_all_unittests.cc", + "test/sql_test_base.cc", + "test/sql_test_base.h", "test/sql_test_suite.cc", "test/sql_test_suite.h", "transaction_unittest.cc", @@ -72,6 +87,7 @@ test("sql_unittests") { deps = [ ":sql", + ":redirection_header", ":test_support", "//base/allocator", "//base/test:test_support", @@ -79,6 +95,10 @@ test("sql_unittests") { "//third_party/sqlite", ] + if (is_android) { + isolate_file = "sql_unittests.isolate" + } + # TODO(GYP) #['OS == "android"', { # 'dependencies': [ diff --git a/chromium/sql/connection.cc b/chromium/sql/connection.cc index b91234bac3d..d1b104908c9 100644 --- a/chromium/sql/connection.cc +++ b/chromium/sql/connection.cc @@ -141,6 +141,24 @@ int GetSqlite3File(sqlite3* db, sqlite3_file** file) { return rc; } +// This should match UMA_HISTOGRAM_MEDIUM_TIMES(). +base::HistogramBase* GetMediumTimeHistogram(const std::string& name) { + return base::Histogram::FactoryTimeGet( + name, + base::TimeDelta::FromMilliseconds(10), + base::TimeDelta::FromMinutes(3), + 50, + base::HistogramBase::kUmaTargetedHistogramFlag); +} + +std::string AsUTF8ForSQL(const base::FilePath& path) { +#if defined(OS_WIN) + return base::WideToUTF8(path.value()); +#elif defined(OS_POSIX) + return path.value(); +#endif +} + } // namespace namespace sql { @@ -219,13 +237,74 @@ Connection::Connection() transaction_nesting_(0), needs_rollback_(false), in_memory_(false), - poisoned_(false) { + poisoned_(false), + stats_histogram_(NULL), + commit_time_histogram_(NULL), + autocommit_time_histogram_(NULL), + update_time_histogram_(NULL), + query_time_histogram_(NULL), + clock_(new TimeSource()) { } Connection::~Connection() { Close(); } +void Connection::RecordEvent(Events event, size_t count) { + for (size_t i = 0; i < count; ++i) { + UMA_HISTOGRAM_ENUMERATION("Sqlite.Stats", event, EVENT_MAX_VALUE); + } + + if (stats_histogram_) { + for (size_t i = 0; i < count; ++i) { + stats_histogram_->Add(event); + } + } +} + +void Connection::RecordCommitTime(const base::TimeDelta& delta) { + RecordUpdateTime(delta); + UMA_HISTOGRAM_MEDIUM_TIMES("Sqlite.CommitTime", delta); + if (commit_time_histogram_) + commit_time_histogram_->AddTime(delta); +} + +void Connection::RecordAutoCommitTime(const base::TimeDelta& delta) { + RecordUpdateTime(delta); + UMA_HISTOGRAM_MEDIUM_TIMES("Sqlite.AutoCommitTime", delta); + if (autocommit_time_histogram_) + autocommit_time_histogram_->AddTime(delta); +} + +void Connection::RecordUpdateTime(const base::TimeDelta& delta) { + RecordQueryTime(delta); + UMA_HISTOGRAM_MEDIUM_TIMES("Sqlite.UpdateTime", delta); + if (update_time_histogram_) + update_time_histogram_->AddTime(delta); +} + +void Connection::RecordQueryTime(const base::TimeDelta& delta) { + UMA_HISTOGRAM_MEDIUM_TIMES("Sqlite.QueryTime", delta); + if (query_time_histogram_) + query_time_histogram_->AddTime(delta); +} + +void Connection::RecordTimeAndChanges( + const base::TimeDelta& delta, bool read_only) { + if (read_only) { + RecordQueryTime(delta); + } else { + const int changes = sqlite3_changes(db_); + if (sqlite3_get_autocommit(db_)) { + RecordAutoCommitTime(delta); + RecordEvent(EVENT_CHANGES_AUTOCOMMIT, changes); + } else { + RecordUpdateTime(delta); + RecordEvent(EVENT_CHANGES, changes); + } + } +} + bool Connection::Open(const base::FilePath& path) { if (!histogram_tag_.empty()) { int64_t size_64 = 0; @@ -241,11 +320,7 @@ bool Connection::Open(const base::FilePath& path) { } } -#if defined(OS_WIN) - return OpenInternal(base::WideToUTF8(path.value()), RETRY_ON_POISON); -#elif defined(OS_POSIX) - return OpenInternal(path.value(), RETRY_ON_POISON); -#endif + return OpenInternal(AsUTF8ForSQL(path), RETRY_ON_POISON); } bool Connection::OpenInMemory() { @@ -553,13 +628,38 @@ 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::DeleteFile(journal_path, false); - base::DeleteFile(wal_path, false); - base::DeleteFile(path, false); + std::string journal_str = AsUTF8ForSQL(journal_path); + std::string wal_str = AsUTF8ForSQL(wal_path); + std::string path_str = AsUTF8ForSQL(path); + + sqlite3_vfs* vfs = sqlite3_vfs_find(NULL); + CHECK(vfs); + CHECK(vfs->xDelete); + CHECK(vfs->xAccess); - return !base::PathExists(journal_path) && - !base::PathExists(wal_path) && - !base::PathExists(path); + // We only work with unix, win32 and mojo filesystems. If you're trying to + // use this code with any other VFS, you're not in a good place. + CHECK(strncmp(vfs->zName, "unix", 4) == 0 || + strncmp(vfs->zName, "win32", 5) == 0 || + strcmp(vfs->zName, "mojo") == 0); + + vfs->xDelete(vfs, journal_str.c_str(), 0); + vfs->xDelete(vfs, wal_str.c_str(), 0); + vfs->xDelete(vfs, path_str.c_str(), 0); + + int journal_exists = 0; + vfs->xAccess(vfs, journal_str.c_str(), SQLITE_ACCESS_EXISTS, + &journal_exists); + + int wal_exists = 0; + vfs->xAccess(vfs, wal_str.c_str(), SQLITE_ACCESS_EXISTS, + &wal_exists); + + int path_exists = 0; + vfs->xAccess(vfs, path_str.c_str(), SQLITE_ACCESS_EXISTS, + &path_exists); + + return !journal_exists && !wal_exists && !path_exists; } bool Connection::BeginTransaction() { @@ -576,6 +676,7 @@ bool Connection::BeginTransaction() { needs_rollback_ = false; Statement begin(GetCachedStatement(SQL_FROM_HERE, "BEGIN TRANSACTION")); + RecordOneEvent(EVENT_BEGIN); if (!begin.Run()) return false; } @@ -618,7 +719,17 @@ bool Connection::CommitTransaction() { } Statement commit(GetCachedStatement(SQL_FROM_HERE, "COMMIT")); - return commit.Run(); + + // Collect the commit time manually, sql::Statement would register it as query + // time only. + const base::TimeTicks before = Now(); + bool ret = commit.RunWithoutTimers(); + const base::TimeDelta delta = Now() - before; + + RecordCommitTime(delta); + RecordOneEvent(EVENT_COMMIT); + + return ret; } void Connection::RollbackAllTransactions() { @@ -650,13 +761,65 @@ bool Connection::DetachDatabase(const char* attachment_point) { return s.Run(); } +// TODO(shess): Consider changing this to execute exactly one statement. If a +// caller wishes to execute multiple statements, that should be explicit, and +// perhaps tucked into an explicit transaction with rollback in case of error. int Connection::ExecuteAndReturnErrorCode(const char* sql) { AssertIOAllowed(); if (!db_) { DLOG_IF(FATAL, !poisoned_) << "Illegal use of connection without a db"; return SQLITE_ERROR; } - return sqlite3_exec(db_, sql, NULL, NULL, NULL); + DCHECK(sql); + + RecordOneEvent(EVENT_EXECUTE); + int rc = SQLITE_OK; + while ((rc == SQLITE_OK) && *sql) { + sqlite3_stmt *stmt = NULL; + const char *leftover_sql; + + const base::TimeTicks before = Now(); + rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, &leftover_sql); + sql = leftover_sql; + + // Stop if an error is encountered. + if (rc != SQLITE_OK) + break; + + // This happens if |sql| originally only contained comments or whitespace. + // TODO(shess): Audit to see if this can become a DCHECK(). Having + // extraneous comments and whitespace in the SQL statements increases + // runtime cost and can easily be shifted out to the C++ layer. + if (!stmt) + continue; + + // Save for use after statement is finalized. + const bool read_only = !!sqlite3_stmt_readonly(stmt); + + RecordOneEvent(Connection::EVENT_STATEMENT_RUN); + while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { + // TODO(shess): Audit to see if this can become a DCHECK. I think PRAGMA + // is the only legitimate case for this. + RecordOneEvent(Connection::EVENT_STATEMENT_ROWS); + } + + // sqlite3_finalize() returns SQLITE_OK if the most recent sqlite3_step() + // returned SQLITE_DONE or SQLITE_ROW, otherwise the error code. + rc = sqlite3_finalize(stmt); + if (rc == SQLITE_OK) + RecordOneEvent(Connection::EVENT_STATEMENT_SUCCESS); + + // sqlite3_exec() does this, presumably to avoid spinning the parser for + // trailing whitespace. + // TODO(shess): Audit to see if this can become a DCHECK. + while (base::IsAsciiWhitespace(*sql)) { + sql++; + } + + const base::TimeDelta delta = Now() - before; + RecordTimeAndChanges(delta, read_only); + } + return rc; } bool Connection::Execute(const char* sql) { @@ -886,6 +1049,32 @@ bool Connection::OpenInternal(const std::string& file_name, // Make sure sqlite3_initialize() is called before anything else. InitializeSqlite(); + // Setup the stats histograms immediately rather than allocating lazily. + // Connections which won't exercise all of these probably shouldn't exist. + if (!histogram_tag_.empty()) { + stats_histogram_ = + base::LinearHistogram::FactoryGet( + "Sqlite.Stats." + histogram_tag_, + 1, EVENT_MAX_VALUE, EVENT_MAX_VALUE + 1, + base::HistogramBase::kUmaTargetedHistogramFlag); + + // The timer setup matches UMA_HISTOGRAM_MEDIUM_TIMES(). 3 minutes is an + // unreasonable time for any single operation, so there is not much value to + // knowing if it was 3 minutes or 5 minutes. In reality at that point + // things are entirely busted. + commit_time_histogram_ = + GetMediumTimeHistogram("Sqlite.CommitTime." + histogram_tag_); + + autocommit_time_histogram_ = + GetMediumTimeHistogram("Sqlite.AutoCommitTime." + histogram_tag_); + + update_time_histogram_ = + GetMediumTimeHistogram("Sqlite.UpdateTime." + histogram_tag_); + + query_time_histogram_ = + GetMediumTimeHistogram("Sqlite.QueryTime." + histogram_tag_); + } + // If |poisoned_| is set, it means an error handler called // RazeAndClose(). Until regular Close() is called, the caller // should be treating the database as open, but is_open() currently @@ -988,12 +1177,10 @@ bool Connection::OpenInternal(const std::string& file_name, // DELETE (default) - delete -journal file to commit. // TRUNCATE - truncate -journal file to commit. // PERSIST - zero out header of -journal file to commit. - // journal_size_limit provides size to trim to in PERSIST. - // TODO(shess): Figure out if PERSIST and journal_size_limit really - // matter. In theory, it keeps pages pre-allocated, so if - // transactions usually fit, it should be faster. - ignore_result(Execute("PRAGMA journal_mode = PERSIST")); - ignore_result(Execute("PRAGMA journal_size_limit = 16384")); + // TRUNCATE should be faster than DELETE because it won't need directory + // changes for each transaction. PERSIST may break the spirit of using + // secure_delete. + ignore_result(Execute("PRAGMA journal_mode = TRUNCATE")); const base::TimeDelta kBusyTimeout = base::TimeDelta::FromSeconds(kBusyTimeoutSeconds); @@ -1028,7 +1215,16 @@ bool Connection::OpenInternal(const std::string& file_name, void Connection::DoRollback() { Statement rollback(GetCachedStatement(SQL_FROM_HERE, "ROLLBACK")); - rollback.Run(); + + // Collect the rollback time manually, sql::Statement would register it as + // query time only. + const base::TimeTicks before = Now(); + rollback.RunWithoutTimers(); + const base::TimeDelta delta = Now() - before; + + RecordUpdateTime(delta); + RecordOneEvent(EVENT_ROLLBACK); + needs_rollback_ = false; } @@ -1045,6 +1241,11 @@ void Connection::StatementRefDeleted(StatementRef* ref) { open_statements_.erase(i); } +void Connection::set_histogram_tag(const std::string& tag) { + DCHECK(!is_open()); + histogram_tag_ = tag; +} + void Connection::AddTaggedHistogram(const std::string& name, size_t sample) const { if (histogram_tag_.empty()) @@ -1137,4 +1338,8 @@ bool Connection::IntegrityCheckHelper( return ret; } +base::TimeTicks TimeSource::Now() { + return base::TimeTicks::Now(); +} + } // namespace sql diff --git a/chromium/sql/connection.h b/chromium/sql/connection.h index 17d11914ae0..19592d9f0e4 100644 --- a/chromium/sql/connection.h +++ b/chromium/sql/connection.h @@ -25,6 +25,7 @@ struct sqlite3_stmt; namespace base { class FilePath; +class HistogramBase; } namespace sql { @@ -32,6 +33,13 @@ namespace sql { class Recovery; class Statement; +// To allow some test classes to be friended. +namespace test { +class ScopedCommitHook; +class ScopedScalarFunction; +class ScopedMockTimeSource; +} + // Uniquely identifies a statement. There are two modes of operation: // // - In the most common mode, you will use the source file and line number to @@ -80,6 +88,20 @@ class StatementID { class Connection; +// Abstract the source of timing information for metrics (RecordCommitTime, etc) +// to allow testing control. +class SQL_EXPORT TimeSource { + public: + TimeSource() {} + virtual ~TimeSource() {} + + // Return the current time (by default base::TimeTicks::Now()). + virtual base::TimeTicks Now(); + + private: + DISALLOW_COPY_AND_ASSIGN(TimeSource); +}; + class SQL_EXPORT Connection { private: class StatementRef; // Forward declaration, see real one below. @@ -140,17 +162,52 @@ class SQL_EXPORT Connection { error_callback_.Reset(); } - // 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) { - histogram_tag_ = tag; - } + // Set this to enable additional per-connection histogramming. Must be called + // before Open(). + void set_histogram_tag(const std::string& tag); // Record a sparse UMA histogram sample under // |name|+"."+|histogram_tag_|. If |histogram_tag_| is empty, no // histogram is recorded. void AddTaggedHistogram(const std::string& name, size_t sample) const; + // Track various API calls and results. Values corrospond to UMA + // histograms, do not modify, or add or delete other than directly + // before EVENT_MAX_VALUE. + enum Events { + // Number of statements run, either with sql::Statement or Execute*(). + EVENT_STATEMENT_RUN = 0, + + // Number of rows returned by statements run. + EVENT_STATEMENT_ROWS, + + // Number of statements successfully run (all steps returned SQLITE_DONE or + // SQLITE_ROW). + EVENT_STATEMENT_SUCCESS, + + // Number of statements run by Execute() or ExecuteAndReturnErrorCode(). + EVENT_EXECUTE, + + // Number of rows changed by autocommit statements. + EVENT_CHANGES_AUTOCOMMIT, + + // Number of rows changed by statements in transactions. + EVENT_CHANGES, + + // Count actual SQLite transaction statements (not including nesting). + EVENT_BEGIN, + EVENT_COMMIT, + EVENT_ROLLBACK, + + // Leave this at the end. + // TODO(shess): |EVENT_MAX| causes compile fail on Windows. + EVENT_MAX_VALUE + }; + void RecordEvent(Events event, size_t count); + void RecordOneEvent(Events event) { + RecordEvent(event, 1); + } + // Run "PRAGMA integrity_check" and post each line of // results into |messages|. Returns the success of running the // statement - per the SQLite documentation, if no errors are found the @@ -415,6 +472,10 @@ class SQL_EXPORT Connection { // (they should go through Statement). friend class Statement; + friend class test::ScopedCommitHook; + friend class test::ScopedScalarFunction; + friend class test::ScopedMockTimeSource; + // 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. @@ -548,6 +609,35 @@ class SQL_EXPORT Connection { const char* pragma_sql, std::vector<std::string>* messages) WARN_UNUSED_RESULT; + // Record time spent executing explicit COMMIT statements. + void RecordCommitTime(const base::TimeDelta& delta); + + // Record time in DML (Data Manipulation Language) statements such as INSERT + // or UPDATE outside of an explicit transaction. Due to implementation + // limitations time spent on DDL (Data Definition Language) statements such as + // ALTER and CREATE is not included. + void RecordAutoCommitTime(const base::TimeDelta& delta); + + // Record all time spent on updating the database. This includes CommitTime() + // and AutoCommitTime(), plus any time spent spilling to the journal if + // transactions do not fit in cache. + void RecordUpdateTime(const base::TimeDelta& delta); + + // Record all time spent running statements, including time spent doing + // updates and time spent on read-only queries. + void RecordQueryTime(const base::TimeDelta& delta); + + // Record |delta| as query time if |read_only| (from sqlite3_stmt_readonly) is + // true, autocommit time if the database is not in a transaction, or update + // time if the database is in a transaction. Also records change count to + // EVENT_CHANGES_AUTOCOMMIT or EVENT_CHANGES_COMMIT. + void RecordTimeAndChanges(const base::TimeDelta& delta, bool read_only); + + // Helper to return the current time from the time source. + base::TimeTicks Now() { + return clock_->Now(); + } + // The actual sqlite database. Will be NULL before Init has been called or if // Init resulted in an error. sqlite3* db_; @@ -594,6 +684,26 @@ class SQL_EXPORT Connection { // Tag for auxiliary histograms. std::string histogram_tag_; + // Linear histogram for RecordEvent(). + base::HistogramBase* stats_histogram_; + + // Histogram for tracking time taken in commit. + base::HistogramBase* commit_time_histogram_; + + // Histogram for tracking time taken in autocommit updates. + base::HistogramBase* autocommit_time_histogram_; + + // Histogram for tracking time taken in updates (including commit and + // autocommit). + base::HistogramBase* update_time_histogram_; + + // Histogram for tracking time taken in all queries. + base::HistogramBase* query_time_histogram_; + + // Source for timing information, provided to allow tests to inject time + // changes. + scoped_ptr<TimeSource> clock_; + DISALLOW_COPY_AND_ASSIGN(Connection); }; diff --git a/chromium/sql/connection_unittest.cc b/chromium/sql/connection_unittest.cc index e3b97739ed1..c7d9080c8f9 100644 --- a/chromium/sql/connection_unittest.cc +++ b/chromium/sql/connection_unittest.cc @@ -7,8 +7,12 @@ #include "base/files/scoped_file.h" #include "base/files/scoped_temp_dir.h" #include "base/logging.h" +#include "base/metrics/statistics_recorder.h" +#include "base/test/histogram_tester.h" #include "sql/connection.h" +#include "sql/correct_sql_test_base.h" #include "sql/meta_table.h" +#include "sql/proxy.h" #include "sql/statement.h" #include "sql/test/error_callback_support.h" #include "sql/test/scoped_error_ignorer.h" @@ -16,6 +20,122 @@ #include "testing/gtest/include/gtest/gtest.h" #include "third_party/sqlite/sqlite3.h" +namespace sql { +namespace test { + +// Replaces the database time source with an object that steps forward 1ms on +// each check, and which can be jumped forward an arbitrary amount of time +// programmatically. +class ScopedMockTimeSource { + public: + ScopedMockTimeSource(Connection& db) + : db_(db), + delta_(base::TimeDelta::FromMilliseconds(1)) { + // Save the current source and replace it. + save_.swap(db_.clock_); + db_.clock_.reset(new MockTimeSource(*this)); + } + ~ScopedMockTimeSource() { + // Put original source back. + db_.clock_.swap(save_); + } + + void adjust(const base::TimeDelta& delta) { + current_time_ += delta; + } + + private: + class MockTimeSource : public TimeSource { + public: + MockTimeSource(ScopedMockTimeSource& owner) + : owner_(owner) { + } + ~MockTimeSource() override {} + + base::TimeTicks Now() override { + base::TimeTicks ret(owner_.current_time_); + owner_.current_time_ += owner_.delta_; + return ret; + } + + private: + ScopedMockTimeSource& owner_; + DISALLOW_COPY_AND_ASSIGN(MockTimeSource); + }; + + Connection& db_; + + // Saves original source from |db_|. + scoped_ptr<TimeSource> save_; + + // Current time returned by mock. + base::TimeTicks current_time_; + + // How far to jump on each Now() call. + base::TimeDelta delta_; + + DISALLOW_COPY_AND_ASSIGN(ScopedMockTimeSource); +}; + +// Allow a test to add a SQLite function in a scoped context. +class ScopedScalarFunction { + public: + ScopedScalarFunction( + sql::Connection& db, + const char* function_name, + int args, + base::Callback<void(sqlite3_context*,int,sqlite3_value**)> cb) + : db_(db.db_), function_name_(function_name), cb_(cb) { + sql::sqlite3_create_function_v2(db_, function_name, args, SQLITE_UTF8, + this, &Run, NULL, NULL, NULL); + } + ~ScopedScalarFunction() { + sql::sqlite3_create_function_v2(db_, function_name_, 0, SQLITE_UTF8, + NULL, NULL, NULL, NULL, NULL); + } + + private: + static void Run(sqlite3_context* context, int argc, sqlite3_value** argv) { + ScopedScalarFunction* t = static_cast<ScopedScalarFunction*>( + sqlite3_user_data(context)); + t->cb_.Run(context, argc, argv); + } + + sqlite3* db_; + const char* function_name_; + base::Callback<void(sqlite3_context*,int,sqlite3_value**)> cb_; + + DISALLOW_COPY_AND_ASSIGN(ScopedScalarFunction); +}; + +// Allow a test to add a SQLite commit hook in a scoped context. +class ScopedCommitHook { + public: + ScopedCommitHook(sql::Connection& db, + base::Callback<int(void)> cb) + : db_(db.db_), + cb_(cb) { + sql::sqlite3_commit_hook(db_, &Run, this); + } + ~ScopedCommitHook() { + sql::sqlite3_commit_hook(db_, NULL, NULL); + } + + private: + static int Run(void* p) { + ScopedCommitHook* t = static_cast<ScopedCommitHook*>(p); + return t->cb_.Run(); + } + + sqlite3* db_; + base::Callback<int(void)> cb_; + + DISALLOW_COPY_AND_ASSIGN(ScopedCommitHook); +}; + +} // namespace test +} // namespace sql + namespace { // Helper to return the count of items in sqlite_master. Return -1 in @@ -88,29 +208,21 @@ class ScopedUmaskSetter { }; #endif -class SQLConnectionTest : public testing::Test { +class SQLConnectionTest : public sql::SQLTestBase { public: void SetUp() override { - ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); - db_path_ = temp_dir_.path().AppendASCII("SQLConnectionTest.db"); - ASSERT_TRUE(db_.Open(db_path_)); - } + // Any macro histograms which fire before the recorder is initialized cannot + // be tested. So this needs to be ahead of Open(). + base::StatisticsRecorder::Initialize(); - void TearDown() override { db_.Close(); } - - sql::Connection& db() { return db_; } - const base::FilePath& db_path() { return db_path_; } + SQLTestBase::SetUp(); + } // Handle errors by blowing away the database. void RazeErrorCallback(int expected_error, int error, sql::Statement* stmt) { EXPECT_EQ(expected_error, error); - db_.RazeAndClose(); + db().RazeAndClose(); } - - private: - sql::Connection db_; - base::FilePath db_path_; - base::ScopedTempDir temp_dir_; }; TEST_F(SQLConnectionTest, Execute) { @@ -243,7 +355,7 @@ TEST_F(SQLConnectionTest, ScopedIgnoreUntracked) { db().Close(); // Corrupt the database so that nothing works, including PRAGMAs. - ASSERT_TRUE(sql::test::CorruptSizeInHeader(db_path())); + ASSERT_TRUE(CorruptSizeInHeaderOfDB()); { sql::ScopedErrorIgnorer ignore_errors; @@ -411,6 +523,8 @@ TEST_F(SQLConnectionTest, RazeMultiple) { ASSERT_EQ(0, SqliteMasterCount(&other_db)); } +// TODO(erg): Enable this in the next patch once I add locking. +#if !defined(MOJO_APPTEST_IMPL) TEST_F(SQLConnectionTest, RazeLocked) { const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"; ASSERT_TRUE(db().Execute(kCreateSql)); @@ -445,6 +559,7 @@ TEST_F(SQLConnectionTest, RazeLocked) { ASSERT_FALSE(s.Step()); ASSERT_TRUE(db().Raze()); } +#endif // Verify that Raze() can handle an empty file. SQLite should treat // this as an empty database. @@ -453,12 +568,7 @@ TEST_F(SQLConnectionTest, RazeEmptyDB) { ASSERT_TRUE(db().Execute(kCreateSql)); db().Close(); - { - base::ScopedFILE file(base::OpenFile(db_path(), "rb+")); - ASSERT_TRUE(file.get() != NULL); - ASSERT_EQ(0, fseek(file.get(), 0, SEEK_SET)); - ASSERT_TRUE(base::TruncateFile(file.get())); - } + TruncateDatabase(); ASSERT_TRUE(db().Open(db_path())); ASSERT_TRUE(db().Raze()); @@ -469,16 +579,10 @@ TEST_F(SQLConnectionTest, RazeEmptyDB) { TEST_F(SQLConnectionTest, RazeNOTADB) { db().Close(); sql::Connection::Delete(db_path()); - ASSERT_FALSE(base::PathExists(db_path())); - - { - base::ScopedFILE file(base::OpenFile(db_path(), "wb")); - ASSERT_TRUE(file.get() != NULL); + ASSERT_FALSE(GetPathExists(db_path())); - const char* kJunk = "This is the hour of our discontent."; - fputs(kJunk, file.get()); - } - ASSERT_TRUE(base::PathExists(db_path())); + WriteJunkToDatabase(SQLTestBase::TYPE_OVERWRITE_AND_TRUNCATE); + ASSERT_TRUE(GetPathExists(db_path())); // SQLite will successfully open the handle, but fail when running PRAGMA // statements that access the database. @@ -512,14 +616,7 @@ TEST_F(SQLConnectionTest, RazeNOTADB2) { ASSERT_EQ(1, SqliteMasterCount(&db())); db().Close(); - { - base::ScopedFILE file(base::OpenFile(db_path(), "rb+")); - 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()); - } + WriteJunkToDatabase(SQLTestBase::TYPE_OVERWRITE); // SQLite will successfully open the handle, but will fail with // SQLITE_NOTADB on pragma statemenets which attempt to read the @@ -549,7 +646,7 @@ TEST_F(SQLConnectionTest, RazeCallbackReopen) { db().Close(); // Corrupt the database so that nothing works, including PRAGMAs. - ASSERT_TRUE(sql::test::CorruptSizeInHeader(db_path())); + ASSERT_TRUE(CorruptSizeInHeaderOfDB()); // Open() will succeed, even though the PRAGMA calls within will // fail with SQLITE_CORRUPT, as will this PRAGMA. @@ -692,17 +789,19 @@ TEST_F(SQLConnectionTest, Delete) { db().Close(); // Should have both a main database file and a journal file because - // of journal_mode PERSIST. + // of journal_mode TRUNCATE. base::FilePath journal(db_path().value() + FILE_PATH_LITERAL("-journal")); - ASSERT_TRUE(base::PathExists(db_path())); - ASSERT_TRUE(base::PathExists(journal)); + ASSERT_TRUE(GetPathExists(db_path())); + ASSERT_TRUE(GetPathExists(journal)); sql::Connection::Delete(db_path()); - EXPECT_FALSE(base::PathExists(db_path())); - EXPECT_FALSE(base::PathExists(journal)); + EXPECT_FALSE(GetPathExists(db_path())); + EXPECT_FALSE(GetPathExists(journal)); } -#if defined(OS_POSIX) +// This test manually sets on disk permissions; this doesn't apply to the mojo +// fork. +#if defined(OS_POSIX) && !defined(MOJO_APPTEST_IMPL) // Test that set_restrict_to_user() trims database permissions so that // only the owner (and root) can read. TEST_F(SQLConnectionTest, UserPermission) { @@ -712,7 +811,7 @@ TEST_F(SQLConnectionTest, UserPermission) { // Temporarily provide a more permissive umask. db().Close(); sql::Connection::Delete(db_path()); - ASSERT_FALSE(base::PathExists(db_path())); + ASSERT_FALSE(GetPathExists(db_path())); ScopedUmaskSetter permissive_umask(S_IWGRP | S_IWOTH); ASSERT_TRUE(db().Open(db_path())); @@ -726,8 +825,8 @@ TEST_F(SQLConnectionTest, UserPermission) { // Given a permissive umask, the database is created with permissive // read access for the database and journal. - ASSERT_TRUE(base::PathExists(db_path())); - ASSERT_TRUE(base::PathExists(journal)); + ASSERT_TRUE(GetPathExists(db_path())); + ASSERT_TRUE(GetPathExists(journal)); mode = base::FILE_PERMISSION_MASK; EXPECT_TRUE(base::GetPosixFilePermissions(db_path(), &mode)); ASSERT_NE((mode & base::FILE_PERMISSION_USER_MASK), mode); @@ -740,8 +839,8 @@ TEST_F(SQLConnectionTest, UserPermission) { db().Close(); db().set_restrict_to_user(); ASSERT_TRUE(db().Open(db_path())); - ASSERT_TRUE(base::PathExists(db_path())); - ASSERT_TRUE(base::PathExists(journal)); + ASSERT_TRUE(GetPathExists(db_path())); + ASSERT_TRUE(GetPathExists(journal)); mode = base::FILE_PERMISSION_MASK; EXPECT_TRUE(base::GetPosixFilePermissions(db_path(), &mode)); ASSERT_EQ((mode & base::FILE_PERMISSION_USER_MASK), mode); @@ -753,15 +852,15 @@ TEST_F(SQLConnectionTest, UserPermission) { db().Close(); sql::Connection::Delete(db_path()); ASSERT_TRUE(db().Open(db_path())); - ASSERT_TRUE(base::PathExists(db_path())); - ASSERT_FALSE(base::PathExists(journal)); + ASSERT_TRUE(GetPathExists(db_path())); + ASSERT_FALSE(GetPathExists(journal)); mode = base::FILE_PERMISSION_MASK; EXPECT_TRUE(base::GetPosixFilePermissions(db_path(), &mode)); ASSERT_EQ((mode & base::FILE_PERMISSION_USER_MASK), mode); // Verify that journal creation inherits the restriction. EXPECT_TRUE(db().Execute("CREATE TABLE x (x)")); - ASSERT_TRUE(base::PathExists(journal)); + ASSERT_TRUE(GetPathExists(journal)); mode = base::FILE_PERMISSION_MASK; EXPECT_TRUE(base::GetPosixFilePermissions(journal, &mode)); ASSERT_EQ((mode & base::FILE_PERMISSION_USER_MASK), mode); @@ -867,7 +966,7 @@ TEST_F(SQLConnectionTest, Basic_QuickIntegrityCheck) { EXPECT_TRUE(db().QuickIntegrityCheck()); db().Close(); - ASSERT_TRUE(sql::test::CorruptSizeInHeader(db_path())); + ASSERT_TRUE(CorruptSizeInHeaderOfDB()); { sql::ScopedErrorIgnorer ignore_errors; @@ -889,7 +988,7 @@ TEST_F(SQLConnectionTest, Basic_FullIntegrityCheck) { EXPECT_EQ(kOk, messages[0]); db().Close(); - ASSERT_TRUE(sql::test::CorruptSizeInHeader(db_path())); + ASSERT_TRUE(CorruptSizeInHeaderOfDB()); { sql::ScopedErrorIgnorer ignore_errors; @@ -905,4 +1004,299 @@ TEST_F(SQLConnectionTest, Basic_FullIntegrityCheck) { // file that would pass the quick check and fail the full check. } +// Test Sqlite.Stats histogram for execute-oriented calls. +TEST_F(SQLConnectionTest, EventsExecute) { + // Re-open with histogram tag. + db().Close(); + db().set_histogram_tag("Test"); + ASSERT_TRUE(db().Open(db_path())); + + // Open() uses Execute() extensively, don't track those calls. + base::HistogramTester tester; + + const char kHistogramName[] = "Sqlite.Stats.Test"; + const char kGlobalHistogramName[] = "Sqlite.Stats"; + + ASSERT_TRUE(db().BeginTransaction()); + const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"; + EXPECT_TRUE(db().Execute(kCreateSql)); + EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (10, 'text')")); + EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (11, 'text')")); + EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (12, 'text')")); + EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (13, 'text')")); + EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (14, 'text')")); + EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (15, 'text');" + "INSERT INTO foo VALUES (16, 'text');" + "INSERT INTO foo VALUES (17, 'text');" + "INSERT INTO foo VALUES (18, 'text');" + "INSERT INTO foo VALUES (19, 'text')")); + ASSERT_TRUE(db().CommitTransaction()); + ASSERT_TRUE(db().BeginTransaction()); + EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (20, 'text')")); + db().RollbackTransaction(); + EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (20, 'text')")); + EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (21, 'text')")); + + // The create, 5 inserts, multi-statement insert, rolled-back insert, 2 + // inserts outside transaction. + tester.ExpectBucketCount(kHistogramName, sql::Connection::EVENT_EXECUTE, 10); + tester.ExpectBucketCount(kGlobalHistogramName, + sql::Connection::EVENT_EXECUTE, 10); + + // All of the executes, with the multi-statement inserts broken out, plus one + // for each begin, commit, and rollback. + tester.ExpectBucketCount(kHistogramName, + sql::Connection::EVENT_STATEMENT_RUN, 18); + tester.ExpectBucketCount(kGlobalHistogramName, + sql::Connection::EVENT_STATEMENT_RUN, 18); + + tester.ExpectBucketCount(kHistogramName, + sql::Connection::EVENT_STATEMENT_ROWS, 0); + tester.ExpectBucketCount(kGlobalHistogramName, + sql::Connection::EVENT_STATEMENT_ROWS, 0); + tester.ExpectBucketCount(kHistogramName, + sql::Connection::EVENT_STATEMENT_SUCCESS, 18); + tester.ExpectBucketCount(kGlobalHistogramName, + sql::Connection::EVENT_STATEMENT_SUCCESS, 18); + + // The 2 inserts outside the transaction. + tester.ExpectBucketCount(kHistogramName, + sql::Connection::EVENT_CHANGES_AUTOCOMMIT, 2); + tester.ExpectBucketCount(kGlobalHistogramName, + sql::Connection::EVENT_CHANGES_AUTOCOMMIT, 2); + + // 11 inserts inside transactions. + tester.ExpectBucketCount(kHistogramName, sql::Connection::EVENT_CHANGES, 11); + tester.ExpectBucketCount(kGlobalHistogramName, + sql::Connection::EVENT_CHANGES, 11); + + tester.ExpectBucketCount(kHistogramName, sql::Connection::EVENT_BEGIN, 2); + tester.ExpectBucketCount(kGlobalHistogramName, + sql::Connection::EVENT_BEGIN, 2); + tester.ExpectBucketCount(kHistogramName, sql::Connection::EVENT_COMMIT, 1); + tester.ExpectBucketCount(kGlobalHistogramName, + sql::Connection::EVENT_COMMIT, 1); + tester.ExpectBucketCount(kHistogramName, sql::Connection::EVENT_ROLLBACK, 1); + tester.ExpectBucketCount(kGlobalHistogramName, + sql::Connection::EVENT_ROLLBACK, 1); +} + +// Test Sqlite.Stats histogram for prepared statements. +TEST_F(SQLConnectionTest, EventsStatement) { + // Re-open with histogram tag. + db().Close(); + db().set_histogram_tag("Test"); + ASSERT_TRUE(db().Open(db_path())); + + const char kHistogramName[] = "Sqlite.Stats.Test"; + const char kGlobalHistogramName[] = "Sqlite.Stats"; + + const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"; + EXPECT_TRUE(db().Execute(kCreateSql)); + EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (10, 'text')")); + EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (11, 'text')")); + EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (12, 'text')")); + + { + base::HistogramTester tester; + + { + sql::Statement s(db().GetUniqueStatement("SELECT value FROM foo")); + while (s.Step()) { + } + } + + tester.ExpectBucketCount(kHistogramName, + sql::Connection::EVENT_STATEMENT_RUN, 1); + tester.ExpectBucketCount(kGlobalHistogramName, + sql::Connection::EVENT_STATEMENT_RUN, 1); + tester.ExpectBucketCount(kHistogramName, + sql::Connection::EVENT_STATEMENT_ROWS, 3); + tester.ExpectBucketCount(kGlobalHistogramName, + sql::Connection::EVENT_STATEMENT_ROWS, 3); + tester.ExpectBucketCount(kHistogramName, + sql::Connection::EVENT_STATEMENT_SUCCESS, 1); + tester.ExpectBucketCount(kGlobalHistogramName, + sql::Connection::EVENT_STATEMENT_SUCCESS, 1); + } + + { + base::HistogramTester tester; + + { + sql::Statement s(db().GetUniqueStatement( + "SELECT value FROM foo WHERE id > 10")); + while (s.Step()) { + } + } + + tester.ExpectBucketCount(kHistogramName, + sql::Connection::EVENT_STATEMENT_RUN, 1); + tester.ExpectBucketCount(kGlobalHistogramName, + sql::Connection::EVENT_STATEMENT_RUN, 1); + tester.ExpectBucketCount(kHistogramName, + sql::Connection::EVENT_STATEMENT_ROWS, 2); + tester.ExpectBucketCount(kGlobalHistogramName, + sql::Connection::EVENT_STATEMENT_ROWS, 2); + tester.ExpectBucketCount(kHistogramName, + sql::Connection::EVENT_STATEMENT_SUCCESS, 1); + tester.ExpectBucketCount(kGlobalHistogramName, + sql::Connection::EVENT_STATEMENT_SUCCESS, 1); + } +} + +// SQLite function to adjust mock time by |argv[0]| milliseconds. +void sqlite_adjust_millis(sql::test::ScopedMockTimeSource* time_mock, + sqlite3_context* context, + int argc, sqlite3_value** argv) { + int64 milliseconds = argc > 0 ? sqlite3_value_int64(argv[0]) : 1000; + time_mock->adjust(base::TimeDelta::FromMilliseconds(milliseconds)); + sqlite3_result_int64(context, milliseconds); +} + +// Adjust mock time by |milliseconds| on commit. +int adjust_commit_hook(sql::test::ScopedMockTimeSource* time_mock, + int64 milliseconds) { + time_mock->adjust(base::TimeDelta::FromMilliseconds(milliseconds)); + return SQLITE_OK; +} + +const char kCommitTime[] = "Sqlite.CommitTime.Test"; +const char kAutoCommitTime[] = "Sqlite.AutoCommitTime.Test"; +const char kUpdateTime[] = "Sqlite.UpdateTime.Test"; +const char kQueryTime[] = "Sqlite.QueryTime.Test"; + +// Read-only query allocates time to QueryTime, but not others. +TEST_F(SQLConnectionTest, TimeQuery) { + // Re-open with histogram tag. Use an in-memory database to minimize variance + // due to filesystem. + db().Close(); + db().set_histogram_tag("Test"); + ASSERT_TRUE(db().OpenInMemory()); + + sql::test::ScopedMockTimeSource time_mock(db()); + + const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"; + EXPECT_TRUE(db().Execute(kCreateSql)); + + // Function to inject pauses into statements. + sql::test::ScopedScalarFunction scoper( + db(), "milliadjust", 1, base::Bind(&sqlite_adjust_millis, &time_mock)); + + base::HistogramTester tester; + + EXPECT_TRUE(db().Execute("SELECT milliadjust(10)")); + + scoped_ptr<base::HistogramSamples> samples( + tester.GetHistogramSamplesSinceCreation(kQueryTime)); + ASSERT_TRUE(samples); + // 10 for the adjust, 1 for the measurement. + EXPECT_EQ(11, samples->sum()); + + samples = tester.GetHistogramSamplesSinceCreation(kUpdateTime); + EXPECT_TRUE(!samples || samples->sum() == 0); + + samples = tester.GetHistogramSamplesSinceCreation(kCommitTime); + EXPECT_TRUE(!samples || samples->sum() == 0); + + samples = tester.GetHistogramSamplesSinceCreation(kAutoCommitTime); + EXPECT_TRUE(!samples || samples->sum() == 0); +} + +// Autocommit update allocates time to QueryTime, UpdateTime, and +// AutoCommitTime. +TEST_F(SQLConnectionTest, TimeUpdateAutocommit) { + // Re-open with histogram tag. Use an in-memory database to minimize variance + // due to filesystem. + db().Close(); + db().set_histogram_tag("Test"); + ASSERT_TRUE(db().OpenInMemory()); + + sql::test::ScopedMockTimeSource time_mock(db()); + + const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"; + EXPECT_TRUE(db().Execute(kCreateSql)); + + // Function to inject pauses into statements. + sql::test::ScopedScalarFunction scoper( + db(), "milliadjust", 1, base::Bind(&sqlite_adjust_millis, &time_mock)); + + base::HistogramTester tester; + + EXPECT_TRUE(db().Execute("INSERT INTO foo VALUES (10, milliadjust(10))")); + + scoped_ptr<base::HistogramSamples> samples( + tester.GetHistogramSamplesSinceCreation(kQueryTime)); + ASSERT_TRUE(samples); + // 10 for the adjust, 1 for the measurement. + EXPECT_EQ(11, samples->sum()); + + samples = tester.GetHistogramSamplesSinceCreation(kUpdateTime); + ASSERT_TRUE(samples); + // 10 for the adjust, 1 for the measurement. + EXPECT_EQ(11, samples->sum()); + + samples = tester.GetHistogramSamplesSinceCreation(kCommitTime); + EXPECT_TRUE(!samples || samples->sum() == 0); + + samples = tester.GetHistogramSamplesSinceCreation(kAutoCommitTime); + ASSERT_TRUE(samples); + // 10 for the adjust, 1 for the measurement. + EXPECT_EQ(11, samples->sum()); +} + +// Update with explicit transaction allocates time to QueryTime, UpdateTime, and +// CommitTime. +TEST_F(SQLConnectionTest, TimeUpdateTransaction) { + // Re-open with histogram tag. Use an in-memory database to minimize variance + // due to filesystem. + db().Close(); + db().set_histogram_tag("Test"); + ASSERT_TRUE(db().OpenInMemory()); + + sql::test::ScopedMockTimeSource time_mock(db()); + + const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"; + EXPECT_TRUE(db().Execute(kCreateSql)); + + // Function to inject pauses into statements. + sql::test::ScopedScalarFunction scoper( + db(), "milliadjust", 1, base::Bind(&sqlite_adjust_millis, &time_mock)); + + base::HistogramTester tester; + + { + // Make the commit slow. + sql::test::ScopedCommitHook scoped_hook( + db(), base::Bind(adjust_commit_hook, &time_mock, 100)); + ASSERT_TRUE(db().BeginTransaction()); + EXPECT_TRUE(db().Execute( + "INSERT INTO foo VALUES (11, milliadjust(10))")); + EXPECT_TRUE(db().Execute( + "UPDATE foo SET value = milliadjust(10) WHERE id = 11")); + EXPECT_TRUE(db().CommitTransaction()); + } + + scoped_ptr<base::HistogramSamples> samples( + tester.GetHistogramSamplesSinceCreation(kQueryTime)); + ASSERT_TRUE(samples); + // 10 for insert adjust, 10 for update adjust, 100 for commit adjust, 1 for + // measuring each of BEGIN, INSERT, UPDATE, and COMMIT. + EXPECT_EQ(124, samples->sum()); + + samples = tester.GetHistogramSamplesSinceCreation(kUpdateTime); + ASSERT_TRUE(samples); + // 10 for insert adjust, 10 for update adjust, 100 for commit adjust, 1 for + // measuring each of INSERT, UPDATE, and COMMIT. + EXPECT_EQ(123, samples->sum()); + + samples = tester.GetHistogramSamplesSinceCreation(kCommitTime); + ASSERT_TRUE(samples); + // 100 for commit adjust, 1 for measuring COMMIT. + EXPECT_EQ(101, samples->sum()); + + samples = tester.GetHistogramSamplesSinceCreation(kAutoCommitTime); + EXPECT_TRUE(!samples || samples->sum() == 0); +} + } // namespace diff --git a/chromium/sql/correct_sql_test_base.h b/chromium/sql/correct_sql_test_base.h new file mode 100644 index 00000000000..7056dea19ab --- /dev/null +++ b/chromium/sql/correct_sql_test_base.h @@ -0,0 +1,27 @@ +// Copyright 2015 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_CORRECT_SQL_TEST_BASE_H_ +#define SQL_CORRECT_SQL_TEST_BASE_H_ + +// This header exists to get around gn check. We want to use the same testing +// code in both the sql_unittests target (which uses gtest and targets the +// filesystem directly) and sql_apptests.mojo (which uses mojo:apptest and +// proxies the additional filesystem access to mojo:filesystem). Both of these +// files define a class named sql::SQLTestBase and have the same interface. +// +// Unfortunately, gn check does not understand preprocessor directives. If it +// did, the following code would be gn check clean, but since it isn't, we +// stuff this redirection header in its own file, give it its own source_set +// target, and then set check_includes to false. +// +// This work around was suggested by brettw@. +#if defined(MOJO_APPTEST_IMPL) +#include "sql/mojo/sql_test_base.h" +#else +#include "sql/test/sql_test_base.h" +#endif + +#endif // SQL_CORRECT_SQL_TEST_BASE_H_ + diff --git a/chromium/sql/meta_table_unittest.cc b/chromium/sql/meta_table_unittest.cc index 14123921793..13d0b5d2a1d 100644 --- a/chromium/sql/meta_table_unittest.cc +++ b/chromium/sql/meta_table_unittest.cc @@ -8,25 +8,12 @@ #include "base/files/scoped_temp_dir.h" #include "sql/connection.h" #include "sql/statement.h" +#include "sql/test/sql_test_base.h" #include "testing/gtest/include/gtest/gtest.h" namespace { -class SQLMetaTableTest : public testing::Test { - public: - void SetUp() override { - ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); - ASSERT_TRUE(db_.Open(temp_dir_.path().AppendASCII("SQLMetaTableTest.db"))); - } - - void TearDown() override { db_.Close(); } - - sql::Connection& db() { return db_; } - - private: - base::ScopedTempDir temp_dir_; - sql::Connection db_; -}; +using SQLMetaTableTest = sql::SQLTestBase; TEST_F(SQLMetaTableTest, DoesTableExist) { EXPECT_FALSE(sql::MetaTable::DoesTableExist(&db())); diff --git a/chromium/sql/mojo/BUILD.gn b/chromium/sql/mojo/BUILD.gn new file mode 100644 index 00000000000..18feaec1094 --- /dev/null +++ b/chromium/sql/mojo/BUILD.gn @@ -0,0 +1,62 @@ +# Copyright 2015 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. + +import("//mojo/public/mojo_application.gni") + +source_set("mojo") { + sources = [ + "mojo_vfs.cc", + "mojo_vfs.h", + ] + + # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. + configs += [ "//build/config/compiler:no_size_t_to_int_warning" ] + + defines = [ "SQL_IMPLEMENTATION" ] + + deps = [ + "//base", + "//base/third_party/dynamic_annotations", + "//components/filesystem/public/interfaces", + "//mojo/application/public/cpp", + "//mojo/common", + "//mojo/platform_handle", + "//third_party/sqlite", + ] +} + +mojo_native_application("apptests") { + output_name = "sql_apptests" + + testonly = true + + # Instead of using the code in //sql/test/sql_test_base.h, we should use the + # mojo test base class. + defines = [ "MOJO_APPTEST_IMPL" ] + + sources = [ + "../connection_unittest.cc", + "../statement_unittest.cc", + "../test/paths.cc", + "../test/paths.h", + "../transaction_unittest.cc", + "sql_test_base.cc", + "sql_test_base.h", + "vfs_unittest.cc", + ] + + deps = [ + ":mojo", + "//base", + "//base/test:test_support", + "//components/filesystem/public/interfaces", + "//mojo/application/public/cpp:sources", + "//mojo/application/public/cpp:test_support", + "//sql", + "//sql:redirection_header", + "//sql:test_support", + "//testing/gtest:gtest", + "//third_party/mojo/src/mojo/public/cpp/bindings", + ] +} diff --git a/chromium/sql/mojo/DEPS b/chromium/sql/mojo/DEPS new file mode 100644 index 00000000000..be63411efe9 --- /dev/null +++ b/chromium/sql/mojo/DEPS @@ -0,0 +1,6 @@ +include_rules = [ + "+components/filesystem", + "+mojo/application", + "+mojo/public", + "+mojo/util", +] diff --git a/chromium/sql/mojo/mojo_vfs.cc b/chromium/sql/mojo/mojo_vfs.cc new file mode 100644 index 00000000000..6e38af97040 --- /dev/null +++ b/chromium/sql/mojo/mojo_vfs.cc @@ -0,0 +1,413 @@ +// Copyright 2015 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/mojo/mojo_vfs.h" + +#include "base/logging.h" +#include "base/rand_util.h" +#include "components/filesystem/public/interfaces/file.mojom.h" +#include "components/filesystem/public/interfaces/file_system.mojom.h" +#include "components/filesystem/public/interfaces/types.mojom.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" +#include "mojo/util/capture_util.h" +#include "third_party/sqlite/sqlite3.h" + +using mojo::Capture; + +namespace sql { + +sqlite3_vfs* GetParentVFS(sqlite3_vfs* mojo_vfs) { + return static_cast<ScopedMojoFilesystemVFS*>(mojo_vfs->pAppData)->parent_; +} + +filesystem::DirectoryPtr& GetRootDirectory(sqlite3_vfs* mojo_vfs) { + return static_cast<ScopedMojoFilesystemVFS*>(mojo_vfs->pAppData)-> + root_directory_; +} + +namespace { + +// Implementation of the sqlite3 Mojo proxying vfs. +// +// This is a bunch of C callback objects which transparently proxy sqlite3's +// filesystem reads/writes over the mojo:filesystem service. The main +// entrypoint is sqlite3_mojovfs(), which proxies all the file open/delete/etc +// operations. mojo:filesystem has support for passing a raw file descriptor +// over the IPC barrier, and most of the implementation of sqlite3_io_methods +// is derived from the default sqlite3 unix VFS and operates on the raw file +// descriptors. + +const int kMaxPathName = 512; + +// A struct which extends the base sqlite3_file to also hold on to a file +// pipe. We reinterpret_cast our sqlite3_file structs to this struct +// instead. This is "safe" because this struct is really just a slab of +// malloced memory, of which we tell sqlite how large we want with szOsFile. +struct MojoVFSFile { + // The "vtable" of our sqlite3_file "subclass". + sqlite3_file base; + + // We keep an open pipe to the File object to keep it from cleaning itself + // up. + filesystem::FilePtr file_ptr; +}; + +filesystem::FilePtr& GetFSFile(sqlite3_file* vfs_file) { + return reinterpret_cast<MojoVFSFile*>(vfs_file)->file_ptr; +} + +int MojoVFSClose(sqlite3_file* file) { + DVLOG(1) << "MojoVFSClose(*)"; + using filesystem::FilePtr; + GetFSFile(file).~FilePtr(); + return SQLITE_OK; +} + +int MojoVFSRead(sqlite3_file* sql_file, + void* buffer, + int size, + sqlite3_int64 offset) { + DVLOG(1) << "MojoVFSRead (" << size << " @ " << offset << ")"; + filesystem::FileError error = filesystem::FILE_ERROR_FAILED; + mojo::Array<uint8_t> mojo_data; + GetFSFile(sql_file)->Read(size, offset, filesystem::WHENCE_FROM_BEGIN, + Capture(&error, &mojo_data)); + GetFSFile(sql_file).WaitForIncomingResponse(); + + if (error != filesystem::FILE_ERROR_OK) { + // TODO(erg): Better implementation here. + NOTIMPLEMENTED(); + return SQLITE_IOERR_READ; + } + + if (mojo_data.size()) + memcpy(buffer, &mojo_data.front(), mojo_data.size()); + if (mojo_data.size() == static_cast<size_t>(size)) + return SQLITE_OK; + + // We didn't read the entire buffer. Fill the rest of the buffer with zeros. + memset(reinterpret_cast<char*>(buffer) + mojo_data.size(), 0, + size - mojo_data.size()); + + return SQLITE_IOERR_SHORT_READ; +} + +int MojoVFSWrite(sqlite3_file* sql_file, + const void* buffer, + int size, + sqlite_int64 offset) { + DVLOG(1) << "MojoVFSWrite(*, " << size << ", " << offset << ")"; + mojo::Array<uint8_t> mojo_data(size); + memcpy(&mojo_data.front(), buffer, size); + + filesystem::FileError error = filesystem::FILE_ERROR_FAILED; + uint32_t num_bytes_written = 0; + GetFSFile(sql_file)->Write(mojo_data.Pass(), offset, + filesystem::WHENCE_FROM_BEGIN, + Capture(&error, &num_bytes_written)); + GetFSFile(sql_file).WaitForIncomingResponse(); + if (error != filesystem::FILE_ERROR_OK) { + // TODO(erg): Better implementation here. + NOTIMPLEMENTED(); + return SQLITE_IOERR_WRITE; + } + if (num_bytes_written != static_cast<uint32_t>(size)) { + NOTIMPLEMENTED(); + return SQLITE_IOERR_WRITE; + } + + return SQLITE_OK; +} + +int MojoVFSTruncate(sqlite3_file* sql_file, sqlite_int64 size) { + DVLOG(1) << "MojoVFSTruncate(*, " << size << ")"; + filesystem::FileError error = filesystem::FILE_ERROR_FAILED; + GetFSFile(sql_file)->Truncate(size, Capture(&error)); + GetFSFile(sql_file).WaitForIncomingResponse(); + if (error != filesystem::FILE_ERROR_OK) { + // TODO(erg): Better implementation here. + NOTIMPLEMENTED(); + return SQLITE_IOERR_TRUNCATE; + } + + return SQLITE_OK; +} + +int MojoVFSSync(sqlite3_file* sql_file, int flags) { + DVLOG(1) << "MojoVFSSync(*, " << flags << ")"; + filesystem::FileError error = filesystem::FILE_ERROR_FAILED; + GetFSFile(sql_file)->Flush(Capture(&error)); + GetFSFile(sql_file).WaitForIncomingResponse(); + if (error != filesystem::FILE_ERROR_OK) { + // TODO(erg): Better implementation here. + NOTIMPLEMENTED(); + return SQLITE_IOERR_FSYNC; + } + + return SQLITE_OK; +} + +int MojoVFSFileSize(sqlite3_file* sql_file, sqlite_int64* size) { + DVLOG(1) << "MojoVFSFileSize(*)"; + + filesystem::FileError err = filesystem::FILE_ERROR_FAILED; + filesystem::FileInformationPtr file_info; + GetFSFile(sql_file)->Stat(Capture(&err, &file_info)); + GetFSFile(sql_file).WaitForIncomingResponse(); + + if (err != filesystem::FILE_ERROR_OK) { + // TODO(erg): Better implementation here. + NOTIMPLEMENTED(); + return SQLITE_IOERR_FSTAT; + } + + *size = file_info->size; + return SQLITE_OK; +} + +// TODO(erg): The current base::File interface isn't sufficient to handle +// sqlite's locking primitives, which are done on byte ranges in the file. (See +// "File Locking Notes" in sqlite3.c.) +int MojoVFSLock(sqlite3_file* pFile, int eLock) { + DVLOG(1) << "MojoVFSLock(*, " << eLock << ")"; + return SQLITE_OK; +} +int MojoVFSUnlock(sqlite3_file* pFile, int eLock) { + DVLOG(1) << "MojoVFSUnlock(*, " << eLock << ")"; + return SQLITE_OK; +} +int MojoVFSCheckReservedLock(sqlite3_file* pFile, int* pResOut) { + DVLOG(1) << "MojoVFSCheckReservedLock(*)"; + *pResOut = 0; + return SQLITE_OK; +} + +// TODO(erg): This is the minimal implementation to get a few tests passing; +// lots more needs to be done here. +int MojoVFSFileControl(sqlite3_file* pFile, int op, void* pArg) { + DVLOG(1) << "MojoVFSFileControl(*, " << op << ", *)"; + if (op == SQLITE_FCNTL_PRAGMA) { + // Returning NOTFOUND tells sqlite that we aren't doing any processing. + return SQLITE_NOTFOUND; + } + + return SQLITE_OK; +} + +int MojoVFSSectorSize(sqlite3_file* pFile) { + DVLOG(1) << "MojoVFSSectorSize(*)"; + // Use the default sector size. + return 0; +} + +int MojoVFSDeviceCharacteristics(sqlite3_file* pFile) { + DVLOG(1) << "MojoVFSDeviceCharacteristics(*)"; + NOTIMPLEMENTED(); + return 0; +} + +static sqlite3_io_methods mojo_vfs_io_methods = { + 1, /* iVersion */ + MojoVFSClose, /* xClose */ + MojoVFSRead, /* xRead */ + MojoVFSWrite, /* xWrite */ + MojoVFSTruncate, /* xTruncate */ + MojoVFSSync, /* xSync */ + MojoVFSFileSize, /* xFileSize */ + MojoVFSLock, /* xLock */ + MojoVFSUnlock, /* xUnlock */ + MojoVFSCheckReservedLock, /* xCheckReservedLock */ + MojoVFSFileControl, /* xFileControl */ + MojoVFSSectorSize, /* xSectorSize */ + MojoVFSDeviceCharacteristics, /* xDeviceCharacteristics */ +}; + +int MojoVFSOpen(sqlite3_vfs* mojo_vfs, + const char* name, + sqlite3_file* file, + int flags, + int* pOutFlags) { + DVLOG(1) << "MojoVFSOpen(*, " << name << ", *, " << flags << ")"; + int open_flags = 0; + if (flags & SQLITE_OPEN_EXCLUSIVE) { + DCHECK(flags & SQLITE_OPEN_CREATE); + open_flags = filesystem::kFlagCreate; + } else if (flags & SQLITE_OPEN_CREATE) { + DCHECK(flags & SQLITE_OPEN_READWRITE); + open_flags = filesystem::kFlagOpenAlways; + } else { + open_flags = filesystem::kFlagOpen; + } + open_flags |= filesystem::kFlagRead; + if (flags & SQLITE_OPEN_READWRITE) + open_flags |= filesystem::kFlagWrite; + if (flags & SQLITE_OPEN_DELETEONCLOSE) + open_flags |= filesystem::kDeleteOnClose; + + // Grab the incoming file + filesystem::FilePtr file_ptr; + filesystem::FileError error = filesystem::FILE_ERROR_FAILED; + GetRootDirectory(mojo_vfs)->OpenFile(mojo::String(name), GetProxy(&file_ptr), + open_flags, Capture(&error)); + GetRootDirectory(mojo_vfs).WaitForIncomingResponse(); + if (error != filesystem::FILE_ERROR_OK) { + // TODO(erg): Translate more of the mojo error codes into sqlite error + // codes. + return SQLITE_CANTOPEN; + } + + // Set the method table so we can be closed (and run the manual dtor call to + // match the following placement news). + file->pMethods = &mojo_vfs_io_methods; + + // |file| is actually a malloced buffer of size szOsFile. This means that we + // need to manually use placement new to construct the C++ object which owns + // the pipe to our file. + new (&GetFSFile(file)) filesystem::FilePtr(file_ptr.Pass()); + + return SQLITE_OK; +} + +int MojoVFSDelete(sqlite3_vfs* mojo_vfs, const char* filename, int sync_dir) { + DVLOG(1) << "MojoVFSDelete(*, " << filename << ", " << sync_dir << ")"; + // TODO(erg): The default windows sqlite VFS has retry code to work around + // antivirus software keeping files open. We'll probably have to do something + // like that in the far future if we ever support Windows. + filesystem::FileError error = filesystem::FILE_ERROR_FAILED; + GetRootDirectory(mojo_vfs)->Delete(filename, 0, Capture(&error)); + GetRootDirectory(mojo_vfs).WaitForIncomingResponse(); + + if (error == filesystem::FILE_ERROR_OK && sync_dir) { + GetRootDirectory(mojo_vfs)->Flush(Capture(&error)); + GetRootDirectory(mojo_vfs).WaitForIncomingResponse(); + } + + return error == filesystem::FILE_ERROR_OK ? SQLITE_OK : SQLITE_IOERR_DELETE; +} + +int MojoVFSAccess(sqlite3_vfs* mojo_vfs, + const char* filename, + int flags, + int* result) { + DVLOG(1) << "MojoVFSAccess(*, " << filename << ", " << flags << ", *)"; + filesystem::FileError error = filesystem::FILE_ERROR_FAILED; + + if (flags == SQLITE_ACCESS_READWRITE || flags == SQLITE_ACCESS_READ) { + bool is_writable = false; + GetRootDirectory(mojo_vfs) + ->IsWritable(filename, Capture(&error, &is_writable)); + GetRootDirectory(mojo_vfs).WaitForIncomingResponse(); + *result = is_writable; + return SQLITE_OK; + } + + if (flags == SQLITE_ACCESS_EXISTS) { + bool exists = false; + GetRootDirectory(mojo_vfs)->Exists(filename, Capture(&error, &exists)); + GetRootDirectory(mojo_vfs).WaitForIncomingResponse(); + *result = exists; + return SQLITE_OK; + } + + return SQLITE_IOERR; +} + +int MojoVFSFullPathname(sqlite3_vfs* mojo_vfs, + const char* relative_path, + int absolute_path_size, + char* absolute_path) { + // The sandboxed process doesn't need to know the absolute path of the file. + sqlite3_snprintf(absolute_path_size, absolute_path, "%s", relative_path); + return SQLITE_OK; +} + +// Don't let SQLite dynamically load things. (If we are using the +// mojo:filesystem proxying VFS, then it's highly likely that we are sandboxed +// and that any attempt to dlopen() a shared object is folly.) +void* MojoVFSDlOpen(sqlite3_vfs*, const char*) { + return 0; +} + +void MojoVFSDlError(sqlite3_vfs*, int buf_size, char* error_msg) { + sqlite3_snprintf(buf_size, error_msg, "Dynamic loading not supported"); +} + +void (*MojoVFSDlSym(sqlite3_vfs*, void*, const char*))(void) { + return 0; +} + +void MojoVFSDlClose(sqlite3_vfs*, void*) { + return; +} + +int MojoVFSRandomness(sqlite3_vfs* mojo_vfs, int size, char* out) { + base::RandBytes(out, size); + return size; +} + +// Proxy the rest of the calls down to the OS specific handler. +int MojoVFSSleep(sqlite3_vfs* mojo_vfs, int micro) { + return GetParentVFS(mojo_vfs)->xSleep(GetParentVFS(mojo_vfs), micro); +} + +int MojoVFSCurrentTime(sqlite3_vfs* mojo_vfs, double* time) { + return GetParentVFS(mojo_vfs)->xCurrentTime(GetParentVFS(mojo_vfs), time); +} + +int MojoVFSGetLastError(sqlite3_vfs* mojo_vfs, int a, char* b) { + return 0; +} + +int MojoVFSCurrentTimeInt64(sqlite3_vfs* mojo_vfs, sqlite3_int64* out) { + return GetParentVFS(mojo_vfs)->xCurrentTimeInt64(GetParentVFS(mojo_vfs), out); +} + +static sqlite3_vfs mojo_vfs = { + 1, /* iVersion */ + sizeof(MojoVFSFile), /* szOsFile */ + kMaxPathName, /* mxPathname */ + 0, /* pNext */ + "mojo", /* zName */ + 0, /* pAppData */ + MojoVFSOpen, /* xOpen */ + MojoVFSDelete, /* xDelete */ + MojoVFSAccess, /* xAccess */ + MojoVFSFullPathname, /* xFullPathname */ + MojoVFSDlOpen, /* xDlOpen */ + MojoVFSDlError, /* xDlError */ + MojoVFSDlSym, /* xDlSym */ + MojoVFSDlClose, /* xDlClose */ + MojoVFSRandomness, /* xRandomness */ + MojoVFSSleep, /* xSleep */ + MojoVFSCurrentTime, /* xCurrentTime */ + MojoVFSGetLastError, /* xGetLastError */ + MojoVFSCurrentTimeInt64 /* xCurrentTimeInt64 */ +}; + +} // namespace + +ScopedMojoFilesystemVFS::ScopedMojoFilesystemVFS( + filesystem::DirectoryPtr root_directory) + : parent_(sqlite3_vfs_find(NULL)), + root_directory_(root_directory.Pass()) { + CHECK(!mojo_vfs.pAppData); + mojo_vfs.pAppData = this; + mojo_vfs.mxPathname = parent_->mxPathname; + + CHECK(sqlite3_vfs_register(&mojo_vfs, 1) == SQLITE_OK); +} + +ScopedMojoFilesystemVFS::~ScopedMojoFilesystemVFS() { + CHECK(mojo_vfs.pAppData); + mojo_vfs.pAppData = nullptr; + + CHECK(sqlite3_vfs_register(parent_, 1) == SQLITE_OK); + CHECK(sqlite3_vfs_unregister(&mojo_vfs) == SQLITE_OK); +} + +filesystem::DirectoryPtr& ScopedMojoFilesystemVFS::GetDirectory() { + return root_directory_; +} + +} // namespace sql diff --git a/chromium/sql/mojo/mojo_vfs.h b/chromium/sql/mojo/mojo_vfs.h new file mode 100644 index 00000000000..dc835938eff --- /dev/null +++ b/chromium/sql/mojo/mojo_vfs.h @@ -0,0 +1,45 @@ +// Copyright 2015 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_MOJO_MOJO_VFS_H_ +#define SQL_MOJO_MOJO_VFS_H_ + +#include "base/macros.h" +#include "components/filesystem/public/interfaces/directory.mojom.h" + +typedef struct sqlite3_vfs sqlite3_vfs; + +namespace sql { + +// Changes the default sqlite3 vfs to a vfs that uses proxies calls to the +// mojo:filesystem service. Instantiating this object transparently changes how +// the entire //sql/ subsystem works in the process of the caller; all paths +// are treated as relative to |directory|. +class ScopedMojoFilesystemVFS { + public: + explicit ScopedMojoFilesystemVFS(filesystem::DirectoryPtr directory); + ~ScopedMojoFilesystemVFS(); + + // Returns the directory of the current VFS. + filesystem::DirectoryPtr& GetDirectory(); + + private: + friend sqlite3_vfs* GetParentVFS(sqlite3_vfs* mojo_vfs); + friend filesystem::DirectoryPtr& GetRootDirectory(sqlite3_vfs* mojo_vfs); + + // The default vfs at the time MojoVFS was installed. We use the to pass + // through things like randomness requests and per-platform sleep calls. + sqlite3_vfs* parent_; + + // When we initialize the subsystem, we are given a filesystem::Directory + // object, which is the root directory of a mojo:filesystem. All access to + // various files are specified from this root directory. + filesystem::DirectoryPtr root_directory_; + + DISALLOW_COPY_AND_ASSIGN(ScopedMojoFilesystemVFS); +}; + +} // namespace sql + +#endif // SQL_MOJO_MOJO_VFS_H_ diff --git a/chromium/sql/mojo/sql_test_base.cc b/chromium/sql/mojo/sql_test_base.cc new file mode 100644 index 00000000000..57645dd55d1 --- /dev/null +++ b/chromium/sql/mojo/sql_test_base.cc @@ -0,0 +1,156 @@ +// Copyright 2015 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/mojo/sql_test_base.h" + +#include "mojo/application/public/cpp/application_impl.h" +#include "mojo/util/capture_util.h" +#include "sql/mojo/mojo_vfs.h" +#include "sql/test/test_helpers.h" + +using mojo::Capture; + +namespace sql { + +SQLTestBase::SQLTestBase() { +} + +SQLTestBase::~SQLTestBase() { +} + +base::FilePath SQLTestBase::db_path() { + return base::FilePath(FILE_PATH_LITERAL("SQLTest.db")); +} + +sql::Connection& SQLTestBase::db() { + return db_; +} + +bool SQLTestBase::Reopen() { + db_.Close(); + return db_.Open(db_path()); +} + +bool SQLTestBase::GetPathExists(const base::FilePath& path) { + filesystem::FileError error = filesystem::FILE_ERROR_FAILED; + bool exists = false; + vfs_->GetDirectory()->Exists(path.AsUTF8Unsafe(), Capture(&error, &exists)); + vfs_->GetDirectory().WaitForIncomingResponse(); + if (error != filesystem::FILE_ERROR_OK) + return false; + return exists; +} + +bool SQLTestBase::CorruptSizeInHeaderOfDB() { + // See http://www.sqlite.org/fileformat.html#database_header + const size_t kHeaderSize = 100; + + mojo::Array<uint8_t> header; + + filesystem::FileError error = filesystem::FILE_ERROR_FAILED; + filesystem::FilePtr file_ptr; + vfs_->GetDirectory()->OpenFile( + mojo::String(db_path().AsUTF8Unsafe()), GetProxy(&file_ptr), + filesystem::kFlagRead | filesystem::kFlagWrite | + filesystem::kFlagOpenAlways, + Capture(&error)); + vfs_->GetDirectory().WaitForIncomingResponse(); + if (error != filesystem::FILE_ERROR_OK) + return false; + + file_ptr->Read(kHeaderSize, 0, filesystem::WHENCE_FROM_BEGIN, + Capture(&error, &header)); + file_ptr.WaitForIncomingResponse(); + if (error != filesystem::FILE_ERROR_OK) + return false; + + filesystem::FileInformationPtr info; + file_ptr->Stat(Capture(&error, &info)); + file_ptr.WaitForIncomingResponse(); + if (error != filesystem::FILE_ERROR_OK) + return false; + int64_t db_size = info->size; + + test::CorruptSizeInHeaderMemory(&header.front(), db_size); + + uint32_t num_bytes_written = 0; + file_ptr->Write(header.Pass(), 0, filesystem::WHENCE_FROM_BEGIN, + Capture(&error, &num_bytes_written)); + file_ptr.WaitForIncomingResponse(); + if (error != filesystem::FILE_ERROR_OK) + return false; + if (num_bytes_written != kHeaderSize) + return false; + + return true; +} + +void SQLTestBase::WriteJunkToDatabase(WriteJunkType type) { + uint32_t flags = 0; + if (type == TYPE_OVERWRITE_AND_TRUNCATE) + flags = filesystem::kFlagWrite | filesystem::kFlagCreate; + else + flags = filesystem::kFlagWrite | filesystem::kFlagOpen; + + filesystem::FileError error = filesystem::FILE_ERROR_FAILED; + filesystem::FilePtr file_ptr; + vfs_->GetDirectory()->OpenFile( + mojo::String(db_path().AsUTF8Unsafe()), GetProxy(&file_ptr), + flags, + Capture(&error)); + vfs_->GetDirectory().WaitForIncomingResponse(); + if (error != filesystem::FILE_ERROR_OK) + return; + + const char* kJunk = "Now is the winter of our discontent."; + mojo::Array<uint8_t> data(strlen(kJunk)); + memcpy(&data.front(), kJunk, strlen(kJunk)); + + uint32_t num_bytes_written = 0; + file_ptr->Write(data.Pass(), 0, filesystem::WHENCE_FROM_BEGIN, + Capture(&error, &num_bytes_written)); + file_ptr.WaitForIncomingResponse(); +} + +void SQLTestBase::TruncateDatabase() { + filesystem::FileError error = filesystem::FILE_ERROR_FAILED; + filesystem::FilePtr file_ptr; + vfs_->GetDirectory()->OpenFile( + mojo::String(db_path().AsUTF8Unsafe()), GetProxy(&file_ptr), + filesystem::kFlagWrite | filesystem::kFlagOpen, + Capture(&error)); + vfs_->GetDirectory().WaitForIncomingResponse(); + if (error != filesystem::FILE_ERROR_OK) + return; + + file_ptr->Truncate(0, Capture(&error)); + file_ptr.WaitForIncomingResponse(); + ASSERT_EQ(filesystem::FILE_ERROR_OK, error); +} + +void SQLTestBase::SetUp() { + ApplicationTestBase::SetUp(); + + mojo::URLRequestPtr request(mojo::URLRequest::New()); + request->url = mojo::String::From("mojo:filesystem"); + application_impl()->ConnectToService(request.Pass(), &files_); + + filesystem::FileError error = filesystem::FILE_ERROR_FAILED; + filesystem::DirectoryPtr directory; + files()->OpenFileSystem("temp", GetProxy(&directory), Capture(&error)); + ASSERT_TRUE(files().WaitForIncomingResponse()); + ASSERT_EQ(filesystem::FILE_ERROR_OK, error); + + vfs_.reset(new ScopedMojoFilesystemVFS(directory.Pass())); + ASSERT_TRUE(db_.Open(db_path())); +} + +void SQLTestBase::TearDown() { + db_.Close(); + vfs_.reset(); + + ApplicationTestBase::TearDown(); +} + +} // namespace sql diff --git a/chromium/sql/mojo/sql_test_base.h b/chromium/sql/mojo/sql_test_base.h new file mode 100644 index 00000000000..f2bfb5d4c48 --- /dev/null +++ b/chromium/sql/mojo/sql_test_base.h @@ -0,0 +1,85 @@ +// Copyright 2015 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_MOJO_SQL_TEST_BASE_H_ +#define SQL_MOJO_SQL_TEST_BASE_H_ + +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "components/filesystem/public/interfaces/file_system.mojom.h" +#include "mojo/application/public/cpp/application_test_base.h" +#include "sql/connection.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sql { + +class Connection; +class ScopedMojoFilesystemVFS; + +// Base class for SQL tests. +// +// WARNING: We want to run the same gtest based unit test code both against +// chromium (which uses this implementation here), and the mojo code (which +// uses a different class named SQLTestBase). These two classes need to have +// the same interface because we compile time switch them based on a +// #define. We need to have two different implementations because the mojo +// version derives from mojo::test::ApplicationTestBase instead of +// testing::Test. +class SQLTestBase : public mojo::test::ApplicationTestBase { + public: + SQLTestBase(); + ~SQLTestBase() override; + + enum WriteJunkType { + TYPE_OVERWRITE_AND_TRUNCATE, + TYPE_OVERWRITE + }; + + // Returns the path to the database. + base::FilePath db_path(); + + // Returns a connection to the database at db_path(). + sql::Connection& db(); + + // Closes the current connection to the database and reopens it. + bool Reopen(); + + // Proxying method around base::PathExists. + bool GetPathExists(const base::FilePath& path); + + // SQLite stores the database size in the header, and if the actual + // OS-derived size is smaller, the database is considered corrupt. + // [This case is actually a common form of corruption in the wild.] + // This helper sets the in-header size to one page larger than the + // actual file size. The resulting file will return SQLITE_CORRUPT + // for most operations unless PRAGMA writable_schema is turned ON. + // + // Returns false if any error occurs accessing the file. + bool CorruptSizeInHeaderOfDB(); + + // Writes junk to the start of the file. + void WriteJunkToDatabase(WriteJunkType type); + + // Sets the database file size to 0. + void TruncateDatabase(); + + // Overridden from testing::Test: + void SetUp() override; + void TearDown() override; + + protected: + filesystem::FileSystemPtr& files() { return files_; } + + private: + filesystem::FileSystemPtr files_; + + scoped_ptr<ScopedMojoFilesystemVFS> vfs_; + sql::Connection db_; + + DISALLOW_COPY_AND_ASSIGN(SQLTestBase); +}; + +} // namespace sql + +#endif // SQL_MOJO_SQL_TEST_BASE_H_ diff --git a/chromium/sql/mojo/vfs_unittest.cc b/chromium/sql/mojo/vfs_unittest.cc new file mode 100644 index 00000000000..8ca7c5c21a7 --- /dev/null +++ b/chromium/sql/mojo/vfs_unittest.cc @@ -0,0 +1,317 @@ +// Copyright 2015 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 "components/filesystem/public/interfaces/file_system.mojom.h" +#include "mojo/application/public/cpp/application_impl.h" +#include "mojo/application/public/cpp/application_test_base.h" +#include "mojo/util/capture_util.h" +#include "sql/mojo/mojo_vfs.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/sqlite/sqlite3.h" + +namespace base { + +// This deleter lets us be safe with sqlite3 objects, which aren't really the +// structs, but slabs of new uint8_t[size]. +template <> +struct DefaultDeleter<sqlite3_file> { + inline void operator()(sqlite3_file* ptr) const { + // Why don't we call file->pMethods->xClose() here? Because it's not + // guaranteed to be valid. sqlite3_file "objects" can be in partially + // initialized states. + delete [] reinterpret_cast<uint8_t*>(ptr); + } +}; + +} // namespace base + +namespace sql { + +const char kFileName[] = "TestingDatabase.db"; + +class VFSTest : public mojo::test::ApplicationTestBase { + public: + VFSTest() {} + ~VFSTest() override {} + + sqlite3_vfs* vfs() { + return sqlite3_vfs_find(NULL); + } + + scoped_ptr<sqlite3_file> MakeFile() { + return scoped_ptr<sqlite3_file>(reinterpret_cast<sqlite3_file*>( + new uint8_t[vfs()->szOsFile])); + } + + void SetUp() override { + mojo::test::ApplicationTestBase::SetUp(); + + mojo::URLRequestPtr request(mojo::URLRequest::New()); + request->url = mojo::String::From("mojo:filesystem"); + application_impl()->ConnectToService(request.Pass(), &files_); + + filesystem::FileError error = filesystem::FILE_ERROR_FAILED; + filesystem::DirectoryPtr directory; + files_->OpenFileSystem("temp", GetProxy(&directory), mojo::Capture(&error)); + ASSERT_TRUE(files_.WaitForIncomingResponse()); + ASSERT_EQ(filesystem::FILE_ERROR_OK, error); + + vfs_.reset(new ScopedMojoFilesystemVFS(directory.Pass())); + } + + void TearDown() override { + vfs_.reset(); + mojo::test::ApplicationTestBase::TearDown(); + } + + private: + filesystem::FileSystemPtr files_; + scoped_ptr<ScopedMojoFilesystemVFS> vfs_; + + DISALLOW_COPY_AND_ASSIGN(VFSTest); +}; + +TEST_F(VFSTest, TestInstalled) { + EXPECT_EQ("mojo", vfs()->zName); +} + +TEST_F(VFSTest, ExclusiveOpen) { + // Opening a non-existent file exclusively should work. + scoped_ptr<sqlite3_file> file(MakeFile()); + int out_flags; + int rc = vfs()->xOpen(vfs(), kFileName, file.get(), + SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_CREATE, + &out_flags); + EXPECT_EQ(SQLITE_OK, rc); + + // Opening it a second time exclusively shouldn't. + scoped_ptr<sqlite3_file> file2(MakeFile()); + rc = vfs()->xOpen(vfs(), kFileName, file2.get(), + SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_CREATE, + &out_flags); + EXPECT_NE(SQLITE_OK, rc); + + file->pMethods->xClose(file.get()); +} + +TEST_F(VFSTest, NonexclusiveOpen) { + // Opening a non-existent file should work. + scoped_ptr<sqlite3_file> file(MakeFile()); + int out_flags; + int rc = vfs()->xOpen(vfs(), kFileName, file.get(), + SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, + &out_flags); + EXPECT_EQ(SQLITE_OK, rc); + + // Opening it a second time should work. + scoped_ptr<sqlite3_file> file2(MakeFile()); + rc = vfs()->xOpen(vfs(), kFileName, file2.get(), + SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, + &out_flags); + EXPECT_EQ(SQLITE_OK, rc); + + file->pMethods->xClose(file.get()); + file->pMethods->xClose(file2.get()); +} + +TEST_F(VFSTest, DeleteOnClose) { + { + scoped_ptr<sqlite3_file> file(MakeFile()); + int out_flags; + int rc = vfs()->xOpen( + vfs(), kFileName, file.get(), + SQLITE_OPEN_DELETEONCLOSE | SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, + &out_flags); + EXPECT_EQ(SQLITE_OK, rc); + file->pMethods->xClose(file.get()); + } + + // The file shouldn't exist now. + int result = 0; + vfs()->xAccess(vfs(), kFileName, SQLITE_ACCESS_EXISTS, &result); + EXPECT_FALSE(result); +} + +TEST_F(VFSTest, TestNonExistence) { + // We shouldn't have a file exist yet in a fresh directory. + int result = 0; + vfs()->xAccess(vfs(), kFileName, SQLITE_ACCESS_EXISTS, &result); + EXPECT_FALSE(result); +} + +TEST_F(VFSTest, TestExistence) { + { + scoped_ptr<sqlite3_file> file(MakeFile()); + int out_flags; + int rc = vfs()->xOpen(vfs(), kFileName, file.get(), + SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, + &out_flags); + EXPECT_EQ(SQLITE_OK, rc); + + file->pMethods->xClose(file.get()); + } + + int result = 0; + vfs()->xAccess(vfs(), kFileName, SQLITE_ACCESS_EXISTS, &result); + EXPECT_TRUE(result); +} + +TEST_F(VFSTest, TestDelete) { + { + scoped_ptr<sqlite3_file> file(MakeFile()); + int out_flags; + int rc = vfs()->xOpen(vfs(), kFileName, file.get(), + SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, + &out_flags); + EXPECT_EQ(SQLITE_OK, rc); + + file->pMethods->xClose(file.get()); + } + + int result = 0; + vfs()->xAccess(vfs(), kFileName, SQLITE_ACCESS_EXISTS, &result); + EXPECT_TRUE(result); + + vfs()->xDelete(vfs(), kFileName, 0); + + vfs()->xAccess(vfs(), kFileName, SQLITE_ACCESS_EXISTS, &result); + EXPECT_FALSE(result); +} + +TEST_F(VFSTest, TestWriteAndRead) { + const char kBuffer[] = "One Two Three Four Five Six Seven"; + const int kBufferSize = arraysize(kBuffer); + + { + scoped_ptr<sqlite3_file> file(MakeFile()); + int out_flags; + int rc = vfs()->xOpen(vfs(), kFileName, file.get(), + SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, + &out_flags); + EXPECT_EQ(SQLITE_OK, rc); + + for (int i = 0; i < 10; ++i) { + rc = file->pMethods->xWrite(file.get(), kBuffer, kBufferSize, + i * kBufferSize); + EXPECT_EQ(SQLITE_OK, rc); + } + + file->pMethods->xClose(file.get()); + } + + // Expect that the size of the file is 10 * arraysize(kBuffer); + { + scoped_ptr<sqlite3_file> file(MakeFile()); + int out_flags; + int rc = vfs()->xOpen(vfs(), kFileName, file.get(), + SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, + &out_flags); + EXPECT_EQ(SQLITE_OK, rc); + + sqlite_int64 size; + rc = file->pMethods->xFileSize(file.get(), &size); + EXPECT_EQ(SQLITE_OK, rc); + EXPECT_EQ(10 * kBufferSize, size); + + file->pMethods->xClose(file.get()); + } + + // We should be able to read things back. + { + scoped_ptr<sqlite3_file> file(MakeFile()); + int out_flags; + int rc = vfs()->xOpen(vfs(), kFileName, file.get(), + SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, + &out_flags); + EXPECT_EQ(SQLITE_OK, rc); + + char data_buffer[kBufferSize]; + memset(data_buffer, '8', kBufferSize); + for (int i = 0; i < 10; ++i) { + rc = file->pMethods->xRead(file.get(), data_buffer, kBufferSize, + i * kBufferSize); + EXPECT_EQ(SQLITE_OK, rc); + EXPECT_TRUE(strncmp(kBuffer, &data_buffer[0], kBufferSize) == 0); + } + + file->pMethods->xClose(file.get()); + } +} + +TEST_F(VFSTest, PartialRead) { + const char kBuffer[] = "One Two Three Four Five Six Seven"; + const int kBufferSize = arraysize(kBuffer); + + // Write the data once. + { + scoped_ptr<sqlite3_file> file(MakeFile()); + int out_flags; + int rc = vfs()->xOpen(vfs(), kFileName, file.get(), + SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, + &out_flags); + EXPECT_EQ(SQLITE_OK, rc); + + rc = file->pMethods->xWrite(file.get(), kBuffer, kBufferSize, 0); + EXPECT_EQ(SQLITE_OK, rc); + + file->pMethods->xClose(file.get()); + } + + // Now attempt to read kBufferSize + 5 from a file sized to kBufferSize. + { + scoped_ptr<sqlite3_file> file(MakeFile()); + int out_flags; + int rc = vfs()->xOpen(vfs(), kFileName, file.get(), + SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, + &out_flags); + EXPECT_EQ(SQLITE_OK, rc); + + const char kBufferWithFiveNulls[] = + "One Two Three Four Five Six Seven\0\0\0\0\0"; + const int kBufferWithFiveNullsSize = arraysize(kBufferWithFiveNulls); + + char data_buffer[kBufferWithFiveNullsSize]; + memset(data_buffer, '8', kBufferWithFiveNullsSize); + rc = file->pMethods->xRead(file.get(), data_buffer, + kBufferWithFiveNullsSize, 0); + EXPECT_EQ(SQLITE_IOERR_SHORT_READ, rc); + + EXPECT_TRUE(strncmp(kBufferWithFiveNulls, &data_buffer[0], + kBufferWithFiveNullsSize) == 0); + + file->pMethods->xClose(file.get()); + } +} + +TEST_F(VFSTest, Truncate) { + const char kBuffer[] = "One Two Three Four Five Six Seven"; + const int kBufferSize = arraysize(kBuffer); + const int kCharsToThree = 13; + + scoped_ptr<sqlite3_file> file(MakeFile()); + int out_flags; + int rc = vfs()->xOpen(vfs(), kFileName, file.get(), + SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, + &out_flags); + EXPECT_EQ(SQLITE_OK, rc); + + rc = file->pMethods->xWrite(file.get(), kBuffer, kBufferSize, 0); + EXPECT_EQ(SQLITE_OK, rc); + + sqlite_int64 size; + rc = file->pMethods->xFileSize(file.get(), &size); + EXPECT_EQ(SQLITE_OK, rc); + EXPECT_EQ(kBufferSize, size); + + rc = file->pMethods->xTruncate(file.get(), kCharsToThree); + EXPECT_EQ(SQLITE_OK, rc); + + rc = file->pMethods->xFileSize(file.get(), &size); + EXPECT_EQ(SQLITE_OK, rc); + EXPECT_EQ(kCharsToThree, size); + + file->pMethods->xClose(file.get()); +} + +} // namespace sql diff --git a/chromium/sql/proxy.cc b/chromium/sql/proxy.cc new file mode 100644 index 00000000000..51048122c3e --- /dev/null +++ b/chromium/sql/proxy.cc @@ -0,0 +1,28 @@ +// Copyright 2015 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/proxy.h" + +namespace sql { + +int sqlite3_create_function_v2( + sqlite3 *db, + const char *zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*), + void (*xDestroy)(void*)) { + return ::sqlite3_create_function_v2( + db, zFunctionName, nArg, eTextRep, pApp, + xFunc, xStep, xFinal, xDestroy); +} + +void *sqlite3_commit_hook(sqlite3* db, int(*func)(void*), void* arg) { + return ::sqlite3_commit_hook(db, func, arg); +} + +} // namespace sql diff --git a/chromium/sql/proxy.h b/chromium/sql/proxy.h new file mode 100644 index 00000000000..7a2863bfa78 --- /dev/null +++ b/chromium/sql/proxy.h @@ -0,0 +1,39 @@ +// Copyright 2015 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_PROXY_H_ +#define SQL_PROXY_H_ + +#include "sql/sql_export.h" +#include "third_party/sqlite/sqlite3.h" + +// TODO(shess): third_party/sqlite does not track component build correctly, so +// each shared library gets a private copy of everything, so sqlite3_* calls +// outside of the main sql/ component don't work right. Hack around this by +// adding pass-through functions while I land a separate fix for the component +// issue. + +// This is only required for tests - if these abilities are desired for +// production code, they should probably do obvious things like live in +// sql::Connection and use C++ wrappers. + +// http://crbug.com/489444 + +namespace sql { + +SQL_EXPORT int sqlite3_create_function_v2( + sqlite3 *db, + const char *zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*), + void (*xDestroy)(void*)); +SQL_EXPORT void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); + +} // namespace sql + +#endif // SQL_PROXY_H_ diff --git a/chromium/sql/recovery_unittest.cc b/chromium/sql/recovery_unittest.cc index 78a147815f9..1f930cbad34 100644 --- a/chromium/sql/recovery_unittest.cc +++ b/chromium/sql/recovery_unittest.cc @@ -16,6 +16,7 @@ #include "sql/statement.h" #include "sql/test/paths.h" #include "sql/test/scoped_error_ignorer.h" +#include "sql/test/sql_test_base.h" #include "sql/test/test_helpers.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/sqlite/sqlite3.h" @@ -61,32 +62,7 @@ std::string GetSchema(sql::Connection* db) { return ExecuteWithResults(db, kSql, "|", "\n"); } -class SQLRecoveryTest : public testing::Test { - public: - SQLRecoveryTest() {} - - void SetUp() override { - ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); - ASSERT_TRUE(db_.Open(db_path())); - } - - void TearDown() override { db_.Close(); } - - sql::Connection& db() { return db_; } - - base::FilePath db_path() { - return temp_dir_.path().AppendASCII("SQLRecoveryTest.db"); - } - - bool Reopen() { - db_.Close(); - return db_.Open(db_path()); - } - - private: - base::ScopedTempDir temp_dir_; - sql::Connection db_; -}; +using SQLRecoveryTest = sql::SQLTestBase; TEST_F(SQLRecoveryTest, RecoverBasic) { const char kCreateSql[] = "CREATE TABLE x (t TEXT)"; diff --git a/chromium/sql/sql.gyp b/chromium/sql/sql.gyp index 825d3d07bab..d983a458089 100644 --- a/chromium/sql/sql.gyp +++ b/chromium/sql/sql.gyp @@ -27,6 +27,8 @@ 'init_status.h', 'meta_table.cc', 'meta_table.h', + 'proxy.cc', + 'proxy.h', 'recovery.cc', 'recovery.h', 'statement.cc', @@ -94,6 +96,8 @@ 'test/paths.cc', 'test/paths.h', 'test/run_all_unittests.cc', + 'test/sql_test_base.cc', + 'test/sql_test_base.h', 'test/sql_test_suite.cc', 'test/sql_test_suite.h', 'transaction_unittest.cc', @@ -132,6 +136,7 @@ ], 'variables': { 'test_suite_name': 'sql_unittests', + 'isolate_file': 'sql_unittests.isolate', }, 'includes': [ '../build/apk_test.gypi' ], }, diff --git a/chromium/sql/sql_unittests.isolate b/chromium/sql/sql_unittests.isolate index f422ae6eab6..44246d7da3f 100644 --- a/chromium/sql/sql_unittests.isolate +++ b/chromium/sql/sql_unittests.isolate @@ -1,4 +1,4 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2015 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. { @@ -10,6 +10,23 @@ ], }, }], + ['OS=="linux" or OS=="mac" or OS=="win"', { + 'variables': { + 'command': [ + '../testing/test_env.py', + '<(PRODUCT_DIR)/sql_unittests<(EXECUTABLE_SUFFIX)', + '--brave-new-test-launcher', + '--test-launcher-bot-mode', + '--asan=<(asan)', + '--msan=<(msan)', + '--tsan=<(tsan)', + ], + 'files': [ + '../testing/test_env.py', + '<(PRODUCT_DIR)/sql_unittests<(EXECUTABLE_SUFFIX)', + ], + }, + }], ], 'includes': [ '../base/base.isolate', diff --git a/chromium/sql/sqlite_features_unittest.cc b/chromium/sql/sqlite_features_unittest.cc index 2b95bb8d3e7..20e002d3916 100644 --- a/chromium/sql/sqlite_features_unittest.cc +++ b/chromium/sql/sqlite_features_unittest.cc @@ -9,6 +9,7 @@ #include "base/files/scoped_temp_dir.h" #include "sql/connection.h" #include "sql/statement.h" +#include "sql/test/sql_test_base.h" #include "sql/test/test_helpers.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/sqlite/sqlite3.h" @@ -24,34 +25,30 @@ void CaptureErrorCallback(int* error_pointer, std::string* sql_text, *sql_text = text ? text : "no statement available"; } -class SQLiteFeaturesTest : public testing::Test { +class SQLiteFeaturesTest : public sql::SQLTestBase { public: SQLiteFeaturesTest() : error_(SQLITE_OK) {} void SetUp() override { - ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); - ASSERT_TRUE(db_.Open(temp_dir_.path().AppendASCII("SQLStatementTest.db"))); + SQLTestBase::SetUp(); // 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_)); + db().set_error_callback( + base::Bind(&CaptureErrorCallback, &error_, &sql_text_)); } void TearDown() override { // If any error happened the original sql statement can be found in // |sql_text_|. EXPECT_EQ(SQLITE_OK, error_) << sql_text_; - db_.Close(); + + SQLTestBase::TearDown(); } - sql::Connection& db() { return db_; } int error() { return error_; } private: - base::ScopedTempDir temp_dir_; - sql::Connection db_; - // The error code of the most recent error. int error_; // Original statement which has caused the error. diff --git a/chromium/sql/statement.cc b/chromium/sql/statement.cc index 42b4598c51d..038ebde0885 100644 --- a/chromium/sql/statement.cc +++ b/chromium/sql/statement.cc @@ -51,34 +51,63 @@ bool Statement::CheckValid() const { return is_valid(); } -bool Statement::Run() { - DCHECK(!stepped_); +int Statement::StepInternal(bool timer_flag) { ref_->AssertIOAllowed(); if (!CheckValid()) - return false; + return SQLITE_ERROR; + const bool was_stepped = stepped_; stepped_ = true; - return CheckError(sqlite3_step(ref_->stmt())) == SQLITE_DONE; + int ret = SQLITE_ERROR; + if (!ref_->connection()) { + ret = sqlite3_step(ref_->stmt()); + } else { + if (!timer_flag) { + ret = sqlite3_step(ref_->stmt()); + } else { + const base::TimeTicks before = ref_->connection()->Now(); + ret = sqlite3_step(ref_->stmt()); + const base::TimeTicks after = ref_->connection()->Now(); + const bool read_only = !!sqlite3_stmt_readonly(ref_->stmt()); + ref_->connection()->RecordTimeAndChanges(after - before, read_only); + } + + if (!was_stepped) + ref_->connection()->RecordOneEvent(Connection::EVENT_STATEMENT_RUN); + + if (ret == SQLITE_ROW) + ref_->connection()->RecordOneEvent(Connection::EVENT_STATEMENT_ROWS); + } + return CheckError(ret); } -bool Statement::Step() { - ref_->AssertIOAllowed(); - if (!CheckValid()) - return false; +bool Statement::Run() { + DCHECK(!stepped_); + return StepInternal(true) == SQLITE_DONE; +} - stepped_ = true; - return CheckError(sqlite3_step(ref_->stmt())) == SQLITE_ROW; +bool Statement::RunWithoutTimers() { + DCHECK(!stepped_); + return StepInternal(false) == SQLITE_DONE; +} + +bool Statement::Step() { + return StepInternal(true) == SQLITE_ROW; } void Statement::Reset(bool clear_bound_vars) { ref_->AssertIOAllowed(); if (is_valid()) { - // We don't call CheckError() here because sqlite3_reset() returns - // the last error that Step() caused thereby generating a second - // spurious error callback. if (clear_bound_vars) sqlite3_clear_bindings(ref_->stmt()); - sqlite3_reset(ref_->stmt()); + + // StepInternal() cannot track success because statements may be reset + // before reaching SQLITE_DONE. Don't call CheckError() because + // sqlite3_reset() returns the last step error, which StepInternal() already + // checked. + const int rc =sqlite3_reset(ref_->stmt()); + if (rc == SQLITE_OK && ref_->connection()) + ref_->connection()->RecordOneEvent(Connection::EVENT_STATEMENT_SUCCESS); } succeeded_ = false; diff --git a/chromium/sql/statement.h b/chromium/sql/statement.h index b411e809b6c..83a5a2dfa83 100644 --- a/chromium/sql/statement.h +++ b/chromium/sql/statement.h @@ -150,6 +150,8 @@ class SQL_EXPORT Statement { const char* GetSQLStatement(); private: + friend class Connection; + // This is intended to check for serious errors and report them to the // connection object. It takes a sqlite error code, and returns the same // code. Currently this function just updates the succeeded flag, but will be @@ -174,6 +176,16 @@ class SQL_EXPORT Statement { // ensuring that contracts are honored in error edge cases. bool CheckValid() const; + // Helper for Run() and Step(), calls sqlite3_step() and then generates + // sql::Connection histograms based on the results. Timing and change count + // are only recorded if |timer_flag| is true. The checked value from + // sqlite3_step() is returned. + int StepInternal(bool timer_flag); + + // sql::Connection uses cached statments for transactions, but tracks their + // runtime independently. + bool RunWithoutTimers(); + // The actual sqlite statement. This may be unique to us, or it may be cached // by the connection, which is why it's refcounted. This pointer is // guaranteed non-NULL. diff --git a/chromium/sql/statement_unittest.cc b/chromium/sql/statement_unittest.cc index 38f1778d736..1565b3e48a9 100644 --- a/chromium/sql/statement_unittest.cc +++ b/chromium/sql/statement_unittest.cc @@ -8,6 +8,7 @@ #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "sql/connection.h" +#include "sql/correct_sql_test_base.h" #include "sql/statement.h" #include "sql/test/error_callback_support.h" #include "sql/test/scoped_error_ignorer.h" @@ -16,21 +17,7 @@ namespace { -class SQLStatementTest : public testing::Test { - public: - void SetUp() override { - ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); - ASSERT_TRUE(db_.Open(temp_dir_.path().AppendASCII("SQLStatementTest.db"))); - } - - void TearDown() override { db_.Close(); } - - sql::Connection& db() { return db_; } - - private: - base::ScopedTempDir temp_dir_; - sql::Connection db_; -}; +using SQLStatementTest = sql::SQLTestBase; } // namespace diff --git a/chromium/sql/transaction_unittest.cc b/chromium/sql/transaction_unittest.cc index 83d41259b24..179adcf3e55 100644 --- a/chromium/sql/transaction_unittest.cc +++ b/chromium/sql/transaction_unittest.cc @@ -5,35 +5,26 @@ #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "sql/connection.h" +#include "sql/correct_sql_test_base.h" #include "sql/statement.h" #include "sql/transaction.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/sqlite/sqlite3.h" -class SQLTransactionTest : public testing::Test { +class SQLTransactionTest : public sql::SQLTestBase { public: void SetUp() override { - ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); - ASSERT_TRUE(db_.Open( - temp_dir_.path().AppendASCII("SQLTransactionTest.db"))); + SQLTestBase::SetUp(); ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)")); } - void TearDown() override { db_.Close(); } - - sql::Connection& db() { return db_; } - // Returns the number of rows in table "foo". int CountFoo() { sql::Statement count(db().GetUniqueStatement("SELECT count(*) FROM foo")); count.Step(); return count.ColumnInt(0); } - - private: - base::ScopedTempDir temp_dir_; - sql::Connection db_; }; TEST_F(SQLTransactionTest, Commit) { |