Annotation of embedaddon/sqlite3/test/malloc_common.tcl, revision 1.1.1.1

1.1       misho       1: # 2007 May 05
                      2: #
                      3: # The author disclaims copyright to this source code.  In place of
                      4: # a legal notice, here is a blessing:
                      5: #
                      6: #    May you do good and not evil.
                      7: #    May you find forgiveness for yourself and forgive others.
                      8: #    May you share freely, never taking more than you give.
                      9: #
                     10: #***********************************************************************
                     11: #
                     12: # This file contains common code used by many different malloc tests
                     13: # within the test suite.
                     14: #
                     15: # $Id: malloc_common.tcl,v 1.22 2008/09/23 16:41:30 danielk1977 Exp $
                     16: 
                     17: # If we did not compile with malloc testing enabled, then do nothing.
                     18: #
                     19: ifcapable builtin_test {
                     20:   set MEMDEBUG 1
                     21: } else {
                     22:   set MEMDEBUG 0
                     23:   return 0
                     24: }
                     25: 
                     26: # Transient and persistent OOM errors:
                     27: #
                     28: set FAULTSIM(oom-transient) [list          \
                     29:   -injectstart   {oom_injectstart 0}       \
                     30:   -injectstop    oom_injectstop            \
                     31:   -injecterrlist {{1 {out of memory}}}     \
                     32: ]
                     33: set FAULTSIM(oom-persistent) [list         \
                     34:   -injectstart {oom_injectstart 1000000}   \
                     35:   -injectstop oom_injectstop               \
                     36:   -injecterrlist {{1 {out of memory}}}     \
                     37: ]
                     38:   
                     39: # Transient and persistent IO errors:
                     40: #
                     41: set FAULTSIM(ioerr-transient) [list        \
                     42:   -injectstart   {ioerr_injectstart 0}     \
                     43:   -injectstop    ioerr_injectstop          \
                     44:   -injecterrlist {{1 {disk I/O error}}}    \
                     45: ]
                     46: set FAULTSIM(ioerr-persistent) [list       \
                     47:   -injectstart   {ioerr_injectstart 1}     \
                     48:   -injectstop    ioerr_injectstop          \
                     49:   -injecterrlist {{1 {disk I/O error}}}    \
                     50: ]
                     51: 
                     52: # SQLITE_FULL errors (always persistent):
                     53: #
                     54: set FAULTSIM(full) [list                   \
                     55:   -injectinstall   fullerr_injectinstall   \
                     56:   -injectstart     fullerr_injectstart     \
                     57:   -injectstop      fullerr_injectstop      \
                     58:   -injecterrlist   {{1 {database or disk is full}}} \
                     59:   -injectuninstall fullerr_injectuninstall \
                     60: ]
                     61: 
                     62: # Transient and persistent SHM errors:
                     63: #
                     64: set FAULTSIM(shmerr-transient) [list       \
                     65:   -injectinstall   shmerr_injectinstall    \
                     66:   -injectstart     {shmerr_injectstart 0}  \
                     67:   -injectstop      shmerr_injectstop       \
                     68:   -injecterrlist   {{1 {disk I/O error}}}  \
                     69:   -injectuninstall shmerr_injectuninstall  \
                     70: ]
                     71: set FAULTSIM(shmerr-persistent) [list      \
                     72:   -injectinstall   shmerr_injectinstall    \
                     73:   -injectstart     {shmerr_injectstart 1}  \
                     74:   -injectstop      shmerr_injectstop       \
                     75:   -injecterrlist   {{1 {disk I/O error}}}  \
                     76:   -injectuninstall shmerr_injectuninstall  \
                     77: ]
                     78: 
                     79: # Transient and persistent CANTOPEN errors:
                     80: #
                     81: set FAULTSIM(cantopen-transient) [list       \
                     82:   -injectinstall   cantopen_injectinstall    \
                     83:   -injectstart     {cantopen_injectstart 0}  \
                     84:   -injectstop      cantopen_injectstop       \
                     85:   -injecterrlist   {{1 {unable to open database file}}}  \
                     86:   -injectuninstall cantopen_injectuninstall  \
                     87: ]
                     88: set FAULTSIM(cantopen-persistent) [list      \
                     89:   -injectinstall   cantopen_injectinstall    \
                     90:   -injectstart     {cantopen_injectstart 1}  \
                     91:   -injectstop      cantopen_injectstop       \
                     92:   -injecterrlist   {{1 {unable to open database file}}}  \
                     93:   -injectuninstall cantopen_injectuninstall  \
                     94: ]
                     95: 
                     96: 
                     97: 
                     98: #--------------------------------------------------------------------------
                     99: # Usage do_faultsim_test NAME ?OPTIONS...? 
                    100: #
                    101: #     -faults           List of fault types to simulate.
                    102: #
                    103: #     -prep             Script to execute before -body.
                    104: #
                    105: #     -body             Script to execute (with fault injection).
                    106: #
                    107: #     -test             Script to execute after -body.
                    108: #
                    109: #     -install          Script to execute after faultsim -injectinstall
                    110: #
                    111: #     -uninstall        Script to execute after faultsim -uninjectinstall
                    112: #
                    113: proc do_faultsim_test {name args} {
                    114:   global FAULTSIM
                    115:   
                    116:   set DEFAULT(-faults)        [array names FAULTSIM]
                    117:   set DEFAULT(-prep)          ""
                    118:   set DEFAULT(-body)          ""
                    119:   set DEFAULT(-test)          ""
                    120:   set DEFAULT(-install)       ""
                    121:   set DEFAULT(-uninstall)     ""
                    122: 
                    123:   fix_testname name
                    124: 
                    125:   array set O [array get DEFAULT]
                    126:   array set O $args
                    127:   foreach o [array names O] {
                    128:     if {[info exists DEFAULT($o)]==0} { error "unknown option: $o" }
                    129:   }
                    130: 
                    131:   set faultlist [list]
                    132:   foreach f $O(-faults) {
                    133:     set flist [array names FAULTSIM $f]
                    134:     if {[llength $flist]==0} { error "unknown fault: $f" }
                    135:     set faultlist [concat $faultlist $flist]
                    136:   }
                    137: 
                    138:   set testspec [list -prep $O(-prep) -body $O(-body) \
                    139:       -test $O(-test) -install $O(-install) -uninstall $O(-uninstall)
                    140:   ]
                    141:   foreach f [lsort -unique $faultlist] {
                    142:     eval do_one_faultsim_test "$name-$f" $FAULTSIM($f) $testspec
                    143:   }
                    144: }
                    145: 
                    146: 
                    147: #-------------------------------------------------------------------------
                    148: # Procedures to save and restore the current file-system state:
                    149: #
                    150: #   faultsim_save
                    151: #   faultsim_restore
                    152: #   faultsim_save_and_close
                    153: #   faultsim_restore_and_reopen
                    154: #   faultsim_delete_and_reopen
                    155: #
                    156: proc faultsim_save {args} { uplevel db_save $args }
                    157: proc faultsim_save_and_close {args} { uplevel db_save_and_close $args }
                    158: proc faultsim_restore {args} { uplevel db_restore $args }
                    159: proc faultsim_restore_and_reopen {args} { 
                    160:   uplevel db_restore_and_reopen $args 
                    161:   sqlite3_extended_result_codes db 1
                    162:   sqlite3_db_config_lookaside db 0 0 0
                    163: }
                    164: proc faultsim_delete_and_reopen {args} {
                    165:   uplevel db_delete_and_reopen $args 
                    166:   sqlite3_extended_result_codes db 1
                    167:   sqlite3_db_config_lookaside db 0 0 0
                    168: }
                    169: 
                    170: proc faultsim_integrity_check {{db db}} {
                    171:   set ic [$db eval { PRAGMA integrity_check }]
                    172:   if {$ic != "ok"} { error "Integrity check: $ic" }
                    173: }
                    174: 
                    175: 
                    176: # The following procs are used as [do_one_faultsim_test] callbacks when 
                    177: # injecting OOM faults into test cases.
                    178: #
                    179: proc oom_injectstart {nRepeat iFail} {
                    180:   sqlite3_memdebug_fail [expr $iFail-1] -repeat $nRepeat
                    181: }
                    182: proc oom_injectstop {} {
                    183:   sqlite3_memdebug_fail -1
                    184: }
                    185: 
                    186: # The following procs are used as [do_one_faultsim_test] callbacks when 
                    187: # injecting IO error faults into test cases.
                    188: #
                    189: proc ioerr_injectstart {persist iFail} {
                    190:   set ::sqlite_io_error_persist $persist
                    191:   set ::sqlite_io_error_pending $iFail
                    192: }
                    193: proc ioerr_injectstop {} {
                    194:   set sv $::sqlite_io_error_hit
                    195:   set ::sqlite_io_error_persist 0
                    196:   set ::sqlite_io_error_pending 0
                    197:   set ::sqlite_io_error_hardhit 0
                    198:   set ::sqlite_io_error_hit     0
                    199:   set ::sqlite_io_error_pending 0
                    200:   return $sv
                    201: }
                    202: 
                    203: # The following procs are used as [do_one_faultsim_test] callbacks when 
                    204: # injecting shared-memory related error faults into test cases.
                    205: #
                    206: proc shmerr_injectinstall {} {
                    207:   testvfs shmfault -default true
                    208:   shmfault filter {xShmOpen xShmMap xShmLock}
                    209: }
                    210: proc shmerr_injectuninstall {} {
                    211:   catch {db  close}
                    212:   catch {db2 close}
                    213:   shmfault delete
                    214: }
                    215: proc shmerr_injectstart {persist iFail} {
                    216:   shmfault ioerr $iFail $persist
                    217: }
                    218: proc shmerr_injectstop {} {
                    219:   shmfault ioerr
                    220: }
                    221: 
                    222: # The following procs are used as [do_one_faultsim_test] callbacks when 
                    223: # injecting SQLITE_FULL error faults into test cases.
                    224: #
                    225: proc fullerr_injectinstall {} {
                    226:   testvfs shmfault -default true
                    227: }
                    228: proc fullerr_injectuninstall {} {
                    229:   catch {db  close}
                    230:   catch {db2 close}
                    231:   shmfault delete
                    232: }
                    233: proc fullerr_injectstart {iFail} {
                    234:   shmfault full $iFail 1
                    235: }
                    236: proc fullerr_injectstop {} {
                    237:   shmfault full
                    238: }
                    239: 
                    240: # The following procs are used as [do_one_faultsim_test] callbacks when 
                    241: # injecting SQLITE_CANTOPEN error faults into test cases.
                    242: #
                    243: proc cantopen_injectinstall {} {
                    244:   testvfs shmfault -default true
                    245: }
                    246: proc cantopen_injectuninstall {} {
                    247:   catch {db  close}
                    248:   catch {db2 close}
                    249:   shmfault delete
                    250: }
                    251: proc cantopen_injectstart {persist iFail} {
                    252:   shmfault cantopen $iFail $persist
                    253: }
                    254: proc cantopen_injectstop {} {
                    255:   shmfault cantopen
                    256: }
                    257: 
                    258: # This command is not called directly. It is used by the 
                    259: # [faultsim_test_result] command created by [do_faultsim_test] and used
                    260: # by -test scripts.
                    261: #
                    262: proc faultsim_test_result_int {args} {
                    263:   upvar testrc testrc testresult testresult testnfail testnfail
                    264:   set t [list $testrc $testresult]
                    265:   set r $args
                    266:   if { ($testnfail==0 && $t != [lindex $r 0]) || [lsearch $r $t]<0 } {
                    267:     error "nfail=$testnfail rc=$testrc result=$testresult"
                    268:   }
                    269: }
                    270: 
                    271: #--------------------------------------------------------------------------
                    272: # Usage do_one_faultsim_test NAME ?OPTIONS...? 
                    273: #
                    274: # The first argument, <test number>, is used as a prefix of the test names
                    275: # taken by tests executed by this command. Options are as follows. All
                    276: # options take a single argument.
                    277: #
                    278: #     -injectstart      Script to enable fault-injection.
                    279: #
                    280: #     -injectstop       Script to disable fault-injection.
                    281: #
                    282: #     -injecterrlist    List of generally acceptable test results (i.e. error
                    283: #                       messages). Example: [list {1 {out of memory}}]
                    284: #
                    285: #     -injectinstall
                    286: #
                    287: #     -injectuninstall
                    288: #
                    289: #     -prep             Script to execute before -body.
                    290: #
                    291: #     -body             Script to execute (with fault injection).
                    292: #
                    293: #     -test             Script to execute after -body.
                    294: #
                    295: proc do_one_faultsim_test {testname args} {
                    296: 
                    297:   set DEFAULT(-injectstart)     "expr"
                    298:   set DEFAULT(-injectstop)      "expr 0"
                    299:   set DEFAULT(-injecterrlist)   [list]
                    300:   set DEFAULT(-injectinstall)   ""
                    301:   set DEFAULT(-injectuninstall) ""
                    302:   set DEFAULT(-prep)            ""
                    303:   set DEFAULT(-body)            ""
                    304:   set DEFAULT(-test)            ""
                    305:   set DEFAULT(-install)         ""
                    306:   set DEFAULT(-uninstall)       ""
                    307: 
                    308:   array set O [array get DEFAULT]
                    309:   array set O $args
                    310:   foreach o [array names O] {
                    311:     if {[info exists DEFAULT($o)]==0} { error "unknown option: $o" }
                    312:   }
                    313: 
                    314:   proc faultsim_test_proc {testrc testresult testnfail} $O(-test)
                    315:   proc faultsim_test_result {args} "
                    316:     uplevel faultsim_test_result_int \$args [list $O(-injecterrlist)]
                    317:   "
                    318: 
                    319:   eval $O(-injectinstall)
                    320:   eval $O(-install)
                    321: 
                    322:   set stop 0
                    323:   for {set iFail 1} {!$stop} {incr iFail} {
                    324: 
                    325:     # Evaluate the -prep script.
                    326:     #
                    327:     eval $O(-prep)
                    328: 
                    329:     # Start the fault-injection. Run the -body script. Stop the fault
                    330:     # injection. Local var $nfail is set to the total number of faults 
                    331:     # injected into the system this trial.
                    332:     #
                    333:     eval $O(-injectstart) $iFail
                    334:     set rc [catch $O(-body) res]
                    335:     set nfail [eval $O(-injectstop)]
                    336: 
                    337:     # Run the -test script. If it throws no error, consider this trial
                    338:     # sucessful. If it does throw an error, cause a [do_test] test to
                    339:     # fail (and print out the unexpected exception thrown by the -test
                    340:     # script at the same time).
                    341:     #
                    342:     set rc [catch [list faultsim_test_proc $rc $res $nfail] res]
                    343:     if {$rc == 0} {set res ok}
                    344:     do_test $testname.$iFail [list list $rc $res] {0 ok}
                    345: 
                    346:     # If no faults where injected this trial, don't bother running
                    347:     # any more. This test is finished.
                    348:     #
                    349:     if {$nfail==0} { set stop 1 }
                    350:   }
                    351: 
                    352:   eval $O(-uninstall)
                    353:   eval $O(-injectuninstall)
                    354: }
                    355: 
                    356: # Usage: do_malloc_test <test number> <options...>
                    357: #
                    358: # The first argument, <test number>, is an integer used to name the
                    359: # tests executed by this proc. Options are as follows:
                    360: #
                    361: #     -tclprep          TCL script to run to prepare test.
                    362: #     -sqlprep          SQL script to run to prepare test.
                    363: #     -tclbody          TCL script to run with malloc failure simulation.
                    364: #     -sqlbody          TCL script to run with malloc failure simulation.
                    365: #     -cleanup          TCL script to run after the test.
                    366: #
                    367: # This command runs a series of tests to verify SQLite's ability
                    368: # to handle an out-of-memory condition gracefully. It is assumed
                    369: # that if this condition occurs a malloc() call will return a
                    370: # NULL pointer. Linux, for example, doesn't do that by default. See
                    371: # the "BUGS" section of malloc(3).
                    372: #
                    373: # Each iteration of a loop, the TCL commands in any argument passed
                    374: # to the -tclbody switch, followed by the SQL commands in any argument
                    375: # passed to the -sqlbody switch are executed. Each iteration the
                    376: # Nth call to sqliteMalloc() is made to fail, where N is increased
                    377: # each time the loop runs starting from 1. When all commands execute
                    378: # successfully, the loop ends.
                    379: #
                    380: proc do_malloc_test {tn args} {
                    381:   array unset ::mallocopts 
                    382:   array set ::mallocopts $args
                    383: 
                    384:   if {[string is integer $tn]} {
                    385:     set tn malloc-$tn
                    386:   }
                    387:   if {[info exists ::mallocopts(-start)]} {
                    388:     set start $::mallocopts(-start)
                    389:   } else {
                    390:     set start 0
                    391:   }
                    392:   if {[info exists ::mallocopts(-end)]} {
                    393:     set end $::mallocopts(-end)
                    394:   } else {
                    395:     set end 50000
                    396:   }
                    397:   save_prng_state
                    398: 
                    399:   foreach ::iRepeat {0 10000000} {
                    400:     set ::go 1
                    401:     for {set ::n $start} {$::go && $::n <= $end} {incr ::n} {
                    402: 
                    403:       # If $::iRepeat is 0, then the malloc() failure is transient - it
                    404:       # fails and then subsequent calls succeed. If $::iRepeat is 1, 
                    405:       # then the failure is persistent - once malloc() fails it keeps
                    406:       # failing.
                    407:       #
                    408:       set zRepeat "transient"
                    409:       if {$::iRepeat} {set zRepeat "persistent"}
                    410:       restore_prng_state
                    411:       foreach file [glob -nocomplain test.db-mj*] {forcedelete $file}
                    412: 
                    413:       do_test ${tn}.${zRepeat}.${::n} {
                    414:   
                    415:         # Remove all traces of database files test.db and test2.db 
                    416:         # from the file-system. Then open (empty database) "test.db" 
                    417:         # with the handle [db].
                    418:         # 
                    419:         catch {db close} 
                    420:         catch {db2 close} 
                    421:         forcedelete test.db
                    422:         forcedelete test.db-journal
                    423:         forcedelete test.db-wal
                    424:         forcedelete test2.db
                    425:         forcedelete test2.db-journal
                    426:         forcedelete test2.db-wal
                    427:         if {[info exists ::mallocopts(-testdb)]} {
                    428:           copy_file $::mallocopts(-testdb) test.db
                    429:         }
                    430:         catch { sqlite3 db test.db }
                    431:         if {[info commands db] ne ""} {
                    432:           sqlite3_extended_result_codes db 1
                    433:         }
                    434:         sqlite3_db_config_lookaside db 0 0 0
                    435:   
                    436:         # Execute any -tclprep and -sqlprep scripts.
                    437:         #
                    438:         if {[info exists ::mallocopts(-tclprep)]} {
                    439:           eval $::mallocopts(-tclprep)
                    440:         }
                    441:         if {[info exists ::mallocopts(-sqlprep)]} {
                    442:           execsql $::mallocopts(-sqlprep)
                    443:         }
                    444:   
                    445:         # Now set the ${::n}th malloc() to fail and execute the -tclbody 
                    446:         # and -sqlbody scripts.
                    447:         #
                    448:         sqlite3_memdebug_fail $::n -repeat $::iRepeat
                    449:         set ::mallocbody {}
                    450:         if {[info exists ::mallocopts(-tclbody)]} {
                    451:           append ::mallocbody "$::mallocopts(-tclbody)\n"
                    452:         }
                    453:         if {[info exists ::mallocopts(-sqlbody)]} {
                    454:           append ::mallocbody "db eval {$::mallocopts(-sqlbody)}"
                    455:         }
                    456: 
                    457:         # The following block sets local variables as follows:
                    458:         #
                    459:         #     isFail  - True if an error (any error) was reported by sqlite.
                    460:         #     nFail   - The total number of simulated malloc() failures.
                    461:         #     nBenign - The number of benign simulated malloc() failures.
                    462:         #
                    463:         set isFail [catch $::mallocbody msg]
                    464:         set nFail [sqlite3_memdebug_fail -1 -benigncnt nBenign]
                    465:         # puts -nonewline " (isFail=$isFail nFail=$nFail nBenign=$nBenign) "
                    466: 
                    467:         # If one or more mallocs failed, run this loop body again.
                    468:         #
                    469:         set go [expr {$nFail>0}]
                    470: 
                    471:         if {($nFail-$nBenign)==0} {
                    472:           if {$isFail} {
                    473:             set v2 $msg
                    474:           } else {
                    475:             set isFail 1
                    476:             set v2 1
                    477:           }
                    478:         } elseif {!$isFail} {
                    479:           set v2 $msg
                    480:         } elseif {
                    481:           [info command db]=="" || 
                    482:           [db errorcode]==7 ||
                    483:           $msg=="out of memory"
                    484:         } {
                    485:           set v2 1
                    486:         } else {
                    487:           set v2 $msg
                    488:           puts [db errorcode]
                    489:         }
                    490:         lappend isFail $v2
                    491:       } {1 1}
                    492:   
                    493:       if {[info exists ::mallocopts(-cleanup)]} {
                    494:         catch [list uplevel #0 $::mallocopts(-cleanup)] msg
                    495:       }
                    496:     }
                    497:   }
                    498:   unset ::mallocopts
                    499:   sqlite3_memdebug_fail -1
                    500: }
                    501: 
                    502: 
                    503: #-------------------------------------------------------------------------
                    504: # This proc is used to test a single SELECT statement. Parameter $name is
                    505: # passed a name for the test case (i.e. "fts3_malloc-1.4.1") and parameter
                    506: # $sql is passed the text of the SELECT statement. Parameter $result is
                    507: # set to the expected output if the SELECT statement is successfully
                    508: # executed using [db eval].
                    509: #
                    510: # Example:
                    511: #
                    512: #   do_select_test testcase-1.1 "SELECT 1+1, 1+2" {1 2}
                    513: #
                    514: # If global variable DO_MALLOC_TEST is set to a non-zero value, or if
                    515: # it is not defined at all, then OOM testing is performed on the SELECT
                    516: # statement. Each OOM test case is said to pass if either (a) executing
                    517: # the SELECT statement succeeds and the results match those specified
                    518: # by parameter $result, or (b) TCL throws an "out of memory" error.
                    519: #
                    520: # If DO_MALLOC_TEST is defined and set to zero, then the SELECT statement
                    521: # is executed just once. In this case the test case passes if the results
                    522: # match the expected results passed via parameter $result.
                    523: #
                    524: proc do_select_test {name sql result} {
                    525:   uplevel [list doPassiveTest 0 $name $sql [list 0 [list {*}$result]]]
                    526: }
                    527: 
                    528: proc do_restart_select_test {name sql result} {
                    529:   uplevel [list doPassiveTest 1 $name $sql [list 0 $result]]
                    530: }
                    531: 
                    532: proc do_error_test {name sql error} {
                    533:   uplevel [list doPassiveTest 0 $name $sql [list 1 $error]]
                    534: }
                    535: 
                    536: proc doPassiveTest {isRestart name sql catchres} {
                    537:   if {![info exists ::DO_MALLOC_TEST]} { set ::DO_MALLOC_TEST 1 }
                    538: 
                    539:   if {[info exists ::testprefix] 
                    540:    && [string is integer [string range $name 0 0]]
                    541:   } {
                    542:     set name $::testprefix.$name
                    543:   }
                    544: 
                    545:   switch $::DO_MALLOC_TEST {
                    546:     0 { # No malloc failures.
                    547:       do_test $name [list set {} [uplevel [list catchsql $sql]]] $catchres
                    548:       return
                    549:     }
                    550:     1 { # Simulate transient failures.
                    551:       set nRepeat 1
                    552:       set zName "transient"
                    553:       set nStartLimit 100000
                    554:       set nBackup 1
                    555:     }
                    556:     2 { # Simulate persistent failures.
                    557:       set nRepeat 1
                    558:       set zName "persistent"
                    559:       set nStartLimit 100000
                    560:       set nBackup 1
                    561:     }
                    562:     3 { # Simulate transient failures with extra brute force.
                    563:       set nRepeat 100000
                    564:       set zName "ridiculous"
                    565:       set nStartLimit 1
                    566:       set nBackup 10
                    567:     }
                    568:   }
                    569: 
                    570:   # The set of acceptable results from running [catchsql $sql].
                    571:   #
                    572:   set answers [list {1 {out of memory}} $catchres]
                    573:   set str [join $answers " OR "]
                    574: 
                    575:   set nFail 1
                    576:   for {set iLimit $nStartLimit} {$nFail} {incr iLimit} {
                    577:     for {set iFail 1} {$nFail && $iFail<=$iLimit} {incr iFail} {
                    578:       for {set iTest 0} {$iTest<$nBackup && ($iFail-$iTest)>0} {incr iTest} {
                    579: 
                    580:         if {$isRestart} { sqlite3 db test.db }
                    581: 
                    582:         sqlite3_memdebug_fail [expr $iFail-$iTest] -repeat $nRepeat
                    583:         set res [uplevel [list catchsql $sql]]
                    584:         if {[lsearch -exact $answers $res]>=0} { set res $str }
                    585:         set testname "$name.$zName.$iFail"
                    586:         do_test "$name.$zName.$iLimit.$iFail" [list set {} $res] $str
                    587: 
                    588:         set nFail [sqlite3_memdebug_fail -1 -benigncnt nBenign]
                    589:       }
                    590:     }
                    591:   }
                    592: }
                    593: 
                    594: 
                    595: #-------------------------------------------------------------------------
                    596: # Test a single write to the database. In this case a  "write" is a 
                    597: # DELETE, UPDATE or INSERT statement.
                    598: #
                    599: # If OOM testing is performed, there are several acceptable outcomes:
                    600: #
                    601: #   1) The write succeeds. No error is returned.
                    602: #
                    603: #   2) An "out of memory" exception is thrown and:
                    604: #
                    605: #     a) The statement has no effect, OR
                    606: #     b) The current transaction is rolled back, OR
                    607: #     c) The statement succeeds. This can only happen if the connection
                    608: #        is in auto-commit mode (after the statement is executed, so this
                    609: #        includes COMMIT statements).
                    610: #
                    611: # If the write operation eventually succeeds, zero is returned. If a
                    612: # transaction is rolled back, non-zero is returned.
                    613: #
                    614: # Parameter $name is the name to use for the test case (or test cases).
                    615: # The second parameter, $tbl, should be the name of the database table
                    616: # being modified. Parameter $sql contains the SQL statement to test.
                    617: #
                    618: proc do_write_test {name tbl sql} {
                    619:   if {![info exists ::DO_MALLOC_TEST]} { set ::DO_MALLOC_TEST 1 }
                    620: 
                    621:   # Figure out an statement to get a checksum for table $tbl.
                    622:   db eval "SELECT * FROM $tbl" V break
                    623:   set cksumsql "SELECT md5sum([join [concat rowid $V(*)] ,]) FROM $tbl"
                    624: 
                    625:   # Calculate the initial table checksum.
                    626:   set cksum1 [db one $cksumsql]
                    627: 
                    628:   if {$::DO_MALLOC_TEST } {
                    629:     set answers [list {1 {out of memory}} {0 {}}]
                    630:     if {$::DO_MALLOC_TEST==1} {
                    631:       set modes {100000 persistent}
                    632:     } else {
                    633:       set modes {1 transient}
                    634:     }
                    635:   } else {
                    636:     set answers [list {0 {}}]
                    637:     set modes [list 0 nofail]
                    638:   }
                    639:   set str [join $answers " OR "]
                    640: 
                    641:   foreach {nRepeat zName} $modes {
                    642:     for {set iFail 1} 1 {incr iFail} {
                    643:       if {$::DO_MALLOC_TEST} {sqlite3_memdebug_fail $iFail -repeat $nRepeat}
                    644: 
                    645:       set res [uplevel [list catchsql $sql]]
                    646:       set nFail [sqlite3_memdebug_fail -1 -benigncnt nBenign]
                    647:       if {$nFail==0} {
                    648:         do_test $name.$zName.$iFail [list set {} $res] {0 {}}
                    649:         return
                    650:       } else {
                    651:         if {[lsearch $answers $res]>=0} {
                    652:           set res $str
                    653:         }
                    654:         do_test $name.$zName.$iFail [list set {} $res] $str
                    655:         set cksum2 [db one $cksumsql]
                    656:         if {$cksum1 != $cksum2} return
                    657:       }
                    658:     }
                    659:   }
                    660: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>