AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3 4#include <stdio.h>5#include <stdlib.h>6#include <string.h>7#include <sys/stat.h>8#include <errno.h>9#include <unistd.h>10 11/* Utility: remove database base, -wal and -shm files */12static void cleanup_db_files(const char *dbpath){13 if( dbpath==NULL ) return;14 char wal[1024];15 char shm[1024];16 snprintf(wal, sizeof(wal), "%s-wal", dbpath);17 snprintf(shm, sizeof(shm), "%s-shm", dbpath);18 unlink(dbpath);19 unlink(wal);20 unlink(shm);21}22 23/* Utility: get size of a file; return 0 if not found */24static long long file_size_or_zero(const char *path){25 struct stat st;26 if( stat(path, &st)==0 ){27 return (long long)st.st_size;28 }29 return 0LL;30}31 32/* Utility: compute wal path from db path into buffer */33static void wal_path_for(const char *dbpath, char *out, size_t outsz){34 snprintf(out, outsz, "%s-wal", dbpath);35}36 37/* Utility: run SQL and assert success */38static void exec_or_fail(sqlite3 *db, const char *sql){39 char *zErr = 0;40 int rc = sqlite3_exec(db, sql, 0, 0, &zErr);41 if( rc!=SQLITE_OK ){42 const char *msg = zErr ? zErr : "exec failed";43 TEST_FAIL_MESSAGE(msg);44 }45 if( zErr ) sqlite3_free(zErr);46}47 48/* Open a database at 'dbpath', switch to WAL mode, create a table and insert rows49** in a single transaction to generate WAL frames. Returns opened db handle. */50static sqlite3* open_populate_wal_db(const char *dbpath, int nInserts){51 sqlite3 *db = 0;52 int rc = sqlite3_open(dbpath, &db);53 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "sqlite3_open failed");54 55 /* Enable WAL mode */56 exec_or_fail(db, "PRAGMA journal_mode=WAL;");57 58 /* Create schema and populate in a transaction */59 exec_or_fail(db, "BEGIN IMMEDIATE;");60 exec_or_fail(db, "CREATE TABLE IF NOT EXISTS t(x);");61 sqlite3_stmt *pStmt = 0;62 rc = sqlite3_prepare_v2(db, "INSERT INTO t(x) VALUES (?1);", -1, &pStmt, 0);63 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "prepare failed");64 for(int i=0; i<nInserts; i++){65 sqlite3_bind_int(pStmt, 1, i);66 rc = sqlite3_step(pStmt);67 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_DONE, rc, "step failed");68 sqlite3_reset(pStmt);69 sqlite3_clear_bindings(pStmt);70 }71 sqlite3_finalize(pStmt);72 exec_or_fail(db, "COMMIT;");73 74 return db;75}76 77void setUp(void) {78 /* nothing */79}80void tearDown(void) {81 /* nothing */82}83 84/* Test: when nFrame is below the threshold, no checkpoint should be run. */85void test_sqlite3WalDefaultHook_no_checkpoint_when_below_threshold(void){86 const char *dbfile = "test_wal_hook_no_checkpoint.db";87 cleanup_db_files(dbfile);88 sqlite3 *db = open_populate_wal_db(dbfile, 200);89 char walpath[1024]; wal_path_for(dbfile, walpath, sizeof(walpath));90 91 long long size_before = file_size_or_zero(walpath);92 TEST_ASSERT_MESSAGE(size_before > 0, "Expected non-zero WAL size before hook call");93 94 /* nFrame below threshold => should NOT checkpoint */95 int rc = sqlite3WalDefaultHook(SQLITE_INT_TO_PTR(1000), db, "main", 100);96 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);97 98 long long size_after = file_size_or_zero(walpath);99 TEST_ASSERT_EQUAL_INT64(size_before, size_after);100 101 sqlite3_close(db);102 cleanup_db_files(dbfile);103}104 105/* Test: when nFrame equals threshold, a checkpoint should be performed. */106void test_sqlite3WalDefaultHook_triggers_checkpoint_equal_threshold(void){107 const char *dbfile = "test_wal_hook_checkpoint_equal.db";108 cleanup_db_files(dbfile);109 sqlite3 *db = open_populate_wal_db(dbfile, 300);110 char walpath[1024]; wal_path_for(dbfile, walpath, sizeof(walpath));111 112 long long size_before = file_size_or_zero(walpath);113 TEST_ASSERT_MESSAGE(size_before > 0, "Expected non-zero WAL size before hook call");114 115 /* Trigger checkpoint: nFrame >= threshold */116 int rc = sqlite3WalDefaultHook(SQLITE_INT_TO_PTR(10), db, "main", 10);117 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);118 119 long long size_after = file_size_or_zero(walpath);120 TEST_ASSERT_MESSAGE(size_after < size_before, "Expected WAL size to shrink after checkpoint");121 122 sqlite3_close(db);123 cleanup_db_files(dbfile);124}125 126/* Test: returns SQLITE_OK even if checkpoint would error (invalid zDb name).127 We do not assert internal error forwarding, only that hook returns OK. */128void test_sqlite3WalDefaultHook_returns_OK_even_if_checkpoint_error(void){129 const char *dbfile = "test_wal_hook_error.db";130 cleanup_db_files(dbfile);131 sqlite3 *db = open_populate_wal_db(dbfile, 50);132 133 /* Force trigger with invalid schema name to provoke an underlying error */134 int rc = sqlite3WalDefaultHook(SQLITE_INT_TO_PTR(0), db, "doesnotexist", 1);135 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);136 137 sqlite3_close(db);138 cleanup_db_files(dbfile);139}140 141/* Test: safe when db is NULL and condition is not triggered (no call into checkpoint). */142void test_sqlite3WalDefaultHook_null_db_not_triggered(void){143 /* Not triggered: nFrame < threshold => should not dereference db */144 int rc = sqlite3WalDefaultHook(SQLITE_INT_TO_PTR(100), NULL, "main", 1);145 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);146}147 148/* Test: zDb == NULL triggers checkpoint of all attached DBs (main included). */149void test_sqlite3WalDefaultHook_triggers_with_null_zDb(void){150 const char *dbfile = "test_wal_hook_null_zdb.db";151 cleanup_db_files(dbfile);152 sqlite3 *db = open_populate_wal_db(dbfile, 400);153 char walpath[1024]; wal_path_for(dbfile, walpath, sizeof(walpath));154 155 long long size_before = file_size_or_zero(walpath);156 TEST_ASSERT_MESSAGE(size_before > 0, "Expected non-zero WAL size before hook call");157 158 /* Trigger with zDb == NULL; threshold 0 so always triggers */159 int rc = sqlite3WalDefaultHook(SQLITE_INT_TO_PTR(0), db, NULL, 1);160 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);161 162 long long size_after = file_size_or_zero(walpath);163 TEST_ASSERT_MESSAGE(size_after < size_before, "Expected WAL size to shrink after checkpoint");164 165 sqlite3_close(db);166 cleanup_db_files(dbfile);167}168 169int main(void){170 UNITY_BEGIN();171 RUN_TEST(test_sqlite3WalDefaultHook_no_checkpoint_when_below_threshold);172 RUN_TEST(test_sqlite3WalDefaultHook_triggers_checkpoint_equal_threshold);173 RUN_TEST(test_sqlite3WalDefaultHook_returns_OK_even_if_checkpoint_error);174 RUN_TEST(test_sqlite3WalDefaultHook_null_db_not_triggered);175 RUN_TEST(test_sqlite3WalDefaultHook_triggers_with_null_zDb);176 return UNITY_END();177}