blob: e8eb9950c99a0946f574fea17fa61db50e27fb82 [file] [log] [blame]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sql/test/test_vfs.h"
#include <stddef.h>
#include <memory>
#include <type_traits>
#include <utility>
#include "base/check.h"
#include "base/check_deref.h"
#include "base/check_op.h"
#include "base/memory/raw_ref.h"
#include "sql/initialization.h"
#include "third_party/sqlite/sqlite3.h"
namespace sql::test {
namespace {
// Functor for deleting memory allocated by SQLite.
struct SqliteMemoryDeleter {
void operator()(void* ptr) const { sqlite3_free(ptr); }
};
// An `std::unique_ptr` that deletes objects using `sqlite3_free`.
template <typename T>
using UniqueSqlitePtr = std::unique_ptr<T, SqliteMemoryDeleter>;
// A file created by `TestVfs`.
//
// `TestVfsFile` is a C-style child class of `sqlite3_file` (i.e. it's safe to
// cast `TestVfsFile*` to `sqlite3_file*`), providing SQLite with callbacks to
// invoke to do file IOs. These callbacks simply forward the calls to the
// `TestVfs` that created this file, which then forward the call back to the
// original `sqlite3_io_methods` of the file that this `TestVfsFile` wraps.
// `TestVfs` uses virtual methods, allowing unit tests to customize all VFS and
// file IO operations in a central place.
class TestVfsFile {
public:
// Constructs an instance of `TestVfsFile` in the memory reserved in `file`.
// This memory is pre-allocated by SQLite when calling `xOpen`.
static void InplaceCreate(sqlite3_file* file,
UniqueSqlitePtr<sqlite3_file> wrapped_file,
TestVfs& test_vfs) {
TestVfsFile* test_vfs_file = CastFrom(file);
new (test_vfs_file) TestVfsFile(std::move(wrapped_file), test_vfs);
}
// Destroys the `TestVfsFile` object in `file`.
static void Destroy(sqlite3_file* file) {
TestVfsFile* test_vfs_file = CastFrom(file);
test_vfs_file->~TestVfsFile();
}
// Casts from `sqlite3_file*` to `TestVfsFile*`. This is safe because
// `TestVfsFile` has a standard memory layout and its first member is an
// instance of `sqlite3_file`.
static TestVfsFile* CastFrom(sqlite3_file* file) {
static_assert(std::is_standard_layout_v<TestVfsFile>,
"Needed for the reinterpret_cast below");
static_assert(offsetof(TestVfsFile, file_) == 0,
"file_ must be the first member of the class");
return reinterpret_cast<TestVfsFile*>(file);
}
sqlite3_file* wrapped_file() const { return wrapped_file_.get(); }
TestVfs& test_vfs() const { return test_vfs_.get(); }
private:
TestVfsFile(UniqueSqlitePtr<sqlite3_file> wrapped_file, TestVfs& test_vfs)
: file_({.pMethods = GetSqliteIoMethods()}),
wrapped_file_(std::move(wrapped_file)),
test_vfs_(test_vfs) {}
// Returns a pointer to a static `sqlite3_io_methods` instance, telling SQLite
// what callback to call for file IOs.
static const sqlite3_io_methods* GetSqliteIoMethods();
// C-style base class, `file_` must be the first member in the class.
const sqlite3_file file_;
// The `sqlite3_file` from the original VFS that this `TestVfsFile` wraps.
const UniqueSqlitePtr<sqlite3_file> wrapped_file_;
// The TestVfs that created this TestVfsFile.
const raw_ref<TestVfs> test_vfs_;
};
// Returns the `TestVfs` instance associated with the given SQLite object.
TestVfs& GetTestVfs(sqlite3_vfs* vfs) {
return CHECK_DEREF(reinterpret_cast<TestVfs*>(vfs->pAppData));
}
TestVfs& GetTestVfs(sqlite3_file* file) {
return TestVfsFile::CastFrom(file)->test_vfs();
}
// Defines a C-compatible function `Callback` forwarding the call to the
// `TestVfs` method pointer `callback`.
template <auto callback>
struct ForwardTo;
template <typename Return,
typename SqliteObject,
typename... Args,
Return (TestVfs::*callback)(SqliteObject* obj, Args... args)>
struct ForwardTo<callback> {
static Return Callback(SqliteObject* obj, Args... args) {
return (GetTestVfs(obj).*callback)(obj, args...);
}
};
const sqlite3_io_methods* TestVfsFile::GetSqliteIoMethods() {
static const sqlite3_io_methods kIoMethods = {
// VFS IO API entry points are listed at:
// https://www.sqlite.org/c3ref/io_methods.html
.iVersion = 3,
.xClose = ForwardTo<&TestVfs::Close>::Callback,
.xRead = ForwardTo<&TestVfs::Read>::Callback,
.xWrite = ForwardTo<&TestVfs::Write>::Callback,
.xTruncate = ForwardTo<&TestVfs::Truncate>::Callback,
.xSync = ForwardTo<&TestVfs::Sync>::Callback,
.xFileSize = ForwardTo<&TestVfs::FileSize>::Callback,
.xLock = ForwardTo<&TestVfs::Lock>::Callback,
.xUnlock = ForwardTo<&TestVfs::Unlock>::Callback,
.xCheckReservedLock = ForwardTo<&TestVfs::CheckReservedLock>::Callback,
.xFileControl = ForwardTo<&TestVfs::FileControl>::Callback,
.xSectorSize = ForwardTo<&TestVfs::SectorSize>::Callback,
.xDeviceCharacteristics =
ForwardTo<&TestVfs::DeviceCharacteristics>::Callback,
.xShmMap = ForwardTo<&TestVfs::ShmMap>::Callback,
.xShmLock = ForwardTo<&TestVfs::ShmLock>::Callback,
.xShmBarrier = ForwardTo<&TestVfs::ShmBarrier>::Callback,
.xShmUnmap = ForwardTo<&TestVfs::ShmUnmap>::Callback,
.xFetch = ForwardTo<&TestVfs::Fetch>::Callback,
.xUnfetch = ForwardTo<&TestVfs::Unfetch>::Callback,
};
return &kIoMethods;
}
} // namespace
TestVfs::TestVfs()
: vfs_({
// VFS API entry points are listed at:
// https://www.sqlite.org/c3ref/vfs.html
.iVersion = 3,
.szOsFile = sizeof(TestVfsFile),
.mxPathname = 512,
.pNext = nullptr,
.zName = TestVfs::kName,
.pAppData = this,
.xOpen = ForwardTo<&TestVfs::Open>::Callback,
.xDelete = ForwardTo<&TestVfs::Delete>::Callback,
.xAccess = ForwardTo<&TestVfs::Access>::Callback,
.xFullPathname = ForwardTo<&TestVfs::FullPathname>::Callback,
.xDlOpen = nullptr,
.xDlError = nullptr,
.xDlSym = nullptr,
.xDlClose = nullptr,
.xRandomness = ForwardTo<&TestVfs::Randomness>::Callback,
.xSleep = ForwardTo<&TestVfs::Sleep>::Callback,
.xCurrentTime = nullptr,
.xGetLastError = ForwardTo<&TestVfs::GetLastError>::Callback,
.xCurrentTimeInt64 = ForwardTo<&TestVfs::CurrentTimeInt64>::Callback,
.xSetSystemCall = nullptr,
.xGetSystemCall = nullptr,
.xNextSystemCall = nullptr,
}) {
// Register `VfsWrapper` first, so that `TestVfs` wraps over `VfsWrapper`.
// `VfsWrapper` is needed for the VFS to work on all platforms (e.g. Fuchsia).
// `VfsWrapper` cannot wrap `TestVfs` because it's designed to wrap the
// platform's default VFS directly (e.g. for Fuchsia, it explicitly wraps
// "unix-none" instead of the default VFS).
EnsureSqliteInitialized();
CHECK_EQ(sqlite3_vfs_find(TestVfs::kName), nullptr)
<< "Two instances of TestVfs can't be live at the same time.";
original_vfs_ = sqlite3_vfs_find(nullptr); // Lookup default VFS.
CHECK(original_vfs_) << "No default VFS found.";
int rc = sqlite3_vfs_register(&vfs_, /*makeDflt=*/true);
CHECK_EQ(rc, SQLITE_OK);
}
TestVfs::~TestVfs() {
CHECK_EQ(sqlite3_vfs_unregister(&vfs_), SQLITE_OK);
// Restore the original default because `sqlite3_vfs_unregister` choses an
// arbitrary VFS as default.
CHECK_EQ(sqlite3_vfs_register(original_vfs_, /*makeDflt=*/true), SQLITE_OK);
}
int TestVfs::Open(sqlite3_vfs* vfs,
const char* full_path,
sqlite3_file* result_file,
int requested_flags,
int* granted_flags) {
UniqueSqlitePtr<sqlite3_file> wrapped_file(
reinterpret_cast<sqlite3_file*>(sqlite3_malloc(original_vfs_->szOsFile)));
if (!wrapped_file) {
return SQLITE_NOMEM;
}
int rc = original_vfs_->xOpen(original_vfs_, full_path, wrapped_file.get(),
requested_flags, granted_flags);
if (rc != SQLITE_OK) {
return rc;
}
TestVfsFile::InplaceCreate(result_file, std::move(wrapped_file), *this);
return rc;
}
int TestVfs::Delete(sqlite3_vfs* vfs, const char* full_path, int sync_dir) {
return original_vfs_->xDelete(original_vfs_, full_path, sync_dir);
}
int TestVfs::Access(sqlite3_vfs* vfs,
const char* full_path,
int flags,
int* result) {
return original_vfs_->xAccess(original_vfs_, full_path, flags, result);
}
int TestVfs::FullPathname(sqlite3_vfs* vfs,
const char* file_path,
int result_size,
char* result) {
return original_vfs_->xFullPathname(original_vfs_, file_path, result_size,
result);
}
int TestVfs::Randomness(sqlite3_vfs* vfs, int result_size, char* result) {
return original_vfs_->xRandomness(original_vfs_, result_size, result);
}
int TestVfs::Sleep(sqlite3_vfs* vfs, int microseconds) {
return original_vfs_->xSleep(original_vfs_, microseconds);
}
int TestVfs::GetLastError(sqlite3_vfs* vfs, int message_size, char* message) {
return original_vfs_->xGetLastError(original_vfs_, message_size, message);
}
int TestVfs::CurrentTimeInt64(sqlite3_vfs* vfs, sqlite3_int64* result_ms) {
return original_vfs_->xCurrentTimeInt64(original_vfs_, result_ms);
}
int TestVfs::Close(sqlite3_file* file) {
sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file();
int result = wrapped_file->pMethods->xClose(wrapped_file);
TestVfsFile::Destroy(file);
return result;
}
int TestVfs::Read(sqlite3_file* file,
void* buffer,
int size,
sqlite3_int64 offset) {
sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file();
return wrapped_file->pMethods->xRead(wrapped_file, buffer, size, offset);
}
int TestVfs::Write(sqlite3_file* file,
const void* buffer,
int size,
sqlite3_int64 offset) {
sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file();
return wrapped_file->pMethods->xWrite(wrapped_file, buffer, size, offset);
}
int TestVfs::Truncate(sqlite3_file* file, sqlite3_int64 size) {
sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file();
return wrapped_file->pMethods->xTruncate(wrapped_file, size);
}
int TestVfs::Sync(sqlite3_file* file, int flags) {
sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file();
return wrapped_file->pMethods->xSync(wrapped_file, flags);
}
int TestVfs::FileSize(sqlite3_file* file, sqlite3_int64* result_size) {
sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file();
return wrapped_file->pMethods->xFileSize(wrapped_file, result_size);
}
int TestVfs::Lock(sqlite3_file* file, int mode) {
sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file();
return wrapped_file->pMethods->xLock(wrapped_file, mode);
}
int TestVfs::Unlock(sqlite3_file* file, int mode) {
sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file();
return wrapped_file->pMethods->xUnlock(wrapped_file, mode);
}
int TestVfs::CheckReservedLock(sqlite3_file* file, int* has_reserved_lock) {
sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file();
return wrapped_file->pMethods->xCheckReservedLock(wrapped_file,
has_reserved_lock);
}
int TestVfs::FileControl(sqlite3_file* file, int opcode, void* data) {
sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file();
return wrapped_file->pMethods->xFileControl(wrapped_file, opcode, data);
}
int TestVfs::SectorSize(sqlite3_file* file) {
sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file();
return wrapped_file->pMethods->xSectorSize(wrapped_file);
}
int TestVfs::DeviceCharacteristics(sqlite3_file* file) {
sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file();
return wrapped_file->pMethods->xDeviceCharacteristics(wrapped_file);
}
int TestVfs::ShmMap(sqlite3_file* file,
int page_index,
int page_size,
int extend_file_if_needed,
void volatile** result) {
sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file();
return wrapped_file->pMethods->xShmMap(wrapped_file, page_index, page_size,
extend_file_if_needed, result);
}
int TestVfs::ShmLock(sqlite3_file* file, int offset, int size, int flags) {
sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file();
return wrapped_file->pMethods->xShmLock(wrapped_file, offset, size, flags);
}
void TestVfs::ShmBarrier(sqlite3_file* file) {
sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file();
return wrapped_file->pMethods->xShmBarrier(wrapped_file);
}
int TestVfs::ShmUnmap(sqlite3_file* file, int also_delete_file) {
sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file();
return wrapped_file->pMethods->xShmUnmap(wrapped_file, also_delete_file);
}
int TestVfs::Fetch(sqlite3_file* file,
sqlite3_int64 offset,
int size,
void** result) {
sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file();
return wrapped_file->pMethods->xFetch(wrapped_file, offset, size, result);
}
int TestVfs::Unfetch(sqlite3_file* file,
sqlite3_int64 offset,
void* fetch_result) {
sqlite3_file* wrapped_file = TestVfsFile::CastFrom(file)->wrapped_file();
return wrapped_file->pMethods->xUnfetch(wrapped_file, offset, fetch_result);
}
} // namespace sql::test