AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <string.h>4#include <stdio.h>5 6/* Global test context */7static sqlite3 *gDb = NULL;8static Btree *gBt = NULL;9static Pgno gRoot = 0;10 11/* Wrapper for the static function under test (provided by build as per instructions) */12extern int test_btreeRestoreCursorPosition(BtCursor *pCur);13 14/* Helper: exec callback to capture integer from first column */15static int cb_int(void *pArg, int nCol, char **azVal, char **azCol){16 (void)nCol; (void)azCol;17 if( azVal && azVal[0] ){18 *(int*)pArg = atoi(azVal[0]);19 }20 return 0;21}22 23static void open_table_and_fill(void){24 char *zErr = NULL;25 int rc;26 27 rc = sqlite3_exec(gDb,28 "PRAGMA page_size=4096;"29 "CREATE TABLE t(x INTEGER PRIMARY KEY, y TEXT);"30 "INSERT INTO t(x,y) VALUES (1,'a'),(2,'b'),(4,'d');",31 0, 0, &zErr32 );33 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, zErr ? zErr : "exec failed");34 if( zErr ) sqlite3_free(zErr);35 36 /* Query rootpage of table t */37 int root = 0;38 rc = sqlite3_exec(gDb,39 "SELECT rootpage FROM sqlite_master WHERE type='table' AND name='t';",40 cb_int, &root, &zErr41 );42 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, zErr ? zErr : "root exec failed");43 if( zErr ) sqlite3_free(zErr);44 TEST_ASSERT_MESSAGE(root>1, "Expected valid root page > 1");45 gRoot = (Pgno)root;46}47 48static BtCursor* open_btree_cursor_readonly(void){49 int rc;50 int schemaVersion = 0;51 BtCursor *pCur = NULL;52 53 /* Must hold the db mutex for Btree APIs */54 sqlite3_mutex_enter(gDb->mutex);55 56 /* Begin a read transaction */57 rc = sqlite3BtreeBeginTrans(gBt, 0, &schemaVersion);58 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);59 60 int sz = sqlite3BtreeCursorSize();61 pCur = (BtCursor*)sqlite3MallocZero(sz);62 TEST_ASSERT_NOT_NULL(pCur);63 sqlite3BtreeCursorZero(pCur);64 65 rc = sqlite3BtreeCursor(gBt, gRoot, 0, NULL, pCur);66 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);67 68 /* Position at first entry to initialize internal fields */69 int isEmpty = 0;70 rc = sqlite3BtreeFirst(pCur, &isEmpty);71 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);72 TEST_ASSERT_EQUAL_INT(0, isEmpty);73 74 sqlite3_mutex_leave(gDb->mutex);75 return pCur;76}77 78static void close_btree_cursor(BtCursor *pCur){79 int rc;80 sqlite3_mutex_enter(gDb->mutex);81 rc = sqlite3BtreeCloseCursor(pCur);82 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);83 sqlite3_mutex_leave(gDb->mutex);84 sqlite3_free(pCur);85}86 87/* Unity setUp/tearDown */88void setUp(void) {89 int rc = sqlite3_open(":memory:", &gDb);90 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);91 TEST_ASSERT_NOT_NULL(gDb);92 93 /* Access internal Btree for main db */94 gBt = gDb->aDb[0].pBt;95 TEST_ASSERT_NOT_NULL(gBt);96 97 open_table_and_fill();98}99 100void tearDown(void) {101 if( gDb ){102 int rc = sqlite3_close(gDb);103 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);104 gDb = NULL;105 gBt = NULL;106 gRoot = 0;107 }108}109 110/* Test 1: If cursor is in CURSOR_FAULT state, function returns skipNext value */111void test_btreeRestoreCursorPosition_fault_returns_skipNext(void){112 BtCursor *pCur = open_btree_cursor_readonly();113 114 /* Put cursor into fault state */115 sqlite3_mutex_enter(gDb->mutex);116 sqlite3BtreeEnter(gBt);117 118 pCur->eState = CURSOR_FAULT;119 pCur->skipNext = -12345;120 121 int rc = test_btreeRestoreCursorPosition(pCur);122 123 sqlite3BtreeLeave(gBt);124 sqlite3_mutex_leave(gDb->mutex);125 126 TEST_ASSERT_EQUAL_INT(-12345, rc);127 128 close_btree_cursor(pCur);129}130 131/* Test 2: REQUIRESEEK to an existing rowid seeks correctly and yields VALID state */132void test_btreeRestoreCursorPosition_seek_existing_rowid(void){133 BtCursor *pCur = open_btree_cursor_readonly();134 135 /* Prepare cursor to require seek to existing key 2 */136 sqlite3_mutex_enter(gDb->mutex);137 sqlite3BtreeEnter(gBt);138 139 pCur->eState = CURSOR_REQUIRESEEK;140 pCur->nKey = 2; /* existing rowid */141 pCur->pKey = NULL;142 pCur->skipNext = 0;143 144 int rc = test_btreeRestoreCursorPosition(pCur);145 146 sqlite3BtreeLeave(gBt);147 sqlite3_mutex_leave(gDb->mutex);148 149 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);150 TEST_ASSERT_EQUAL_INT(CURSOR_VALID, pCur->eState);151 /* Confirm pointing at rowid 2 */152 sqlite3_mutex_enter(gDb->mutex);153 sqlite3BtreeEnter(gBt);154 i64 k = sqlite3BtreeIntegerKey(pCur);155 sqlite3BtreeLeave(gBt);156 sqlite3_mutex_leave(gDb->mutex);157 TEST_ASSERT_EQUAL_INT64(2, k);158 159 close_btree_cursor(pCur);160}161 162/* Test 3: REQUIRESEEK to a non-existing rowid results in SKIPNEXT state */163void test_btreeRestoreCursorPosition_seek_nonexisting_rowid_sets_skipnext(void){164 BtCursor *pCur = open_btree_cursor_readonly();165 166 /* We have rows 1,2,4. Seek to 3 (non-existent). */167 sqlite3_mutex_enter(gDb->mutex);168 sqlite3BtreeEnter(gBt);169 170 pCur->eState = CURSOR_REQUIRESEEK;171 pCur->nKey = 3; /* non-existing rowid between 2 and 4 */172 pCur->pKey = NULL;173 pCur->skipNext = 0;174 175 int rc = test_btreeRestoreCursorPosition(pCur);176 177 sqlite3BtreeLeave(gBt);178 sqlite3_mutex_leave(gDb->mutex);179 180 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);181 TEST_ASSERT_EQUAL_INT(CURSOR_SKIPNEXT, pCur->eState);182 TEST_ASSERT_TRUE_MESSAGE(pCur->skipNext==1 || pCur->skipNext==-1,183 "skipNext should be +/-1 for non-existing key");184 185 /* Verify cursor is on neighbor: either 2 (skipNext<0) or 4 (skipNext>0) */186 sqlite3_mutex_enter(gDb->mutex);187 sqlite3BtreeEnter(gBt);188 i64 k = sqlite3BtreeIntegerKey(pCur);189 sqlite3BtreeLeave(gBt);190 sqlite3_mutex_leave(gDb->mutex);191 192 if( pCur->skipNext < 0 ){193 TEST_ASSERT_EQUAL_INT64(2, k);194 }else{195 TEST_ASSERT_EQUAL_INT64(4, k);196 }197 198 close_btree_cursor(pCur);199}200 201int main(void){202 UNITY_BEGIN();203 RUN_TEST(test_btreeRestoreCursorPosition_fault_returns_skipNext);204 RUN_TEST(test_btreeRestoreCursorPosition_seek_existing_rowid);205 RUN_TEST(test_btreeRestoreCursorPosition_seek_nonexisting_rowid_sets_skipnext);206 return UNITY_END();207}