# 2005 March 18 # # The author disclaims copyright to this source code. In place of # a legal notice, here is a blessing: # # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** # # This file attempts to check that the library can recover from a malloc() # failure when sqlite3_global_recover() is invoked. # # (Later:) The sqlite3_global_recover() interface is now a no-op. # Recovery from malloc() failures is automatic. But we keep these # tests around because you can never have too many test cases. # # $Id: malloc2.test,v 1.8 2007/10/03 08:46:45 danielk1977 Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl # Only run these tests if memory debugging is turned on. # ifcapable !memdebug { puts "Skipping malloc tests: not compiled with -DSQLITE_MEMDEBUG..." finish_test return } sqlite3_extended_result_codes db 1 # Generate a checksum based on the contents of the database. If the # checksum of two databases is the same, and the integrity-check passes # for both, the two databases are identical. # proc cksum {db} { set ret [list] ifcapable tempdb { set sql { SELECT name FROM sqlite_master WHERE type = 'table' UNION SELECT name FROM sqlite_temp_master WHERE type = 'table' UNION SELECT 'sqlite_master' UNION SELECT 'sqlite_temp_master' } } else { set sql { SELECT name FROM sqlite_master WHERE type = 'table' UNION SELECT 'sqlite_master' } } set tbllist [$db eval $sql] set txt {} foreach tbl $tbllist { append txt [$db eval "SELECT * FROM $tbl"] } # puts txt=$txt return [md5 $txt] } proc do_malloc2_test {tn args} { array set ::mallocopts $args set sum [cksum db] for {set ::n 1} {true} {incr ::n} { # Run the SQL. Malloc number $::n is set to fail. A malloc() failure # may or may not be reported. sqlite3_memdebug_fail $::n -repeat 1 do_test malloc2-$tn.$::n.2 { set res [catchsql [string trim $::mallocopts(-sql)]] set rc [expr { 0==[string compare $res {1 {out of memory}}] || [db errorcode] == 3082 || 0==[lindex $res 0] }] if {$rc!=1} { puts "Error: $res" } set rc } {1} # If $::n is greater than the number of malloc() calls required to # execute the SQL, then this test is finished. Break out of the loop. set nFail [sqlite3_memdebug_fail -1] if {$nFail==0} break # Nothing should work now, because the allocator should refuse to # allocate any memory. # # Update: SQLite now automatically recovers from a malloc() failure. # So the statement in the test below would work. if 0 { do_test malloc2-$tn.$::n.3 { catchsql {SELECT 'nothing should work'} } {1 {out of memory}} } # Recover from the malloc failure. # # Update: The new malloc() failure handling means that a transaction may # still be active even if a malloc() has failed. But when these tests were # written this was not the case. So do a manual ROLLBACK here so that the # tests pass. do_test malloc2-$tn.$::n.4 { sqlite3_global_recover catch { execsql { ROLLBACK; } } expr 0 } {0} # Checksum the database. do_test malloc2-$tn.$::n.5 { cksum db } $sum integrity_check malloc2-$tn.$::n.6 if {$::nErr>1} return } unset ::mallocopts } do_test malloc2.1.setup { execsql { CREATE TABLE abc(a, b, c); INSERT INTO abc VALUES(10, 20, 30); INSERT INTO abc VALUES(40, 50, 60); CREATE INDEX abc_i ON abc(a, b, c); } } {} do_malloc2_test 1.1 -sql { SELECT * FROM abc; } do_malloc2_test 1.2 -sql { UPDATE abc SET c = c+10; } do_malloc2_test 1.3 -sql { INSERT INTO abc VALUES(70, 80, 90); } do_malloc2_test 1.4 -sql { DELETE FROM abc; } do_test malloc2.1.5 { execsql { SELECT * FROM abc; } } {} do_test malloc2.2.setup { execsql { CREATE TABLE def(a, b, c); CREATE INDEX def_i1 ON def(a); CREATE INDEX def_i2 ON def(c); BEGIN; } for {set i 0} {$i<20} {incr i} { execsql { INSERT INTO def VALUES(randstr(300,300),randstr(300,300),randstr(300,300)); } } execsql { COMMIT; } } {} do_malloc2_test 2 -sql { BEGIN; UPDATE def SET a = randstr(100,100) WHERE (oid%9)==0; INSERT INTO def SELECT * FROM def WHERE (oid%13)==0; CREATE INDEX def_i3 ON def(b); UPDATE def SET a = randstr(100,100) WHERE (oid%9)==1; INSERT INTO def SELECT * FROM def WHERE (oid%13)==1; CREATE TABLE def2 AS SELECT * FROM def; DROP TABLE def; CREATE TABLE def AS SELECT * FROM def2; DROP TABLE def2; DELETE FROM def WHERE (oid%9)==2; INSERT INTO def SELECT * FROM def WHERE (oid%13)==2; COMMIT; } ifcapable tempdb { do_test malloc2.3.setup { execsql { CREATE TEMP TABLE ghi(a, b, c); BEGIN; } for {set i 0} {$i<20} {incr i} { execsql { INSERT INTO ghi VALUES(randstr(300,300),randstr(300,300),randstr(300,300)); } } execsql { COMMIT; } } {} do_malloc2_test 3 -sql { BEGIN; CREATE INDEX ghi_i1 ON ghi(a); UPDATE def SET a = randstr(100,100) WHERE (oid%2)==0; UPDATE ghi SET a = randstr(100,100) WHERE (oid%2)==0; COMMIT; } } ############################################################################ # The test cases below are to increase the code coverage in btree.c and # pager.c of this test file. The idea is that each malloc() that occurs in # these two source files should be made to fail at least once. # catchsql { DROP TABLE ghi; } do_malloc2_test 4.1 -sql { SELECT * FROM def ORDER BY oid ASC; SELECT * FROM def ORDER BY oid DESC; } do_malloc2_test 4.2 -sql { PRAGMA cache_size = 10; BEGIN; -- This will put about 25 pages on the free list. DELETE FROM def WHERE 1; -- Allocate 32 new root pages. This will exercise the 'extract specific -- page from the freelist' code when in auto-vacuum mode (see the -- allocatePage() routine in btree.c). CREATE TABLE t1(a UNIQUE, b UNIQUE, c UNIQUE); CREATE TABLE t2(a UNIQUE, b UNIQUE, c UNIQUE); CREATE TABLE t3(a UNIQUE, b UNIQUE, c UNIQUE); CREATE TABLE t4(a UNIQUE, b UNIQUE, c UNIQUE); CREATE TABLE t5(a UNIQUE, b UNIQUE, c UNIQUE); CREATE TABLE t6(a UNIQUE, b UNIQUE, c UNIQUE); CREATE TABLE t7(a UNIQUE, b UNIQUE, c UNIQUE); CREATE TABLE t8(a UNIQUE, b UNIQUE, c UNIQUE); ROLLBACK; } ######################################################################## # Test that the global linked list of database handles works. An assert() # will fail if there is some problem. do_test malloc2-5 { sqlite3 db1 test.db sqlite3 db2 test.db sqlite3 db3 test.db sqlite3 db4 test.db sqlite3 db5 test.db sqlite3_extended_result_codes db1 1 sqlite3_extended_result_codes db2 1 sqlite3_extended_result_codes db3 1 sqlite3_extended_result_codes db4 1 sqlite3_extended_result_codes db5 1 # Close the head of the list: db5 close # Close the end of the list: db1 close # Close a handle from the middle of the list: db3 close # Close the other two. Then open and close one more database, to make # sure the head of the list was set back to NULL. db2 close db4 close sqlite db1 test.db db1 close } {} ######################################################################## # Check that if a statement is active sqlite3_global_recover doesn't reset # the sqlite3_malloc_failed variable. # # Update: There is now no sqlite3_malloc_failed variable, so these tests # are not run. # # do_test malloc2-6.1 { # set ::STMT [sqlite3_prepare $::DB {SELECT * FROM def} -1 DUMMY] # sqlite3_step $::STMT # } {SQLITE_ROW} # do_test malloc2-6.2 { # sqlite3 db1 test.db # sqlite_malloc_fail 100 # catchsql { # SELECT * FROM def; # } db1 # } {1 {out of memory}} # do_test malloc2-6.3 { # sqlite3_global_recover # } {SQLITE_BUSY} # do_test malloc2-6.4 { # catchsql { # SELECT 'hello'; # } # } {1 {out of memory}} # do_test malloc2-6.5 { # sqlite3_reset $::STMT # } {SQLITE_OK} # do_test malloc2-6.6 { # sqlite3_global_recover # } {SQLITE_OK} # do_test malloc2-6.7 { # catchsql { # SELECT 'hello'; # } # } {0 hello} # do_test malloc2-6.8 { # sqlite3_step $::STMT # } {SQLITE_ERROR} # do_test malloc2-6.9 { # sqlite3_finalize $::STMT # } {SQLITE_SCHEMA} # do_test malloc2-6.10 { # db1 close # } {} ######################################################################## # Check that if an in-memory database is being used it is not possible # to recover from a malloc() failure. # # Update: An in-memory database can now survive a malloc() failure, so these # tests are not run. # # ifcapable memorydb { # do_test malloc2-7.1 { # sqlite3 db1 :memory: # list # } {} # do_test malloc2-7.2 { # sqlite_malloc_fail 100 # catchsql { # SELECT * FROM def; # } # } {1 {out of memory}} # do_test malloc2-7.3 { # sqlite3_global_recover # } {SQLITE_ERROR} # do_test malloc2-7.4 { # catchsql { # SELECT 'hello'; # } # } {1 {out of memory}} # do_test malloc2-7.5 { # db1 close # } {} # do_test malloc2-7.6 { # sqlite3_global_recover # } {SQLITE_OK} # do_test malloc2-7.7 { # catchsql { # SELECT 'hello'; # } # } {0 hello} # } finish_test