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 10#ifdef _WIN3211#include <io.h>12#define unlink _unlink13#else14#include <unistd.h>15#endif16 17/* Prototype for the auto-generated wrapper around the static function */18extern int test_zeroJournalHdr(Pager *pPager, int doTruncate);19 20/* Helpers */21 22static int exec_sql(sqlite3 *db, const char *sql){23 return sqlite3_exec(db, sql, 0, 0, 0);24}25 26static sqlite3* open_db(const char *path){27 sqlite3 *db = NULL;28 int rc = sqlite3_open(path, &db);29 if( rc!=SQLITE_OK ){30 /* Fail fast in tests if DB can't be opened */31 return NULL;32 }33 return db;34}35 36static void close_db(sqlite3 *db){37 if( db ){38 sqlite3_close(db);39 }40}41 42static int file_exists(const char *path){43 struct stat st;44 return stat(path, &st)==0;45}46 47static sqlite3_int64 file_size(const char *path){48 struct stat st;49 if( stat(path, &st)!=0 ) return -1;50#ifdef _WIN3251 return (sqlite3_int64)st.st_size;52#else53 return (sqlite3_int64)st.st_size;54#endif55}56 57static int read_bytes(const char *path, void *buf, size_t n){58 FILE *f = fopen(path, "rb");59 if( !f ) return -1;60 size_t got = fread(buf, 1, n, f);61 fclose(f);62 return (int)got;63}64 65static void build_journal_path(sqlite3 *db, char *out, size_t nOut){66 const char *dbfile = sqlite3_db_filename(db, "main");67 TEST_ASSERT_NOT_NULL_MESSAGE(dbfile, "Database filename is NULL");68 TEST_ASSERT_TRUE_MESSAGE(strlen(dbfile)+9 < nOut, "Journal path buffer too small");69 strcpy(out, dbfile);70 strcat(out, "-journal");71}72 73/* Create a write transaction that definitely creates a rollback journal.74 For the first creation, set a page size to ensure larger journal when needed. */75static void begin_write_with_data(sqlite3 *db, int ensureLarge){76 int rc;77 78 /* Ensure rollback journal (not WAL) and persistence of journal file */79 rc = exec_sql(db, "PRAGMA journal_mode=PERSIST;");80 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);81 82 /* Optionally set page size big enough to ensure large journal content */83 if( ensureLarge ){84 /* Set before any objects are created to take effect */85 rc = exec_sql(db, "PRAGMA page_size=4096;");86 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);87 }88 89 rc = exec_sql(db, "BEGIN IMMEDIATE;");90 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);91 92 /* Create table if not exists and insert some data to force page writes */93 rc = exec_sql(db, "CREATE TABLE IF NOT EXISTS t(a);");94 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);95 96 /* Insert multiple rows to ensure journal grows */97 rc = exec_sql(db, "INSERT INTO t VALUES (randomblob(1000));"98 "INSERT INTO t VALUES (randomblob(1000));"99 "INSERT INTO t VALUES (randomblob(1000));");100 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);101}102 103static void commit_and_cleanup(sqlite3 *db){104 int rc = exec_sql(db, "COMMIT;");105 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);106}107 108/* Global per-test setup/teardown */109void setUp(void) {110 /* No-op */111}112void tearDown(void) {113 /* No-op */114}115 116/* Utility to create a unique db path per test */117static void make_db_path(char *out, size_t n, const char *suffix){118 snprintf(out, n, "test_zerojournalhdr_%s.db", suffix);119}120 121/* Test 1: doTruncate=1 should truncate the journal file to 0 bytes */122void test_zeroJournalHdr_truncate_to_zero(void){123 char dbpath[256];124 char jpath[512];125 make_db_path(dbpath, sizeof(dbpath), "truncate");126 unlink(dbpath);127 unlink("test_zerojournalhdr_truncate.db-journal");128 129 sqlite3 *db = open_db(dbpath);130 TEST_ASSERT_NOT_NULL(db);131 132 begin_write_with_data(db, 0);133 134 build_journal_path(db, jpath, sizeof(jpath));135 TEST_ASSERT_TRUE_MESSAGE(file_exists(jpath), "Journal file does not exist after write");136 sqlite3_int64 before = file_size(jpath);137 TEST_ASSERT_TRUE_MESSAGE(before > 0, "Journal file size not greater than 0 before zeroJournalHdr");138 139 /* Get Pager* from Btree */140 TEST_ASSERT_NOT_NULL(db->aDb);141 TEST_ASSERT_NOT_NULL(db->aDb[0].pBt);142 Pager *pPager = sqlite3BtreePager(db->aDb[0].pBt);143 TEST_ASSERT_NOT_NULL(pPager);144 145 int rc = test_zeroJournalHdr(pPager, 1);146 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);147 148 sqlite3_int64 after = file_size(jpath);149 TEST_ASSERT_EQUAL_INT64(0, after);150 151 commit_and_cleanup(db);152 close_db(db);153 154 unlink(dbpath);155 unlink(jpath);156}157 158/* Test 2: doTruncate=0 with default journal_size_limit (-1) zeroes 28-byte header, size unchanged */159void test_zeroJournalHdr_zero_header_maintains_size(void){160 char dbpath[256];161 char jpath[512];162 make_db_path(dbpath, sizeof(dbpath), "zerohdr");163 unlink(dbpath);164 unlink("test_zerojournalhdr_zerohdr.db-journal");165 166 sqlite3 *db = open_db(dbpath);167 TEST_ASSERT_NOT_NULL(db);168 169 begin_write_with_data(db, 0);170 171 build_journal_path(db, jpath, sizeof(jpath));172 TEST_ASSERT_TRUE_MESSAGE(file_exists(jpath), "Journal file does not exist after write");173 sqlite3_int64 sz_before = file_size(jpath);174 TEST_ASSERT_TRUE_MESSAGE(sz_before > 28, "Journal file too small before zeroJournalHdr");175 176 TEST_ASSERT_NOT_NULL(db->aDb);177 TEST_ASSERT_NOT_NULL(db->aDb[0].pBt);178 Pager *pPager = sqlite3BtreePager(db->aDb[0].pBt);179 TEST_ASSERT_NOT_NULL(pPager);180 181 int rc = test_zeroJournalHdr(pPager, 0);182 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);183 184 sqlite3_int64 sz_after = file_size(jpath);185 TEST_ASSERT_EQUAL_INT64(sz_before, sz_after);186 187 unsigned char hdr[28];188 int got = read_bytes(jpath, hdr, sizeof(hdr));189 TEST_ASSERT_EQUAL_INT(28, got);190 for(int i=0;i<28;i++){191 if( hdr[i]!=0 ){192 TEST_FAIL_MESSAGE("Journal header not zeroed");193 }194 }195 196 commit_and_cleanup(db);197 close_db(db);198 199 unlink(dbpath);200 unlink(jpath);201}202 203/* Test 3: doTruncate=0 with journal_size_limit=0 should truncate to 0 */204void test_zeroJournalHdr_limit_zero_truncates(void){205 char dbpath[256];206 char jpath[512];207 make_db_path(dbpath, sizeof(dbpath), "limit0");208 unlink(dbpath);209 unlink("test_zerojournalhdr_limit0.db-journal");210 211 sqlite3 *db = open_db(dbpath);212 TEST_ASSERT_NOT_NULL(db);213 214 /* Set journal mode and limit before write */215 int rc = exec_sql(db, "PRAGMA journal_mode=PERSIST;");216 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);217 rc = exec_sql(db, "PRAGMA journal_size_limit=0;");218 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);219 220 rc = exec_sql(db, "BEGIN IMMEDIATE;");221 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);222 rc = exec_sql(db, "CREATE TABLE IF NOT EXISTS t(a); INSERT INTO t VALUES(1);");223 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);224 225 build_journal_path(db, jpath, sizeof(jpath));226 TEST_ASSERT_TRUE_MESSAGE(file_exists(jpath), "Journal file does not exist after write");227 228 TEST_ASSERT_NOT_NULL(db->aDb);229 TEST_ASSERT_NOT_NULL(db->aDb[0].pBt);230 Pager *pPager = sqlite3BtreePager(db->aDb[0].pBt);231 TEST_ASSERT_NOT_NULL(pPager);232 233 rc = test_zeroJournalHdr(pPager, 0);234 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);235 236 sqlite3_int64 after = file_size(jpath);237 TEST_ASSERT_EQUAL_INT64(0, after);238 239 commit_and_cleanup(db);240 close_db(db);241 242 unlink(dbpath);243 unlink(jpath);244}245 246/* Test 4: doTruncate=0 with positive journal_size_limit truncates to the limit when larger */247void test_zeroJournalHdr_truncate_to_limit(void){248 char dbpath[256];249 char jpath[512];250 make_db_path(dbpath, sizeof(dbpath), "limit1024");251 unlink(dbpath);252 unlink("test_zerojournalhdr_limit1024.db-journal");253 254 sqlite3 *db = open_db(dbpath);255 TEST_ASSERT_NOT_NULL(db);256 257 /* Set journal mode and page size to grow journal, and set small size limit */258 int rc = exec_sql(db, "PRAGMA journal_mode=PERSIST;");259 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);260 rc = exec_sql(db, "PRAGMA journal_size_limit=1024;");261 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);262 263 begin_write_with_data(db, 1); /* ensureLarge=1 to set page_size=4096 */264 265 build_journal_path(db, jpath, sizeof(jpath));266 TEST_ASSERT_TRUE_MESSAGE(file_exists(jpath), "Journal file does not exist after write");267 268 sqlite3_int64 before = file_size(jpath);269 TEST_ASSERT_TRUE_MESSAGE(before > 2048, "Journal file unexpectedly small before zeroJournalHdr");270 271 TEST_ASSERT_NOT_NULL(db->aDb);272 TEST_ASSERT_NOT_NULL(db->aDb[0].pBt);273 Pager *pPager = sqlite3BtreePager(db->aDb[0].pBt);274 TEST_ASSERT_NOT_NULL(pPager);275 276 rc = test_zeroJournalHdr(pPager, 0);277 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);278 279 sqlite3_int64 after = file_size(jpath);280 TEST_ASSERT_EQUAL_INT64(1024, after);281 282 /* Verify first 28 bytes are zeroed after the call and truncation */283 unsigned char hdr[28];284 int got = read_bytes(jpath, hdr, sizeof(hdr));285 TEST_ASSERT_EQUAL_INT(28, got);286 for(int i=0;i<28;i++){287 if( hdr[i]!=0 ){288 TEST_FAIL_MESSAGE("Journal header not zeroed after truncate-to-limit");289 }290 }291 292 commit_and_cleanup(db);293 close_db(db);294 295 unlink(dbpath);296 unlink(jpath);297}298 299int main(void){300 UNITY_BEGIN();301 RUN_TEST(test_zeroJournalHdr_truncate_to_zero);302 RUN_TEST(test_zeroJournalHdr_zero_header_maintains_size);303 RUN_TEST(test_zeroJournalHdr_limit_zero_truncates);304 RUN_TEST(test_zeroJournalHdr_truncate_to_limit);305 return UNITY_END();306}