123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446 |
- # 2001 September 15
- #
- # 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. The
- # focus of this script is btree database backend
- #
- # $Id: btree2.test,v 1.10 2002/02/19 13:39:23 drh Exp $
- set testdir [file dirname $argv0]
- source $testdir/tester.tcl
- if {[info commands btree_open]!=""} {
- # Create a new database file containing no entries. The database should
- # contain 5 tables:
- #
- # 2 The descriptor table
- # 3 The foreground table
- # 4 The background table
- # 5 The long key table
- # 6 The long data table
- #
- # An explanation for what all these tables are used for is provided below.
- #
- do_test btree2-1.1 {
- expr srand(1)
- file delete -force test2.bt
- file delete -force test2.bt-journal
- set ::b [btree_open test2.bt]
- btree_begin_transaction $::b
- btree_create_table $::b
- } {3}
- do_test btree2-1.2 {
- btree_create_table $::b
- } {4}
- do_test btree2-1.3 {
- btree_create_table $::b
- } {5}
- do_test btree2-1.4 {
- btree_create_table $::b
- } {6}
- do_test btree2-1.5 {
- set ::c2 [btree_cursor $::b 2 1]
- btree_insert $::c2 {one} {1}
- btree_delete $::c2
- btree_close_cursor $::c2
- btree_commit $::b
- btree_integrity_check $::b 2 3 4 5 6
- } {}
- # This test module works by making lots of pseudo-random changes to a
- # database while simultaneously maintaining an invariant on that database.
- # Periodically, the script does a sanity check on the database and verifies
- # that the invariant is satisfied.
- #
- # The invariant is as follows:
- #
- # 1. The descriptor table always contains 2 enters. An entry keyed by
- # "N" is the number of elements in the foreground and background tables
- # combined. The entry keyed by "L" is the number of digits in the keys
- # for foreground and background tables.
- #
- # 2. The union of the foreground an background tables consists of N entries
- # where each entry an L-digit key. (Actually, some keys can be longer
- # than L characters, but they always start with L digits.) The keys
- # cover all integers between 1 and N. Whenever an entry is added to
- # the foreground it is removed form the background and vice versa.
- #
- # 3. Some entries in the foreground and background tables have keys that
- # begin with an L-digit number but are followed by additional characters.
- # For each such entry there is a corresponding entry in the long key
- # table. The long key table entry has a key which is just the L-digit
- # number and data which is the length of the key in the foreground and
- # background tables.
- #
- # 4. The data for both foreground and background entries is usually a
- # short string. But some entries have long data strings. For each
- # such entries there is an entry in the long data type. The key to
- # long data table is an L-digit number. (The extension on long keys
- # is omitted.) The data is the number of charaters in the data of the
- # foreground or background entry.
- #
- # The following function builds a database that satisfies all of the above
- # invariants.
- #
- proc build_db {N L} {
- for {set i 2} {$i<=6} {incr i} {
- catch {btree_close_cursor [set ::c$i]}
- btree_clear_table $::b $i
- set ::c$i [btree_cursor $::b $i 1]
- }
- btree_insert $::c2 N $N
- btree_insert $::c2 L $L
- set format %0${L}d
- for {set i 1} {$i<=$N} {incr i} {
- set key [format $format $i]
- set data $key
- btree_insert $::c3 $key $data
- }
- }
- # Given a base key number and a length, construct the full text of the key
- # or data.
- #
- proc make_payload {keynum L len} {
- set key [format %0${L}d $keynum]
- set r $key
- set i 1
- while {[string length $r]<$len} {
- append r " ($i) $key"
- incr i
- }
- return [string range $r 0 [expr {$len-1}]]
- }
- # Verify the invariants on the database. Return an empty string on
- # success or an error message if something is amiss.
- #
- proc check_invariants {} {
- set ck [btree_integrity_check $::b 2 3 4 5 6]
- if {$ck!=""} {
- puts "\n*** SANITY:\n$ck"
- exit
- return $ck
- }
- btree_move_to $::c3 {}
- btree_move_to $::c4 {}
- btree_move_to $::c2 N
- set N [btree_data $::c2]
- btree_move_to $::c2 L
- set L [btree_data $::c2]
- set LM1 [expr {$L-1}]
- for {set i 1} {$i<=$N} {incr i} {
- set key [btree_key $::c3]
- if {[scan $key %d k]<1} {set k 0}
- if {$k!=$i} {
- set key [btree_key $::c4]
- if {[scan $key %d k]<1} {set k 0}
- if {$k!=$i} {
- # puts "MISSING $i"
- # puts {Page 3:}; btree_page_dump $::b 3
- # puts {Page 4:}; btree_page_dump $::b 4
- # exit
- return "Key $i is missing from both foreground and background"
- }
- set data [btree_data $::c4]
- btree_next $::c4
- } else {
- set data [btree_data $::c3]
- btree_next $::c3
- }
- set skey [string range $key 0 $LM1]
- if {[btree_move_to $::c5 $skey]==0} {
- set keylen [btree_data $::c5]
- } else {
- set keylen $L
- }
- if {[string length $key]!=$keylen} {
- return "Key $i is the wrong size.\
- Is \"$key\" but should be \"[make_payload $k $L $keylen]\""
- }
- if {[make_payload $k $L $keylen]!=$key} {
- return "Key $i has an invalid extension"
- }
- if {[btree_move_to $::c6 $skey]==0} {
- set datalen [btree_data $::c6]
- } else {
- set datalen $L
- }
- if {[string length $data]!=$datalen} {
- return "Data for $i is the wrong size.\
- Is [string length $data] but should be $datalen"
- }
- if {[make_payload $k $L $datalen]!=$data} {
- return "Entry $i has an incorrect data"
- }
- }
- }
- # Make random changes to the database such that each change preserves
- # the invariants. The number of changes is $n*N where N is the parameter
- # from the descriptor table. Each changes begins with a random key.
- # the entry with that key is put in the foreground table with probability
- # $I and it is put in background with probability (1.0-$I). It gets
- # a long key with probability $K and long data with probability $D.
- #
- set chngcnt 0
- proc random_changes {n I K D} {
- btree_move_to $::c2 N
- set N [btree_data $::c2]
- btree_move_to $::c2 L
- set L [btree_data $::c2]
- set LM1 [expr {$L-1}]
- set total [expr {int($N*$n)}]
- set format %0${L}d
- for {set i 0} {$i<$total} {incr i} {
- set k [expr {int(rand()*$N)+1}]
- set insert [expr {rand()<=$I}]
- set longkey [expr {rand()<=$K}]
- set longdata [expr {rand()<=$D}]
- # incr ::chngcnt
- # if {$::chngcnt==251} {btree_tree_dump $::b 3}
- # puts "CHANGE $::chngcnt: $k $insert $longkey $longdata"
- if {$longkey} {
- set x [expr {rand()}]
- set keylen [expr {int($x*$x*$x*$x*3000)+10}]
- } else {
- set keylen $L
- }
- set key [make_payload $k $L $keylen]
- if {$longdata} {
- set x [expr {rand()}]
- set datalen [expr {int($x*$x*$x*$x*3000)+10}]
- } else {
- set datalen $L
- }
- set data [make_payload $k $L $datalen]
- set basekey [format $format $k]
- if {[set c [btree_move_to $::c3 $basekey]]==0} {
- btree_delete $::c3
- } else {
- if {$c<0} {btree_next $::c3}
- if {[string match $basekey* [btree_key $::c3]]} {
- btree_delete $::c3
- }
- }
- if {[set c [btree_move_to $::c4 $basekey]]==0} {
- btree_delete $::c4
- } else {
- if {$c<0} {btree_next $::c4}
- if {[string match $basekey* [btree_key $::c4]]} {
- btree_delete $::c4
- }
- }
- if {[scan [btree_key $::c4] %d kx]<1} {set kx -1}
- if {$kx==$k} {
- btree_delete $::c4
- }
- if {$insert} {
- btree_insert $::c3 $key $data
- } else {
- btree_insert $::c4 $key $data
- }
- if {$longkey} {
- btree_insert $::c5 $basekey $keylen
- } elseif {[btree_move_to $::c5 $basekey]==0} {
- btree_delete $::c5
- }
- if {$longdata} {
- btree_insert $::c6 $basekey $datalen
- } elseif {[btree_move_to $::c6 $basekey]==0} {
- btree_delete $::c6
- }
- # set ck [btree_integrity_check $::b 2 3 4 5 6]
- # if {$ck!=""} {
- # puts "\nSANITY CHECK FAILED!\n$ck"
- # exit
- # }
- # puts "PAGE 3:"; btree_page_dump $::b 3
- # puts "PAGE 4:"; btree_page_dump $::b 4
- }
- }
- # Repeat this test sequence on database of various sizes
- #
- set testno 2
- foreach {N L} {
- 10 2
- 50 2
- 200 3
- 2000 5
- } {
- puts "**** N=$N L=$L ****"
- set hash [md5file test2.bt]
- do_test btree2-$testno.1 [subst -nocommands {
- set ::c2 [btree_cursor $::b 2 1]
- set ::c3 [btree_cursor $::b 3 1]
- set ::c4 [btree_cursor $::b 4 1]
- set ::c5 [btree_cursor $::b 5 1]
- set ::c6 [btree_cursor $::b 6 1]
- btree_begin_transaction $::b
- build_db $N $L
- check_invariants
- }] {}
- do_test btree2-$testno.2 {
- btree_close_cursor $::c2
- btree_close_cursor $::c3
- btree_close_cursor $::c4
- btree_close_cursor $::c5
- btree_close_cursor $::c6
- btree_rollback $::b
- md5file test2.bt
- } $hash
- do_test btree2-$testno.3 [subst -nocommands {
- btree_begin_transaction $::b
- set ::c2 [btree_cursor $::b 2 1]
- set ::c3 [btree_cursor $::b 3 1]
- set ::c4 [btree_cursor $::b 4 1]
- set ::c5 [btree_cursor $::b 5 1]
- set ::c6 [btree_cursor $::b 6 1]
- build_db $N $L
- check_invariants
- }] {}
- do_test btree2-$testno.4 {
- btree_commit $::b
- check_invariants
- } {}
- do_test btree2-$testno.5 {
- lindex [btree_pager_stats $::b] 1
- } {6}
- do_test btree2-$testno.6 {
- btree_close_cursor $::c2
- btree_close_cursor $::c3
- btree_close_cursor $::c4
- btree_close_cursor $::c5
- btree_close_cursor $::c6
- lindex [btree_pager_stats $::b] 1
- } {0}
- do_test btree2-$testno.7 {
- btree_close $::b
- } {}
- after 100
- # For each database size, run various changes tests.
- #
- set num2 1
- foreach {n I K D} {
- 0.5 0.5 0.1 0.1
- 1.0 0.2 0.1 0.1
- 1.0 0.8 0.1 0.1
- 2.0 0.0 0.1 0.1
- 2.0 1.0 0.1 0.1
- 2.0 0.0 0.0 0.0
- 2.0 1.0 0.0 0.0
- } {
- set testid btree2-$testno.8.$num2
- set hash [md5file test2.bt]
- do_test $testid.0 {
- set ::b [btree_open test2.bt]
- set ::c2 [btree_cursor $::b 2 1]
- set ::c3 [btree_cursor $::b 3 1]
- set ::c4 [btree_cursor $::b 4 1]
- set ::c5 [btree_cursor $::b 5 1]
- set ::c6 [btree_cursor $::b 6 1]
- check_invariants
- } {}
- set cnt 6
- for {set i 2} {$i<=6} {incr i} {
- if {[lindex [btree_cursor_dump [set ::c$i]] 0]!=$i} {incr cnt}
- }
- do_test $testid.1 {
- btree_begin_transaction $::b
- lindex [btree_pager_stats $::b] 1
- } $cnt
- # exec cp test2.bt test2.bt.bu1
- do_test $testid.2 [subst {
- random_changes $n $I $K $D
- }] {}
- do_test $testid.3 {
- check_invariants
- } {}
- do_test $testid.4 {
- btree_close_cursor $::c2
- btree_close_cursor $::c3
- btree_close_cursor $::c4
- btree_close_cursor $::c5
- btree_close_cursor $::c6
- btree_rollback $::b
- md5file test2.bt
- } $hash
- # exec cp test2.bt test2.bt.bu2
- btree_begin_transaction $::b
- set ::c2 [btree_cursor $::b 2 1]
- set ::c3 [btree_cursor $::b 3 1]
- set ::c4 [btree_cursor $::b 4 1]
- set ::c5 [btree_cursor $::b 5 1]
- set ::c6 [btree_cursor $::b 6 1]
- do_test $testid.5 [subst {
- random_changes $n $I $K $D
- }] {}
- do_test $testid.6 {
- check_invariants
- } {}
- do_test $testid.7 {
- btree_commit $::b
- check_invariants
- } {}
- set hash [md5file test2.bt]
- do_test $testid.8 {
- btree_close_cursor $::c2
- btree_close_cursor $::c3
- btree_close_cursor $::c4
- btree_close_cursor $::c5
- btree_close_cursor $::c6
- lindex [btree_pager_stats $::b] 1
- } {0}
- do_test $testid.9 {
- btree_close $::b
- set ::b [btree_open test2.bt]
- set ::c2 [btree_cursor $::b 2 1]
- set ::c3 [btree_cursor $::b 3 1]
- set ::c4 [btree_cursor $::b 4 1]
- set ::c5 [btree_cursor $::b 5 1]
- set ::c6 [btree_cursor $::b 6 1]
- check_invariants
- } {}
- do_test $testid.10 {
- btree_close_cursor $::c2
- btree_close_cursor $::c3
- btree_close_cursor $::c4
- btree_close_cursor $::c5
- btree_close_cursor $::c6
- lindex [btree_pager_stats $::b] 1
- } {0}
- do_test $testid.11 {
- btree_close $::b
- } {}
- incr num2
- }
- incr testno
- set ::b [btree_open test2.bt]
- }
- # Testing is complete. Shut everything down.
- #
- do_test btree-999.1 {
- lindex [btree_pager_stats $::b] 1
- } {0}
- do_test btree-999.2 {
- btree_close $::b
- } {}
- do_test btree-999.3 {
- file delete -force test2.bt
- file exists test2.bt-journal
- } {0}
- } ;# end if( not mem: and has pager_open command );
- finish_test
|