AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <string.h>4#include <stdlib.h>5#include <stdio.h>6 7/* Prototype of the test wrapper provided in the module */8int test_memdbOpen(sqlite3_vfs *pVfs, const char *zName, sqlite3_file *pFd, int flags, int *pOutFlags);9 10/* A large-enough sqlite3_file object so memdbOpen can treat it as MemFile */11typedef struct BigFile {12 sqlite3_file base;13 unsigned char extra[2048];14} BigFile;15 16static void BigFile_Init(BigFile *p){17 memset(p, 0, sizeof(*p));18}19 20void setUp(void) {21 /* Ensure SQLite is initialized for mutex/malloc subsystems used by memdb */22 sqlite3_initialize();23}24 25void tearDown(void) {26 /* Must ensure all files are closed prior to shutdown in tests */27 sqlite3_shutdown();28}29 30/* Helper to open a memdb file using the wrapper */31static int open_memdb(const char *zName, BigFile *pFile, int flags, int *pOutFlags){32 BigFile_Init(pFile);33 return test_memdbOpen(NULL, zName, &pFile->base, flags, pOutFlags);34}35 36/* Helper to close a memdb file if opened */37static void close_memdb(BigFile *pFile){38 if( pFile && pFile->base.pMethods ){39 pFile->base.pMethods->xClose(&pFile->base);40 pFile->base.pMethods = NULL;41 }42}43 44/* Basic separate store open and read/write/size behavior */45void test_memdbOpen_separate_basic_rw(void){46 BigFile f;47 int rc;48 sqlite3_int64 sz = -1;49 const char *zName = "separate_db"; /* No leading '/' => separate store */50 51 rc = open_memdb(zName, &f, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, NULL);52 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);53 TEST_ASSERT_NOT_NULL(f.base.pMethods);54 55 /* Initial size must be 0 */56 rc = f.base.pMethods->xFileSize(&f.base, &sz);57 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);58 TEST_ASSERT_EQUAL_INT64(0, sz);59 60 /* Write some data at offset 0 */61 const char payload1[] = "Hello, memdb";62 rc = f.base.pMethods->xWrite(&f.base, payload1, (int)sizeof(payload1), 0);63 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);64 65 /* File size should now be sizeof(payload1) */66 rc = f.base.pMethods->xFileSize(&f.base, &sz);67 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);68 TEST_ASSERT_EQUAL_INT64((sqlite3_int64)sizeof(payload1), sz);69 70 /* Read it back fully */71 char buf[64];72 memset(buf, 0, sizeof(buf));73 rc = f.base.pMethods->xRead(&f.base, buf, (int)sizeof(payload1), 0);74 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);75 TEST_ASSERT_EQUAL_INT(0, memcmp(buf, payload1, sizeof(payload1)));76 77 /* Read beyond EOF should zero-fill tail and return SHORT_READ */78 memset(buf, 0xAA, sizeof(buf));79 rc = f.base.pMethods->xRead(&f.base, buf, (int)sizeof(payload1)+5, 0);80 TEST_ASSERT_EQUAL_INT(SQLITE_IOERR_SHORT_READ, rc);81 TEST_ASSERT_EQUAL_INT(0, memcmp(buf, payload1, sizeof(payload1)));82 for(int i=(int)sizeof(payload1); i<(int)sizeof(payload1)+5; i++){83 TEST_ASSERT_EQUAL_UINT8(0, (unsigned char)buf[i]);84 }85 86 /* Sparse write: write at offset > current size, gap should be zero-filled */87 const char payload2[] = "XYZ";88 rc = f.base.pMethods->xWrite(&f.base, payload2, (int)sizeof(payload2), 5 + (sqlite3_int64)sizeof(payload1));89 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);90 91 /* New size */92 rc = f.base.pMethods->xFileSize(&f.base, &sz);93 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);94 TEST_ASSERT_EQUAL_INT64(5 + (sqlite3_int64)sizeof(payload1) + (sqlite3_int64)sizeof(payload2), sz);95 96 /* Verify the zero-filled gap: read from end of payload1 to start of payload2 */97 char verify[64];98 memset(verify, 0xCC, sizeof(verify));99 rc = f.base.pMethods->xRead(&f.base, verify, 5 + (int)sizeof(payload2), (sqlite3_int64)sizeof(payload1));100 /* Some portion is beyond EOF if earlier size was smaller; however after write it should all exist */101 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);102 103 /* First 5 bytes after payload1 must be zeros */104 for(int i=0; i<5; i++){105 TEST_ASSERT_EQUAL_UINT8(0, (unsigned char)verify[i]);106 }107 TEST_ASSERT_EQUAL_INT(0, memcmp(&verify[5], payload2, sizeof(payload2)));108 109 /* Truncate smaller: OK */110 rc = f.base.pMethods->xTruncate(&f.base, 7);111 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);112 rc = f.base.pMethods->xFileSize(&f.base, &sz);113 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);114 TEST_ASSERT_EQUAL_INT64(7, sz);115 116 /* Truncate larger than current size: should be SQLITE_CORRUPT */117 rc = f.base.pMethods->xTruncate(&f.base, 1000);118 TEST_ASSERT_EQUAL_INT(SQLITE_CORRUPT, rc);119 120 /* Sync should be OK (no-op) */121 rc = f.base.pMethods->xSync(&f.base, 0);122 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);123 124 close_memdb(&f);125}126 127/* Verify initial size limit via SQLITE_FCNTL_SIZE_LIMIT and enforcement of SQLITE_FULL */128void test_memdbOpen_separate_size_limit(void){129 BigFile f;130 int rc;131 sqlite3_int64 sizeLimit;132 133 rc = open_memdb("limit_db", &f, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, NULL);134 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);135 136 /* With empty file, passing -1 should return current szMax (sqlite3GlobalConfig.mxMemdbSize) */137 sizeLimit = -1;138 rc = f.base.pMethods->xFileControl(&f.base, SQLITE_FCNTL_SIZE_LIMIT, &sizeLimit);139 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);140 /* We cannot assert an exact value easily, but it must be >= 0 */141 TEST_ASSERT_TRUE(sizeLimit >= 0);142 143 /* Set a small limit and verify writes beyond it fail with SQLITE_FULL */144 sizeLimit = 1024;145 rc = f.base.pMethods->xFileControl(&f.base, SQLITE_FCNTL_SIZE_LIMIT, &sizeLimit);146 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);147 TEST_ASSERT_EQUAL_INT64(1024, sizeLimit);148 149 /* Attempt to write past the limit: offset 900, 200 bytes => end at 1100 > 1024 */150 unsigned char data[200];151 memset(data, 0x5A, sizeof(data));152 rc = f.base.pMethods->xWrite(&f.base, data, (int)sizeof(data), 900);153 TEST_ASSERT_EQUAL_INT(SQLITE_FULL, rc);154 155 /* Write under the limit should succeed (e.g., 512 bytes at 0) */156 rc = f.base.pMethods->xWrite(&f.base, data, 512, 0);157 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);158 159 close_memdb(&f);160}161 162/* Shared store behavior: two handles to the same name share content and locks */163void test_memdbOpen_shared_two_handles_share_and_locking(void){164 BigFile f1, f2;165 int rc;166 const char *zName = "/shared_memdb_A"; /* Leading '/' => shared store */167 168 rc = open_memdb(zName, &f1, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, NULL);169 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);170 rc = open_memdb(zName, &f2, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, NULL);171 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);172 173 /* Write through f1, read through f2 */174 const char msg[] = "Shared hello";175 rc = f1.base.pMethods->xWrite(&f1.base, msg, (int)sizeof(msg), 0);176 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);177 char buf[32];178 memset(buf, 0, sizeof(buf));179 rc = f2.base.pMethods->xRead(&f2.base, buf, (int)sizeof(msg), 0);180 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);181 TEST_ASSERT_EQUAL_INT(0, memcmp(buf, msg, sizeof(msg)));182 183 /* Locking: both can take SHARED */184 rc = f1.base.pMethods->xLock(&f1.base, SQLITE_LOCK_SHARED);185 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);186 rc = f2.base.pMethods->xLock(&f2.base, SQLITE_LOCK_SHARED);187 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);188 189 /* With two shared readers, EXCLUSIVE should be BUSY */190 rc = f2.base.pMethods->xLock(&f2.base, SQLITE_LOCK_EXCLUSIVE);191 TEST_ASSERT_EQUAL_INT(SQLITE_BUSY, rc);192 193 /* Release f1, then f2 should be able to escalate to EXCLUSIVE */194 rc = f1.base.pMethods->xUnlock(&f1.base, SQLITE_LOCK_NONE);195 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);196 197 /* Go to RESERVED then EXCLUSIVE */198 rc = f2.base.pMethods->xLock(&f2.base, SQLITE_LOCK_RESERVED);199 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);200 rc = f2.base.pMethods->xLock(&f2.base, SQLITE_LOCK_EXCLUSIVE);201 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);202 203 /* Drop back to NONE */204 rc = f2.base.pMethods->xUnlock(&f2.base, SQLITE_LOCK_NONE);205 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);206 207 close_memdb(&f1);208 close_memdb(&f2);209}210 211/* pOutFlags behavior and null handling */212void test_memdbOpen_pOutFlags_and_null(void){213 BigFile f1, f2;214 int rc;215 int outFlags = 0;216 217 rc = open_memdb("flags_db", &f1, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, &outFlags);218 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);219 /* Must contain SQLITE_OPEN_MEMORY plus passed flags */220 TEST_ASSERT_TRUE((outFlags & SQLITE_OPEN_MEMORY) != 0);221 TEST_ASSERT_TRUE((outFlags & SQLITE_OPEN_READWRITE) != 0);222 TEST_ASSERT_TRUE((outFlags & SQLITE_OPEN_CREATE) != 0);223 close_memdb(&f1);224 225 /* Null pOutFlags should be accepted */226 rc = open_memdb("flags_db_null", &f2, 0, NULL);227 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);228 close_memdb(&f2);229}230 231/* VFSNAME file-control returns a formatted string */232void test_memdbOpen_filecontrol_vfsname(void){233 BigFile f;234 int rc;235 rc = open_memdb("vfsname_db", &f, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, NULL);236 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);237 238 char *zName = NULL;239 rc = f.base.pMethods->xFileControl(&f.base, SQLITE_FCNTL_VFSNAME, &zName);240 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);241 TEST_ASSERT_NOT_NULL(zName);242 /* Should begin with "memdb(" */243 TEST_ASSERT_TRUE(strncmp(zName, "memdb(", 6) == 0);244 sqlite3_free(zName);245 246 close_memdb(&f);247}248 249/* Fetch should return NULL when RESIZEABLE flag is set (default) */250void test_memdbOpen_fetch_behavior_resizeable(void){251 BigFile f;252 int rc;253 void *pPage = (void*)0x1; /* will be overwritten */254 rc = open_memdb("fetch_db", &f, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, NULL);255 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);256 257 rc = f.base.pMethods->xFetch(&f.base, 0, 100, &pPage);258 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);259 TEST_ASSERT_NULL(pPage);260 261 close_memdb(&f);262}263 264int main(void) {265 UNITY_BEGIN();266 267 RUN_TEST(test_memdbOpen_separate_basic_rw);268 RUN_TEST(test_memdbOpen_separate_size_limit);269 RUN_TEST(test_memdbOpen_shared_two_handles_share_and_locking);270 RUN_TEST(test_memdbOpen_pOutFlags_and_null);271 RUN_TEST(test_memdbOpen_filecontrol_vfsname);272 RUN_TEST(test_memdbOpen_fetch_behavior_resizeable);273 274 return UNITY_END();275}