2N/A
2N/A#pragma ident "%Z%%M% %I% %E% SMI"
2N/A
2N/A# 2001 September 15
2N/A#
2N/A# The author disclaims copyright to this source code. In place of
2N/A# a legal notice, here is a blessing:
2N/A#
2N/A# May you do good and not evil.
2N/A# May you find forgiveness for yourself and forgive others.
2N/A# May you share freely, never taking more than you give.
2N/A#
2N/A#***********************************************************************
2N/A# This file implements some common TCL routines used for regression
2N/A# testing the SQLite library
2N/A#
2N/A# $Id: tester.tcl,v 1.28 2004/02/14 01:39:50 drh Exp $
2N/A
2N/A# Make sure tclsqlite was compiled correctly. Abort now with an
2N/A# error message if not.
2N/A#
2N/Aif {[sqlite -tcl-uses-utf]} {
2N/A if {"\u1234"=="u1234"} {
2N/A puts stderr "***** BUILD PROBLEM *****"
2N/A puts stderr "$argv0 was linked against an older version"
2N/A puts stderr "of TCL that does not support Unicode, but uses a header"
2N/A puts stderr "file (\"tcl.h\") from a new TCL version that does support"
2N/A puts stderr "Unicode. This combination causes internal errors."
2N/A puts stderr "Recompile using a TCL library and header file that match"
2N/A puts stderr "and try again.\n**************************"
2N/A exit 1
2N/A }
2N/A} else {
2N/A if {"\u1234"!="u1234"} {
2N/A puts stderr "***** BUILD PROBLEM *****"
2N/A puts stderr "$argv0 was linked against an newer version"
2N/A puts stderr "of TCL that supports Unicode, but uses a header file"
2N/A puts stderr "(\"tcl.h\") from a old TCL version that does not support"
2N/A puts stderr "Unicode. This combination causes internal errors."
2N/A puts stderr "Recompile using a TCL library and header file that match"
2N/A puts stderr "and try again.\n**************************"
2N/A exit 1
2N/A }
2N/A}
2N/A
2N/A# Use the pager codec if it is available
2N/A#
2N/Aif {[sqlite -has-codec] && [info command sqlite_orig]==""} {
2N/A rename sqlite sqlite_orig
2N/A proc sqlite {args} {
2N/A if {[llength $args]==2 && [string index [lindex $args 0] 0]!="-"} {
2N/A lappend args -key {xyzzy}
2N/A }
2N/A uplevel 1 sqlite_orig $args
2N/A }
2N/A}
2N/A
2N/A
2N/A# Create a test database
2N/A#
2N/Acatch {db close}
2N/Afile delete -force test.db
2N/Afile delete -force test.db-journal
2N/Asqlite db ./test.db
2N/Aif {[info exists ::SETUP_SQL]} {
2N/A db eval $::SETUP_SQL
2N/A}
2N/A
2N/A# Abort early if this script has been run before.
2N/A#
2N/Aif {[info exists nTest]} return
2N/A
2N/A# Set the test counters to zero
2N/A#
2N/Aset nErr 0
2N/Aset nTest 0
2N/Aset nProb 0
2N/Aset skip_test 0
2N/Aset failList {}
2N/A
2N/A# Invoke the do_test procedure to run a single test
2N/A#
2N/Aproc do_test {name cmd expected} {
2N/A global argv nErr nTest skip_test
2N/A if {$skip_test} {
2N/A set skip_test 0
2N/A return
2N/A }
2N/A if {[llength $argv]==0} {
2N/A set go 1
2N/A } else {
2N/A set go 0
2N/A foreach pattern $argv {
2N/A if {[string match $pattern $name]} {
2N/A set go 1
2N/A break
2N/A }
2N/A }
2N/A }
2N/A if {!$go} return
2N/A incr nTest
2N/A puts -nonewline $name...
2N/A flush stdout
2N/A if {[catch {uplevel #0 "$cmd;\n"} result]} {
2N/A puts "\nError: $result"
2N/A incr nErr
2N/A lappend ::failList $name
2N/A if {$nErr>100} {puts "*** Giving up..."; finalize_testing}
2N/A } elseif {[string compare $result $expected]} {
2N/A puts "\nExpected: \[$expected\]\n Got: \[$result\]"
2N/A incr nErr
2N/A lappend ::failList $name
2N/A if {$nErr>100} {puts "*** Giving up..."; finalize_testing}
2N/A } else {
2N/A puts " Ok"
2N/A }
2N/A}
2N/A
2N/A# Invoke this procedure on a test that is probabilistic
2N/A# and might fail sometimes.
2N/A#
2N/Aproc do_probtest {name cmd expected} {
2N/A global argv nProb nTest skip_test
2N/A if {$skip_test} {
2N/A set skip_test 0
2N/A return
2N/A }
2N/A if {[llength $argv]==0} {
2N/A set go 1
2N/A } else {
2N/A set go 0
2N/A foreach pattern $argv {
2N/A if {[string match $pattern $name]} {
2N/A set go 1
2N/A break
2N/A }
2N/A }
2N/A }
2N/A if {!$go} return
2N/A incr nTest
2N/A puts -nonewline $name...
2N/A flush stdout
2N/A if {[catch {uplevel #0 "$cmd;\n"} result]} {
2N/A puts "\nError: $result"
2N/A incr nErr
2N/A } elseif {[string compare $result $expected]} {
2N/A puts "\nExpected: \[$expected\]\n Got: \[$result\]"
2N/A puts "NOTE: The results of the previous test depend on system load"
2N/A puts "and processor speed. The test may sometimes fail even if the"
2N/A puts "library is working correctly."
2N/A incr nProb
2N/A } else {
2N/A puts " Ok"
2N/A }
2N/A}
2N/A
2N/A# The procedure uses the special "sqlite_malloc_stat" command
2N/A# (which is only available if SQLite is compiled with -DMEMORY_DEBUG=1)
2N/A# to see how many malloc()s have not been free()ed. The number
2N/A# of surplus malloc()s is stored in the global variable $::Leak.
2N/A# If the value in $::Leak grows, it may mean there is a memory leak
2N/A# in the library.
2N/A#
2N/Aproc memleak_check {} {
2N/A if {[info command sqlite_malloc_stat]!=""} {
2N/A set r [sqlite_malloc_stat]
2N/A set ::Leak [expr {[lindex $r 0]-[lindex $r 1]}]
2N/A }
2N/A}
2N/A
2N/A# Run this routine last
2N/A#
2N/Aproc finish_test {} {
2N/A finalize_testing
2N/A}
2N/Aproc finalize_testing {} {
2N/A global nTest nErr nProb sqlite_open_file_count
2N/A if {$nErr==0} memleak_check
2N/A catch {db close}
2N/A puts "$nErr errors out of $nTest tests"
2N/A puts "Failures on these tests: $::failList"
2N/A if {$nProb>0} {
2N/A puts "$nProb probabilistic tests also failed, but this does"
2N/A puts "not necessarily indicate a malfunction."
2N/A }
2N/A if {$sqlite_open_file_count} {
2N/A puts "$sqlite_open_file_count files were left open"
2N/A incr nErr
2N/A }
2N/A exit [expr {$nErr>0}]
2N/A}
2N/A
2N/A# A procedure to execute SQL
2N/A#
2N/Aproc execsql {sql {db db}} {
2N/A # puts "SQL = $sql"
2N/A return [$db eval $sql]
2N/A}
2N/A
2N/A# Execute SQL and catch exceptions.
2N/A#
2N/Aproc catchsql {sql {db db}} {
2N/A # puts "SQL = $sql"
2N/A set r [catch {$db eval $sql} msg]
2N/A lappend r $msg
2N/A return $r
2N/A}
2N/A
2N/A# Do an VDBE code dump on the SQL given
2N/A#
2N/Aproc explain {sql {db db}} {
2N/A puts ""
2N/A puts "addr opcode p1 p2 p3 "
2N/A puts "---- ------------ ------ ------ ---------------"
2N/A $db eval "explain $sql" {} {
2N/A puts [format {%-4d %-12.12s %-6d %-6d %s} $addr $opcode $p1 $p2 $p3]
2N/A }
2N/A}
2N/A
2N/A# Another procedure to execute SQL. This one includes the field
2N/A# names in the returned list.
2N/A#
2N/Aproc execsql2 {sql} {
2N/A set result {}
2N/A db eval $sql data {
2N/A foreach f $data(*) {
2N/A lappend result $f $data($f)
2N/A }
2N/A }
2N/A return $result
2N/A}
2N/A
2N/A# Use the non-callback API to execute multiple SQL statements
2N/A#
2N/Aproc stepsql {dbptr sql} {
2N/A set sql [string trim $sql]
2N/A set r 0
2N/A while {[string length $sql]>0} {
2N/A if {[catch {sqlite_compile $dbptr $sql sqltail} vm]} {
2N/A return [list 1 $vm]
2N/A }
2N/A set sql [string trim $sqltail]
2N/A while {[sqlite_step $vm N VAL COL]=="SQLITE_ROW"} {
2N/A foreach v $VAL {lappend r $v}
2N/A }
2N/A if {[catch {sqlite_finalize $vm} errmsg]} {
2N/A return [list 1 $errmsg]
2N/A }
2N/A }
2N/A return $r
2N/A}
2N/A
2N/A# Delete a file or directory
2N/A#
2N/Aproc forcedelete {filename} {
2N/A if {[catch {file delete -force $filename}]} {
2N/A exec rm -rf $filename
2N/A }
2N/A}
2N/A
2N/A# Do an integrity check of the entire database
2N/A#
2N/Aproc integrity_check {name} {
2N/A do_test $name {
2N/A execsql {PRAGMA integrity_check}
2N/A } {ok}
2N/A}