1# 2# 2001 September 15 3# 4# The author disclaims copyright to this source code. In place of 5# a legal notice, here is a blessing: 6# 7# May you do good and not evil. 8# May you find forgiveness for yourself and forgive others. 9# May you share freely, never taking more than you give. 10# 11#*********************************************************************** 12# This file implements some common TCL routines used for regression 13# testing the SQLite library 14# 15# $Id: tester.tcl,v 1.28 2004/02/14 01:39:50 drh Exp $ 16 17# Make sure tclsqlite was compiled correctly. Abort now with an 18# error message if not. 19# 20if {[sqlite -tcl-uses-utf]} { 21 if {"\u1234"=="u1234"} { 22 puts stderr "***** BUILD PROBLEM *****" 23 puts stderr "$argv0 was linked against an older version" 24 puts stderr "of TCL that does not support Unicode, but uses a header" 25 puts stderr "file (\"tcl.h\") from a new TCL version that does support" 26 puts stderr "Unicode. This combination causes internal errors." 27 puts stderr "Recompile using a TCL library and header file that match" 28 puts stderr "and try again.\n**************************" 29 exit 1 30 } 31} else { 32 if {"\u1234"!="u1234"} { 33 puts stderr "***** BUILD PROBLEM *****" 34 puts stderr "$argv0 was linked against an newer version" 35 puts stderr "of TCL that supports Unicode, but uses a header file" 36 puts stderr "(\"tcl.h\") from a old TCL version that does not support" 37 puts stderr "Unicode. This combination causes internal errors." 38 puts stderr "Recompile using a TCL library and header file that match" 39 puts stderr "and try again.\n**************************" 40 exit 1 41 } 42} 43 44# Use the pager codec if it is available 45# 46if {[sqlite -has-codec] && [info command sqlite_orig]==""} { 47 rename sqlite sqlite_orig 48 proc sqlite {args} { 49 if {[llength $args]==2 && [string index [lindex $args 0] 0]!="-"} { 50 lappend args -key {xyzzy} 51 } 52 uplevel 1 sqlite_orig $args 53 } 54} 55 56 57# Create a test database 58# 59catch {db close} 60file delete -force test.db 61file delete -force test.db-journal 62sqlite db ./test.db 63if {[info exists ::SETUP_SQL]} { 64 db eval $::SETUP_SQL 65} 66 67# Abort early if this script has been run before. 68# 69if {[info exists nTest]} return 70 71# Set the test counters to zero 72# 73set nErr 0 74set nTest 0 75set nProb 0 76set skip_test 0 77set failList {} 78 79# Invoke the do_test procedure to run a single test 80# 81proc do_test {name cmd expected} { 82 global argv nErr nTest skip_test 83 if {$skip_test} { 84 set skip_test 0 85 return 86 } 87 if {[llength $argv]==0} { 88 set go 1 89 } else { 90 set go 0 91 foreach pattern $argv { 92 if {[string match $pattern $name]} { 93 set go 1 94 break 95 } 96 } 97 } 98 if {!$go} return 99 incr nTest 100 puts -nonewline $name... 101 flush stdout 102 if {[catch {uplevel #0 "$cmd;\n"} result]} { 103 puts "\nError: $result" 104 incr nErr 105 lappend ::failList $name 106 if {$nErr>100} {puts "*** Giving up..."; finalize_testing} 107 } elseif {[string compare $result $expected]} { 108 puts "\nExpected: \[$expected\]\n Got: \[$result\]" 109 incr nErr 110 lappend ::failList $name 111 if {$nErr>100} {puts "*** Giving up..."; finalize_testing} 112 } else { 113 puts " Ok" 114 } 115} 116 117# Invoke this procedure on a test that is probabilistic 118# and might fail sometimes. 119# 120proc do_probtest {name cmd expected} { 121 global argv nProb nTest skip_test 122 if {$skip_test} { 123 set skip_test 0 124 return 125 } 126 if {[llength $argv]==0} { 127 set go 1 128 } else { 129 set go 0 130 foreach pattern $argv { 131 if {[string match $pattern $name]} { 132 set go 1 133 break 134 } 135 } 136 } 137 if {!$go} return 138 incr nTest 139 puts -nonewline $name... 140 flush stdout 141 if {[catch {uplevel #0 "$cmd;\n"} result]} { 142 puts "\nError: $result" 143 incr nErr 144 } elseif {[string compare $result $expected]} { 145 puts "\nExpected: \[$expected\]\n Got: \[$result\]" 146 puts "NOTE: The results of the previous test depend on system load" 147 puts "and processor speed. The test may sometimes fail even if the" 148 puts "library is working correctly." 149 incr nProb 150 } else { 151 puts " Ok" 152 } 153} 154 155# The procedure uses the special "sqlite_malloc_stat" command 156# (which is only available if SQLite is compiled with -DMEMORY_DEBUG=1) 157# to see how many malloc()s have not been free()ed. The number 158# of surplus malloc()s is stored in the global variable $::Leak. 159# If the value in $::Leak grows, it may mean there is a memory leak 160# in the library. 161# 162proc memleak_check {} { 163 if {[info command sqlite_malloc_stat]!=""} { 164 set r [sqlite_malloc_stat] 165 set ::Leak [expr {[lindex $r 0]-[lindex $r 1]}] 166 } 167} 168 169# Run this routine last 170# 171proc finish_test {} { 172 finalize_testing 173} 174proc finalize_testing {} { 175 global nTest nErr nProb sqlite_open_file_count 176 if {$nErr==0} memleak_check 177 catch {db close} 178 puts "$nErr errors out of $nTest tests" 179 puts "Failures on these tests: $::failList" 180 if {$nProb>0} { 181 puts "$nProb probabilistic tests also failed, but this does" 182 puts "not necessarily indicate a malfunction." 183 } 184 if {$sqlite_open_file_count} { 185 puts "$sqlite_open_file_count files were left open" 186 incr nErr 187 } 188 exit [expr {$nErr>0}] 189} 190 191# A procedure to execute SQL 192# 193proc execsql {sql {db db}} { 194 # puts "SQL = $sql" 195 return [$db eval $sql] 196} 197 198# Execute SQL and catch exceptions. 199# 200proc catchsql {sql {db db}} { 201 # puts "SQL = $sql" 202 set r [catch {$db eval $sql} msg] 203 lappend r $msg 204 return $r 205} 206 207# Do an VDBE code dump on the SQL given 208# 209proc explain {sql {db db}} { 210 puts "" 211 puts "addr opcode p1 p2 p3 " 212 puts "---- ------------ ------ ------ ---------------" 213 $db eval "explain $sql" {} { 214 puts [format {%-4d %-12.12s %-6d %-6d %s} $addr $opcode $p1 $p2 $p3] 215 } 216} 217 218# Another procedure to execute SQL. This one includes the field 219# names in the returned list. 220# 221proc execsql2 {sql} { 222 set result {} 223 db eval $sql data { 224 foreach f $data(*) { 225 lappend result $f $data($f) 226 } 227 } 228 return $result 229} 230 231# Use the non-callback API to execute multiple SQL statements 232# 233proc stepsql {dbptr sql} { 234 set sql [string trim $sql] 235 set r 0 236 while {[string length $sql]>0} { 237 if {[catch {sqlite_compile $dbptr $sql sqltail} vm]} { 238 return [list 1 $vm] 239 } 240 set sql [string trim $sqltail] 241 while {[sqlite_step $vm N VAL COL]=="SQLITE_ROW"} { 242 foreach v $VAL {lappend r $v} 243 } 244 if {[catch {sqlite_finalize $vm} errmsg]} { 245 return [list 1 $errmsg] 246 } 247 } 248 return $r 249} 250 251# Delete a file or directory 252# 253proc forcedelete {filename} { 254 if {[catch {file delete -force $filename}]} { 255 exec rm -rf $filename 256 } 257} 258 259# Do an integrity check of the entire database 260# 261proc integrity_check {name} { 262 do_test $name { 263 execsql {PRAGMA integrity_check} 264 } {ok} 265} 266