AryaWu/sqlite
0
1# 2007 May 052#3# The author disclaims copyright to this source code. In place of4# 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 tests13# 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#19ifcapable builtin_test {20 set MEMDEBUG 121} else {22 set MEMDEBUG 023 return 024}25 26# Transient and persistent OOM errors:27#28set FAULTSIM(oom-transient) [list \29 -injectstart {oom_injectstart 0} \30 -injectstop oom_injectstop \31 -injecterrlist {{1 {out of memory}}} \32]33set 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#41set FAULTSIM(ioerr-transient) [list \42 -injectstart {ioerr_injectstart 0} \43 -injectstop ioerr_injectstop \44 -injecterrlist {{1 {disk I/O error}}} \45]46set 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#54set 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#64set 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]71set 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#81set 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]88set 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 96set FAULTSIM(interrupt) [list \97 -injectinstall interrupt_injectinstall \98 -injectstart interrupt_injectstart \99 -injectstop interrupt_injectstop \100 -injecterrlist {{1 interrupted} {1 interrupt}} \101 -injectuninstall interrupt_injectuninstall \102]103 104 105 106#--------------------------------------------------------------------------107# Usage do_faultsim_test NAME ?OPTIONS...? 108#109# -faults List of fault types to simulate.110#111# -prep Script to execute before -body.112#113# -body Script to execute (with fault injection).114#115# -test Script to execute after -body.116#117# -install Script to execute after faultsim -injectinstall118#119# -uninstall Script to execute after faultsim -uninjectinstall120#121proc do_faultsim_test {name args} {122 global FAULTSIM123 124 foreach n [array names FAULTSIM] {125 if {$n != "interrupt"} {lappend DEFAULT(-faults) $n}126 }127 set DEFAULT(-prep) ""128 set DEFAULT(-body) ""129 set DEFAULT(-test) ""130 set DEFAULT(-install) ""131 set DEFAULT(-uninstall) ""132 set DEFAULT(-start) 1133 set DEFAULT(-end) 0134 135 fix_testname name136 137 array set O [array get DEFAULT]138 array set O $args139 foreach o [array names O] {140 if {[info exists DEFAULT($o)]==0} { error "unknown option: $o" }141 }142 143 set faultlist [list]144 foreach f $O(-faults) {145 set flist [array names FAULTSIM $f]146 if {[llength $flist]==0} { error "unknown fault: $f" }147 set faultlist [concat $faultlist $flist]148 }149 150 set testspec [list -prep $O(-prep) -body $O(-body) \151 -test $O(-test) -install $O(-install) -uninstall $O(-uninstall) \152 -start $O(-start) -end $O(-end)153 ]154 foreach f [lsort -unique $faultlist] {155 eval do_one_faultsim_test "$name-$f" $FAULTSIM($f) $testspec156 }157}158 159 160#-------------------------------------------------------------------------161# Procedures to save and restore the current file-system state:162#163# faultsim_save164# faultsim_restore165# faultsim_save_and_close166# faultsim_restore_and_reopen167# faultsim_delete_and_reopen168#169proc faultsim_save {args} { uplevel db_save $args }170proc faultsim_save_and_close {args} { uplevel db_save_and_close $args }171proc faultsim_restore {args} { uplevel db_restore $args }172proc faultsim_restore_and_reopen {args} { 173 uplevel db_restore_and_reopen $args 174 sqlite3_extended_result_codes db 1175 sqlite3_db_config_lookaside db 0 0 0176}177proc faultsim_delete_and_reopen {args} {178 uplevel db_delete_and_reopen $args 179 sqlite3_extended_result_codes db 1180 sqlite3_db_config_lookaside db 0 0 0181}182 183proc faultsim_integrity_check {{db db}} {184 set ic [$db eval { PRAGMA integrity_check }]185 if {$ic != "ok"} { error "Integrity check: $ic" }186}187 188 189# The following procs are used as [do_one_faultsim_test] callbacks when 190# injecting OOM faults into test cases.191#192proc oom_injectstart {nRepeat iFail} {193 sqlite3_memdebug_fail [expr $iFail-1] -repeat $nRepeat194}195proc oom_injectstop {} {196 sqlite3_memdebug_fail -1197}198 199# The following procs are used as [do_one_faultsim_test] callbacks when 200# injecting IO error faults into test cases.201#202proc ioerr_injectstart {persist iFail} {203 set ::sqlite_io_error_persist $persist204 set ::sqlite_io_error_pending $iFail205}206proc ioerr_injectstop {} {207 set sv $::sqlite_io_error_hit208 set ::sqlite_io_error_persist 0209 set ::sqlite_io_error_pending 0210 set ::sqlite_io_error_hardhit 0211 set ::sqlite_io_error_hit 0212 set ::sqlite_io_error_pending 0213 return $sv214}215 216# The following procs are used as [do_one_faultsim_test] callbacks when 217# injecting shared-memory related error faults into test cases.218#219proc shmerr_injectinstall {} {220 testvfs shmfault -default true221 shmfault filter {xShmOpen xShmMap xShmLock}222}223proc shmerr_injectuninstall {} {224 catch {db close}225 catch {db2 close}226 shmfault delete227}228proc shmerr_injectstart {persist iFail} {229 shmfault ioerr $iFail $persist230}231proc shmerr_injectstop {} {232 shmfault ioerr233}234 235# The following procs are used as [do_one_faultsim_test] callbacks when 236# injecting SQLITE_FULL error faults into test cases.237#238proc fullerr_injectinstall {} {239 testvfs shmfault -default true240}241proc fullerr_injectuninstall {} {242 catch {db close}243 catch {db2 close}244 shmfault delete245}246proc fullerr_injectstart {iFail} {247 shmfault full $iFail 1248}249proc fullerr_injectstop {} {250 shmfault full251}252 253# The following procs are used as [do_one_faultsim_test] callbacks when 254# injecting SQLITE_CANTOPEN error faults into test cases.255#256proc cantopen_injectinstall {} {257 testvfs shmfault -default true258}259proc cantopen_injectuninstall {} {260 catch {db close}261 catch {db2 close}262 shmfault delete263}264proc cantopen_injectstart {persist iFail} {265 shmfault cantopen $iFail $persist266}267proc cantopen_injectstop {} {268 shmfault cantopen269}270 271# The following procs are used as [do_one_faultsim_test] callbacks 272# when injecting SQLITE_INTERRUPT error faults into test cases.273#274proc interrupt_injectinstall {} {275}276proc interrupt_injectuninstall {} {277}278proc interrupt_injectstart {iFail} {279 set ::sqlite_interrupt_count $iFail280}281proc interrupt_injectstop {} {282 set res [expr $::sqlite_interrupt_count<=0]283 set ::sqlite_interrupt_count 0284 set res285}286 287# This command is not called directly. It is used by the 288# [faultsim_test_result] command created by [do_faultsim_test] and used289# by -test scripts.290#291proc faultsim_test_result_int {args} {292 upvar testrc testrc testresult testresult testnfail testnfail293 set t [list $testrc $testresult]294 set r $args295 if { ($testnfail==0 && $t != [lindex $r 0]) || [lsearch -exact $r $t]<0 } {296 error "nfail=$testnfail rc=$testrc result=$testresult list=$r"297 }298}299 300#--------------------------------------------------------------------------301# Usage do_one_faultsim_test NAME ?OPTIONS...? 302#303# The first argument, <test number>, is used as a prefix of the test names304# taken by tests executed by this command. Options are as follows. All305# options take a single argument.306#307# -injectstart Script to enable fault-injection.308#309# -injectstop Script to disable fault-injection.310#311# -injecterrlist List of generally acceptable test results (i.e. error312# messages). Example: [list {1 {out of memory}}]313#314# -injectinstall315#316# -injectuninstall317#318# -prep Script to execute before -body.319#320# -body Script to execute (with fault injection).321#322# -test Script to execute after -body.323#324# -start Index of first fault to inject (default 1)325#326proc do_one_faultsim_test {testname args} {327 328 set DEFAULT(-injectstart) "expr"329 set DEFAULT(-injectstop) "expr 0"330 set DEFAULT(-injecterrlist) [list]331 set DEFAULT(-injectinstall) ""332 set DEFAULT(-injectuninstall) ""333 set DEFAULT(-prep) ""334 set DEFAULT(-body) ""335 set DEFAULT(-test) ""336 set DEFAULT(-install) ""337 set DEFAULT(-uninstall) ""338 set DEFAULT(-start) 1339 set DEFAULT(-end) 0340 341 array set O [array get DEFAULT]342 array set O $args343 foreach o [array names O] {344 if {[info exists DEFAULT($o)]==0} { error "unknown option: $o" }345 }346 347 proc faultsim_test_proc {testrc testresult testnfail} $O(-test)348 proc faultsim_test_result {args} "349 uplevel faultsim_test_result_int \$args [list $O(-injecterrlist)]350 "351 352 eval $O(-injectinstall)353 eval $O(-install)354 355 set stop 0356 for {set iFail $O(-start)} \357 {!$stop && ($O(-end)==0 || $iFail<=$O(-end))} \358 {incr iFail} \359 {360 361 # Evaluate the -prep script.362 #363 eval $O(-prep)364 365 # Start the fault-injection. Run the -body script. Stop the fault366 # injection. Local var $nfail is set to the total number of faults 367 # injected into the system this trial.368 #369 eval $O(-injectstart) $iFail370 set rc [catch $O(-body) res]371 set nfail [eval $O(-injectstop)]372 373 # Run the -test script. If it throws no error, consider this trial374 # sucessful. If it does throw an error, cause a [do_test] test to375 # fail (and print out the unexpected exception thrown by the -test376 # script at the same time).377 #378 set rc [catch [list faultsim_test_proc $rc $res $nfail] res]379 if {$rc == 0} {set res ok}380 do_test $testname.$iFail [list list $rc $res] {0 ok}381 382 # If no faults where injected this trial, don't bother running383 # any more. This test is finished.384 #385 if {$nfail==0} { set stop 1 }386 }387 388 eval $O(-uninstall)389 eval $O(-injectuninstall)390}391 392# Usage: do_malloc_test <test number> <options...>393#394# The first argument, <test number>, is an integer used to name the395# tests executed by this proc. Options are as follows:396#397# -tclprep TCL script to run to prepare test.398# -sqlprep SQL script to run to prepare test.399# -tclbody TCL script to run with malloc failure simulation.400# -sqlbody TCL script to run with malloc failure simulation.401# -cleanup TCL script to run after the test.402#403# This command runs a series of tests to verify SQLite's ability404# to handle an out-of-memory condition gracefully. It is assumed405# that if this condition occurs a malloc() call will return a406# NULL pointer. Linux, for example, doesn't do that by default. See407# the "BUGS" section of malloc(3).408#409# Each iteration of a loop, the TCL commands in any argument passed410# to the -tclbody switch, followed by the SQL commands in any argument411# passed to the -sqlbody switch are executed. Each iteration the412# Nth call to sqliteMalloc() is made to fail, where N is increased413# each time the loop runs starting from 1. When all commands execute414# successfully, the loop ends.415#416proc do_malloc_test {tn args} {417 array unset ::mallocopts 418 array set ::mallocopts $args419 420 if {[string is integer $tn]} {421 set tn malloc-$tn422 catch { set tn $::testprefix-$tn }423 }424 if {[info exists ::mallocopts(-start)]} {425 set start $::mallocopts(-start)426 } else {427 set start 0428 }429 if {[info exists ::mallocopts(-end)]} {430 set end $::mallocopts(-end)431 } else {432 set end 50000433 }434 save_prng_state435 436 foreach ::iRepeat {0 10000000} {437 set ::go 1438 for {set ::n $start} {$::go && $::n <= $end} {incr ::n} {439 440 # If $::iRepeat is 0, then the malloc() failure is transient - it441 # fails and then subsequent calls succeed. If $::iRepeat is 1, 442 # then the failure is persistent - once malloc() fails it keeps443 # failing.444 #445 set zRepeat "transient"446 if {$::iRepeat} {set zRepeat "persistent"}447 restore_prng_state448 foreach file [glob -nocomplain test.db-mj*] {forcedelete $file}449 450 do_test ${tn}.${zRepeat}.${::n} {451 452 # Remove all traces of database files test.db and test2.db 453 # from the file-system. Then open (empty database) "test.db" 454 # with the handle [db].455 # 456 catch {db close} 457 catch {db2 close} 458 forcedelete test.db459 forcedelete test.db-journal460 forcedelete test.db-wal461 forcedelete test2.db462 forcedelete test2.db-journal463 forcedelete test2.db-wal464 if {[info exists ::mallocopts(-testdb)]} {465 copy_file $::mallocopts(-testdb) test.db466 }467 catch { sqlite3 db test.db }468 if {[info commands db] ne ""} {469 sqlite3_extended_result_codes db 1470 }471 sqlite3_db_config_lookaside db 0 0 0472 473 # Execute any -tclprep and -sqlprep scripts.474 #475 if {[info exists ::mallocopts(-tclprep)]} {476 eval $::mallocopts(-tclprep)477 }478 if {[info exists ::mallocopts(-sqlprep)]} {479 execsql $::mallocopts(-sqlprep)480 }481 482 # Now set the ${::n}th malloc() to fail and execute the -tclbody 483 # and -sqlbody scripts.484 #485 sqlite3_memdebug_fail $::n -repeat $::iRepeat486 set ::mallocbody {}487 if {[info exists ::mallocopts(-tclbody)]} {488 append ::mallocbody "$::mallocopts(-tclbody)\n"489 }490 if {[info exists ::mallocopts(-sqlbody)]} {491 append ::mallocbody "db eval {$::mallocopts(-sqlbody)}"492 }493 494 # The following block sets local variables as follows:495 #496 # isFail - True if an error (any error) was reported by sqlite.497 # nFail - The total number of simulated malloc() failures.498 # nBenign - The number of benign simulated malloc() failures.499 #500 set isFail [catch $::mallocbody msg]501 set nFail [sqlite3_memdebug_fail -1 -benigncnt nBenign]502 # puts -nonewline " (isFail=$isFail nFail=$nFail nBenign=$nBenign) "503 504 # If one or more mallocs failed, run this loop body again.505 #506 set go [expr {$nFail>0}]507 508 if {($nFail-$nBenign)==0} {509 if {$isFail} {510 set v2 $msg511 } else {512 set isFail 1513 set v2 1514 }515 } elseif {!$isFail} {516 set v2 $msg517 } elseif {518 [info command db]=="" || 519 [db errorcode]==7 ||520 $msg=="out of memory"521 } {522 set v2 1523 } else {524 set v2 $msg525 puts [db errorcode]526 }527 lappend isFail $v2528 } {1 1}529 530 if {[info exists ::mallocopts(-cleanup)]} {531 catch [list uplevel #0 $::mallocopts(-cleanup)] msg532 }533 }534 }535 unset ::mallocopts536 sqlite3_memdebug_fail -1537}538 539 540#-------------------------------------------------------------------------541# This proc is used to test a single SELECT statement. Parameter $name is542# passed a name for the test case (i.e. "fts3_malloc-1.4.1") and parameter543# $sql is passed the text of the SELECT statement. Parameter $result is544# set to the expected output if the SELECT statement is successfully545# executed using [db eval].546#547# Example:548#549# do_select_test testcase-1.1 "SELECT 1+1, 1+2" {1 2}550#551# If global variable DO_MALLOC_TEST is set to a non-zero value, or if552# it is not defined at all, then OOM testing is performed on the SELECT553# statement. Each OOM test case is said to pass if either (a) executing554# the SELECT statement succeeds and the results match those specified555# by parameter $result, or (b) TCL throws an "out of memory" error.556#557# If DO_MALLOC_TEST is defined and set to zero, then the SELECT statement558# is executed just once. In this case the test case passes if the results559# match the expected results passed via parameter $result.560#561proc do_select_test {name sql result} {562 uplevel [list doPassiveTest 0 $name $sql [list 0 [list {*}$result]]]563}564 565proc do_restart_select_test {name sql result} {566 uplevel [list doPassiveTest 1 $name $sql [list 0 $result]]567}568 569proc do_error_test {name sql error} {570 uplevel [list doPassiveTest 0 $name $sql [list 1 $error]]571}572 573proc doPassiveTest {isRestart name sql catchres} {574 if {![info exists ::DO_MALLOC_TEST]} { set ::DO_MALLOC_TEST 1 }575 576 if {[info exists ::testprefix] 577 && [string is integer [string range $name 0 0]]578 } {579 set name $::testprefix.$name580 }581 582 switch $::DO_MALLOC_TEST {583 0 { # No malloc failures.584 do_test $name [list set {} [uplevel [list catchsql $sql]]] $catchres585 return586 }587 1 { # Simulate transient failures.588 set nRepeat 1589 set zName "transient"590 set nStartLimit 100000591 set nBackup 1592 }593 2 { # Simulate persistent failures.594 set nRepeat 1595 set zName "persistent"596 set nStartLimit 100000597 set nBackup 1598 }599 3 { # Simulate transient failures with extra brute force.600 set nRepeat 100000601 set zName "ridiculous"602 set nStartLimit 1603 set nBackup 10604 }605 }606 607 # The set of acceptable results from running [catchsql $sql].608 #609 set answers [list {1 {out of memory}} $catchres]610 set str [join $answers " OR "]611 612 set nFail 1613 for {set iLimit $nStartLimit} {$nFail} {incr iLimit} {614 for {set iFail 1} {$nFail && $iFail<=$iLimit} {incr iFail} {615 for {set iTest 0} {$iTest<$nBackup && ($iFail-$iTest)>0} {incr iTest} {616 617 if {$isRestart} { sqlite3 db test.db }618 619 sqlite3_memdebug_fail [expr $iFail-$iTest] -repeat $nRepeat620 set res [uplevel [list catchsql $sql]]621 if {[lsearch -exact $answers $res]>=0} { set res $str }622 set testname "$name.$zName.$iFail"623 do_test "$name.$zName.$iLimit.$iFail" [list set {} $res] $str624 625 set nFail [sqlite3_memdebug_fail -1 -benigncnt nBenign]626 }627 }628 }629}630 631 632#-------------------------------------------------------------------------633# Test a single write to the database. In this case a "write" is a 634# DELETE, UPDATE or INSERT statement.635#636# If OOM testing is performed, there are several acceptable outcomes:637#638# 1) The write succeeds. No error is returned.639#640# 2) An "out of memory" exception is thrown and:641#642# a) The statement has no effect, OR643# b) The current transaction is rolled back, OR644# c) The statement succeeds. This can only happen if the connection645# is in auto-commit mode (after the statement is executed, so this646# includes COMMIT statements).647#648# If the write operation eventually succeeds, zero is returned. If a649# transaction is rolled back, non-zero is returned.650#651# Parameter $name is the name to use for the test case (or test cases).652# The second parameter, $tbl, should be the name of the database table653# being modified. Parameter $sql contains the SQL statement to test.654#655proc do_write_test {name tbl sql} {656 if {![info exists ::DO_MALLOC_TEST]} { set ::DO_MALLOC_TEST 1 }657 658 # Figure out an statement to get a checksum for table $tbl.659 db eval "SELECT * FROM $tbl" V break660 set cksumsql "SELECT md5sum([join [concat rowid $V(*)] ,]) FROM $tbl"661 662 # Calculate the initial table checksum.663 set cksum1 [db one $cksumsql]664 665 if {$::DO_MALLOC_TEST } {666 set answers [list {1 {out of memory}} {0 {}}]667 lappend answers [list 1 {unable to open a temporary database file for storing temporary tables}]668 if {$::DO_MALLOC_TEST==1} {669 set modes {100000 persistent}670 } else {671 set modes {1 transient}672 }673 } else {674 set answers [list {0 {}}]675 set modes [list 0 nofail]676 }677 set str [join $answers " OR "]678 679 foreach {nRepeat zName} $modes {680 for {set iFail 1} 1 {incr iFail} {681 if {$::DO_MALLOC_TEST} {sqlite3_memdebug_fail $iFail -repeat $nRepeat}682 683 set res [uplevel [list catchsql $sql]]684 set nFail [sqlite3_memdebug_fail -1 -benigncnt nBenign]685 if {$nFail==0} {686 do_test $name.$zName.$iFail [list set {} $res] {0 {}}687 return688 } else {689 if {[lsearch $answers $res]>=0} {690 set res $str691 }692 do_test $name.$zName.$iFail [list set {} $res] $str693 set cksum2 [db one $cksumsql]694 if {$cksum1 != $cksum2} return695 }696 }697 }698}699 