sqlite/test/malloc2.test

343 lines
8.2 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.
#
# $Id: malloc2.test,v 1.3 2005/06/06 14:45:43 drh Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
# Only run these tests if memory debugging is turned on.
#
if {[info command sqlite_malloc_stat]==""} {
puts "Skipping malloc tests: not compiled with -DSQLITE_DEBUG..."
finish_test
return
}
ifcapable !globalrecover {
finish_test
return
}
# 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.
sqlite_malloc_fail $::n
do_test malloc2-$tn.$::n.2 {
set res [catchsql [string trim $::mallocopts(-sql)]]
set rc [expr {
0==[string compare $res {1 {out of memory}}] ||
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.
if {[lindex [sqlite_malloc_stat] 2]>0} {
sqlite_malloc_fail -1
break
}
# Nothing should work now, because the allocator should refuse to
# allocate any memory.
do_test malloc2-$tn.$::n.3 {
catchsql {SELECT 'nothing should work'}
} {1 {out of memory}}
# Recover from the malloc failure.
do_test malloc2-$tn.$::n.4 {
if 0 {
db close
sqlite_malloc_fail -1
set ::DB [sqlite3 db test.db]
set dummy SQLITE_OK
} else {
sqlite3_global_recover
}
} {SQLITE_OK}
# 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
# 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.
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.
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