AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <string.h>4#include <stdlib.h>5 6/* Unity setup/teardown */7void setUp(void) {8 /* No global setup needed */9}10void tearDown(void) {11 /* No global teardown needed */12}13 14/* A small helper to open an in-memory database and extract its Pager */15static void open_memory_db_and_pager(sqlite3 **ppDb, Pager **ppPager){16 int rc;17 sqlite3 *db = 0;18 rc = sqlite3_open(":memory:", &db);19 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "sqlite3_open(:memory:) failed");20 TEST_ASSERT_NOT_NULL(db);21 22 /* Access the main database Btree and underlying Pager */23 TEST_ASSERT_NOT_NULL(db->aDb);24 Btree *pBtree = db->aDb[0].pBt;25 TEST_ASSERT_NOT_NULL_MESSAGE(pBtree, "Main Btree is NULL");26 BtShared *pBtShared = pBtree->pBt;27 TEST_ASSERT_NOT_NULL_MESSAGE(pBtShared, "BtShared is NULL");28 Pager *pPager = pBtShared->pPager;29 TEST_ASSERT_NOT_NULL_MESSAGE(pPager, "Pager is NULL");30 31 *ppDb = db;32 *ppPager = pPager;33}34 35/* ---- Test infrastructure for simulating xUnfetch ---- */36 37typedef struct TestFile {38 sqlite3_file base;39 int nUnfetch;40 i64 lastOfst;41 void *lastP;42} TestFile;43 44static int tf_xUnfetch(sqlite3_file *pFile, i64 iOfst, void *pArg){45 TestFile *tf = (TestFile*)pFile;46 tf->nUnfetch++;47 tf->lastOfst = iOfst;48 tf->lastP = pArg;49 return SQLITE_OK;50}51 52/* sqlite3_io_methods for TestFile. Only xUnfetch is used here. */53static struct sqlite3_io_methods gTestIoMethods;54 55/* Initialize the test io-methods (idempotent) */56static void init_test_io_methods(void){57 memset(&gTestIoMethods, 0, sizeof(gTestIoMethods));58 gTestIoMethods.iVersion = 3;59 gTestIoMethods.xUnfetch = tf_xUnfetch;60}61 62/* ---- Tests ---- */63 64void test_sqlite3PagerUnrefNotNull_mmap_unfetch_and_freelist_empty(void){65 sqlite3 *db = 0;66 Pager *pPager = 0;67 open_memory_db_and_pager(&db, &pPager);68 69 /* Install our test sqlite3_file with iVersion>=3 and xUnfetch */70 init_test_io_methods();71 TestFile tf;72 memset(&tf, 0, sizeof(tf));73 tf.base.pMethods = &gTestIoMethods;74 75 sqlite3_file *origFd = pPager->fd;76 pPager->fd = &tf.base;77 78 /* Prepare a fake mapped page (PGHDR_MMAP) */79 PgHdr *pPg = (PgHdr*)sqlite3_malloc(sizeof(PgHdr));80 TEST_ASSERT_NOT_NULL(pPg);81 memset(pPg, 0, sizeof(PgHdr));82 83 /* Data buffer whose pointer should be passed to xUnfetch */84 void *pData = sqlite3_malloc(128);85 TEST_ASSERT_NOT_NULL(pData);86 87 pPg->pPager = pPager;88 pPg->flags = PGHDR_MMAP; /* Force mapped-page code path */89 pPg->pgno = 2; /* Must not be 1, enforced by assert in helper */90 pPg->pData = pData;91 92 /* Start state: empty freelist, known nMmapOut value */93 PgHdr *oldFree = NULL;94 pPager->pMmapFreelist = oldFree;95 pPager->nMmapOut = 5;96 97 i64 expectedOfst = (i64)(pPg->pgno - 1) * (i64)pPager->pageSize;98 99 /* Call the function under test */100 sqlite3PagerUnrefNotNull((DbPage*)pPg);101 102 /* Verify xUnfetch called with correct args */103 TEST_ASSERT_EQUAL_INT(1, tf.nUnfetch);104 TEST_ASSERT_EQUAL_INT64(expectedOfst, tf.lastOfst);105 TEST_ASSERT_EQUAL_PTR(pData, tf.lastP);106 107 /* Verify freelist push and nMmapOut decrement */108 TEST_ASSERT_EQUAL_PTR(pPg, pPager->pMmapFreelist);109 TEST_ASSERT_EQUAL_PTR(oldFree, pPg->pDirty); /* pDirty points to old freelist head */110 TEST_ASSERT_EQUAL_INT(4, pPager->nMmapOut);111 112 /* Restore pager fd before closing the database */113 pPager->fd = origFd;114 115 /* Cleanup our allocations and detach our fake page from freelist */116 pPager->pMmapFreelist = oldFree; /* remove our injected head safely */117 sqlite3_free(pPg);118 sqlite3_free(pData);119 120 sqlite3_close(db);121}122 123void test_sqlite3PagerUnrefNotNull_mmap_unfetch_and_freelist_nonempty(void){124 sqlite3 *db = 0;125 Pager *pPager = 0;126 open_memory_db_and_pager(&db, &pPager);127 128 init_test_io_methods();129 TestFile tf;130 memset(&tf, 0, sizeof(tf));131 tf.base.pMethods = &gTestIoMethods;132 133 sqlite3_file *origFd = pPager->fd;134 pPager->fd = &tf.base;135 136 /* Existing freelist head */137 PgHdr *existing = (PgHdr*)sqlite3_malloc(sizeof(PgHdr));138 TEST_ASSERT_NOT_NULL(existing);139 memset(existing, 0, sizeof(PgHdr));140 141 pPager->pMmapFreelist = existing;142 pPager->nMmapOut = 2;143 144 /* New page to unref (mapped) */145 PgHdr *pPg = (PgHdr*)sqlite3_malloc(sizeof(PgHdr));146 TEST_ASSERT_NOT_NULL(pPg);147 memset(pPg, 0, sizeof(PgHdr));148 149 void *pData = sqlite3_malloc(64);150 TEST_ASSERT_NOT_NULL(pData);151 152 pPg->pPager = pPager;153 pPg->flags = PGHDR_MMAP;154 pPg->pgno = 3;155 pPg->pData = pData;156 157 i64 expectedOfst = (i64)(pPg->pgno - 1) * (i64)pPager->pageSize;158 159 sqlite3PagerUnrefNotNull((DbPage*)pPg);160 161 /* Verify xUnfetch called and freelist chain updated */162 TEST_ASSERT_EQUAL_INT(1, tf.nUnfetch);163 TEST_ASSERT_EQUAL_INT64(expectedOfst, tf.lastOfst);164 TEST_ASSERT_EQUAL_PTR(pData, tf.lastP);165 166 TEST_ASSERT_EQUAL_PTR(pPg, pPager->pMmapFreelist);167 TEST_ASSERT_EQUAL_PTR(existing, pPg->pDirty);168 TEST_ASSERT_EQUAL_INT(1, pPager->nMmapOut);169 170 /* Restore fd and cleanup */171 pPager->fd = origFd;172 173 /* Detach our injected node; keep original freelist head */174 pPager->pMmapFreelist = existing;175 176 sqlite3_free(pPg);177 sqlite3_free(existing);178 sqlite3_free(pData);179 180 sqlite3_close(db);181}182 183void test_sqlite3PagerUnrefNotNull_non_mmap_reduces_refcount(void){184 sqlite3 *db = 0;185 Pager *pPager = 0;186 open_memory_db_and_pager(&db, &pPager);187 188 /* Fetch a real page via pager to obtain a referenced DbPage.189 Use page 2 (not page 1) so it's safe to unref here. */190 DbPage *pPage2 = 0;191 int rc = sqlite3PagerGet(pPager, 2, &pPage2, 0);192 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "sqlite3PagerGet failed");193 TEST_ASSERT_NOT_NULL(pPage2);194 195 int before = sqlite3PcacheRefCount(pPager->pPCache);196 197 /* Ensure the page is not flagged as mmap (typical for :memory:) */198 /* We don't assert on flags, but we rely on natural behavior: non-mmap path. */199 200 sqlite3PagerUnrefNotNull(pPage2);201 202 int after = sqlite3PcacheRefCount(pPager->pPCache);203 204 TEST_ASSERT_EQUAL_INT_MESSAGE(before - 1, after,205 "Refcount should decrease by exactly 1 after unref (non-mmap)");206 207 sqlite3_close(db);208}209 210/* Unity main */211int main(void){212 UNITY_BEGIN();213 RUN_TEST(test_sqlite3PagerUnrefNotNull_mmap_unfetch_and_freelist_empty);214 RUN_TEST(test_sqlite3PagerUnrefNotNull_mmap_unfetch_and_freelist_nonempty);215 RUN_TEST(test_sqlite3PagerUnrefNotNull_non_mmap_reduces_refcount);216 return UNITY_END();217}