# 2007 March 24 # # 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 implements regression tests for SQLite library. # # $Id: exclusive2.test,v 1.10 2008/11/27 02:22:11 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl ifcapable {!pager_pragmas} { finish_test return } # This module does not work right if the cache spills at unexpected # moments. So disable the soft-heap-limit. # sqlite3_soft_heap_limit 0 proc pagerChangeCounter {filename new {fd ""}} { if {$fd==""} { set fd [open $filename RDWR] fconfigure $fd -translation binary -encoding binary set needClose 1 } else { set needClose 0 } if {$new ne ""} { seek $fd 24 set a [expr {($new&0xFF000000)>>24}] set b [expr {($new&0x00FF0000)>>16}] set c [expr {($new&0x0000FF00)>>8}] set d [expr {($new&0x000000FF)}] puts -nonewline $fd [binary format cccc $a $b $c $d] flush $fd } seek $fd 24 foreach {a b c d} [list 0 0 0 0] {} binary scan [read $fd 4] cccc a b c d set ret [expr ($a&0x000000FF)<<24] incr ret [expr ($b&0x000000FF)<<16] incr ret [expr ($c&0x000000FF)<<8] incr ret [expr ($d&0x000000FF)<<0] if {$needClose} {close $fd} return $ret } proc readPagerChangeCounter {filename} { set fd [open $filename RDONLY] fconfigure $fd -translation binary -encoding binary seek $fd 24 foreach {a b c d} [list 0 0 0 0] {} binary scan [read $fd 4] cccc a b c d set ret [expr ($a&0x000000FF)<<24] incr ret [expr ($b&0x000000FF)<<16] incr ret [expr ($c&0x000000FF)<<8] incr ret [expr ($d&0x000000FF)<<0] close $fd return $ret } proc t1sig {{db db}} { execsql {SELECT count(*), md5sum(a) FROM t1} $db } do_test exclusive2-1.0 { readPagerChangeCounter test.db } {0} #----------------------------------------------------------------------- # The following tests - exclusive2-1.X - check that: # # 1-3: Build a database with connection 1, calculate a signature. # 4-7: Modify the database using a second connection in a way that # does not modify the freelist, then reset the pager change-counter # to the value it had before the modifications. # 8: Check that using the first connection, the database signature # is still the same. This is because it uses the in-memory cache. # It can't tell the db has changed because we reset the change-counter. # 9: Increment the change-counter. # 10: Ensure that the first connection now sees the updated database. It # sees the change-counter has been incremented and discards the # invalid in-memory cache. # # This will only work if the database cache is large enough to hold # the entire database. In the case of 1024 byte pages, this means # the cache size must be at least 17. Otherwise, some pages will be # loaded from the database file in step 8. # # For similar reasons, this test does not work with the memsubsys1 permutation. # Permutation memsubsys1 configures the pcache subsystem to use a static # allocation of 24 pages (shared between all pagers). This is not enough for # this test. # do_test exclusive2-1.1 { execsql { BEGIN; CREATE TABLE t1(a, b); INSERT INTO t1(a) VALUES(randstr(10, 400)); INSERT INTO t1(a) VALUES(randstr(10, 400)); INSERT INTO t1(a) SELECT randstr(10, 400) FROM t1; INSERT INTO t1(a) SELECT randstr(10, 400) FROM t1; INSERT INTO t1(a) SELECT randstr(10, 400) FROM t1; INSERT INTO t1(a) SELECT randstr(10, 400) FROM t1; INSERT INTO t1(a) SELECT randstr(10, 400) FROM t1; COMMIT; SELECT count(*) FROM t1; } } {64} do_test exclusive2-1.2.1 { # Make sure the pager cache is large enough to store the # entire database. set nPage [expr [file size test.db]/1024] if {$::SQLITE_DEFAULT_CACHE_SIZE < $nPage} { execsql "PRAGMA cache_size = $nPage" } expr {[execsql {PRAGMA cache_size}] >= $nPage} } {1} do_test exclusive2-1.2 { set ::sig [t1sig] readPagerChangeCounter test.db } {1} do_test exclusive2-1.3 { t1sig } $::sig do_test exclusive2-1.4 { sqlite3 db2 test.db t1sig db2 } $::sig do_test exclusive2-1.5 { execsql { UPDATE t1 SET b=a, a=NULL; } db2 expr {[t1sig db2] eq $::sig} } 0 do_test exclusive2-1.6 { readPagerChangeCounter test.db } {2} do_test exclusive2-1.7 { pagerChangeCounter test.db 1 } {1} if {[permutation] != "memsubsys1"} { do_test exclusive2-1.9 { t1sig expr {[t1sig] eq $::sig} } {1} } do_test exclusive2-1.10 { pagerChangeCounter test.db 2 } {2} do_test exclusive2-1.11 { expr {[t1sig] eq $::sig} } {0} db2 close #-------------------------------------------------------------------- # These tests - exclusive2-2.X - are similar to exclusive2-1.X, # except that they are run with locking_mode=EXCLUSIVE. # # 1-3: Build a database with exclusive-access connection 1, # calculate a signature. # 4: Corrupt the database by writing 10000 bytes of garbage # starting at the beginning of page 2. Check that connection 1 # still works. It should be accessing the in-memory cache. # 5-6: Modify the dataase change-counter. Connection 1 still works # entirely from in-memory cache, because it doesn't check the # change-counter. # 7-8 Set the locking-mode back to normal. After the db is unlocked, # SQLite detects the modified change-counter and discards the # in-memory cache. Then it finds the corruption caused in step 4.... # # As above, this test is only applicable if the pager cache is # large enough to hold the entire database. With 1024 byte pages, # this means 19 pages. We also need to disable the soft-heap-limit # to prevent memory-induced cache spills. # do_test exclusive2-2.1 { execsql {PRAGMA cache_size=1000;} execsql {PRAGMA locking_mode = exclusive;} execsql { BEGIN; DELETE FROM t1; INSERT INTO t1(a) VALUES(randstr(10, 400)); INSERT INTO t1(a) VALUES(randstr(10, 400)); INSERT INTO t1(a) SELECT randstr(10, 400) FROM t1; INSERT INTO t1(a) SELECT randstr(10, 400) FROM t1; INSERT INTO t1(a) SELECT randstr(10, 400) FROM t1; INSERT INTO t1(a) SELECT randstr(10, 400) FROM t1; INSERT INTO t1(a) SELECT randstr(10, 400) FROM t1; COMMIT; SELECT count(*) FROM t1; } } {64} do_test exclusive2-2.2.1 { # Make sure the pager cache is large enough to store the # entire database. set nPage [expr [file size test.db]/1024] if {$::SQLITE_DEFAULT_CACHE_SIZE < $nPage} { execsql "PRAGMA cache_size = $nPage" } expr {[execsql {PRAGMA cache_size}] >= $nPage} } {1} do_test exclusive2-2.2 { set ::sig [t1sig] readPagerChangeCounter test.db } {3} do_test exclusive2-2.3 { t1sig } $::sig do_test exclusive2-2.4 { set ::fd [open test.db RDWR] fconfigure $::fd -translation binary seek $::fd 1024 puts -nonewline $::fd [string repeat [binary format c 0] 10000] flush $::fd t1sig } $::sig do_test exclusive2-2.5 { pagerChangeCounter test.db 5 $::fd } {5} do_test exclusive2-2.6 { t1sig } $::sig do_test exclusive2-2.7 { execsql {PRAGMA locking_mode = normal} t1sig } $::sig do_test exclusive2-2.8 { set rc [catch {t1sig} msg] list $rc $msg } {1 {database disk image is malformed}} #-------------------------------------------------------------------- # These tests - exclusive2-3.X - verify that the pager change-counter # is only incremented by the first change when in exclusive access # mode. In normal mode, the change-counter is incremented once # per write-transaction. # db close catch {close $::fd} file delete -force test.db file delete -force test.db-journal do_test exclusive2-3.0 { sqlite3 db test.db execsql { BEGIN; CREATE TABLE t1(a UNIQUE); INSERT INTO t1 VALUES(randstr(200, 200)); INSERT INTO t1 VALUES(randstr(200, 200)); COMMIT; } readPagerChangeCounter test.db } {1} do_test exclusive2-3.1 { execsql { INSERT INTO t1 VALUES(randstr(200, 200)); } readPagerChangeCounter test.db } {2} do_test exclusive2-3.2 { execsql { INSERT INTO t1 VALUES(randstr(200, 200)); } readPagerChangeCounter test.db } {3} do_test exclusive2-3.3 { execsql { PRAGMA locking_mode = exclusive; INSERT INTO t1 VALUES(randstr(200, 200)); } readPagerChangeCounter test.db } {4} do_test exclusive2-3.4 { breakpoint execsql { INSERT INTO t1 VALUES(randstr(200, 200)); } readPagerChangeCounter test.db } {4} do_test exclusive2-3.5 { execsql { PRAGMA locking_mode = normal; INSERT INTO t1 VALUES(randstr(200, 200)); } readPagerChangeCounter test.db } {4} do_test exclusive2-3.6 { execsql { INSERT INTO t1 VALUES(randstr(200, 200)); } readPagerChangeCounter test.db } {5} sqlite3_soft_heap_limit $cmdlinearg(soft-heap-limit) finish_test