blob: 992f545f293cf3f1c9b62fb4fcf8eba0672bd1b3 [file] [log] [blame]
drh5a3032b2007-09-03 16:12:09 +00001# 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#
danielk197798c21902008-09-23 16:41:29 +000015# $Id: malloc_common.tcl,v 1.22 2008/09/23 16:41:30 danielk1977 Exp $
danielk1977c9cf9012007-05-30 10:36:47 +000016
drh5a3032b2007-09-03 16:12:09 +000017# If we did not compile with malloc testing enabled, then do nothing.
18#
drh3088d592008-03-21 16:45:47 +000019ifcapable builtin_test {
drh5efaf072008-03-18 00:07:10 +000020 set MEMDEBUG 1
21} else {
drheee4c8c2008-02-18 22:24:57 +000022 set MEMDEBUG 0
danielk1977369ff422007-09-03 07:31:09 +000023 return 0
danielk1977cdc3a6b2007-08-25 13:09:26 +000024}
25
dan8521abf2010-05-31 06:38:34 +000026# The following procs are used as [do_faultsim_test] when injecting OOM
27# faults into test cases.
28#
29proc oom_injectstart {nRepeat iFail} {
30 sqlite3_memdebug_fail $iFail -repeat $nRepeat
31}
32proc oom_injectstop {} {
33 sqlite3_memdebug_fail -1
34}
35
36# This command is only useful when used by the -test script of a
37# [do_faultsim_test] test case.
38#
39proc faultsim_test_result {args} {
40 upvar testrc testrc testresult testresult testnfail testnfail
41 set t [list $testrc $testresult]
42 set r [concat $args [list {1 {out of memory}}]]
43 if { ($testnfail==0 && $t != [lindex $r 0]) || [lsearch $r $t]<0 } {
44 error "nfail=$testnfail rc=$testrc result=$testresult"
45 }
46}
47
48# Usage do_faultsim_test NAME ?OPTIONS...?
49#
50# The first argument, <test number>, is used as a prefix of the test names
51# taken by tests executed by this command. Options are as follows. All
52# options take a single argument.
53#
54# -injectstart Script to enable fault-injection.
55#
56# -injectstop Script to disable fault-injection.
57#
58# -prep Script to execute before -body.
59#
60# -body Script to execute (with fault injection).
61#
62# -test Script to execute after -body.
63#
64proc do_faultsim_test {testname args} {
65
66 set DEFAULT(-injectstart) {oom_injectstart 0}
67 set DEFAULT(-injectstop) {oom_injectstop}
68 set DEFAULT(-prep) ""
69 set DEFAULT(-body) ""
70 set DEFAULT(-test) ""
71
72 array set O [array get DEFAULT]
73 array set O $args
74 foreach o [array names O] {
75 if {[info exists DEFAULT($o)]==0} { error "unknown option: $o" }
76 }
77
78 proc faultsim_test_proc {testrc testresult testnfail} $O(-test)
79
80 set stop 0
81 for {set iFail 1} {!$stop} {incr iFail} {
82
83 # Evaluate the -prep script.
84 #
85 eval $O(-prep)
86
87 # Start the fault-injection. Run the -body script. Stop the fault
88 # injection. Local var $nfail is set to the total number of faults
89 # injected into the system this trial.
90 #
91 eval $O(-injectstart) $iFail
92 set rc [catch $O(-body) res]
93 set nfail [eval $O(-injectstop)]
94
95 # Run the -test script. If it throws no error, consider this trial
96 # sucessful. If it does throw an error, cause a [do_test] test to
97 # fail (and print out the unexpected exception thrown by the -test
98 # script at the same time).
99 #
100 set rc [catch [list faultsim_test_proc $rc $res $nfail] res]
101 if {$rc == 0} {set res ok}
102 do_test $testname.$iFail [list list $rc $res] {0 ok}
103
104 # If no faults where injected this trial, don't bother running
105 # any more. This test is finished.
106 #
107 if {$nfail==0} { set stop 1 }
108 }
109}
110
danielk1977c9cf9012007-05-30 10:36:47 +0000111# Usage: do_malloc_test <test number> <options...>
112#
113# The first argument, <test number>, is an integer used to name the
114# tests executed by this proc. Options are as follows:
115#
116# -tclprep TCL script to run to prepare test.
117# -sqlprep SQL script to run to prepare test.
118# -tclbody TCL script to run with malloc failure simulation.
119# -sqlbody TCL script to run with malloc failure simulation.
120# -cleanup TCL script to run after the test.
121#
122# This command runs a series of tests to verify SQLite's ability
123# to handle an out-of-memory condition gracefully. It is assumed
124# that if this condition occurs a malloc() call will return a
125# NULL pointer. Linux, for example, doesn't do that by default. See
126# the "BUGS" section of malloc(3).
127#
128# Each iteration of a loop, the TCL commands in any argument passed
129# to the -tclbody switch, followed by the SQL commands in any argument
130# passed to the -sqlbody switch are executed. Each iteration the
131# Nth call to sqliteMalloc() is made to fail, where N is increased
132# each time the loop runs starting from 1. When all commands execute
133# successfully, the loop ends.
134#
135proc do_malloc_test {tn args} {
136 array unset ::mallocopts
137 array set ::mallocopts $args
138
139 if {[string is integer $tn]} {
140 set tn malloc-$tn
141 }
drhf3a65f72007-08-22 20:18:21 +0000142 if {[info exists ::mallocopts(-start)]} {
143 set start $::mallocopts(-start)
144 } else {
danielk1977ae72d982007-10-03 08:46:44 +0000145 set start 0
drhf3a65f72007-08-22 20:18:21 +0000146 }
drh1527ff42008-01-18 17:03:32 +0000147 if {[info exists ::mallocopts(-end)]} {
148 set end $::mallocopts(-end)
149 } else {
150 set end 50000
151 }
drh93aed5a2008-01-16 17:46:38 +0000152 save_prng_state
danielk1977c9cf9012007-05-30 10:36:47 +0000153
drh643167f2008-01-22 21:30:53 +0000154 foreach ::iRepeat {0 10000000} {
danielk1977a1644fd2007-08-29 12:31:25 +0000155 set ::go 1
drh1527ff42008-01-18 17:03:32 +0000156 for {set ::n $start} {$::go && $::n <= $end} {incr ::n} {
danielk1977c9cf9012007-05-30 10:36:47 +0000157
danielk1977a1644fd2007-08-29 12:31:25 +0000158 # If $::iRepeat is 0, then the malloc() failure is transient - it
159 # fails and then subsequent calls succeed. If $::iRepeat is 1,
160 # then the failure is persistent - once malloc() fails it keeps
161 # failing.
danielk1977c9cf9012007-05-30 10:36:47 +0000162 #
danielk1977a1644fd2007-08-29 12:31:25 +0000163 set zRepeat "transient"
164 if {$::iRepeat} {set zRepeat "persistent"}
drh93aed5a2008-01-16 17:46:38 +0000165 restore_prng_state
166 foreach file [glob -nocomplain test.db-mj*] {file delete -force $file}
danielk1977c9cf9012007-05-30 10:36:47 +0000167
danielk1977a1644fd2007-08-29 12:31:25 +0000168 do_test ${tn}.${zRepeat}.${::n} {
169
170 # Remove all traces of database files test.db and test2.db
171 # from the file-system. Then open (empty database) "test.db"
172 # with the handle [db].
173 #
174 catch {db close}
175 catch {file delete -force test.db}
176 catch {file delete -force test.db-journal}
dan5e9e4822010-05-03 18:01:21 +0000177 catch {file delete -force test.db-wal}
danielk1977a1644fd2007-08-29 12:31:25 +0000178 catch {file delete -force test2.db}
179 catch {file delete -force test2.db-journal}
dan5e9e4822010-05-03 18:01:21 +0000180 catch {file delete -force test2.db-wal}
danielk1977a1644fd2007-08-29 12:31:25 +0000181 if {[info exists ::mallocopts(-testdb)]} {
182 file copy $::mallocopts(-testdb) test.db
danielk1977c9cf9012007-05-30 10:36:47 +0000183 }
danielk1977ae72d982007-10-03 08:46:44 +0000184 catch { sqlite3 db test.db }
185 if {[info commands db] ne ""} {
186 sqlite3_extended_result_codes db 1
187 }
drhe9d1c722008-08-04 20:13:26 +0000188 sqlite3_db_config_lookaside db 0 0 0
danielk1977a1644fd2007-08-29 12:31:25 +0000189
190 # Execute any -tclprep and -sqlprep scripts.
191 #
192 if {[info exists ::mallocopts(-tclprep)]} {
193 eval $::mallocopts(-tclprep)
194 }
195 if {[info exists ::mallocopts(-sqlprep)]} {
196 execsql $::mallocopts(-sqlprep)
197 }
198
199 # Now set the ${::n}th malloc() to fail and execute the -tclbody
200 # and -sqlbody scripts.
201 #
202 sqlite3_memdebug_fail $::n -repeat $::iRepeat
203 set ::mallocbody {}
204 if {[info exists ::mallocopts(-tclbody)]} {
205 append ::mallocbody "$::mallocopts(-tclbody)\n"
206 }
207 if {[info exists ::mallocopts(-sqlbody)]} {
208 append ::mallocbody "db eval {$::mallocopts(-sqlbody)}"
209 }
danielk1977c9cf9012007-05-30 10:36:47 +0000210
danielk1977a1644fd2007-08-29 12:31:25 +0000211 # The following block sets local variables as follows:
212 #
213 # isFail - True if an error (any error) was reported by sqlite.
214 # nFail - The total number of simulated malloc() failures.
215 # nBenign - The number of benign simulated malloc() failures.
216 #
217 set isFail [catch $::mallocbody msg]
218 set nFail [sqlite3_memdebug_fail -1 -benigncnt nBenign]
drh643167f2008-01-22 21:30:53 +0000219 # puts -nonewline " (isFail=$isFail nFail=$nFail nBenign=$nBenign) "
danielk1977a1644fd2007-08-29 12:31:25 +0000220
221 # If one or more mallocs failed, run this loop body again.
222 #
223 set go [expr {$nFail>0}]
224
225 if {($nFail-$nBenign)==0} {
226 if {$isFail} {
227 set v2 $msg
228 } else {
229 set isFail 1
230 set v2 1
231 }
232 } elseif {!$isFail} {
233 set v2 $msg
danielk1977ae72d982007-10-03 08:46:44 +0000234 } elseif {
235 [info command db]=="" ||
236 [db errorcode]==7 ||
danielk1977ae72d982007-10-03 08:46:44 +0000237 $msg=="out of memory"
238 } {
danielk1977a1644fd2007-08-29 12:31:25 +0000239 set v2 1
240 } else {
241 set v2 $msg
danielk1977ae72d982007-10-03 08:46:44 +0000242 puts [db errorcode]
danielk1977a1644fd2007-08-29 12:31:25 +0000243 }
244 lappend isFail $v2
245 } {1 1}
246
247 if {[info exists ::mallocopts(-cleanup)]} {
248 catch [list uplevel #0 $::mallocopts(-cleanup)] msg
249 }
danielk1977c9cf9012007-05-30 10:36:47 +0000250 }
251 }
252 unset ::mallocopts
danielk197777519402007-08-30 11:48:31 +0000253 sqlite3_memdebug_fail -1
danielk1977c9cf9012007-05-30 10:36:47 +0000254}
danef378022010-05-04 11:06:03 +0000255
256
257#-------------------------------------------------------------------------
258# This proc is used to test a single SELECT statement. Parameter $name is
259# passed a name for the test case (i.e. "fts3_malloc-1.4.1") and parameter
260# $sql is passed the text of the SELECT statement. Parameter $result is
261# set to the expected output if the SELECT statement is successfully
262# executed using [db eval].
263#
264# Example:
265#
266# do_select_test testcase-1.1 "SELECT 1+1, 1+2" {1 2}
267#
268# If global variable DO_MALLOC_TEST is set to a non-zero value, or if
269# it is not defined at all, then OOM testing is performed on the SELECT
270# statement. Each OOM test case is said to pass if either (a) executing
271# the SELECT statement succeeds and the results match those specified
272# by parameter $result, or (b) TCL throws an "out of memory" error.
273#
274# If DO_MALLOC_TEST is defined and set to zero, then the SELECT statement
275# is executed just once. In this case the test case passes if the results
276# match the expected results passed via parameter $result.
277#
278proc do_select_test {name sql result} {
279 uplevel [list doPassiveTest 0 $name $sql [list 0 $result]]
280}
281
282proc do_restart_select_test {name sql result} {
283 uplevel [list doPassiveTest 1 $name $sql [list 0 $result]]
284}
285
286proc do_error_test {name sql error} {
287 uplevel [list doPassiveTest 0 $name $sql [list 1 $error]]
288}
289
290proc doPassiveTest {isRestart name sql catchres} {
291 if {![info exists ::DO_MALLOC_TEST]} { set ::DO_MALLOC_TEST 1 }
292
293 switch $::DO_MALLOC_TEST {
294 0 { # No malloc failures.
295 do_test $name [list set {} [uplevel [list catchsql $sql]]] $catchres
296 return
297 }
298 1 { # Simulate transient failures.
299 set nRepeat 1
300 set zName "transient"
301 set nStartLimit 100000
302 set nBackup 1
303 }
304 2 { # Simulate persistent failures.
305 set nRepeat 1
306 set zName "persistent"
307 set nStartLimit 100000
308 set nBackup 1
309 }
310 3 { # Simulate transient failures with extra brute force.
311 set nRepeat 100000
312 set zName "ridiculous"
313 set nStartLimit 1
314 set nBackup 10
315 }
316 }
317
318 # The set of acceptable results from running [catchsql $sql].
319 #
320 set answers [list {1 {out of memory}} $catchres]
321 set str [join $answers " OR "]
322
323 set nFail 1
324 for {set iLimit $nStartLimit} {$nFail} {incr iLimit} {
325 for {set iFail 1} {$nFail && $iFail<=$iLimit} {incr iFail} {
326 for {set iTest 0} {$iTest<$nBackup && ($iFail-$iTest)>0} {incr iTest} {
327
328 if {$isRestart} { sqlite3 db test.db }
329
330 sqlite3_memdebug_fail [expr $iFail-$iTest] -repeat $nRepeat
331 set res [uplevel [list catchsql $sql]]
332 if {[lsearch -exact $answers $res]>=0} { set res $str }
333 set testname "$name.$zName.$iFail"
334 do_test "$name.$zName.$iLimit.$iFail" [list set {} $res] $str
335
336 set nFail [sqlite3_memdebug_fail -1 -benigncnt nBenign]
337 }
338 }
339 }
340}
341
342
343#-------------------------------------------------------------------------
344# Test a single write to the database. In this case a "write" is a
345# DELETE, UPDATE or INSERT statement.
346#
347# If OOM testing is performed, there are several acceptable outcomes:
348#
349# 1) The write succeeds. No error is returned.
350#
351# 2) An "out of memory" exception is thrown and:
352#
353# a) The statement has no effect, OR
354# b) The current transaction is rolled back, OR
355# c) The statement succeeds. This can only happen if the connection
356# is in auto-commit mode (after the statement is executed, so this
357# includes COMMIT statements).
358#
359# If the write operation eventually succeeds, zero is returned. If a
360# transaction is rolled back, non-zero is returned.
361#
362# Parameter $name is the name to use for the test case (or test cases).
363# The second parameter, $tbl, should be the name of the database table
364# being modified. Parameter $sql contains the SQL statement to test.
365#
366proc do_write_test {name tbl sql} {
367 if {![info exists ::DO_MALLOC_TEST]} { set ::DO_MALLOC_TEST 1 }
368
369 # Figure out an statement to get a checksum for table $tbl.
370 db eval "SELECT * FROM $tbl" V break
371 set cksumsql "SELECT md5sum([join [concat rowid $V(*)] ,]) FROM $tbl"
372
373 # Calculate the initial table checksum.
374 set cksum1 [db one $cksumsql]
375
376 if {$::DO_MALLOC_TEST } {
377 set answers [list {1 {out of memory}} {0 {}}]
378 if {$::DO_MALLOC_TEST==1} {
379 set modes {100000 transient}
380 } else {
381 set modes {1 persistent}
382 }
383 } else {
384 set answers [list {0 {}}]
385 set modes [list 0 nofail]
386 }
387 set str [join $answers " OR "]
388
389 foreach {nRepeat zName} $modes {
390 for {set iFail 1} 1 {incr iFail} {
391 if {$::DO_MALLOC_TEST} {sqlite3_memdebug_fail $iFail -repeat $nRepeat}
392
393 set res [uplevel [list catchsql $sql]]
394 set nFail [sqlite3_memdebug_fail -1 -benigncnt nBenign]
395 if {$nFail==0} {
396 do_test $name.$zName.$iFail [list set {} $res] {0 {}}
397 return
398 } else {
399 if {[lsearch $answers $res]>=0} {
400 set res $str
401 }
402 do_test $name.$zName.$iFail [list set {} $res] $str
403 set cksum2 [db one $cksumsql]
404 if {$cksum1 != $cksum2} return
405 }
406 }
407 }
408}