AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <stdlib.h>4#include <string.h>5#include <stdio.h>6#include <math.h>7 8/* The target wrapper we are testing (provided in the module) */9extern void test_percentStep(sqlite3_context *pCtx, int argc, sqlite3_value **argv);10 11static sqlite3 *gDb = NULL;12 13/* Aggregate harness xStep: directly calls the target wrapper */14static void agg_step_forward(sqlite3_context *pCtx, int argc, sqlite3_value **argv){15 test_percentStep(pCtx, argc, argv);16}17 18/* Aggregate harness xFinal: just return a constant to indicate success */19static void agg_final_return_one(sqlite3_context *pCtx){20 sqlite3_result_int(pCtx, 1);21}22 23/* Scalar that returns +infinity to exercise "Inf input" checks */24static void scalar_make_inf(sqlite3_context *pCtx, int argc, sqlite3_value **argv){25 (void)argc; (void)argv;26 double inf = 1.0/0.0; /* +inf */27 sqlite3_result_double(pCtx, inf);28}29 30/* Helpers */31static void register_harness_functions(sqlite3 *db){32 int rc;33 34 /* pct1_harness(Y,P): P must be between 0.0 and 1.0 (user-data bit2 clear) */35 rc = sqlite3_create_function(db, "pct1_harness", 2, SQLITE_UTF8, (void*)0,36 NULL, agg_step_forward, agg_final_return_one);37 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);38 39 /* pct100_harness(Y,P): P must be between 0 and 100 (user-data bit2 set) */40 rc = sqlite3_create_function(db, "pct100_harness", 2, SQLITE_UTF8, (void*)2,41 NULL, agg_step_forward, agg_final_return_one);42 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);43 44 /* median_harness(Y): same as percentile(Y, 50) (argc==1 path) */45 rc = sqlite3_create_function(db, "median_harness", 1, SQLITE_UTF8, (void*)0,46 NULL, agg_step_forward, agg_final_return_one);47 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);48 49 /* Scalar to generate +inf */50 rc = sqlite3_create_function(db, "make_inf", 0, SQLITE_UTF8, NULL,51 scalar_make_inf, NULL, NULL);52 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);53}54 55/* Execute SQL and fetch single integer result (for success-path checks) */56static int exec_single_int(sqlite3 *db, const char *zSql, int *pOut){57 sqlite3_stmt *pStmt = NULL;58 int rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, NULL);59 if( rc!=SQLITE_OK ) return rc;60 rc = sqlite3_step(pStmt);61 if( rc==SQLITE_ROW ){62 if( pOut ) *pOut = sqlite3_column_int(pStmt, 0);63 rc = sqlite3_step(pStmt);64 /* Ensure only one row */65 if( rc==SQLITE_ROW ){66 sqlite3_finalize(pStmt);67 return SQLITE_MISUSE; /* More than one row, unexpected for these tests */68 }69 rc = SQLITE_OK;70 }else if( rc==SQLITE_DONE ){71 rc = SQLITE_OK; /* No rows - treat as OK but no output */72 }else{73 /* rc is an error code */74 }75 sqlite3_finalize(pStmt);76 return rc;77}78 79/* Execute SQL expecting an error, verify error message contains needle */80static void exec_expect_error_contains(sqlite3 *db, const char *zSql, const char *needle){81 char *zErr = NULL;82 int rc = sqlite3_exec(db, zSql, 0, 0, &zErr);83 TEST_ASSERT(rc!=SQLITE_OK);84 TEST_ASSERT_MESSAGE(zErr!=NULL, "Expected an error message but got NULL");85 if( strstr(zErr, needle)==NULL ){86 /* Build a helpful message */87 char buf[1024];88 snprintf(buf, sizeof(buf), "Error message did not contain expected substring.\nSQL: %s\nGot: %s\nExpected substring: %s",89 zSql, zErr, needle);90 TEST_FAIL_MESSAGE(buf);91 }92 sqlite3_free(zErr);93}94 95void setUp(void) {96 int rc = sqlite3_open(":memory:", &gDb);97 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);98 register_harness_functions(gDb);99}100 101void tearDown(void) {102 if( gDb ){103 sqlite3_close(gDb);104 gDb = NULL;105 }106}107 108/* Tests */109 110/* Median path (argc==1) basic success */111void test_percentStep_median_basic_success(void){112 int out = 0;113 int rc = exec_single_int(gDb,114 "SELECT median_harness(x) FROM (VALUES (1),(2),(3));",115 &out);116 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);117 TEST_ASSERT_EQUAL_INT(1, out);118}119 120/* pct1_harness: valid fraction within [0.0, 1.0] succeeds */121void test_percentStep_pct1_valid_fraction(void){122 int out = 0;123 int rc = exec_single_int(gDb,124 "SELECT pct1_harness(x, 0.25) FROM (VALUES (10),(20),(30));",125 &out);126 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);127 TEST_ASSERT_EQUAL_INT(1, out);128}129 130/* pct100_harness: valid fraction within [0, 100] succeeds */131void test_percentStep_pct100_valid_fraction(void){132 int out = 0;133 int rc = exec_single_int(gDb,134 "SELECT pct100_harness(x, 25) FROM (VALUES (1),(2));",135 &out);136 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);137 TEST_ASSERT_EQUAL_INT(1, out);138}139 140/* Non-numeric fraction argument: expect "not between 0.0 and 1.0" error */141void test_percentStep_fraction_not_numeric_pct1(void){142 exec_expect_error_contains(gDb,143 "SELECT pct1_harness(10, 'foo');",144 "the fraction argument to pct1_harness() is not between 0.0 and 1.0");145}146 147/* Fraction out of range low for [0.0,1.0] */148void test_percentStep_fraction_out_of_range_low_pct1(void){149 exec_expect_error_contains(gDb,150 "SELECT pct1_harness(10, -0.1);",151 "the fraction argument to pct1_harness() is not between 0.0 and 1.0");152}153 154/* Fraction out of range high for [0.0,1.0] */155void test_percentStep_fraction_out_of_range_high_pct1(void){156 exec_expect_error_contains(gDb,157 "SELECT pct1_harness(10, 1.1);",158 "the fraction argument to pct1_harness() is not between 0.0 and 1.0");159}160 161/* Fraction out of range high for [0,100] */162void test_percentStep_fraction_out_of_range_high_pct100(void){163 exec_expect_error_contains(gDb,164 "SELECT pct100_harness(10, 120);",165 "the fraction argument to pct100_harness() is not between 0.0 and 100.0");166}167 168/* Non-numeric Y input: expect "input to %s() is not numeric" */169void test_percentStep_input_not_numeric_Y(void){170 exec_expect_error_contains(gDb,171 "SELECT pct1_harness('abc', 0.5);",172 "input to pct1_harness() is not numeric");173}174 175/* Infinity Y input: expect "Inf input to %s()" */176void test_percentStep_infinity_input_Y(void){177 exec_expect_error_contains(gDb,178 "SELECT pct1_harness(make_inf(), 0.5);",179 "Inf input to pct1_harness()");180}181 182/* Inconsistent P across rows: expect consistency error */183void test_percentStep_inconsistent_fraction_across_rows(void){184 exec_expect_error_contains(gDb,185 "SELECT pct1_harness(x, p) "186 "FROM (SELECT 10 AS x, 0.25 AS p UNION ALL SELECT 20, 0.30);",187 "the fraction argument to pct1_harness() is not the same for all input rows");188}189 190/* NULL Y should be ignored and not cause errors */191void test_percentStep_null_Y_ignored(void){192 int out = 0;193 int rc = exec_single_int(gDb, "SELECT pct1_harness(NULL, 0.75);", &out);194 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);195 TEST_ASSERT_EQUAL_INT(1, out);196}197 198/* Median with NULLs should still be OK */199void test_percentStep_median_with_nulls(void){200 int out = 0;201 int rc = exec_single_int(gDb,202 "SELECT median_harness(x) FROM (VALUES (NULL),(NULL),(5));",203 &out);204 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);205 TEST_ASSERT_EQUAL_INT(1, out);206}207 208int main(void){209 UNITY_BEGIN();210 RUN_TEST(test_percentStep_median_basic_success);211 RUN_TEST(test_percentStep_pct1_valid_fraction);212 RUN_TEST(test_percentStep_pct100_valid_fraction);213 RUN_TEST(test_percentStep_fraction_not_numeric_pct1);214 RUN_TEST(test_percentStep_fraction_out_of_range_low_pct1);215 RUN_TEST(test_percentStep_fraction_out_of_range_high_pct1);216 RUN_TEST(test_percentStep_fraction_out_of_range_high_pct100);217 RUN_TEST(test_percentStep_input_not_numeric_Y);218 RUN_TEST(test_percentStep_infinity_input_Y);219 RUN_TEST(test_percentStep_inconsistent_fraction_across_rows);220 RUN_TEST(test_percentStep_null_Y_ignored);221 RUN_TEST(test_percentStep_median_with_nulls);222 return UNITY_END();223}