From 12ca0b5695b874340a4c3e3a45b3d951cd0840ce Mon Sep 17 00:00:00 2001 From: dan Date: Mon, 21 Mar 2011 16:17:42 +0000 Subject: [PATCH] Add start of fault-injection tests for session module. Fix some bugs related to the same. FossilOrigin-Name: 32e95164d1192b87b1ab019549fd2394642cd3fe --- ext/session/session2.test | 106 ++++++++++++--------------------- ext/session/session_common.tcl | 106 +++++++++++++++++++++++++++++++++ ext/session/sessionfault.test | 85 ++++++++++++++++++++++++++ ext/session/sqlite3session.c | 83 ++++++++++++++------------ manifest | 18 +++--- manifest.uuid | 2 +- src/vdbeapi.c | 22 +++---- 7 files changed, 299 insertions(+), 123 deletions(-) create mode 100644 ext/session/session_common.tcl create mode 100644 ext/session/sessionfault.test diff --git a/ext/session/session2.test b/ext/session/session2.test index 357b806043..9065df99a0 100644 --- a/ext/session/session2.test +++ b/ext/session/session2.test @@ -28,67 +28,6 @@ proc test_reset {} { sqlite3 db2 test.db2 } -proc do_common_sql {sql} { - execsql $sql db - execsql $sql db2 -} -proc xConflict args { return "OMIT" } - -proc do_then_apply_sql {sql {dbname main}} { - - sqlite3session S db $dbname - db eval "SELECT name FROM $dbname.sqlite_master WHERE type = 'table'" { - S attach $name - } - - db eval $sql - sqlite3changeset_apply db2 [S changeset] xConflict - S delete -} - -proc do_iterator_test {tn tbl_list sql res} { - sqlite3session S db main - foreach t $tbl_list {S attach $t} - execsql $sql - - set r [list] - foreach v $res { lappend r $v } - - set x [list] - sqlite3session_foreach c [S changeset] { lappend x $c } - uplevel do_test $tn [list [list set {} $x]] [list $r] - - S delete -} - -# Compare the contents of all tables in [db1] and [db2]. Throw an error if -# they are not identical, or return an empty string if they are. -# -proc compare_db {db1 db2} { - - set sql {SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name} - set lot1 [$db1 eval $sql] - set lot2 [$db2 eval $sql] - - if {$lot1 != $lot2} { error "databases contain different tables" } - - foreach tbl $lot1 { - set col1 [list] - set col2 [list] - - $db1 eval "PRAGMA table_info = $tbl" { lappend col1 $name } - $db2 eval "PRAGMA table_info = $tbl" { lappend col2 $name } - if {$col1 != $col2} { error "table $tbl schema mismatch" } - - set sql "SELECT * FROM $tbl ORDER BY [join $col1 ,]" - set data1 [$db1 eval $sql] - set data2 [$db2 eval $sql] - if {$data1 != $data2} { error "table $tbl data mismatch" } - } - - return "" -} - ########################################################################## # End of proc definitions. Start of tests. ########################################################################## @@ -105,12 +44,13 @@ do_iterator_test 1.1 t1 { {DELETE t1 {t i t one} {}} {INSERT t1 {} {t ii t two}} } -do_iterator_test 1.1 t1 { +do_iterator_test 1.2 t1 { INSERT INTO t1 VALUES(1.5, 99.9) } { {INSERT t1 {} {f 1.5 f 99.9}} } + # Execute each of the following blocks of SQL on database [db1]. Collect # changes using a session object. Apply the resulting changeset to # database [db2]. Then check that the contents of the two databases are @@ -178,6 +118,38 @@ set set_of_tests { 12 { INSERT INTO %T2% VALUES(NULL, NULL); } + + 13 { + DELETE FROM %T1% WHERE 1; + + -- Insert many rows with real primary keys. Enough to force the session + -- objects hash table to resize. + INSERT INTO %T1% VALUES(0.1, 0.1); + INSERT INTO %T1% SELECT a+0.1, b+0.1 FROM %T1%; + INSERT INTO %T1% SELECT a+0.2, b+0.2 FROM %T1%; + INSERT INTO %T1% SELECT a+0.4, b+0.4 FROM %T1%; + INSERT INTO %T1% SELECT a+0.8, b+0.8 FROM %T1%; + INSERT INTO %T1% SELECT a+1.6, b+1.6 FROM %T1%; + INSERT INTO %T1% SELECT a+3.2, b+3.2 FROM %T1%; + INSERT INTO %T1% SELECT a+6.4, b+6.4 FROM %T1%; + INSERT INTO %T1% SELECT a+12.8, b+12.8 FROM %T1%; + INSERT INTO %T1% SELECT a+25.6, b+25.6 FROM %T1%; + INSERT INTO %T1% SELECT a+51.2, b+51.2 FROM %T1%; + INSERT INTO %T1% SELECT a+102.4, b+102.4 FROM %T1%; + INSERT INTO %T1% SELECT a+204.8, b+204.8 FROM %T1%; + } + + 14 { + DELETE FROM %T1% WHERE 1; + } + + 15 { + INSERT INTO %T1% VALUES(1, 1); + INSERT INTO %T1% SELECT a+2, b+2 FROM %T1%; + INSERT INTO %T1% SELECT a+4, b+4 FROM %T1%; + INSERT INTO %T1% SELECT a+8, b+8 FROM %T1%; + INSERT INTO %T1% SELECT a+256, b+256 FROM %T1%; + } } test_reset @@ -189,7 +161,7 @@ do_common_sql { foreach {tn sql} [string map {%T1% t1 %T2% t2 %T3% t3} $set_of_tests] { do_then_apply_sql $sql - do_test 1.$tn { compare_db db db2 } {} + do_test 2.$tn { compare_db db db2 } {} } # The following block of tests is similar to the last, except that the @@ -200,7 +172,7 @@ foreach {tn sql} [string map {%T1% t1 %T2% t2 %T3% t3} $set_of_tests] { test_reset forcedelete test.db3 sqlite3 db3 test.db3 -do_test 2.0 { +do_test 3.0 { execsql { ATTACH 'test.db3' AS 'aux'; CREATE TABLE t1(a, b PRIMARY KEY); @@ -224,7 +196,7 @@ foreach {tn sql} [ string map {%T1% aux.t1 %T2% aux.t2 %T3% aux.t3} $set_of_tests ] { do_then_apply_sql $sql aux - do_test 2.$tn { compare_db db3 db2 } {} + do_test 3.$tn { compare_db db3 db2 } {} } catch {db3 close} @@ -234,7 +206,7 @@ catch {db3 close} # handled correctly by the session module. # test_reset -do_execsql_test 3.0 { +do_execsql_test 4.0 { CREATE TABLE t1(a PRIMARY KEY); CREATE TABLE t2(a, b, c, PRIMARY KEY(c, b)); CREATE TABLE t3(a, b INTEGER PRIMARY KEY); @@ -272,7 +244,7 @@ foreach {tn sql changeset} { 9 { DELETE FROM t2 WHERE 1 } { {DELETE t2 {i 1 i 2 i 3} {}} } } { - do_iterator_test 3.$tn {t1 t2 t3} $sql $changeset + do_iterator_test 4.$tn {t1 t2 t3} $sql $changeset } diff --git a/ext/session/session_common.tcl b/ext/session/session_common.tcl new file mode 100644 index 0000000000..1b5ebc5b97 --- /dev/null +++ b/ext/session/session_common.tcl @@ -0,0 +1,106 @@ + + +proc do_conflict_test {tn args} { + proc xConflict {args} { + lappend ::xConflict $args + return "" + } + proc bgerror {args} { set ::background_error $args } + + + set O(-tables) [list] + set O(-sql) [list] + set O(-conflicts) [list] + + array set V $args + foreach key [array names V] { + if {![info exists O($key)]} {error "no such option: $key"} + } + array set O $args + + sqlite3session S db main + foreach t $O(-tables) { S attach $t } + execsql $O(-sql) + + set ::xConflict [list] + sqlite3changeset_apply db2 [S changeset] xConflict + + set conflicts [list] + foreach c $O(-conflicts) { + lappend conflicts $c + } + + after 1 {set go 1} + vwait go + + uplevel do_test $tn [list { set ::xConflict }] [list $conflicts] + S delete +} + +proc do_common_sql {sql} { + execsql $sql db + execsql $sql db2 +} + +proc do_then_apply_sql {sql {dbname main}} { + proc xConflict args { return "OMIT" } + + set rc [catch { + sqlite3session S db $dbname + db eval "SELECT name FROM $dbname.sqlite_master WHERE type = 'table'" { + S attach $name + } + db eval $sql + sqlite3changeset_apply db2 [S changeset] xConflict + } msg] + + catch { S delete } + + if {$rc} {error $msg} +} + +proc do_iterator_test {tn tbl_list sql res} { + sqlite3session S db main + foreach t $tbl_list {S attach $t} + execsql $sql + + set r [list] + foreach v $res { lappend r $v } + + set x [list] + sqlite3session_foreach c [S changeset] { lappend x $c } + uplevel do_test $tn [list [list set {} $x]] [list $r] + + S delete +} + +# Compare the contents of all tables in [db1] and [db2]. Throw an error if +# they are not identical, or return an empty string if they are. +# +proc compare_db {db1 db2} { + + set sql {SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name} + set lot1 [$db1 eval $sql] + set lot2 [$db2 eval $sql] + + if {$lot1 != $lot2} { error "databases contain different tables" } + + foreach tbl $lot1 { + set col1 [list] + set col2 [list] + + $db1 eval "PRAGMA table_info = $tbl" { lappend col1 $name } + $db2 eval "PRAGMA table_info = $tbl" { lappend col2 $name } + if {$col1 != $col2} { error "table $tbl schema mismatch" } + + set sql "SELECT * FROM $tbl ORDER BY [join $col1 ,]" + set data1 [$db1 eval $sql] + set data2 [$db2 eval $sql] + if {$data1 != $data2} { error "table $tbl data mismatch" } + } + + return "" +} + + + diff --git a/ext/session/sessionfault.test b/ext/session/sessionfault.test new file mode 100644 index 0000000000..d6dd361b00 --- /dev/null +++ b/ext/session/sessionfault.test @@ -0,0 +1,85 @@ +# 2011 Mar 21 +# +# 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. +# +#*********************************************************************** +# +# The focus of this file is testing the session module. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] session_common.tcl] +source $testdir/tester.tcl + +set testprefix sessionfault + +forcedelete test.db2 +sqlite3 db2 test.db2 + +do_common_sql { + CREATE TABLE t1(a, b, c, PRIMARY KEY(a, b)); + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES(4, 5, 6); +} +faultsim_save_and_close +db2 close + +# Test OOM error handling when collecting and applying a simple changeset. +# +do_faultsim_test pagerfault-1 -faults oom-* -prep { + catch {db2 close} + catch {db close} + faultsim_restore_and_reopen + sqlite3 db2 test.db2 +} -body { + do_then_apply_sql { + INSERT INTO t1 VALUES(7, 8, 9); + UPDATE t1 SET c = 10 WHERE a = 1; + DELETE FROM t1 WHERE a = 4; + } +} -test { + faultsim_test_result {0 {}} {1 SQLITE_NOMEM} + faultsim_integrity_check + if {$testrc==0} { compare_db db db2 } +} + +# This test case is designed so that a malloc() failure occurs while +# resizing the session object hash-table from 256 to 512 buckets. This +# is not an error, just a sub-optimal condition. +# +do_faultsim_test pagerfault-2 -faults oom-* -prep { + catch {db2 close} + catch {db close} + faultsim_restore_and_reopen + sqlite3 db2 test.db2 + + sqlite3session S db main + S attach t1 + execsql { BEGIN } + for {set i 0} {$i < 125} {incr i} { + execsql {INSERT INTO t1 VALUES(10+$i, 10+$i, 10+$i)} + } +} -body { + for {set i 125} {$i < 133} {incr i} { + execsql {INSERT INTO t1 VALUES(10+$i, 10+$i, 1-+$i)} + } + S changeset + set {} {} +} -test { + faultsim_test_result {0 {}} {1 SQLITE_NOMEM} + if {$testrc==0} { + sqlite3changeset_apply db2 [S changeset] xConflict + compare_db db db2 + } + catch { S delete } + faultsim_integrity_check +} + +finish_test diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index 4663938bb2..e6d4675b08 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -305,6 +305,7 @@ static unsigned int sessionPreupdateHash( }else{ rc = sqlite3_preupdate_old(db, i, &pVal); } + if( rc!=SQLITE_OK ) return rc; eType = sqlite3_value_type(pVal); h = HASH_APPEND(h, eType); @@ -424,33 +425,35 @@ static int sessionPreupdateEqual( } if( rc!=SQLITE_OK || sqlite3_value_type(pVal)!=eType ) return rc; - switch( eType ){ - case SQLITE_INTEGER: - case SQLITE_FLOAT: { - i64 iVal = sessionGetI64(a); - a += 8; - if( eType==SQLITE_INTEGER ){ - if( sqlite3_value_int64(pVal)!=iVal ) return SQLITE_OK; - }else{ - double rVal; - assert( sizeof(iVal)==8 && sizeof(rVal)==8 ); - memcpy(&rVal, &iVal, 8); - if( sqlite3_value_double(pVal)!=rVal ) return SQLITE_OK; - } - break; + /* A SessionChange object never has a NULL value in a PK column */ + assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT + || eType==SQLITE_BLOB || eType==SQLITE_TEXT + ); + + if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ + i64 iVal = sessionGetI64(a); + a += 8; + if( eType==SQLITE_INTEGER ){ + if( sqlite3_value_int64(pVal)!=iVal ) return SQLITE_OK; + }else{ + double rVal; + assert( sizeof(iVal)==8 && sizeof(rVal)==8 ); + memcpy(&rVal, &iVal, 8); + if( sqlite3_value_double(pVal)!=rVal ) return SQLITE_OK; } - case SQLITE_TEXT: - case SQLITE_BLOB: { - int n; - const u8 *z; - a += sessionVarintGet(a, &n); - if( sqlite3_value_bytes(pVal)!=n ) return SQLITE_OK; - z = eType==SQLITE_TEXT ? - sqlite3_value_text(pVal) : sqlite3_value_blob(pVal); - if( memcmp(a, z, n) ) return SQLITE_OK; - a += n; - break; + }else{ + int n; + const u8 *z; + a += sessionVarintGet(a, &n); + if( sqlite3_value_bytes(pVal)!=n ) return SQLITE_OK; + if( eType==SQLITE_TEXT ){ + z = sqlite3_value_text(pVal); + }else{ + z = sqlite3_value_blob(pVal); } + if( memcmp(a, z, n) ) return SQLITE_OK; + a += n; + break; } } } @@ -734,20 +737,22 @@ static void sessionPreupdateOneChange( rc = sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte); } } - pChange->nRecord = nByte; - - /* If an error has occurred, mark the session object as failed. */ - if( rc!=SQLITE_OK ){ - sqlite3_free(pChange); - pSession->rc = rc; - }else{ + if( rc==SQLITE_OK ){ /* Add the change back to the hash-table */ + pChange->nRecord = nByte; pChange->bInsert = (op==SQLITE_INSERT); pChange->pNext = pTab->apChange[iHash]; pTab->apChange[iHash] = pChange; + }else{ + sqlite3_free(pChange); } } } + + /* If an error has occurred, mark the session object as failed. */ + if( rc!=SQLITE_OK ){ + pSession->rc = rc; + } } /* @@ -1345,7 +1350,7 @@ int sqlite3session_changeset( } nNoop = buf.nBuf; - for(i=0; inChange; i++){ + for(i=0; inChange && rc==SQLITE_OK; i++){ SessionChange *p; /* Used to iterate through changes */ for(p=pTab->apChange[i]; rc==SQLITE_OK && p; p=p->pNext){ @@ -1366,13 +1371,14 @@ int sqlite3session_changeset( sessionAppendByte(&buf, SQLITE_DELETE, &rc); sessionAppendBlob(&buf, p->aRecord, p->nRecord, &rc); } - rc = sqlite3_reset(pSel); + if( rc==SQLITE_OK ){ + rc = sqlite3_reset(pSel); + } } } } sqlite3_finalize(pSel); - if( buf.nBuf==nNoop ){ buf.nBuf = nRewind; } @@ -1674,7 +1680,9 @@ int sqlite3changeset_conflict( int sqlite3changeset_finalize(sqlite3_changeset_iter *p){ int i; /* Used to iterate through p->apValue[] */ int rc = p->rc; /* Return code */ - for(i=0; inCol*2; i++) sqlite3ValueFree(p->apValue[i]); + if( p->apValue ){ + for(i=0; inCol*2; i++) sqlite3ValueFree(p->apValue[i]); + } sqlite3_free(p->apValue); sqlite3_free(p); return rc; @@ -2316,7 +2324,8 @@ int sqlite3changeset_apply( SessionApplyCtx sApply; /* changeset_apply() context object */ memset(&sApply, 0, sizeof(sApply)); - sqlite3changeset_start(&pIter, nChangeset, pChangeset); + rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset); + if( rc!=SQLITE_OK ) return rc; sqlite3_mutex_enter(sqlite3_db_mutex(db)); rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0); diff --git a/manifest b/manifest index 63945fc241..3c63f1b8cc 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Clarify\shandling\sof\sNULL\svalues\sin\sPK\scolumns\sin\ssqlite3session.h.\sAdd\stests\sand\sfixes\sfor\sthe\ssame. -D 2011-03-21T11:55:07 +C Add\sstart\sof\sfault-injection\stests\sfor\ssession\smodule.\sFix\ssome\sbugs\srelated\sto\sthe\ssame. +D 2011-03-21T16:17:42 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 27701a1653595a1f2187dc61c8117e00a6c1d50f F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -100,8 +100,10 @@ F ext/rtree/sqlite3rtree.h 1af0899c63a688e272d69d8e746f24e76f10a3f0 F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024 F ext/session/session1.test 3f982c74ee4ba97069917cc35aae25b4ed858e6a -F ext/session/session2.test 3ef304f660b2a929e6bfec2df125c1809f5501ff -F ext/session/sqlite3session.c 70b19f80eadf7060836eaa90928f08a58aa3b35f +F ext/session/session2.test 6462c21d3795d9e48ffea2e7550d1b2d6da66dfb +F ext/session/session_common.tcl 880b554b0bcadcabe1331afb87d58ad1ed2510c4 +F ext/session/sessionfault.test 4190de237b2c76ca7529ef415778a862d7d0fa30 +F ext/session/sqlite3session.c c5a60c2cf21f8892f9ae4850fad2d7859c2c3692 F ext/session/sqlite3session.h 2c071ee5925e82c21c7c9c296a0422c039607106 F ext/session/test_session.c 2559ef68e421c7fb83e2c19ef08a17343b70d535 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 @@ -239,7 +241,7 @@ F src/vacuum.c 924bd1bcee2dfb05376f79845bd3b4cec7b54b2f F src/vdbe.c c90edafd941481506f001b17cd8523683fdac853 F src/vdbe.h edef9c4f0be83e1f1dccd049da37b40e021b63d9 F src/vdbeInt.h 2cf77c1d151a4a54facd596d52be6d60c1cb26e8 -F src/vdbeapi.c c4692c74b31f4abe638fbcbae3944c52b54e2c36 +F src/vdbeapi.c 3d620e00cb74b6034343009af42d5ff5eb7c19dc F src/vdbeaux.c 0216b2c37509a44c3833b297765bee7bdd04fa2f F src/vdbeblob.c c3ccb7c8732858c680f442932e66ad06bb036562 F src/vdbemem.c 0498796b6ffbe45e32960d6a1f5adfb6e419883b @@ -921,7 +923,7 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f -P a192d04f4e3a9e4960a4d96d1d3ee8635bc1034d -R 0282a54a03134becd2cdef1929ff705c +P aed4273054cbd150c86b36ea951d17c981633ba0 +R 3ee0056f48f594a086fa203e62ad6366 U dan -Z 9cc92283568709d19359195efd9bde16 +Z 9a232200bb7ff725d52774ff9ca627f1 diff --git a/manifest.uuid b/manifest.uuid index 754c4bfa3c..7adc729880 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -aed4273054cbd150c86b36ea951d17c981633ba0 \ No newline at end of file +32e95164d1192b87b1ab019549fd2394642cd3fe \ No newline at end of file diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 284e565e13..829d4678b6 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -1348,21 +1348,23 @@ int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){ /* If the old.* record has not yet been loaded into memory, do so now. */ if( p->pUnpacked==0 ){ - u32 nRecord; - u8 *aRecord; + u32 nRec; + u8 *aRec; - rc = sqlite3BtreeDataSize(p->pCsr->pCursor, &nRecord); + rc = sqlite3BtreeDataSize(p->pCsr->pCursor, &nRec); if( rc!=SQLITE_OK ) goto preupdate_old_out; - aRecord = sqlite3DbMallocRaw(db, nRecord); - if( !aRecord ) goto preupdate_old_out; - rc = sqlite3BtreeData(p->pCsr->pCursor, 0, nRecord, aRecord); + aRec = sqlite3DbMallocRaw(db, nRec); + if( !aRec ) goto preupdate_old_out; + rc = sqlite3BtreeData(p->pCsr->pCursor, 0, nRec, aRec); + if( rc==SQLITE_OK ){ + p->pUnpacked = sqlite3VdbeRecordUnpack(&p->keyinfo, nRec, aRec, 0, 0); + if( !p->pUnpacked ) rc = SQLITE_NOMEM; + } if( rc!=SQLITE_OK ){ - sqlite3DbFree(db, aRecord); + sqlite3DbFree(db, aRec); goto preupdate_old_out; } - - p->pUnpacked = sqlite3VdbeRecordUnpack(&p->keyinfo, nRecord, aRecord, 0, 0); - p->aRecord = aRecord; + p->aRecord = aRec; } if( iIdx>=p->pUnpacked->nField ){