From aede9b5bd3cfaaa36f81a4a435ef623cdafc2670 Mon Sep 17 00:00:00 2001 From: dan Date: Mon, 12 Sep 2022 19:23:50 +0000 Subject: [PATCH] More tests for the recover module. FossilOrigin-Name: 37fb093b95c6b7d7ad07a275697df73b69f9fb5c5549aea8544b26e38f24833f --- ext/recover/recovercorrupt.test | 68 ++++++++++++++++++++++ ext/recover/recoverfault.test | 72 +++++++++++++++++++++++ ext/recover/sqlite3recover.c | 100 +++++++++++++++++++------------- ext/recover/test_recover.c | 4 +- manifest | 16 ++--- manifest.uuid | 2 +- 6 files changed, 211 insertions(+), 51 deletions(-) create mode 100644 ext/recover/recovercorrupt.test create mode 100644 ext/recover/recoverfault.test diff --git a/ext/recover/recovercorrupt.test b/ext/recover/recovercorrupt.test new file mode 100644 index 0000000000..ff1d2af19f --- /dev/null +++ b/ext/recover/recovercorrupt.test @@ -0,0 +1,68 @@ +# 2022 August 28 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] recover_common.tcl] +source $testdir/tester.tcl + +set testprefix recovercorrupt + +do_execsql_test 1.0 { + PRAGMA page_size = 512; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES(2, hex(randomblob(100)), randomblob(200)); + CREATE INDEX i1 ON t1(b, c); + CREATE TABLE t2(a PRIMARY KEY, b, c) WITHOUT ROWID; + INSERT INTO t2 VALUES(1, 2, 3); + INSERT INTO t2 VALUES(2, hex(randomblob(100)), randomblob(200)); + ANALYZE; + PRAGMA writable_schema = 1; + DELETE FROM sqlite_schema WHERE name='t2'; +} + +do_test 1.1 { + file size test.db +} {5120} + +proc toggle_bit {blob bit} { + set byte [expr {$bit / 8}] + set bit [expr {$bit & 0x0F}] + binary scan $blob a${byte}ca* A x B + set x [expr {$x ^ (1 << $bit)}] + binary format a*ca* $A $x $B +} + + +db_save_and_close +for {set ii 200} {$ii < 10000} {incr ii} { + db_restore_and_reopen + db func toggle_bit toggle_bit + set pg [expr {($ii / 512)+1}] + set byte [expr {$ii % 512}] + db eval { + UPDATE sqlite_dbpage SET data = toggle_bit(data, $byte) WHERE pgno=$pg + } + + do_test 1.2.$ii { + set R [sqlite3_recover_init db main test.db2] + $R config lostandfound lost_and_found + $R step + $R finish + } {} +} + + +finish_test + diff --git a/ext/recover/recoverfault.test b/ext/recover/recoverfault.test new file mode 100644 index 0000000000..b2411fb6c7 --- /dev/null +++ b/ext/recover/recoverfault.test @@ -0,0 +1,72 @@ +# 2022 August 28 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] recover_common.tcl] +source $testdir/tester.tcl + +set testprefix recoverfault + + +#-------------------------------------------------------------------------- +proc compare_result {db1 db2 sql} { + set r1 [$db1 eval $sql] + set r2 [$db2 eval $sql] + if {$r1 != $r2} { + puts "r1: $r1" + puts "r2: $r2" + error "mismatch for $sql" + } + return "" +} + +proc compare_dbs {db1 db2} { + compare_result $db1 $db2 "SELECT sql FROM sqlite_master ORDER BY 1" + foreach tbl [$db1 eval {SELECT name FROM sqlite_master WHERE type='table'}] { + compare_result $db1 $db2 "SELECT * FROM $tbl" + } +} +#-------------------------------------------------------------------------- + +do_execsql_test 1.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES(2, hex(randomblob(1000)), randomblob(2000)); + CREATE INDEX i1 ON t1(b, c); + CREATE TABLE t2(a INTEGER PRIMARY KEY, b, c); + INSERT INTO t2 VALUES(1, 2, 3); + INSERT INTO t2 VALUES(2, hex(randomblob(1000)), randomblob(2000)); + ANALYZE; + PRAGMA writable_schema = 1; + DELETE FROM sqlite_schema WHERE name='t2'; +} + +do_faultsim_test 1 -faults oom* -prep { + faultsim_restore_and_reopen +} -body { + set R [sqlite3_recover_init db main test.db2] + $R config lostandfound lost_and_found + $R step + $R finish +} -test { + faultsim_test_result {0 {}} {1 {}} + if {$testrc==0} { + sqlite3 db2 test.db2 + compare_dbs db db2 + db2 close + } +} + +finish_test + diff --git a/ext/recover/sqlite3recover.c b/ext/recover/sqlite3recover.c index 9c5e10433b..849c3bc801 100644 --- a/ext/recover/sqlite3recover.c +++ b/ext/recover/sqlite3recover.c @@ -226,12 +226,13 @@ static int recoverError( int errCode, const char *zFmt, ... ){ + char *z = 0; va_list ap; - char *z; va_start(ap, zFmt); - z = sqlite3_vmprintf(zFmt, ap); - va_end(ap); - + if( zFmt ){ + z = sqlite3_vmprintf(zFmt, ap); + va_end(ap); + } sqlite3_free(p->zErrMsg); p->zErrMsg = z; p->errCode = errCode; @@ -448,7 +449,8 @@ static i64 recoverPageCount(sqlite3_recover *p){ if( p->errCode==SQLITE_OK ){ sqlite3_stmt *pStmt = 0; pStmt = recoverPreparePrintf(p, p->dbIn, "PRAGMA %Q.page_count", p->zDb); - if( pStmt && SQLITE_ROW==sqlite3_step(pStmt) ){ + if( pStmt ){ + sqlite3_step(pStmt); nPg = sqlite3_column_int64(pStmt, 0); } recoverFinalize(p, pStmt); @@ -866,11 +868,11 @@ static void recoverAddTable( pNew->pNext = p->pTblList; p->pTblList = pNew; + pNew->bIntkey = 1; } recoverFinalize(p, pStmt); - pNew->bIntkey = 1; pStmt = recoverPreparePrintf(p, p->dbOut, "PRAGMA index_xinfo(%Q)", zName); while( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){ int iField = sqlite3_column_int(pStmt, 0); @@ -884,10 +886,12 @@ static void recoverAddTable( } recoverFinalize(p, pStmt); - if( iPk>=0 ){ - pNew->aCol[iPk].bIPK = 1; - }else if( pNew->bIntkey ){ - pNew->iRowidBind = iBind++; + if( p->errCode==SQLITE_OK ){ + if( iPk>=0 ){ + pNew->aCol[iPk].bIPK = 1; + }else if( pNew->bIntkey ){ + pNew->iRowidBind = iBind++; + } } } } @@ -1068,6 +1072,7 @@ static sqlite3_stmt *recoverInsertStmt( RecoverTable *pTab, int nField ){ + sqlite3_stmt *pRet = 0; const char *zSep = ""; const char *zSqlSep = ""; char *zSql = 0; @@ -1075,7 +1080,8 @@ static sqlite3_stmt *recoverInsertStmt( char *zBind = 0; int ii; int bSql = p->xSql ? 1 : 0; - sqlite3_stmt *pRet = 0; + + if( nField<=0 ) return 0; assert( nField<=pTab->nCol ); @@ -1525,7 +1531,7 @@ static int recoverWriteData(sqlite3_recover *p){ int bNewCell = (iPrevPage!=iPage || iPrevCell!=iCell); assert( bNewCell==0 || (iField==-1 || iField==0) ); - assert( bNewCell || iField==nVal ); + assert( bNewCell || iField==nVal || nVal==pTab->nCol ); if( bNewCell ){ if( nVal>=0 ){ @@ -1538,29 +1544,31 @@ static int recoverWriteData(sqlite3_recover *p){ pInsert = recoverInsertStmt(p, pTab, nVal); nInsert = nVal; } + if( nVal>0 ){ + for(ii=0; iinCol; ii++){ + RecoverColumn *pCol = &pTab->aCol[ii]; - for(ii=0; iinCol; ii++){ - RecoverColumn *pCol = &pTab->aCol[ii]; - - if( pCol->iBind>0 ){ - if( pCol->bIPK ){ - sqlite3_bind_int64(pInsert, pCol->iBind, iRowid); - }else if( pCol->iFieldiBind,apVal[pCol->iField]); + if( pCol->iBind>0 ){ + int iBind = pCol->iBind; + if( pCol->bIPK ){ + sqlite3_bind_int64(pInsert, iBind, iRowid); + }else if( pCol->iFieldiField]); + } } } - } - if( p->bRecoverRowid && pTab->iRowidBind>0 && bHaveRowid ){ - sqlite3_bind_int64(pInsert, pTab->iRowidBind, iRowid); - } + if( p->bRecoverRowid && pTab->iRowidBind>0 && bHaveRowid ){ + sqlite3_bind_int64(pInsert, pTab->iRowidBind, iRowid); + } - if( SQLITE_ROW==sqlite3_step(pInsert) && p->xSql ){ - const char *zSql = (const char*)sqlite3_column_text(pInsert, 0); - recoverSqlCallback(p, zSql); + if( SQLITE_ROW==sqlite3_step(pInsert) && p->xSql ){ + const char *z = (const char*)sqlite3_column_text(pInsert, 0); + recoverSqlCallback(p, z); + } + recoverReset(p, pInsert); + assert( p->errCode || pInsert ); + if( pInsert ) sqlite3_clear_bindings(pInsert); } - recoverReset(p, pInsert); - assert( p->errCode || pInsert ); - if( pInsert ) sqlite3_clear_bindings(pInsert); } for(ii=0; iinCol ){ assert( apVal[iField]==0 ); apVal[iField] = sqlite3_value_dup( pVal ); + if( apVal[iField]==0 ){ + recoverError(p, SQLITE_NOMEM, 0); + } nVal = iField+1; } iPrevCell = iCell; @@ -1686,7 +1697,6 @@ sqlite3_recover *recoverInit( int nByte = 0; if( zDb==0 ){ zDb = "main"; } - if( zUri==0 ){ zUri = ""; } nDb = recoverStrlen(zDb); nUri = recoverStrlen(zUri); @@ -1699,7 +1709,7 @@ sqlite3_recover *recoverInit( pRet->zDb = (char*)&pRet[1]; pRet->zUri = &pRet->zDb[nDb+1]; memcpy(pRet->zDb, zDb, nDb); - memcpy(pRet->zUri, zUri, nUri); + if( nUri>0 ) memcpy(pRet->zUri, zUri, nUri); pRet->xSql = xSql; pRet->pSqlCtx = pSqlCtx; pRet->bRecoverRowid = RECOVER_ROWID_DEFAULT; @@ -1730,14 +1740,14 @@ sqlite3_recover *sqlite3_recover_init_sql( int (*xSql)(void*, const char*), void *pSqlCtx ){ - return recoverInit(db, zDb, "", xSql, pSqlCtx); + return recoverInit(db, zDb, 0, xSql, pSqlCtx); } /* ** Return the handle error message, if any. */ const char *sqlite3_recover_errmsg(sqlite3_recover *p){ - return p ? p->zErrMsg : "not an error"; + return (p && p->errCode!=SQLITE_NOMEM) ? p->zErrMsg : "out of memory"; } /* @@ -1753,6 +1763,7 @@ int sqlite3_recover_errcode(sqlite3_recover *p){ int sqlite3_recover_config(sqlite3_recover *p, int op, void *pArg){ int rc = SQLITE_OK; + if( p==0 ) return SQLITE_NOMEM; switch( op ){ case 789: sqlite3_free(p->zStateDb); @@ -1791,8 +1802,12 @@ int sqlite3_recover_config(sqlite3_recover *p, int op, void *pArg){ */ int sqlite3_recover_run(sqlite3_recover *p){ if( p ){ + recoverExec(p, p->dbIn, "PRAGMA writable_schema=1"); if( p->bRun ) return SQLITE_MISUSE; /* Has already run */ if( p->errCode==SQLITE_OK ) recoverRun(p); + if( sqlite3_exec(p->dbIn, "PRAGMA writable_schema=0", 0, 0, 0) ){ + recoverDbError(p, p->dbIn); + } } return p ? p->errCode : SQLITE_NOMEM; } @@ -1807,11 +1822,16 @@ int sqlite3_recover_run(sqlite3_recover *p){ ** not been called on this handle. */ int sqlite3_recover_finish(sqlite3_recover *p){ - int rc = p->errCode; - sqlite3_free(p->zErrMsg); - sqlite3_free(p->zStateDb); - sqlite3_free(p->zLostAndFound); - sqlite3_free(p); + int rc; + if( p==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = p->errCode; + sqlite3_free(p->zErrMsg); + sqlite3_free(p->zStateDb); + sqlite3_free(p->zLostAndFound); + sqlite3_free(p); + } return rc; } diff --git a/ext/recover/test_recover.c b/ext/recover/test_recover.c index a31ddbb94a..a7c5afd3aa 100644 --- a/ext/recover/test_recover.c +++ b/ext/recover/test_recover.c @@ -165,9 +165,7 @@ static int testRecoverCmd( int res2; if( res!=SQLITE_OK ){ const char *zErr = sqlite3_recover_errmsg(pTest->p); - char *zRes = sqlite3_mprintf("(%d) - %s", res, zErr); - Tcl_SetObjResult(interp, Tcl_NewStringObj(zRes, -1)); - sqlite3_free(zRes); + Tcl_SetObjResult(interp, Tcl_NewStringObj(zErr, -1)); } res2 = sqlite3_recover_finish(pTest->p); assert( res2==res ); diff --git a/manifest b/manifest index d505905e12..c61b4a13ae 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Rework\srecover\sextension\scode\sfor\sreadability. -D 2022-09-10T20:01:49.308 +C More\stests\sfor\sthe\srecover\smodule. +D 2022-09-12T19:23:50.588 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -390,11 +390,13 @@ F ext/rbu/test_rbu.c 03f6f177096a5f822d68d8e4069ad8907fe572c62ff2d19b141f5974282 F ext/recover/recover1.test 942016356f9098ca36933536b194b5878827a3a749e0bf41a83d83530c0d0ea8 F ext/recover/recover_common.tcl 6679af7dffc858e345053a91c9b0a897595b4a13007aceffafca75304ccb137c F ext/recover/recoverclobber.test e6537ebf99f57bfff6cca59550b5f4278319b57a89865abb98d755a8fd561d84 +F ext/recover/recovercorrupt.test 115cdb67ac29b4e8ec786cee9190ced674f62388f126b20deea22fa5fd11b814 +F ext/recover/recoverfault.test 30e3d1b423b33b4c57f4a97f9754b2d493a411d858b2672ab369d65199bc2420 F ext/recover/recoverold.test f368a6ae2db12b6017257b332a19ab5df527f4061e43f12f5c85d8e2b236f074 F ext/recover/recoverrowid.test ec4436cd69e6cdacb48dd2963ff6dd9dbd5fe648376de5e7c0c2f4f6cbacb417 -F ext/recover/sqlite3recover.c 4ed53fd33639ede83505f4397b8e1e46b6c0c5a5188c42f28f8372487141170a +F ext/recover/sqlite3recover.c d40d7c68a118e01d2b06c96325bbe3b85701f3add2ab5aeec289eeafd5e36d6c F ext/recover/sqlite3recover.h 81108efb8c4618d3d9c6da4df785212b0e4501aa0d25edfc463405fe839a6640 -F ext/recover/test_recover.c ed8d0cc8703ab29cf562f793623b045de109b7937f254108ff4132f35abb37fb +F ext/recover/test_recover.c 8f5ef0c9b7523c41a393f65e44d727c23cda8f44d5180fff5b698ee068ba538d F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15 F ext/repair/checkfreelist.c e21f06995ff4efdc1622dcceaea4dcba2caa83ca2f31a1607b98a8509168a996 F ext/repair/checkindex.c 4383e4469c21e5b9ae321d0d63cec53e981af9d7a6564be6374f0eeb93dfc890 @@ -2007,8 +2009,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 599d1f8ec2f9e24924a6f9e66c85664360c7b95531b07a4efe1dd8c096b3fc99 -R cd56b05532a91c08b9e673fffba829f5 +P 1a2540960e40e3c8c622448fd3862e249bd463c29ae4ce5e39942e942533f60a +R 91d8f87cd1493a401d98e4544c1e3f6e U dan -Z 624cfda4b7c658530a41a87671068e55 +Z d3a21f62871d8e066c6c2bd48e5f7060 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 98f37f0563..340320e0cf 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -1a2540960e40e3c8c622448fd3862e249bd463c29ae4ce5e39942e942533f60a \ No newline at end of file +37fb093b95c6b7d7ad07a275697df73b69f9fb5c5549aea8544b26e38f24833f \ No newline at end of file