367 lines
9.3 KiB
Plaintext
367 lines
9.3 KiB
Plaintext
|
# 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
|