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