AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3 4#include <pthread.h>5#include <string.h>6#include <stdlib.h>7#include <stdio.h>8 9/* External symbols from the mutex module under test */10extern void test_checkMutexEnter(sqlite3_mutex *p);11extern void sqlite3MutexWarnOnContention(sqlite3_mutex *p);12 13/* Simple log capture structure */14typedef struct LogCapture {15 int count;16 int lastErr;17 char lastMsg[256];18} LogCapture;19 20static LogCapture gLog;21 22/* Log callback */23static void testLogCallback(void *pArg, int iErrCode, const char *zMsg){24 LogCapture *p = (LogCapture*)pArg;25 if( p ){26 p->count++;27 p->lastErr = iErrCode;28 if( zMsg ){29 /* Use SQLite's safe snprintf if available, otherwise standard */30 sqlite3_snprintf(sizeof(p->lastMsg), p->lastMsg, "%s", zMsg);31 }else{32 p->lastMsg[0] = '\0';33 }34 }35}36 37/* Thread context for creating contention */38typedef struct LockerCtx {39 sqlite3_mutex *p;40 volatile int holding; /* Set to 1 when thread holds the mutex */41 volatile int mainAttempting; /* Main sets to 1 before calling enter() */42} LockerCtx;43 44static void* lockerThread(void *arg){45 LockerCtx *ctx = (LockerCtx*)arg;46 /* Acquire and hold the mutex until main attempts to enter */47 sqlite3_mutex_enter(ctx->p);48 ctx->holding = 1;49 50 /* Wait until the main thread signals it is attempting to enter */51 while( ctx->mainAttempting==0 ){52 sqlite3_sleep(1);53 }54 55 /* Allow the main thread to certainly hit the try/enter path and block */56 sqlite3_sleep(50);57 58 /* Release so the main thread can proceed */59 sqlite3_mutex_leave(ctx->p);60 return NULL;61}62 63static void resetLog(void){64 memset(&gLog, 0, sizeof(gLog));65}66 67/* Allocate a recursive mutex, optionally mark warn-on-contention */68static sqlite3_mutex* alloc_recursive_mutex(int warnOnContention){69 sqlite3_mutex *p = sqlite3_mutex_alloc(SQLITE_MUTEX_RECURSIVE);70 TEST_ASSERT_NOT_NULL(p);71 if( warnOnContention ){72 sqlite3MutexWarnOnContention(p);73 }74 return p;75}76 77void setUp(void) {78 /* Ensure clean state and install log callback before initialization */79 sqlite3_shutdown();80 resetLog();81 TEST_ASSERT_EQUAL_INT(SQLITE_OK,82 sqlite3_config(SQLITE_CONFIG_LOG, testLogCallback, &gLog)83 );84 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_initialize());85}86 87void tearDown(void) {88 sqlite3_shutdown();89}90 91/* Test: No contention, warn-on-contention enabled -> no log, lock acquired */92void test_checkMutexEnter_no_contention_warn(void){93 sqlite3_mutex *p = alloc_recursive_mutex(1);94 95 resetLog();96 test_checkMutexEnter(p); /* Should acquire via Try without logging */97 TEST_ASSERT_EQUAL_INT(0, gLog.count);98 99 /* Release and free */100 sqlite3_mutex_leave(p);101 sqlite3_mutex_free(p);102}103 104/* Test: Contention exists, warn-on-contention enabled -> logs once and then enters */105void test_checkMutexEnter_contention_warn_logs_once(void){106 sqlite3_mutex *p = alloc_recursive_mutex(1);107 108 LockerCtx ctx;109 memset(&ctx, 0, sizeof(ctx));110 ctx.p = p;111 112 pthread_t th;113 int rc = pthread_create(&th, NULL, lockerThread, &ctx);114 TEST_ASSERT_EQUAL_INT(0, rc);115 116 /* Wait until the worker holds the mutex */117 int waited = 0;118 while( ctx.holding==0 && waited<5000 ){119 sqlite3_sleep(1);120 waited++;121 }122 TEST_ASSERT_TRUE_MESSAGE(ctx.holding==1, "Worker failed to acquire mutex");123 124 resetLog();125 ctx.mainAttempting = 1;126 127 /* This should detect contention, log SQLITE_MISUSE, then block until release */128 test_checkMutexEnter(p);129 130 /* After return, we own the mutex */131 TEST_ASSERT_TRUE(gLog.count >= 1);132 TEST_ASSERT_EQUAL_INT(SQLITE_MISUSE, gLog.lastErr);133 /* Verify message content */134 TEST_ASSERT_NOT_NULL(gLog.lastMsg);135 TEST_ASSERT_TRUE(strstr(gLog.lastMsg, "illegal multi-threaded access to database connection") != NULL);136 137 /* Clean up: release our lock, join thread, free mutex */138 sqlite3_mutex_leave(p);139 pthread_join(th, NULL);140 sqlite3_mutex_free(p);141}142 143/* Test: Contention exists, no warn-on-contention -> no log, then enters */144void test_checkMutexEnter_contention_no_warn_no_log(void){145 sqlite3_mutex *p = alloc_recursive_mutex(0);146 147 LockerCtx ctx;148 memset(&ctx, 0, sizeof(ctx));149 ctx.p = p;150 151 pthread_t th;152 int rc = pthread_create(&th, NULL, lockerThread, &ctx);153 TEST_ASSERT_EQUAL_INT(0, rc);154 155 /* Wait until the worker holds the mutex */156 int waited = 0;157 while( ctx.holding==0 && waited<5000 ){158 sqlite3_sleep(1);159 waited++;160 }161 TEST_ASSERT_TRUE_MESSAGE(ctx.holding==1, "Worker failed to acquire mutex");162 163 resetLog();164 ctx.mainAttempting = 1;165 166 /* With no warn-on-contention, should block and acquire without logging */167 test_checkMutexEnter(p);168 169 TEST_ASSERT_EQUAL_INT(0, gLog.count);170 171 /* Clean up */172 sqlite3_mutex_leave(p);173 pthread_join(th, NULL);174 sqlite3_mutex_free(p);175}176 177int main(void) {178 UNITY_BEGIN();179 RUN_TEST(test_checkMutexEnter_no_contention_warn);180 RUN_TEST(test_checkMutexEnter_contention_warn_logs_once);181 RUN_TEST(test_checkMutexEnter_contention_no_warn_no_log);182 return UNITY_END();183}