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/* Forward declaration of the test wrapper exposed in the module */8void test_replaceFunc(sqlite3_context *context, int argc, sqlite3_value **argv);9 10static sqlite3 *gDb = NULL;11 12/* Helpers to manage sqlite3_value arguments */13static sqlite3_value* makeTextValue(sqlite3 *db, const char *z){14 sqlite3_value *v = sqlite3ValueNew(db);15 TEST_ASSERT_NOT_NULL_MESSAGE(v, "sqlite3ValueNew returned NULL");16 /* -1 means NUL-terminated; use SQLITE_TRANSIENT to copy into Mem */17 sqlite3ValueSetStr(v, -1, z, SQLITE_UTF8, SQLITE_TRANSIENT);18 return v;19}20 21static sqlite3_value* makeNullValue(sqlite3 *db){22 sqlite3_value *v = sqlite3ValueNew(db);23 TEST_ASSERT_NOT_NULL_MESSAGE(v, "sqlite3ValueNew returned NULL for NULL value");24 sqlite3VdbeMemSetNull((Mem*)v);25 return v;26}27 28/* Initialize a minimal sqlite3_context suitable for test_replaceFunc */29static void initContext(sqlite3_context *pCtx){30 memset(pCtx, 0, sizeof(*pCtx));31 /* Initialize result as NULL and attach db */32 pCtx->s.flags = MEM_Null;33 pCtx->s.db = gDb;34 pCtx->isError = 0;35}36 37/* Extract result text from context.s as a const char* (or NULL if NULL result) */38static const char* ctxResultText(sqlite3_context *pCtx){39 if( (pCtx->s.flags & MEM_Null)!=0 ){40 return NULL;41 }42 return (const char*)sqlite3_value_text((sqlite3_value*)&pCtx->s);43}44 45/* Clean up the result Mem inside the context between tests */46static void releaseContextResult(sqlite3_context *pCtx){47 sqlite3VdbeMemRelease(&pCtx->s);48 pCtx->s.flags = MEM_Null;49 pCtx->isError = 0;50}51 52/* Reference implementation for expected result (left-to-right, non-overlapping) */53static char* ref_replace(const char *A, const char *B, const char *C){54 if( A==NULL || B==NULL || C==NULL ) return NULL;55 size_t nA = strlen(A);56 size_t nB = strlen(B);57 size_t nC = strlen(C);58 59 if( nB==0 ){60 char *out = (char*)malloc(nA + 1);61 if(out==NULL) return NULL;62 memcpy(out, A, nA+1);63 return out;64 }65 66 /* Worst case: every char replaced and expansion occurs */67 size_t cap = nA + 1 + (nA * (nC>nB ? (nC-nB) : 0));68 char *out = (char*)malloc(cap);69 if(out==NULL) return NULL;70 71 size_t i=0, j=0;72 size_t limit = (nA>=nB)? (nA - nB) : 0;73 while( i<=limit ){74 if( A[i]==B[0] && memcmp(&A[i], B, nB)==0 ){75 memcpy(&out[j], C, nC);76 j += nC;77 i += nB;78 }else{79 out[j++] = A[i++];80 }81 }82 /* tail */83 while( i<nA ) out[j++] = A[i++];84 out[j] = '\0';85 return out;86}87 88/* Convenience to run replace and capture a heap copy of the result string (or NULL) */89static char* run_replace_and_copy(const char *A, const char *B, const char *C, int expectError, int *pErrCodeOut){90 sqlite3_context ctx;91 initContext(&ctx);92 93 sqlite3_value *argv[3];94 argv[0] = (A!=NULL) ? makeTextValue(gDb, A) : makeNullValue(gDb);95 argv[1] = (B!=NULL) ? makeTextValue(gDb, B) : makeNullValue(gDb);96 argv[2] = (C!=NULL) ? makeTextValue(gDb, C) : makeNullValue(gDb);97 98 test_replaceFunc(&ctx, 3, argv);99 100 const char *zRes = ctxResultText(&ctx);101 char *zCopy = NULL;102 if( zRes ){103 size_t n = strlen(zRes);104 zCopy = (char*)malloc(n+1);105 TEST_ASSERT_NOT_NULL_MESSAGE(zCopy, "malloc failed to copy result");106 memcpy(zCopy, zRes, n+1);107 }else{108 zCopy = NULL;109 }110 111 if( pErrCodeOut ) *pErrCodeOut = ctx.isError;112 113 /* Cleanup */114 releaseContextResult(&ctx);115 sqlite3ValueFree(argv[0]);116 sqlite3ValueFree(argv[1]);117 sqlite3ValueFree(argv[2]);118 119 if( expectError ){120 /* On error, result should be NULL */121 TEST_ASSERT_NULL(zCopy);122 }123 return zCopy;124}125 126void setUp(void) {127 int rc = sqlite3_open(":memory:", &gDb);128 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "sqlite3_open failed");129}130 131void tearDown(void) {132 if( gDb ){133 int rc = sqlite3_close(gDb);134 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "sqlite3_close failed");135 gDb = NULL;136 }137}138 139/* Tests */140 141void test_replaceFunc_basic_single_char(void){142 const char *A = "hello world";143 const char *B = "l";144 const char *C = "L";145 146 char *got = run_replace_and_copy(A,B,C, 0, NULL);147 char *exp = ref_replace(A,B,C);148 149 TEST_ASSERT_NOT_NULL(got);150 TEST_ASSERT_NOT_NULL(exp);151 TEST_ASSERT_EQUAL_STRING(exp, got);152 153 free(got);154 free(exp);155}156 157void test_replaceFunc_no_match_returns_original(void){158 const char *A = "abcdef";159 const char *B = "xyz";160 const char *C = "Q";161 162 char *got = run_replace_and_copy(A,B,C, 0, NULL);163 TEST_ASSERT_NOT_NULL(got);164 TEST_ASSERT_EQUAL_STRING(A, got);165 166 free(got);167}168 169void test_replaceFunc_empty_pattern_returns_input(void){170 const char *A = "abc";171 const char *B = "";172 const char *C = "X";173 174 char *got = run_replace_and_copy(A,B,C, 0, NULL);175 TEST_ASSERT_NOT_NULL(got);176 TEST_ASSERT_EQUAL_STRING(A, got);177 178 free(got);179}180 181void test_replaceFunc_null_pattern_results_null(void){182 /* Pattern is NULL: function should leave result as NULL */183 char *got = run_replace_and_copy("abc", NULL, "x", 0, NULL);184 TEST_ASSERT_NULL(got);185 free(got);186}187 188void test_replaceFunc_null_input_results_null(void){189 char *got = run_replace_and_copy(NULL, "a", "b", 0, NULL);190 TEST_ASSERT_NULL(got);191 free(got);192}193 194void test_replaceFunc_null_replacement_results_null(void){195 char *got = run_replace_and_copy("abc", "a", NULL, 0, NULL);196 TEST_ASSERT_NULL(got);197 free(got);198}199 200void test_replaceFunc_shrink_pattern(void){201 const char *A = "abcabcabc";202 const char *B = "abc";203 const char *C = "x";204 205 char *got = run_replace_and_copy(A,B,C, 0, NULL);206 char *exp = ref_replace(A,B,C);207 TEST_ASSERT_NOT_NULL(got);208 TEST_ASSERT_NOT_NULL(exp);209 TEST_ASSERT_EQUAL_STRING(exp, got);210 211 free(got);212 free(exp);213}214 215void test_replaceFunc_overlap_non_overlapping_behavior(void){216 /* Replace "aa" with "a" in "aaaa" => "aa" (non-overlapping) */217 const char *A = "aaaa";218 const char *B = "aa";219 const char *C = "a";220 char *got = run_replace_and_copy(A,B,C, 0, NULL);221 TEST_ASSERT_NOT_NULL(got);222 TEST_ASSERT_EQUAL_STRING("aa", got);223 free(got);224}225 226void test_replaceFunc_expand_and_realloc(void){227 /* Ensure expansion path and realloc happen; many 'a' with "a"->"abcde" */228 const char *A = "xaxaxaxaxaxaxaxaxaxaxaxaxaxaxaxaxa";229 const char *B = "a";230 const char *C = "abcde";231 232 char *got = run_replace_and_copy(A,B,C, 0, NULL);233 char *exp = ref_replace(A,B,C);234 235 TEST_ASSERT_NOT_NULL(got);236 TEST_ASSERT_NOT_NULL(exp);237 TEST_ASSERT_EQUAL_STRING(exp, got);238 239 free(got);240 free(exp);241}242 243void test_replaceFunc_too_big_sets_error_and_null_result(void){244 /* Set a small LENGTH limit to force SQLITE_TOOBIG during expansion */245 int prev = sqlite3_limit(gDb, SQLITE_LIMIT_LENGTH, 50);246 /* Input with 11 matches of 'a' to exceed the limit:247 initial nOut = nStr+1 = 12 (since 11 'a's -> 11 + 1)248 each match adds (5-1)=4 -> 12 + 11*4 = 56, check uses nOut-1=55 > 50 -> error249 */250 const char *A = "aaaaaaaaaaa"; /* 11 'a' */251 const char *B = "a";252 const char *C = "aaaaa";253 254 int errCode = 0;255 char *got = run_replace_and_copy(A,B,C, 1, &errCode);256 TEST_ASSERT_NULL(got);257 TEST_ASSERT_EQUAL_INT(SQLITE_TOOBIG, errCode);258 259 /* Restore original limit */260 sqlite3_limit(gDb, SQLITE_LIMIT_LENGTH, prev);261}262 263int main(void) {264 UNITY_BEGIN();265 RUN_TEST(test_replaceFunc_basic_single_char);266 RUN_TEST(test_replaceFunc_no_match_returns_original);267 RUN_TEST(test_replaceFunc_empty_pattern_returns_input);268 RUN_TEST(test_replaceFunc_null_pattern_results_null);269 RUN_TEST(test_replaceFunc_null_input_results_null);270 RUN_TEST(test_replaceFunc_null_replacement_results_null);271 RUN_TEST(test_replaceFunc_shrink_pattern);272 RUN_TEST(test_replaceFunc_overlap_non_overlapping_behavior);273 RUN_TEST(test_replaceFunc_expand_and_realloc);274 RUN_TEST(test_replaceFunc_too_big_sets_error_and_null_result);275 return UNITY_END();276}