wasCSharpSQLite – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 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*] {file delete -force $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 | file copy $::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 | |||
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 | } |