summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/filesystem/DEPS1
-rw-r--r--components/filesystem/directory_impl.cc38
-rw-r--r--components/filesystem/directory_impl.h5
-rw-r--r--components/filesystem/directory_impl_unittest.cc3
-rw-r--r--components/filesystem/file_impl.cc10
-rw-r--r--components/filesystem/file_impl.h1
-rw-r--r--components/filesystem/file_impl_unittest.cc3
-rw-r--r--components/filesystem/files_test_base.cc3
-rw-r--r--components/filesystem/files_test_base.h67
-rw-r--r--components/filesystem/public/interfaces/directory.mojom10
-rw-r--r--components/filesystem/public/interfaces/file.mojom3
-rw-r--r--components/filesystem/public/interfaces/types.mojom2
-rw-r--r--mandoline/BUILD.gn1
-rw-r--r--mojo/services/network/BUILD.gn1
-rw-r--r--mojo/tools/data/apptests8
-rw-r--r--mojo/util/capture_util.h36
-rw-r--r--sql/BUILD.gn14
-rw-r--r--sql/connection.cc51
-rw-r--r--sql/connection_unittest.cc86
-rw-r--r--sql/correct_sql_test_base.h27
-rw-r--r--sql/meta_table_unittest.cc17
-rw-r--r--sql/mojo/BUILD.gn62
-rw-r--r--sql/mojo/DEPS6
-rw-r--r--sql/mojo/mojo_vfs.cc413
-rw-r--r--sql/mojo/mojo_vfs.h45
-rw-r--r--sql/mojo/sql_test_base.cc156
-rw-r--r--sql/mojo/sql_test_base.h85
-rw-r--r--sql/mojo/vfs_unittest.cc317
-rw-r--r--sql/recovery_unittest.cc28
-rw-r--r--sql/sql.gyp2
-rw-r--r--sql/sqlite_features_unittest.cc17
-rw-r--r--sql/statement_unittest.cc17
-rw-r--r--sql/test/sql_test_base.cc66
-rw-r--r--sql/test/sql_test_base.h78
-rw-r--r--sql/test/test_helpers.cc27
-rw-r--r--sql/test/test_helpers.h5
-rw-r--r--sql/transaction_unittest.cc15
37 files changed, 1501 insertions, 225 deletions
diff --git a/components/filesystem/DEPS b/components/filesystem/DEPS
index 40d8075..b3412d0 100644
--- a/components/filesystem/DEPS
+++ b/components/filesystem/DEPS
@@ -2,4 +2,5 @@ include_rules = [
"+mojo/application",
"+mojo/platform_handle",
"+mojo/public",
+ "+mojo/util",
]
diff --git a/components/filesystem/directory_impl.cc b/components/filesystem/directory_impl.cc
index 4a6caac..3d63f0e 100644
--- a/components/filesystem/directory_impl.cc
+++ b/components/filesystem/directory_impl.cc
@@ -171,4 +171,42 @@ void DirectoryImpl::Delete(const mojo::String& raw_path,
callback.Run(FILE_ERROR_OK);
}
+void DirectoryImpl::Exists(const mojo::String& raw_path,
+ const ExistsCallback& callback) {
+ base::FilePath path;
+ if (FileError error = ValidatePath(raw_path, directory_path_, &path)) {
+ callback.Run(error, false);
+ return;
+ }
+
+ bool exists = base::PathExists(path);
+ callback.Run(FILE_ERROR_OK, exists);
+}
+
+void DirectoryImpl::IsWritable(const mojo::String& raw_path,
+ const IsWritableCallback& callback) {
+ base::FilePath path;
+ if (FileError error = ValidatePath(raw_path, directory_path_, &path)) {
+ callback.Run(error, false);
+ return;
+ }
+
+ callback.Run(FILE_ERROR_OK, base::PathIsWritable(path));
+}
+
+void DirectoryImpl::Flush(const FlushCallback& callback) {
+ base::File file(directory_path_, base::File::FLAG_READ);
+ if (!file.IsValid()) {
+ callback.Run(FILE_ERROR_FAILED);
+ return;
+ }
+
+ if (!file.Flush()) {
+ callback.Run(FILE_ERROR_FAILED);
+ return;
+ }
+
+ callback.Run(FILE_ERROR_OK);
+}
+
} // namespace filesystem
diff --git a/components/filesystem/directory_impl.h b/components/filesystem/directory_impl.h
index 17c752f..ed19d00 100644
--- a/components/filesystem/directory_impl.h
+++ b/components/filesystem/directory_impl.h
@@ -44,6 +44,11 @@ class DirectoryImpl : public Directory {
void Delete(const mojo::String& path,
uint32_t delete_flags,
const DeleteCallback& callback) override;
+ void Exists(const mojo::String& path,
+ const ExistsCallback& callback) override;
+ void IsWritable(const mojo::String& path,
+ const IsWritableCallback& callback) override;
+ void Flush(const FlushCallback& callback) override;
private:
mojo::StrongBinding<Directory> binding_;
diff --git a/components/filesystem/directory_impl_unittest.cc b/components/filesystem/directory_impl_unittest.cc
index a5759b5..983302f 100644
--- a/components/filesystem/directory_impl_unittest.cc
+++ b/components/filesystem/directory_impl_unittest.cc
@@ -6,6 +6,9 @@
#include <string>
#include "components/filesystem/files_test_base.h"
+#include "mojo/util/capture_util.h"
+
+using mojo::Capture;
namespace filesystem {
namespace {
diff --git a/components/filesystem/file_impl.cc b/components/filesystem/file_impl.cc
index f378432..2539498 100644
--- a/components/filesystem/file_impl.cc
+++ b/components/filesystem/file_impl.cc
@@ -260,6 +260,16 @@ void FileImpl::Dup(mojo::InterfaceRequest<File> file,
callback.Run(FILE_ERROR_OK);
}
+void FileImpl::Flush(const FlushCallback& callback) {
+ if (!file_.IsValid()) {
+ callback.Run(GetError(file_));
+ return;
+ }
+
+ bool ret = file_.Flush();
+ callback.Run(ret ? FILE_ERROR_OK : FILE_ERROR_FAILED);
+}
+
void FileImpl::AsHandle(const AsHandleCallback& callback) {
if (!file_.IsValid()) {
callback.Run(GetError(file_), ScopedHandle());
diff --git a/components/filesystem/file_impl.h b/components/filesystem/file_impl.h
index 469e89d..0ca3501 100644
--- a/components/filesystem/file_impl.h
+++ b/components/filesystem/file_impl.h
@@ -47,6 +47,7 @@ class FileImpl : public File {
const TouchCallback& callback) override;
void Dup(mojo::InterfaceRequest<File> file,
const DupCallback& callback) override;
+ void Flush(const FlushCallback& callback) override;
void AsHandle(const AsHandleCallback& callback) override;
private:
diff --git a/components/filesystem/file_impl_unittest.cc b/components/filesystem/file_impl_unittest.cc
index c55e516..86ec3ed 100644
--- a/components/filesystem/file_impl_unittest.cc
+++ b/components/filesystem/file_impl_unittest.cc
@@ -9,6 +9,9 @@
#include "mojo/platform_handle/platform_handle_functions.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "mojo/public/cpp/bindings/type_converter.h"
+#include "mojo/util/capture_util.h"
+
+using mojo::Capture;
namespace filesystem {
namespace {
diff --git a/components/filesystem/files_test_base.cc b/components/filesystem/files_test_base.cc
index 5bd477a..47f7d61 100644
--- a/components/filesystem/files_test_base.cc
+++ b/components/filesystem/files_test_base.cc
@@ -7,6 +7,7 @@
#include "components/filesystem/public/interfaces/directory.mojom.h"
#include "components/filesystem/public/interfaces/types.mojom.h"
#include "mojo/application/public/cpp/application_impl.h"
+#include "mojo/util/capture_util.h"
namespace filesystem {
@@ -26,7 +27,7 @@ void FilesTestBase::SetUp() {
void FilesTestBase::GetTemporaryRoot(DirectoryPtr* directory) {
FileError error = FILE_ERROR_FAILED;
- files()->OpenFileSystem("temp", GetProxy(directory), Capture(&error));
+ files()->OpenFileSystem("temp", GetProxy(directory), mojo::Capture(&error));
ASSERT_TRUE(files().WaitForIncomingResponse());
ASSERT_EQ(FILE_ERROR_OK, error);
}
diff --git a/components/filesystem/files_test_base.h b/components/filesystem/files_test_base.h
index 36e15d5..bc9a9a3 100644
--- a/components/filesystem/files_test_base.h
+++ b/components/filesystem/files_test_base.h
@@ -11,73 +11,6 @@
namespace filesystem {
-// TODO(vtl): Stuff copied from mojo/public/cpp/bindings/lib/template_util.h.
-template <class T, T v>
-struct IntegralConstant {
- static const T value = v;
-};
-
-template <class T, T v>
-const T IntegralConstant<T, v>::value;
-
-typedef IntegralConstant<bool, true> TrueType;
-typedef IntegralConstant<bool, false> FalseType;
-
-template <class T>
-struct IsConst : FalseType {};
-template <class T>
-struct IsConst<const T> : TrueType {};
-
-template <bool B, typename T = void>
-struct EnableIf {};
-
-template <typename T>
-struct EnableIf<true, T> {
- typedef T type;
-};
-
-typedef char YesType;
-
-struct NoType {
- YesType dummy[2];
-};
-
-template <typename T>
-struct IsMoveOnlyType {
- template <typename U>
- static YesType Test(const typename U::MoveOnlyTypeForCPP03*);
-
- template <typename U>
- static NoType Test(...);
-
- static const bool value =
- sizeof(Test<T>(0)) == sizeof(YesType) && !IsConst<T>::value;
-};
-
-template <typename T>
-typename EnableIf<!IsMoveOnlyType<T>::value, T>::type& Forward(T& t) {
- return t;
-}
-
-template <typename T>
-typename EnableIf<IsMoveOnlyType<T>::value, T>::type Forward(T& t) {
- return t.Pass();
-}
-// TODO(vtl): (End of stuff copied from template_util.h.)
-
-template <typename T1>
-mojo::Callback<void(T1)> Capture(T1* t1) {
- return [t1](T1 got_t1) { *t1 = Forward(got_t1); };
-}
-
-template <typename T1, typename T2>
-mojo::Callback<void(T1, T2)> Capture(T1* t1, T2* t2) {
- return [t1, t2](T1 got_t1, T2 got_t2) {
- *t1 = Forward(got_t1);
- *t2 = Forward(got_t2);
- };
-}
-
class FilesTestBase : public mojo::test::ApplicationTestBase {
public:
FilesTestBase();
diff --git a/components/filesystem/public/interfaces/directory.mojom b/components/filesystem/public/interfaces/directory.mojom
index 5d3a1db..bd5aba4 100644
--- a/components/filesystem/public/interfaces/directory.mojom
+++ b/components/filesystem/public/interfaces/directory.mojom
@@ -42,6 +42,16 @@ interface Directory {
// |kDeleteFlag...| for details).
Delete(string path, uint32 delete_flags) => (FileError error);
+ // Returns true if |path| exists.
+ Exists(string path) => (FileError error, bool exists);
+
+ // Returns true if |path| is writable.
+ IsWritable(string path) => (FileError error, bool is_writable);
+
+ // Opens a file descriptor on this directory and calls
+ // fsync()/FlushFileBuffers().
+ Flush() => (FileError error);
+
// TODO(vtl): directory "streaming"?
// TODO(vtl): "make root" (i.e., prevent cd-ing, etc., to parent); note that
// this would require a much more complicated implementation (e.g., it needs
diff --git a/components/filesystem/public/interfaces/file.mojom b/components/filesystem/public/interfaces/file.mojom
index 306b9f7..d64c0f1 100644
--- a/components/filesystem/public/interfaces/file.mojom
+++ b/components/filesystem/public/interfaces/file.mojom
@@ -59,6 +59,9 @@ interface File {
// |open_flags| argument) as well as file position.
Dup(File& file) => (FileError error);
+ // Syncs data to disk.
+ Flush() => (FileError error);
+
// Returns a handle to the file for raw access.
AsHandle() => (FileError error, handle file_handle);
};
diff --git a/components/filesystem/public/interfaces/types.mojom b/components/filesystem/public/interfaces/types.mojom
index 97988f6..6a2ba49b 100644
--- a/components/filesystem/public/interfaces/types.mojom
+++ b/components/filesystem/public/interfaces/types.mojom
@@ -77,6 +77,8 @@ const uint32 kFlagOpenTruncated = 0x10;
const uint32 kFlagRead = 0x20;
const uint32 kFlagWrite = 0x40;
const uint32 kFlagAppend = 0x80;
+// Deletes the file when closed.
+const uint32 kDeleteOnClose = 0x2000;
// File types.
//
diff --git a/mandoline/BUILD.gn b/mandoline/BUILD.gn
index d76d752..7a35fce 100644
--- a/mandoline/BUILD.gn
+++ b/mandoline/BUILD.gn
@@ -35,6 +35,7 @@ group("tests") {
"//components/resource_provider:apptests",
"//components/resource_provider:resource_provider_unittests",
"//components/view_manager:tests",
+ "//sql/mojo:apptests",
"//mandoline/tab:mandoline_frame_apptests",
"//media/mojo/services:media_apptests",
]
diff --git a/mojo/services/network/BUILD.gn b/mojo/services/network/BUILD.gn
index 02ef3f0..2296d44 100644
--- a/mojo/services/network/BUILD.gn
+++ b/mojo/services/network/BUILD.gn
@@ -92,6 +92,7 @@ source_set("lib") {
"//net",
"//net:http_server",
"//url",
+ "//sql/mojo",
]
}
diff --git a/mojo/tools/data/apptests b/mojo/tools/data/apptests
index 50b47e4..1b8bee8 100644
--- a/mojo/tools/data/apptests
+++ b/mojo/tools/data/apptests
@@ -55,13 +55,17 @@ if config.target_os != config.OS_ANDROID:
"args": ["--use-test-config"]
},
{
+ "test": "mojo:mandoline_frame_apptests",
+ "type": "gtest_isolated",
+ "args": ["--use-headless-config"]
+ },
+ {
"test": "mojo:resource_provider_apptests",
"type": "gtest_isolated",
},
{
- "test": "mojo:mandoline_frame_apptests",
+ "test": "mojo:sql_apptests",
"type": "gtest_isolated",
- "args": ["--use-headless-config"]
},
# TODO(xhwang): Fix and enable the media_apptests. http://crbug.com/501417
#{
diff --git a/mojo/util/capture_util.h b/mojo/util/capture_util.h
new file mode 100644
index 0000000..4b89eb7
--- /dev/null
+++ b/mojo/util/capture_util.h
@@ -0,0 +1,36 @@
+// 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 MOJO_UTIL_CAPTURE_UTIL_H_
+#define MOJO_UTIL_CAPTURE_UTIL_H_
+
+#include "mojo/public/cpp/bindings/lib/template_util.h"
+
+namespace mojo {
+
+template <typename T1>
+mojo::Callback<void(T1)> Capture(T1* t1) {
+ return [t1](T1 got_t1) { *t1 = mojo::internal::Forward(got_t1); };
+}
+
+template <typename T1, typename T2>
+mojo::Callback<void(T1, T2)> Capture(T1* t1, T2* t2) {
+ return [t1, t2](T1 got_t1, T2 got_t2) {
+ *t1 = mojo::internal::Forward(got_t1);
+ *t2 = mojo::internal::Forward(got_t2);
+ };
+}
+
+template <typename T1, typename T2, typename T3>
+mojo::Callback<void(T1, T2, T3)> Capture(T1* t1, T2* t2, T3* t3) {
+ return [t1, t2, t3](T1 got_t1, T2 got_t2, T3 got_t3) {
+ *t1 = mojo::internal::Forward(got_t1);
+ *t2 = mojo::internal::Forward(got_t2);
+ *t3 = mojo::internal::Forward(got_t3);
+ };
+}
+
+} // namespace mojo
+
+#endif // MOJO_UTIL_CAPTURE_UTIL_H_
diff --git a/sql/BUILD.gn b/sql/BUILD.gn
index f3c44be..fdf8c7b 100644
--- a/sql/BUILD.gn
+++ b/sql/BUILD.gn
@@ -54,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",
@@ -64,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",
@@ -74,6 +87,7 @@ test("sql_unittests") {
deps = [
":sql",
+ ":redirection_header",
":test_support",
"//base/allocator",
"//base/test:test_support",
diff --git a/sql/connection.cc b/sql/connection.cc
index c196073..b17457b 100644
--- a/sql/connection.cc
+++ b/sql/connection.cc
@@ -151,6 +151,14 @@ base::HistogramBase* GetMediumTimeHistogram(const std::string& name) {
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 {
@@ -312,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() {
@@ -624,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);
+
+ // 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 !base::PathExists(journal_path) &&
- !base::PathExists(wal_path) &&
- !base::PathExists(path);
+ return !journal_exists && !wal_exists && !path_exists;
}
bool Connection::BeginTransaction() {
diff --git a/sql/connection_unittest.cc b/sql/connection_unittest.cc
index a09721a..c7d9080 100644
--- a/sql/connection_unittest.cc
+++ b/sql/connection_unittest.cc
@@ -10,6 +10,7 @@
#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"
@@ -207,33 +208,21 @@ class ScopedUmaskSetter {
};
#endif
-class SQLConnectionTest : public testing::Test {
+class SQLConnectionTest : public sql::SQLTestBase {
public:
void SetUp() override {
// Any macro histograms which fire before the recorder is initialized cannot
// be tested. So this needs to be ahead of Open().
base::StatisticsRecorder::Initialize();
- ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
- db_path_ = temp_dir_.path().AppendASCII("SQLConnectionTest.db");
- ASSERT_TRUE(db_.Open(db_path_));
+ SQLTestBase::SetUp();
}
- void TearDown() override { db_.Close(); }
-
- sql::Connection& db() { return db_; }
- const base::FilePath& db_path() { return db_path_; }
-
// 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) {
@@ -366,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;
@@ -534,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));
@@ -568,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.
@@ -576,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());
@@ -592,16 +579,10 @@ TEST_F(SQLConnectionTest, RazeEmptyDB) {
TEST_F(SQLConnectionTest, RazeNOTADB) {
db().Close();
sql::Connection::Delete(db_path());
- ASSERT_FALSE(base::PathExists(db_path()));
+ ASSERT_FALSE(GetPathExists(db_path()));
- {
- base::ScopedFILE file(base::OpenFile(db_path(), "wb"));
- ASSERT_TRUE(file.get() != NULL);
-
- const char* kJunk = "This is the hour of our discontent.";
- fputs(kJunk, file.get());
- }
- ASSERT_TRUE(base::PathExists(db_path()));
+ 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.
@@ -635,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
@@ -672,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.
@@ -817,15 +791,17 @@ TEST_F(SQLConnectionTest, Delete) {
// Should have both a main database file and a journal file because
// 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) {
@@ -835,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()));
@@ -849,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);
@@ -863,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);
@@ -876,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);
@@ -990,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;
@@ -1012,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;
diff --git a/sql/correct_sql_test_base.h b/sql/correct_sql_test_base.h
new file mode 100644
index 0000000..7056dea
--- /dev/null
+++ b/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/sql/meta_table_unittest.cc b/sql/meta_table_unittest.cc
index 1412392..13d0b5d 100644
--- a/sql/meta_table_unittest.cc
+++ b/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/sql/mojo/BUILD.gn b/sql/mojo/BUILD.gn
new file mode 100644
index 0000000..18feaec
--- /dev/null
+++ b/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/sql/mojo/DEPS b/sql/mojo/DEPS
new file mode 100644
index 0000000..be63411
--- /dev/null
+++ b/sql/mojo/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+components/filesystem",
+ "+mojo/application",
+ "+mojo/public",
+ "+mojo/util",
+]
diff --git a/sql/mojo/mojo_vfs.cc b/sql/mojo/mojo_vfs.cc
new file mode 100644
index 0000000..6e38af9
--- /dev/null
+++ b/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/sql/mojo/mojo_vfs.h b/sql/mojo/mojo_vfs.h
new file mode 100644
index 0000000..dc83593
--- /dev/null
+++ b/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/sql/mojo/sql_test_base.cc b/sql/mojo/sql_test_base.cc
new file mode 100644
index 0000000..57645dd
--- /dev/null
+++ b/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/sql/mojo/sql_test_base.h b/sql/mojo/sql_test_base.h
new file mode 100644
index 0000000..f2bfb5d4
--- /dev/null
+++ b/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/sql/mojo/vfs_unittest.cc b/sql/mojo/vfs_unittest.cc
new file mode 100644
index 0000000..8ca7c5c
--- /dev/null
+++ b/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/sql/recovery_unittest.cc b/sql/recovery_unittest.cc
index 78a1478..1f930cb 100644
--- a/sql/recovery_unittest.cc
+++ b/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/sql/sql.gyp b/sql/sql.gyp
index 5345791..d983a45 100644
--- a/sql/sql.gyp
+++ b/sql/sql.gyp
@@ -96,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',
diff --git a/sql/sqlite_features_unittest.cc b/sql/sqlite_features_unittest.cc
index 2b95bb8..20e002d 100644
--- a/sql/sqlite_features_unittest.cc
+++ b/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/sql/statement_unittest.cc b/sql/statement_unittest.cc
index 38f1778..1565b3e 100644
--- a/sql/statement_unittest.cc
+++ b/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/sql/test/sql_test_base.cc b/sql/test/sql_test_base.cc
new file mode 100644
index 0000000..bdd427f
--- /dev/null
+++ b/sql/test/sql_test_base.cc
@@ -0,0 +1,66 @@
+// 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/test/sql_test_base.h"
+
+#include "base/files/file_util.h"
+#include "sql/test/test_helpers.h"
+
+namespace sql {
+
+SQLTestBase::SQLTestBase() {
+}
+
+SQLTestBase::~SQLTestBase() {
+}
+
+base::FilePath SQLTestBase::db_path() {
+ return temp_dir_.path().AppendASCII("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) {
+ return base::PathExists(path);
+}
+
+bool SQLTestBase::CorruptSizeInHeaderOfDB() {
+ return sql::test::CorruptSizeInHeader(db_path());
+}
+
+void SQLTestBase::WriteJunkToDatabase(WriteJunkType type) {
+ base::ScopedFILE file(base::OpenFile(
+ db_path(),
+ type == TYPE_OVERWRITE_AND_TRUNCATE ? "wb" : "rb+"));
+ ASSERT_TRUE(file.get() != NULL);
+ ASSERT_EQ(0, fseek(file.get(), 0, SEEK_SET));
+
+ const char* kJunk = "Now is the winter of our discontent.";
+ fputs(kJunk, file.get());
+}
+
+void SQLTestBase::TruncateDatabase() {
+ 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()));
+}
+
+void SQLTestBase::SetUp() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ ASSERT_TRUE(db_.Open(db_path()));
+}
+
+void SQLTestBase::TearDown() {
+ db_.Close();
+}
+
+} // namespace sql
diff --git a/sql/test/sql_test_base.h b/sql/test/sql_test_base.h
new file mode 100644
index 0000000..ebe9048
--- /dev/null
+++ b/sql/test/sql_test_base.h
@@ -0,0 +1,78 @@
+// 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_TEST_SQL_TEST_BASE_H_
+#define SQL_TEST_SQL_TEST_BASE_H_
+
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "sql/connection.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace sql {
+
+class Connection;
+
+// 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 testing::Test {
+ 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;
+
+ private:
+ base::ScopedTempDir temp_dir_;
+ sql::Connection db_;
+
+ DISALLOW_COPY_AND_ASSIGN(SQLTestBase);
+};
+
+} // namespace sql
+
+#endif // SQL_TEST_SQL_TEST_BASE_H_
diff --git a/sql/test/test_helpers.cc b/sql/test/test_helpers.cc
index 361b336..16f2a7e 100644
--- a/sql/test/test_helpers.cc
+++ b/sql/test/test_helpers.cc
@@ -69,10 +69,6 @@ namespace test {
bool CorruptSizeInHeader(const base::FilePath& db_path) {
// See http://www.sqlite.org/fileformat.html#database_header
const size_t kHeaderSize = 100;
- const size_t kPageSizeOffset = 16;
- const size_t kFileChangeCountOffset = 24;
- const size_t kPageCountOffset = 28;
- const size_t kVersionValidForOffset = 92; // duplicate kFileChangeCountOffset
unsigned char header[kHeaderSize];
@@ -89,6 +85,22 @@ bool CorruptSizeInHeader(const base::FilePath& db_path) {
if (!base::GetFileSize(db_path, &db_size))
return false;
+ CorruptSizeInHeaderMemory(header, db_size);
+
+ if (0 != fseek(file.get(), 0, SEEK_SET))
+ return false;
+ if (1u != fwrite(header, sizeof(header), 1, file.get()))
+ return false;
+
+ return true;
+}
+
+void CorruptSizeInHeaderMemory(unsigned char* header, int64_t db_size) {
+ const size_t kPageSizeOffset = 16;
+ const size_t kFileChangeCountOffset = 24;
+ const size_t kPageCountOffset = 28;
+ const size_t kVersionValidForOffset = 92; // duplicate kFileChangeCountOffset
+
const unsigned page_size = ReadBigEndian(header + kPageSizeOffset, 2);
// One larger than the expected size.
@@ -101,13 +113,6 @@ bool CorruptSizeInHeader(const base::FilePath& db_path) {
unsigned change_count = ReadBigEndian(header + kFileChangeCountOffset, 4);
WriteBigEndian(change_count + 1, header + kFileChangeCountOffset, 4);
WriteBigEndian(change_count + 1, header + kVersionValidForOffset, 4);
-
- if (0 != fseek(file.get(), 0, SEEK_SET))
- return false;
- if (1u != fwrite(header, sizeof(header), 1, file.get()))
- return false;
-
- return true;
}
bool CorruptTableOrIndex(const base::FilePath& db_path,
diff --git a/sql/test/test_helpers.h b/sql/test/test_helpers.h
index e93b207..b2cecb0 100644
--- a/sql/test/test_helpers.h
+++ b/sql/test/test_helpers.h
@@ -33,6 +33,11 @@ namespace test {
// Returns false if any error occurs accessing the file.
bool CorruptSizeInHeader(const base::FilePath& db_path) WARN_UNUSED_RESULT;
+// Common implementation of CorruptSizeInHeader() which operates on loaded
+// memory. Shared between CorruptSizeInHeader() and the the mojo proxy testing
+// code.
+void CorruptSizeInHeaderMemory(unsigned char* header, int64_t db_size);
+
// Frequently corruption is a result of failure to atomically update
// pages in different structures. For instance, if an index update
// takes effect but the corresponding table update does not. This
diff --git a/sql/transaction_unittest.cc b/sql/transaction_unittest.cc
index 83d4125..179adcf 100644
--- a/sql/transaction_unittest.cc
+++ b/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) {