Merge 'Improve SQLite3 C API tests' from Pekka Enberg

We have tests both in C and Rust, but the latter is never run with
SQLite. Unify them.

Reviewed-by: Pere Diaz Bou <pere-altea@homail.com>

Closes #1481
This commit is contained in:
Pekka Enberg
2025-05-15 09:59:01 +03:00
13 changed files with 213 additions and 396 deletions

View File

@@ -7,21 +7,6 @@ UNAME_S := $(shell uname -s)
# Executable used to execute the compatibility tests.
SQLITE_EXEC ?= scripts/limbo-sqlite3
# Static library to use for SQLite C API compatibility tests.
BASE_SQLITE_LIB = ./target/$(CURRENT_RUST_TARGET)/debug/liblimbo_sqlite3.a
# Static library headers to use for SQLITE C API compatibility tests
BASE_SQLITE_LIB_HEADERS = ./target/$(CURRENT_RUST_TARGET)/debug/include/limbo_sqlite3
# On darwin link core foundation
ifeq ($(UNAME_S),Darwin)
SQLITE_LIB ?= ../../$(BASE_SQLITE_LIB) -framework CoreFoundation
else
SQLITE_LIB ?= ../../$(BASE_SQLITE_LIB)
endif
SQLITE_LIB_HEADERS ?= ../../$(BASE_SQLITE_LIB_HEADERS)
all: check-rust-version check-wasm-target limbo limbo-wasm
.PHONY: all
@@ -89,8 +74,9 @@ test-time:
SQLITE_EXEC=$(SQLITE_EXEC) ./testing/time.test
.PHONY: test-time
test-sqlite3: limbo-c
LIBS="$(SQLITE_LIB)" HEADERS="$(SQLITE_LIB_HEADERS)" make -C sqlite3/tests test
test-sqlite3:
cargo test -p limbo_sqlite3 --test compat
cargo test -p limbo_sqlite3 --test compat --features sqlite3
.PHONY: test-sqlite3
test-json:

View File

@@ -7,10 +7,12 @@ authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
build = "build.rs"
[features]
capi = []
lfs = []
sqlite3 = []
[package.metadata.dist]
dist = true
@@ -25,6 +27,11 @@ libc = "0.2.169"
limbo_core = { path = "../core" }
log = "0.4.22"
[package.metadata.capi.header]
name = "sqlite3.h"
[[test]]
name = "compat"
path = "tests/compat/mod.rs"
[build-dependencies]

3
sqlite3/build.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
println!("cargo:rustc-link-search=native=target/debug");
}

View File

