Annotation of embedaddon/sqlite3/test/malloc_common.tcl, revision 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>