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.16 2008/03/21 16:45:48 drh 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# Usage: do_malloc_test <test number> <options...> 27# 28# The first argument, <test number>, is an integer used to name the 29# tests executed by this proc. Options are as follows: 30# 31# -tclprep TCL script to run to prepare test. 32# -sqlprep SQL script to run to prepare test. 33# -tclbody TCL script to run with malloc failure simulation. 34# -sqlbody TCL script to run with malloc failure simulation. 35# -cleanup TCL script to run after the test. 36# 37# This command runs a series of tests to verify SQLite's ability 38# to handle an out-of-memory condition gracefully. It is assumed 39# that if this condition occurs a malloc() call will return a 40# NULL pointer. Linux, for example, doesn't do that by default. See 41# the "BUGS" section of malloc(3). 42# 43# Each iteration of a loop, the TCL commands in any argument passed 44# to the -tclbody switch, followed by the SQL commands in any argument 45# passed to the -sqlbody switch are executed. Each iteration the 46# Nth call to sqliteMalloc() is made to fail, where N is increased 47# each time the loop runs starting from 1. When all commands execute 48# successfully, the loop ends. 49# 50proc do_malloc_test {tn args} { 51 array unset ::mallocopts 52 array set ::mallocopts $args 53 54 if {[string is integer $tn]} { 55 set tn malloc-$tn 56 } 57 if {[info exists ::mallocopts(-start)]} { 58 set start $::mallocopts(-start) 59 } else { 60 set start 0 61 } 62 if {[info exists ::mallocopts(-end)]} { 63 set end $::mallocopts(-end) 64 } else { 65 set end 50000 66 } 67 save_prng_state 68 69 foreach ::iRepeat {0 10000000} { 70 set ::go 1 71 for {set ::n $start} {$::go && $::n <= $end} {incr ::n} { 72 73 # If $::iRepeat is 0, then the malloc() failure is transient - it 74 # fails and then subsequent calls succeed. If $::iRepeat is 1, 75 # then the failure is persistent - once malloc() fails it keeps 76 # failing. 77 # 78 set zRepeat "transient" 79 if {$::iRepeat} {set zRepeat "persistent"} 80 restore_prng_state 81 foreach file [glob -nocomplain test.db-mj*] {file delete -force $file} 82 83 do_test ${tn}.${zRepeat}.${::n} { 84 85 # Remove all traces of database files test.db and test2.db 86 # from the file-system. Then open (empty database) "test.db" 87 # with the handle [db]. 88 # 89 catch {db close} 90 catch {file delete -force test.db} 91 catch {file delete -force test.db-journal} 92 catch {file delete -force test2.db} 93 catch {file delete -force test2.db-journal} 94 if {[info exists ::mallocopts(-testdb)]} { 95 file copy $::mallocopts(-testdb) test.db 96 } 97 catch { sqlite3 db test.db } 98 if {[info commands db] ne ""} { 99 sqlite3_extended_result_codes db 1 100 } 101 102 # Execute any -tclprep and -sqlprep scripts. 103 # 104 if {[info exists ::mallocopts(-tclprep)]} { 105 eval $::mallocopts(-tclprep) 106 } 107 if {[info exists ::mallocopts(-sqlprep)]} { 108 execsql $::mallocopts(-sqlprep) 109 } 110 111 # Now set the ${::n}th malloc() to fail and execute the -tclbody 112 # and -sqlbody scripts. 113 # 114 sqlite3_memdebug_fail $::n -repeat $::iRepeat 115 set ::mallocbody {} 116 if {[info exists ::mallocopts(-tclbody)]} { 117 append ::mallocbody "$::mallocopts(-tclbody)\n" 118 } 119 if {[info exists ::mallocopts(-sqlbody)]} { 120 append ::mallocbody "db eval {$::mallocopts(-sqlbody)}" 121 } 122 123 # The following block sets local variables as follows: 124 # 125 # isFail - True if an error (any error) was reported by sqlite. 126 # nFail - The total number of simulated malloc() failures. 127 # nBenign - The number of benign simulated malloc() failures. 128 # 129 set isFail [catch $::mallocbody msg] 130 set nFail [sqlite3_memdebug_fail -1 -benigncnt nBenign] 131 # puts -nonewline " (isFail=$isFail nFail=$nFail nBenign=$nBenign) " 132 133 # If one or more mallocs failed, run this loop body again. 134 # 135 set go [expr {$nFail>0}] 136 137 if {($nFail-$nBenign)==0} { 138 if {$isFail} { 139 set v2 $msg 140 } else { 141 set isFail 1 142 set v2 1 143 } 144 } elseif {!$isFail} { 145 set v2 $msg 146 } elseif { 147 [info command db]=="" || 148 [db errorcode]==7 || 149 [db errorcode]==[expr 10+(12<<8)] || 150 $msg=="out of memory" 151 } { 152 set v2 1 153 } else { 154 set v2 $msg 155 breakpoint 156 puts [db errorcode] 157 } 158 lappend isFail $v2 159 } {1 1} 160 161 if {[info exists ::mallocopts(-cleanup)]} { 162 catch [list uplevel #0 $::mallocopts(-cleanup)] msg 163 } 164 } 165 } 166 unset ::mallocopts 167 sqlite3_memdebug_fail -1 168} 169