@@ -124,7 +124,7 @@ pub unsafe extern "C" fn sqlite3_open(
SQLITE_OK
}
Err(e) => {
log::error!("error opening database {:?}", e);
log::trace!("error opening database {}: {:?}", filename, e);
SQLITE_CANTOPEN
}
}
@@ -1083,187 +1083,3 @@ pub unsafe extern "C" fn sqlite3_wal_checkpoint_v2(
}
SQLITE_OK
}
#[cfg(test)]
mod tests {
use super::*;
use std::ptr;
#[test]
fn test_libversion() {
unsafe {
let version = sqlite3_libversion();
assert!(!version.is_null());
}
}
#[test]
fn test_libversion_number() {
unsafe {
let version_num = sqlite3_libversion_number();
assert_eq!(version_num, 3042000);
}
}
#[test]
fn test_open_misuse() {
unsafe {
let mut db = ptr::null_mut();
assert_eq!(sqlite3_open(ptr::null(), &mut db), SQLITE_MISUSE);
}
}
#[test]
fn test_open_not_found() {
unsafe {
let mut db = ptr::null_mut();
assert_eq!(
sqlite3_open(b"not-found/local.db\0".as_ptr() as *const i8, &mut db),
SQLITE_CANTOPEN
);
}
}
#[test]
#[ignore]
fn test_open_existing() {
unsafe {
let mut db = ptr::null_mut();
assert_eq!(
sqlite3_open(b"../testing/testing.db\0".as_ptr() as *const i8, &mut db),
SQLITE_OK
);
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[test]
fn test_close() {
unsafe {
assert_eq!(sqlite3_close(ptr::null_mut()), SQLITE_OK);
}
}
#[test]
#[ignore]
fn test_prepare_misuse() {
unsafe {
let mut db = ptr::null_mut();
assert_eq!(
sqlite3_open(b"../testing/testing.db\0".as_ptr() as *const i8, &mut db),
SQLITE_OK
);
let mut stmt = ptr::null_mut();
assert_eq!(
sqlite3_prepare_v2(
db,
b"SELECT 1\0".as_ptr() as *const i8,
-1,
&mut stmt,
ptr::null_mut()
),
SQLITE_OK
);
assert_eq!(sqlite3_finalize(stmt), SQLITE_OK);
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[test]
#[ignore]
fn test_wal_checkpoint() {
unsafe {
// Test with NULL db handle
assert_eq!(
sqlite3_wal_checkpoint(ptr::null_mut(), ptr::null()),
SQLITE_MISUSE
);
// Test with valid db
let mut db = ptr::null_mut();
assert_eq!(
sqlite3_open(b"../testing/testing.db\0".as_ptr() as *const i8, &mut db),
SQLITE_OK
);
assert_eq!(sqlite3_wal_checkpoint(db, ptr::null()), SQLITE_OK);
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[test]
#[ignore]
fn test_wal_checkpoint_v2() {
unsafe {
// Test with NULL db handle
assert_eq!(
sqlite3_wal_checkpoint_v2(
ptr::null_mut(),
ptr::null(),
SQLITE_CHECKPOINT_PASSIVE,
ptr::null_mut(),
ptr::null_mut()
),
SQLITE_MISUSE
);
// Test with valid db
let mut db = ptr::null_mut();
assert_eq!(
sqlite3_open(b"../testing/testing.db\0".as_ptr() as *const i8, &mut db),
SQLITE_OK
);
let mut log_size = 0;
let mut checkpoint_count = 0;
// Test different checkpoint modes
assert_eq!(
sqlite3_wal_checkpoint_v2(
db,
ptr::null(),
SQLITE_CHECKPOINT_PASSIVE,
&mut log_size,
&mut checkpoint_count
),
SQLITE_OK
);
assert_eq!(
sqlite3_wal_checkpoint_v2(
db,
ptr::null(),
SQLITE_CHECKPOINT_FULL,
&mut log_size,
&mut checkpoint_count
),
SQLITE_OK
);
assert_eq!(
sqlite3_wal_checkpoint_v2(
db,
ptr::null(),
SQLITE_CHECKPOINT_RESTART,
&mut log_size,
&mut checkpoint_count
),
SQLITE_OK
);
assert_eq!(
sqlite3_wal_checkpoint_v2(
db,
ptr::null(),
SQLITE_CHECKPOINT_TRUNCATE,
&mut log_size,
&mut checkpoint_count
),
SQLITE_OK
);
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
}

View File

@@ -1,39 +0,0 @@
# Compiler settings
CC = gcc
CFLAGS = -g -Wall -std=c17 -I../include
# Libraries
LIBS ?= -lsqlite3
LIBS += -lm
# Target program
PROGRAM = sqlite3-tests
# Object files
OBJS = main.o \
test-aux.o \
test-close.o \
test-open.o \
test-prepare.o \
test-wal.o
# Default target
all: $(PROGRAM)
# Test target
test: $(PROGRAM)
./$(PROGRAM)
# Compile source files
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# Link program
$(PROGRAM): $(OBJS)
$(CC) -o $@ $(OBJS) $(LIBS)
# Clean target
clean:
rm -f $(PROGRAM) $(OBJS)
.PHONY: all test clean

View File

@@ -1,11 +0,0 @@
#ifndef CHECK_H
#define CHECK_EQUAL(expected, actual) \
do { \
if ((expected) != (actual)) { \
fprintf(stderr, "%s:%d: Assertion failed: %d != %d\n", __FILE__, __LINE__, (expected), (actual)); \
exit(1); \
} \
} while (0)
#endif

198
sqlite3/tests/compat/mod.rs Normal file
View File

@@ -0,0 +1,198 @@
#![allow(non_camel_case_types)]
use std::ptr;
#[repr(C)]
struct sqlite3 {
_private: [u8; 0],
}
#[repr(C)]
struct sqlite3_stmt {
_private: [u8; 0],
}
#[cfg_attr(not(feature = "sqlite3"), link(name = "limbo_sqlite3"))]
#[cfg_attr(feature = "sqlite3", link(name = "sqlite3"))]
extern "C" {
fn sqlite3_libversion() -> *const libc::c_char;
fn sqlite3_libversion_number() -> i32;
fn sqlite3_close(db: *mut sqlite3) -> i32;
fn sqlite3_open(filename: *const libc::c_char, db: *mut *mut sqlite3) -> i32;
fn sqlite3_prepare_v2(
db: *mut sqlite3,
sql: *const libc::c_char,
n_bytes: i32,
stmt: *mut *mut sqlite3_stmt,
tail: *mut *const libc::c_char,
) -> i32;
fn sqlite3_finalize(stmt: *mut sqlite3_stmt) -> i32;
fn sqlite3_wal_checkpoint(db: *mut sqlite3, db_name: *const libc::c_char) -> i32;
fn sqlite3_wal_checkpoint_v2(
db: *mut sqlite3,
db_name: *const libc::c_char,
mode: i32,
log_size: *mut i32,
checkpoint_count: *mut i32,
) -> i32;
}
const SQLITE_OK: i32 = 0;
const SQLITE_CANTOPEN: i32 = 14;
const SQLITE_CHECKPOINT_PASSIVE: i32 = 0;
const SQLITE_CHECKPOINT_FULL: i32 = 1;
const SQLITE_CHECKPOINT_RESTART: i32 = 2;
const SQLITE_CHECKPOINT_TRUNCATE: i32 = 3;
#[cfg(not(target_os = "windows"))]
mod tests {
use super::*;
#[test]
fn test_libversion() {
unsafe {
let version = sqlite3_libversion();
assert!(!version.is_null());
}
}
#[test]
fn test_libversion_number() {
unsafe {
let version_num = sqlite3_libversion_number();
assert!(version_num >= 3042000);
}
}
#[test]
fn test_open_not_found() {
unsafe {
let mut db = ptr::null_mut();
assert_eq!(
sqlite3_open(b"not-found/local.db\0".as_ptr() as *const i8, &mut db),
SQLITE_CANTOPEN
);
}
}
#[test]
fn test_open_existing() {
unsafe {
let mut db = ptr::null_mut();
assert_eq!(
sqlite3_open(b"../testing/testing.db\0".as_ptr() as *const i8, &mut db),
SQLITE_OK
);
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[test]
fn test_close() {
unsafe {
assert_eq!(sqlite3_close(ptr::null_mut()), SQLITE_OK);
}
}
#[test]
fn test_prepare_misuse() {
unsafe {
let mut db = ptr::null_mut();
assert_eq!(
sqlite3_open(b"../testing/testing.db\0".as_ptr() as *const i8, &mut db),
SQLITE_OK
);
let mut stmt = ptr::null_mut();
assert_eq!(
sqlite3_prepare_v2(
db,
b"SELECT 1\0".as_ptr() as *const i8,
-1,
&mut stmt,
ptr::null_mut()
),
SQLITE_OK
);
assert_eq!(sqlite3_finalize(stmt), SQLITE_OK);
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[test]
fn test_wal_checkpoint() {
unsafe {
// Test with valid db
let mut db = ptr::null_mut();
assert_eq!(
sqlite3_open(b"../testing/testing.db\0".as_ptr() as *const i8, &mut db),
SQLITE_OK
);
assert_eq!(sqlite3_wal_checkpoint(db, ptr::null()), SQLITE_OK);
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
#[test]
fn test_wal_checkpoint_v2() {
unsafe {
// Test with valid db
let mut db = ptr::null_mut();
assert_eq!(
sqlite3_open(b"../testing/testing.db\0".as_ptr() as *const i8, &mut db),
SQLITE_OK
);
let mut log_size = 0;
let mut checkpoint_count = 0;
// Test different checkpoint modes
assert_eq!(
sqlite3_wal_checkpoint_v2(
db,
ptr::null(),
SQLITE_CHECKPOINT_PASSIVE,
&mut log_size,
&mut checkpoint_count
),
SQLITE_OK
);
assert_eq!(
sqlite3_wal_checkpoint_v2(
db,
ptr::null(),
SQLITE_CHECKPOINT_FULL,
&mut log_size,
&mut checkpoint_count
),
SQLITE_OK
);
assert_eq!(
sqlite3_wal_checkpoint_v2(
db,
ptr::null(),
SQLITE_CHECKPOINT_RESTART,
&mut log_size,
&mut checkpoint_count
),
SQLITE_OK
);
assert_eq!(
sqlite3_wal_checkpoint_v2(
db,
ptr::null(),
SQLITE_CHECKPOINT_TRUNCATE,
&mut log_size,
&mut checkpoint_count
),
SQLITE_OK
);
assert_eq!(sqlite3_close(db), SQLITE_OK);
}
}
}

View File

@@ -1,24 +0,0 @@
extern void test_libversion();
extern void test_libversion_number();
extern void test_open_misuse();
extern void test_open_not_found();
extern void test_open_existing();
extern void test_close();
extern void test_prepare_misuse();
extern void test_wal_checkpoint();
extern void test_wal_checkpoint_v2();
int main(int argc, char *argv[])
{
test_libversion();
test_libversion_number();
test_open_misuse();
test_open_not_found();
test_open_existing();
test_close();
test_prepare_misuse();
test_wal_checkpoint();
test_wal_checkpoint_v2();
return 0;
}

View File

@@ -1,11 +0,0 @@
#include "check.h"
#include <sqlite3.h>
void test_libversion(void) {
sqlite3_libversion();
}
void test_libversion_number(void) {
sqlite3_libversion_number();
}

View File

@@ -1,11 +0,0 @@
#include <sqlite3.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include "check.h"
void test_close(void)
{
CHECK_EQUAL(SQLITE_OK, sqlite3_close(NULL));
}

View File

@@ -1,31 +0,0 @@
#include "check.h"
#include <sqlite3.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
void test_open_misuse(void)
{
// TODO: SIGSEGV with sqlite3
// CHECK_EQUAL(SQLITE_MISUSE, sqlite3_open(NULL, NULL));
// CHECK_EQUAL(SQLITE_MISUSE, sqlite3_open("local.db", NULL));
}
void test_open_not_found(void)
{
sqlite3 *db;
CHECK_EQUAL(SQLITE_CANTOPEN, sqlite3_open("not-found/local.db", &db));
}
// TODO: test_open_create
void test_open_existing(void)
{
sqlite3 *db;
CHECK_EQUAL(SQLITE_OK, sqlite3_open("../../testing/testing.db", &db));
CHECK_EQUAL(SQLITE_OK, sqlite3_close(db));
}

View File

@@ -1,27 +0,0 @@
#include "check.h"
#include <sqlite3.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
void test_prepare_misuse(void)
{
sqlite3 *db;
CHECK_EQUAL(SQLITE_OK, sqlite3_open("../../testing/testing.db", &db));
// Database handle is NULL.
// TODO: SIGSEGV with sqlite3
// CHECK_EQUAL(SQLITE_MISUSE, sqlite3_prepare_v2(NULL, "SELECT 1", -1, NULL, NULL));
// Output statement is NULL.
// TODO: SIGSEGV with sqlite3
// CHECK_EQUAL(SQLITE_MISUSE, sqlite3_prepare_v2(db, "SELECT 1", -1, NULL, NULL));
// SQL string length is too short, truncating the statement.
// TODO: SIGSEGV with sqlite3
// CHECK_EQUAL(SQLITE_MISUSE, sqlite3_prepare_v2(db, "SELECT 1", 7, NULL, NULL));
CHECK_EQUAL(SQLITE_OK, sqlite3_close(db));
}

View File

@@ -1,39 +0,0 @@
#include "check.h"
#include <sqlite3.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
void test_wal_checkpoint(void)
{
sqlite3 *db;
// Test with NULL db handle
CHECK_EQUAL(SQLITE_MISUSE, sqlite3_wal_checkpoint(NULL, NULL));
// Test with valid db
CHECK_EQUAL(SQLITE_OK, sqlite3_open("../../testing/testing.db", &db));
CHECK_EQUAL(SQLITE_OK, sqlite3_wal_checkpoint(db, NULL));
CHECK_EQUAL(SQLITE_OK, sqlite3_close(db));
}
void test_wal_checkpoint_v2(void)
{
sqlite3 *db;
int log_size, checkpoint_count;
// Test with NULL db handle
CHECK_EQUAL(SQLITE_MISUSE, sqlite3_wal_checkpoint_v2(NULL, NULL, SQLITE_CHECKPOINT_PASSIVE, NULL, NULL));
// Test with valid db
CHECK_EQUAL(SQLITE_OK, sqlite3_open("../../testing/testing.db", &db));
// Test different checkpoint modes
CHECK_EQUAL(SQLITE_OK, sqlite3_wal_checkpoint_v2(db, NULL, SQLITE_CHECKPOINT_PASSIVE, &log_size, &checkpoint_count));
CHECK_EQUAL(SQLITE_OK, sqlite3_wal_checkpoint_v2(db, NULL, SQLITE_CHECKPOINT_FULL, &log_size, &checkpoint_count));
CHECK_EQUAL(SQLITE_OK, sqlite3_wal_checkpoint_v2(db, NULL, SQLITE_CHECKPOINT_RESTART, &log_size, &checkpoint_count));
CHECK_EQUAL(SQLITE_OK, sqlite3_wal_checkpoint_v2(db, NULL, SQLITE_CHECKPOINT_TRUNCATE, &log_size, &checkpoint_count));
CHECK_EQUAL(SQLITE_OK, sqlite3_close(db));
}