AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <stdlib.h>4#include <string.h>5 6/* The target function is static in the module. We must call the provided7** global wrapper instead of the original static function. */8int test_jsonCacheInsert(sqlite3_context *ctx, JsonParse *pParse);9 10static sqlite3 *gDb = NULL;11 12/* Helper: Create a minimally valid JsonParse object that satisfies asserts13** in jsonCacheInsert. We set nJPRef=1 to avoid frees in jsonParseFree. */14static JsonParse *makeDummyParse(sqlite3 *db, const char *label, int initialRef){15 JsonParse *p = (JsonParse*)sqlite3DbMallocZero(db, sizeof(JsonParse));16 if( p==0 ) return 0;17 p->db = db;18 /* Use a non-NULL string pointer. We mark it as RCStr for assertions,19 ** but ensure nJPRef>1 at free-time so it is not freed. */20 p->zJson = (char*)label;21 p->bJsonIsRCStr = 1;22 p->delta = 0;23 p->nBlobAlloc = 1;24 p->nJPRef = initialRef; /* 1 so insert -> 2; free path will only decrement */25 p->eEdit = 123; /* Non-zero to confirm it is reset to 0 by insert */26 p->bReadOnly = 0;27 return p;28}29 30/* A SQL function wrapper that exercises test_jsonCacheInsert within a valid31** sqlite3_context and returns an integer indicating pass(1)/fail(0).32**33** Usage: SELECT json_cache_test_op(op, n);34** op==1: Basic behavior test:35** - Insert one object, verify flags updated and refcount incremented.36** - Insert a second object in the same context to exercise existing-cache path.37** op==2: Eviction behavior test:38** - Insert many objects (64) to force at least one eviction.39** - Verify oldest's refcount decremented, newest shows insertion changes.40** The second argument 'n' is currently ignored (reserved for future use).41*/42static void json_cache_test_op(sqlite3_context *ctx, int argc, sqlite3_value **argv){43 int op = sqlite3_value_int(argv[0]);44 sqlite3 *db = sqlite3_context_db_handle(ctx);45 46 if( op==1 ){47 /* Basic behavior + existing cache path */48 int rc;49 int ok = 1;50 51 JsonParse *p1 = makeDummyParse(db, "A", 1);52 if( p1==0 ){ sqlite3_result_int(ctx, 0); return; }53 54 rc = test_jsonCacheInsert(ctx, p1);55 if( rc!=SQLITE_OK ) ok = 0;56 if( p1->eEdit!=0 ) ok = 0;57 if( p1->bReadOnly!=1 ) ok = 0;58 if( p1->nJPRef!=2 ) ok = 0;59 60 /* Insert a second object to exercise path where cache already exists */61 JsonParse *p2 = makeDummyParse(db, "B", 1);62 if( p2==0 ){ sqlite3_result_int(ctx, 0); return; }63 64 rc = test_jsonCacheInsert(ctx, p2);65 if( rc!=SQLITE_OK ) ok = 0;66 if( p2->eEdit!=0 ) ok = 0;67 if( p2->bReadOnly!=1 ) ok = 0;68 if( p2->nJPRef!=2 ) ok = 0;69 70 sqlite3_result_int(ctx, ok ? 1 : 0);71 return;72 }else if( op==2 ){73 /* Eviction behavior: push enough entries to cause at least one eviction.74 We avoid directly inspecting the internal cache since its private75 constants are not exported. Instead, we infer eviction by the refcount76 change of the oldest element: it should be decremented by jsonParseFree()77 when the cache is full and the oldest entry is expelled. */78 int ok = 1;79 const int N = 64; /* Big enough to exceed reasonable cache sizes */80 JsonParse **ap = (JsonParse**)sqlite3_malloc(sizeof(JsonParse*)*N);81 int i;82 if( ap==0 ){ sqlite3_result_int(ctx, 0); return; }83 84 for(i=0; i<N; i++){85 char labelBuf[16];86 /* Create small stable labels; pointers are stable string literals here.87 We construct separate literals via a small static table if desired,88 but simple small labels as stack strings are fine since we only89 store the pointer in our own JsonParse and never free it. */90 /* Use a set of static labels to ensure non-NULL zJson with distinct C strings. */91 static const char *labels[] = {92 "L00","L01","L02","L03","L04","L05","L06","L07",93 "L08","L09","L10","L11","L12","L13","L14","L15",94 "L16","L17","L18","L19","L20","L21","L22","L23",95 "L24","L25","L26","L27","L28","L29","L30","L31",96 "L32","L33","L34","L35","L36","L37","L38","L39",97 "L40","L41","L42","L43","L44","L45","L46","L47",98 "L48","L49","L50","L51","L52","L53","L54","L55",99 "L56","L57","L58","L59","L60","L61","L62","L63"100 };101 const char *lbl = labels[i % (sizeof(labels)/sizeof(labels[0]))];102 ap[i] = makeDummyParse(db, lbl, 1);103 if( ap[i]==0 ){ ok = 0; break; }104 if( test_jsonCacheInsert(ctx, ap[i])!=SQLITE_OK ){ ok = 0; break; }105 /* Newest element should always reflect insertion-side changes */106 if( ap[i]->bReadOnly!=1 || ap[i]->eEdit!=0 || ap[i]->nJPRef!=2 ){107 ok = 0; break;108 }109 }110 111 /* If eviction happened at least once, the very first element ap[0] will112 have been expelled and its refcount decremented by jsonParseFree().113 Since we started with 1 and insert incremented to 2, after eviction114 we expect nJPRef==1. If the cache is unusually large and did not evict,115 this check may remain at 2. In typical builds the cache is much smaller116 than 64, so we expect eviction and nJPRef==1. */117 if( ok ){118 if( ap[0]->nJPRef!=1 ){119 /* Did not observe expected eviction; treat as failure */120 ok = 0;121 }122 /* Also ensure the latest element still shows post-insert state */123 if( ap[N-1]->nJPRef!=2 || ap[N-1]->bReadOnly!=1 || ap[N-1]->eEdit!=0 ){124 ok = 0;125 }126 }127 128 sqlite3_free(ap);129 sqlite3_result_int(ctx, ok ? 1 : 0);130 return;131 }132 133 /* Unknown op */134 sqlite3_result_int(ctx, 0);135}136 137void setUp(void) {138 int rc = sqlite3_open(":memory:", &gDb);139 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);140 rc = sqlite3_create_function(gDb, "json_cache_test_op", 2, SQLITE_UTF8, NULL,141 json_cache_test_op, NULL, NULL);142 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);143}144 145void tearDown(void) {146 if( gDb ){147 sqlite3_close(gDb);148 gDb = NULL;149 }150}151 152/* Helper: Evaluate SELECT json_cache_test_op(op, n) and return integer result */153static int eval_json_cache_test(int op, int n){154 sqlite3_stmt *pStmt = NULL;155 int rc, rv = -1;156 157 rc = sqlite3_prepare_v2(gDb, "SELECT json_cache_test_op(?1,?2);", -1, &pStmt, 0);158 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);159 160 rc = sqlite3_bind_int(pStmt, 1, op);161 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);162 rc = sqlite3_bind_int(pStmt, 2, n);163 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);164 165 rc = sqlite3_step(pStmt);166 TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc);167 168 rv = sqlite3_column_int(pStmt, 0);169 170 rc = sqlite3_finalize(pStmt);171 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);172 173 return rv;174}175 176/* Test 1: Basic behavior of jsonCacheInsert.177 - First call must create the cache, set flags, and increment refcount.178 - Second call within same sqlite3_context must use existing cache. */179void test_jsonCacheInsert_basic_behavior(void) {180 int ok = eval_json_cache_test(1, 0);181 TEST_ASSERT_EQUAL_INT_MESSAGE(1, ok, "Basic insert behavior failed");182}183 184/* Test 2: Eviction behavior when the cache is overfilled.185 - Insert many objects to force eviction of the oldest.186 - Verify the oldest object's refcount is decremented (evicted),187 and a newest object's fields are set as expected. */188void test_jsonCacheInsert_eviction_behavior(void) {189 int ok = eval_json_cache_test(2, 0);190 TEST_ASSERT_EQUAL_INT_MESSAGE(1, ok, "Eviction behavior failed");191}192 193int main(void) {194 UNITY_BEGIN();195 RUN_TEST(test_jsonCacheInsert_basic_behavior);196 RUN_TEST(test_jsonCacheInsert_eviction_behavior);197 return UNITY_END();198}