#include "sqliteInt.h" #include "unity.h" #include #include #include /* Wrapper for the static function provided by the build harness */ extern void test_errorMPrintf(sqlite3_context *pCtx, const char *zFmt, ...); /* Unity fixtures */ void setUp(void) { /* no-op */ } void tearDown(void) { /* no-op */ } /* Helpers to register UDFs that invoke test_errorMPrintf */ static void xErr_basic(sqlite3_context *ctx, int argc, sqlite3_value **argv){ /* Expect 4 args: text, int, int64, double */ const char *name = (const char*)sqlite3_value_text(argv[0]); int a = sqlite3_value_int(argv[1]); sqlite3_int64 b = sqlite3_value_int64(argv[2]); double c = sqlite3_value_double(argv[3]); test_errorMPrintf(ctx, "Name:%s Int:%d I64:%lld Dbl:%.2f", name, a, b, c); } static void xErr_percent(sqlite3_context *ctx, int argc, sqlite3_value **argv){ /* No args needed. Just a percent literal test. */ (void)argc; (void)argv; test_errorMPrintf(ctx, "Rate: %d%%", 100); } static void xErr_null(sqlite3_context *ctx, int argc, sqlite3_value **argv){ /* 1 arg: possibly NULL text */ const char *s = (const char*)sqlite3_value_text(argv[0]); /* may be NULL */ test_errorMPrintf(ctx, "S=%s", s); } static void xErr_long(sqlite3_context *ctx, int argc, sqlite3_value **argv){ /* 1 arg: long text string */ const char *s = (const char*)sqlite3_value_text(argv[0]); test_errorMPrintf(ctx, "Long:%s", s); } /* Utility: prepare, step and capture error message on failure. Returns a heap-allocated copy of sqlite3_errmsg(db) when step returns SQLITE_ERROR. Caller must sqlite3_free on the returned string (allocated with sqlite3_malloc). */ static char* step_and_capture_errmsg(sqlite3 *db, sqlite3_stmt *stmt, int *pRc){ int rc = sqlite3_step(stmt); if( pRc ) *pRc = rc; if( rc==SQLITE_ERROR ){ const char *z = sqlite3_errmsg(db); size_t n = strlen(z); char *copy = (char*)sqlite3_malloc((int)n+1); if( copy ){ memcpy(copy, z, n+1); } return copy; } return NULL; } /* Test 1: Basic formatting with multiple specifiers */ void test_errorMPrintf_formats_basic(void){ sqlite3 *db = 0; sqlite3_stmt *stmt = 0; int rc; rc = sqlite3_open(":memory:", &db); TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc); rc = sqlite3_create_function_v2(db, "t_basic", 4, SQLITE_UTF8, NULL, xErr_basic, NULL, NULL, NULL); TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc); const char *sql = "SELECT t_basic('Alice', 42, 1234567890123, 3.14159)"; rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc); int stepRc = SQLITE_OK; char *errmsg = step_and_capture_errmsg(db, stmt, &stepRc); TEST_ASSERT_EQUAL_INT(SQLITE_ERROR, stepRc); /* Expect rounding to two decimals for %.2f */ const char *expected = "Name:Alice Int:42 I64:1234567890123 Dbl:3.14"; TEST_ASSERT_NOT_NULL(errmsg); TEST_ASSERT_EQUAL_STRING(expected, errmsg); sqlite3_free(errmsg); sqlite3_finalize(stmt); sqlite3_close(db); } /* Test 2: Literal percent "%%" */ void test_errorMPrintf_handles_percent_literal(void){ sqlite3 *db = 0; sqlite3_stmt *stmt = 0; int rc; rc = sqlite3_open(":memory:", &db); TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc); rc = sqlite3_create_function_v2(db, "t_pct", 0, SQLITE_UTF8, NULL, xErr_percent, NULL, NULL, NULL); TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc); const char *sql = "SELECT t_pct()"; rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc); int stepRc = SQLITE_OK; char *errmsg = step_and_capture_errmsg(db, stmt, &stepRc); TEST_ASSERT_EQUAL_INT(SQLITE_ERROR, stepRc); const char *expected = "Rate: 100%"; TEST_ASSERT_NOT_NULL(errmsg); TEST_ASSERT_EQUAL_STRING(expected, errmsg); sqlite3_free(errmsg); sqlite3_finalize(stmt); sqlite3_close(db); } /* Test 3: NULL string is rendered as "(NULL)" by sqlite3_mprintf family */ void test_errorMPrintf_null_string_becomes_NULL_literal(void){ sqlite3 *db = 0; sqlite3_stmt *stmt = 0; int rc; rc = sqlite3_open(":memory:", &db); TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc); rc = sqlite3_create_function_v2(db, "t_null", 1, SQLITE_UTF8, NULL, xErr_null, NULL, NULL, NULL); TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc); const char *sql = "SELECT t_null(NULL)"; rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc); int stepRc = SQLITE_OK; char *errmsg = step_and_capture_errmsg(db, stmt, &stepRc); TEST_ASSERT_EQUAL_INT(SQLITE_ERROR, stepRc); const char *expected = "S=(NULL)"; TEST_ASSERT_NOT_NULL(errmsg); TEST_ASSERT_EQUAL_STRING(expected, errmsg); sqlite3_free(errmsg); sqlite3_finalize(stmt); sqlite3_close(db); } /* Test 4: Very long message to exercise allocation and correctness */ void test_errorMPrintf_long_message(void){ sqlite3 *db = 0; sqlite3_stmt *stmt = 0; int rc; /* Build a long input string */ const int N = 50000; char *longInput = (char*)malloc((size_t)N + 1); TEST_ASSERT_NOT_NULL(longInput); memset(longInput, 'A', (size_t)N); longInput[N] = '\0'; /* Expected: "Long:" + longInput */ size_t prefixLen = 5; /* "Long:" */ char *expected = (char*)malloc(prefixLen + (size_t)N + 1); TEST_ASSERT_NOT_NULL(expected); memcpy(expected, "Long:", prefixLen); memcpy(expected + prefixLen, longInput, (size_t)N + 1); rc = sqlite3_open(":memory:", &db); TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc); rc = sqlite3_create_function_v2(db, "t_long", 1, SQLITE_UTF8, NULL, xErr_long, NULL, NULL, NULL); TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc); rc = sqlite3_prepare_v2(db, "SELECT t_long(?)", -1, &stmt, NULL); TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc); rc = sqlite3_bind_text(stmt, 1, longInput, -1, SQLITE_TRANSIENT); TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc); int stepRc = SQLITE_OK; char *errmsg = step_and_capture_errmsg(db, stmt, &stepRc); TEST_ASSERT_EQUAL_INT(SQLITE_ERROR, stepRc); TEST_ASSERT_NOT_NULL(errmsg); TEST_ASSERT_EQUAL_STRING(expected, errmsg); sqlite3_free(errmsg); sqlite3_finalize(stmt); sqlite3_close(db); free(expected); free(longInput); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_errorMPrintf_formats_basic); RUN_TEST(test_errorMPrintf_handles_percent_literal); RUN_TEST(test_errorMPrintf_null_string_becomes_NULL_literal); RUN_TEST(test_errorMPrintf_long_message); return UNITY_END(); }