Annotation of embedaddon/sqlite3/test/malloc_common.tcl, revision 1.1.1.1
1.1 misho 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*] {forcedelete $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: copy_file $::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: 0 { # No malloc failures.
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: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>