1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705
|
#-------------------------------------------------------------------------
# Usage:
#
proc usage {} {
set a0 testrunner.tcl
set ::argv [list]
uplevel [list source $::testdir/permutations.test]
puts stderr "Usage: $a0 ?SWITCHES? ?PERMUTATION? ?PATTERNS?"
puts stderr ""
puts stderr "where SWITCHES are:"
puts stderr " --jobs NUMBER-OF-JOBS"
puts stderr ""
puts stderr "available PERMUTATION values are:"
set ii 0
foreach name [lsort [array names ::testspec]] {
if {($ii % 3)==0} { puts -nonewline stderr " " }
puts -nonewline stderr [format "% -22s" $name]
if {($ii % 3)==2} { puts stderr "" }
incr ii
}
puts stderr ""
puts stderr ""
puts stderr "Examples:"
puts stderr " 1) Run the veryquick tests:"
puts stderr " $a0"
puts stderr " 2) Run all test scripts in the source tree:"
puts stderr " $a0 full"
puts stderr " 2) Run the 'memsubsys1' permutation:"
puts stderr " $a0 memsubsys1"
puts stderr " 3) Run all permutations usually run by \[make fulltest\]"
puts stderr " $a0 release"
puts stderr " 4) Run all scripts that match the pattern 'select%':"
puts stderr " $a0 select%"
puts stderr " $a0 all select%"
puts stderr " $a0 full select%"
puts stderr " 5) Run all scripts that are part of the veryquick permutation and match the pattern 'select%':"
puts stderr " $a0 veryquick select%"
puts stderr " 6) Run the 'memsubsys1' permutation, but just those scripts that match 'window%':"
puts stderr " $a0 memsubsys1 window%"
puts stderr " 7) Run all the permutations, but only the scripts that match either 'fts5%' or 'rtree%':"
puts stderr " $a0 release fts5% rtree%"
exit 1
}
#-------------------------------------------------------------------------
#-------------------------------------------------------------------------
# The database schema used by the testrunner.db database.
#
set R(schema) {
DROP TABLE IF EXISTS script;
DROP TABLE IF EXISTS msg;
DROP TABLE IF EXISTS malloc;
CREATE TABLE script(
config TEXT,
filename TEXT, -- full path to test script
slow BOOLEAN, -- true if script is "slow"
state TEXT CHECK( state IN ('ready', 'running', 'done') ),
testfixtureid, -- Id of process that ran script
time INTEGER, -- Time in ms
nerr INTEGER, -- if 'done', the number of errors
ntest INTEGER, -- if 'done', the number of tests
output TEXT, -- full output of test script
PRIMARY KEY(config, filename)
);
CREATE TABLE malloc(
id INTEGER PRIMARY KEY,
nmalloc INTEGER,
nbyte INTEGER,
leaker TEXT
);
CREATE TABLE msg(
id INTEGER PRIMARY KEY,
msg TEXT
);
}
#-------------------------------------------------------------------------
#-------------------------------------------------------------------------
# Try to estimate a the number of processes to use.
#
# Command [guess_number_of_cores] attempts to glean the number of logical
# cores. Command [default_njob] returns the default value for the --jobs
# switch.
#
proc guess_number_of_cores {} {
set ret 4
if {$::tcl_platform(os)=="Darwin"} {
set cmd "sysctl -n hw.logicalcpu"
} else {
set cmd "nproc"
}
catch {
set fd [open "|$cmd" r]
set ret [gets $fd]
close $fd
set ret [expr $ret]
}
return $ret
}
proc default_njob {} {
set nCore [guess_number_of_cores]
set nHelper [expr int($nCore*0.75)]
expr $nHelper>0 ? $nHelper : 1
}
#-------------------------------------------------------------------------
set R(dbname) [file normalize testrunner.db]
set R(logname) [file normalize testrunner.log]
set R(info_script) [file normalize [info script]]
set R(timeout) 10000 ;# Default busy-timeout for testrunner.
set R(nJob) [default_njob] ;# Default number of helper processes
set R(leaker) "" ;# Name of first script to leak memory
set R(patternlist) [list]
set testdir [file dirname $argv0]
# Parse the command line options. There are two ways to invoke this
# script - to create a helper or coordinator process. If there are
# no helper processes, the coordinator runs test scripts.
#
# To create a helper process:
#
# testrunner.tcl helper ID
#
# where ID is an integer greater than 0. The process will create and
# run tests in the "testdir$ID" directory. Helper processes are only
# created by coordinators - there is no need for a user to create
# helper processes manually.
#
# If the first argument is anything other than "helper", then a coordinator
# process is started. See the implementation of the [usage] proc above for
# details.
#
switch -- [lindex $argv 0] {
helper {
set R(helper) 1
set R(helper_id) [lindex $argv 1]
set argv [list --testdir=testdir$R(helper_id)]
}
default {
set R(helper) 0
set R(helper_id) 0
}
}
if {$R(helper)==0} {
for {set ii 0} {$ii < [llength $argv]} {incr ii} {
set a [lindex $argv $ii]
set n [string length $a]
if {[string range $a 0 0]=="-"} {
if {($n>2 && [string match "$a*" --jobs]) || $a=="-j"} {
incr ii
set R(nJob) [lindex $argv $ii]
} else {
usage
}
} else {
lappend R(patternlist) [string map {% *} $a]
}
}
set argv [list]
}
source $testdir/permutations.test
#-------------------------------------------------------------------------
# Return a list of tests to run. Each element of the list is itself a
# list of two elements - the name of a permuations.test configuration
# followed by the full path to a test script. i.e.:
#
# {CONFIG FILENAME} {CONFIG FILENAME} ...
#
proc testset_patternlist {patternlist} {
set first [lindex $patternlist 0]
if {$first=="all"} { set first "full" }
if {$first=="release"} {
# The following mirrors the set of test suites invoked by "all.test".
#
set clist {
full
no_optimization memsubsys1 memsubsys2 singlethread
multithread onefile utf16 exclusive persistent_journal
persistent_journal_error no_journal no_journal_error
autovacuum_ioerr no_mutex_try fullmutex journaltest
inmemory_journal pcache0 pcache10 pcache50 pcache90
pcache100 prepare mmap
}
ifcapable rbu { lappend clist rbu }
if {$::tcl_platform(platform)=="unix"} {
ifcapable !default_autovacuum {
lappend clist autovacuum_crash
}
}
set patternlist [lrange $patternlist 1 end]
} elseif {[info exists ::testspec($first)]} {
set clist $first
set patternlist [lrange $patternlist 1 end]
} elseif { [llength $patternlist]==0 } {
set clist veryquick
} else {
set clist full
}
set testset [list]
foreach config $clist {
catch { array unset O }
array set O $::testspec($config)
foreach f $O(-files) {
if {[file pathtype $f]!="absolute"} {
set f [file join $::testdir $f]
}
lappend testset [list $config [file normalize $f]]
}
}
if {[llength $patternlist]>0} {
foreach t $testset {
set tail [file tail [lindex $t 1]]
foreach p $patternlist {
if {[string match $p $tail]} {
lappend ret $t
break;
}
}
}
} else {
set ret $testset
}
set ret
}
#--------------------------------------------------------------------------
proc r_write_db {tcl} {
global R
sqlite3_test_control_pending_byte 0x010000
sqlite3 db $R(dbname)
db timeout $R(timeout)
db eval { BEGIN EXCLUSIVE }
uplevel $tcl
db eval { COMMIT }
db close
}
proc make_new_testset {} {
global R
set tests [testset_patternlist $R(patternlist)]
r_write_db {
db eval $R(schema)
foreach t $tests {
foreach {c s} $t {}
set slow 0
set fd [open $s]
for {set ii 0} {$ii<100 && ![eof $fd]} {incr ii} {
set line [gets $fd]
if {[string match -nocase *testrunner:* $line]} {
regexp -nocase {.*testrunner:(.*)} $line -> properties
foreach p $properties {
if {$p=="slow"} { set slow 1 }
}
}
}
close $fd
db eval {
INSERT INTO script(config, filename, slow, state)
VALUES ($c, $s, $slow, 'ready')
}
}
}
}
# Find the next job in the database and mark it as 'running'. Then return
# a list consisting of the
#
# CONFIG FILENAME
#
# pair for the test.
#
proc get_next_test {} {
global R
set myid $R(helper_id)
r_write_db {
set f ""
set c ""
db eval {
SELECT config, filename FROM script WHERE state='ready'
ORDER BY
(slow * (($myid+1) % 2)) DESC,
config!='full',
config,
filename
LIMIT 1
} {
set c $config
set f $filename
}
if {$f!=""} {
db eval {
UPDATE script SET state='running', testfixtureid=$myid
WHERE (config, filename) = ($c, $f)
}
}
}
if {$f==""} { return "" }
list $c $f
}
proc r_testname {config filename} {
set name [file tail $filename]
if {$config!="" && $config!="full" && $config!="veryquick"} {
set name "$config-$name"
}
return $name
}
proc r_set_test_result {config filename ms nerr ntest output} {
global R
set f [r_testname $config $filename]
if {$nerr==0} {
set msg "$f... Ok"
} else {
set msg "$f... FAILED - $nerr errors of $ntest tests"
}
append msg " (${ms}ms)"
if {$R(helper)} {
append msg " (helper $R(helper_id))"
}
sqlite3_shutdown
set nMalloc [lindex [sqlite3_status SQLITE_STATUS_MALLOC_COUNT 0] 1]
set nByte [sqlite3_memory_used]
if {($nByte>0 || $nMalloc>0) && $R(leaker)==""} {
set R(leaker) $f
}
r_write_db {
db eval {
UPDATE script
SET state='done', output=$output, nerr=$nerr, ntest=$ntest, time=$ms
WHERE (config, filename)=($config, $filename);
INSERT INTO msg(msg) VALUES ($msg);
}
}
}
set R(iNextMsg) 1
proc r_get_messages {{db ""}} {
global R
sqlite3_test_control_pending_byte 0x010000
if {$db==""} {
sqlite3 rgmhandle $R(dbname)
set dbhandle rgmhandle
$dbhandle timeout $R(timeout)
} else {
set dbhandle $db
}
$dbhandle transaction {
set next $R(iNextMsg)
set ret [$dbhandle eval {SELECT msg FROM msg WHERE id>=$next}]
set R(iNextMsg) [$dbhandle one {SELECT COALESCE(max(id), 0)+1 FROM msg}]
}
if {$db==""} {
rgmhandle close
}
set ret
}
# This is called after all tests have been run to write the leaked memory
# report into the malloc table of testrunner.db.
#
proc r_memory_report {} {
global R
sqlite3_shutdown
set nMalloc [lindex [sqlite3_status SQLITE_STATUS_MALLOC_COUNT 0] 1]
set nByte [sqlite3_memory_used]
set id $R(helper_id)
set leaker $R(leaker)
r_write_db {
db eval {
INSERT INTO malloc(id, nMalloc, nByte, leaker)
VALUES($id, $nMalloc, $nByte, $leaker)
}
}
}
#--------------------------------------------------------------------------
#
set ::R_INSTALL_PUTS_WRAPPER {
proc puts_sts_wrapper {args} {
set n [llength $args]
if {$n==1 || ($n==2 && [string first [lindex $args 0] -nonewline]==0)} {
uplevel puts_into_caller $args
} else {
# A channel was explicitly specified.
uplevel puts_sts_original $args
}
}
rename puts puts_sts_original
proc puts {args} { uplevel puts_sts_wrapper $args }
}
proc r_install_puts_wrapper {} $::R_INSTALL_PUTS_WRAPPER
proc r_uninstall_puts_wrapper {} {
rename puts ""
rename puts_sts_original puts
}
proc slave_test_script {script} {
# Create the interpreter used to run the test script.
interp create tinterp
# Populate some global variables that tester.tcl expects to see.
foreach {var value} [list \
::argv0 $::argv0 \
::argv {} \
::SLAVE 1 \
] {
interp eval tinterp [list set $var $value]
}
# The alias used to access the global test counters.
tinterp alias set_test_counter set_test_counter
# Set up an empty ::cmdlinearg array in the slave.
interp eval tinterp [list array set ::cmdlinearg [array get ::cmdlinearg]]
# Set up the ::G array in the slave.
interp eval tinterp [list array set ::G [array get ::G]]
interp eval tinterp [list set ::G(runner.tcl) 1]
interp eval tinterp $::R_INSTALL_PUTS_WRAPPER
tinterp alias puts_into_caller puts_into_caller
# Load the various test interfaces implemented in C.
load_testfixture_extensions tinterp
# Run the test script.
set rc [catch { interp eval tinterp $script } msg opt]
if {$rc} {
puts_into_caller $msg
puts_into_caller [dict get $opt -errorinfo]
incr ::TC(errors)
}
# Check if the interpreter call [run_thread_tests]
if { [interp eval tinterp {info exists ::run_thread_tests_called}] } {
set ::run_thread_tests_called 1
}
# Delete the interpreter used to run the test script.
interp delete tinterp
}
proc slave_test_file {zFile} {
set tail [file tail $zFile]
# Remember the value of the shared-cache setting. So that it is possible
# to check afterwards that it was not modified by the test script.
#
ifcapable shared_cache { set scs [sqlite3_enable_shared_cache] }
# Run the test script in a slave interpreter.
#
unset -nocomplain ::run_thread_tests_called
reset_prng_state
set ::sqlite_open_file_count 0
set time [time { slave_test_script [list source $zFile] }]
set ms [expr [lindex $time 0] / 1000]
r_install_puts_wrapper
# Test that all files opened by the test script were closed. Omit this
# if the test script has "thread" in its name. The open file counter
# is not thread-safe.
#
if {[info exists ::run_thread_tests_called]==0} {
do_test ${tail}-closeallfiles { expr {$::sqlite_open_file_count>0} } {0}
}
set ::sqlite_open_file_count 0
# Test that the global "shared-cache" setting was not altered by
# the test script.
#
ifcapable shared_cache {
set res [expr {[sqlite3_enable_shared_cache] == $scs}]
do_test ${tail}-sharedcachesetting [list set {} $res] 1
}
# Add some info to the output.
#
output2 "Time: $tail $ms ms"
show_memstats
r_uninstall_puts_wrapper
return $ms
}
proc puts_into_caller {args} {
global R
if {[llength $args]==1} {
append R(output) [lindex $args 0]
append R(output) "\n"
} else {
append R(output) [lindex $args 1]
}
}
#-------------------------------------------------------------------------
#
proc r_final_report {} {
global R
sqlite3_test_control_pending_byte 0x010000
sqlite3 db $R(dbname)
db timeout $R(timeout)
set errcode 0
# Create the text log file. This is just the concatenation of the
# 'output' column of the database for every script that was run.
set fd [open $R(logname) w]
db eval {SELECT output FROM script ORDER BY config!='full',config,filename} {
puts $fd $output
}
close $fd
# Check if any scripts reported errors. If so, print one line noting
# how many errors, and another identifying the scripts in which they
# occured. Or, if no errors occurred, print out "no errors at all!".
sqlite3 db $R(dbname)
db timeout $R(timeout)
db eval { SELECT sum(nerr) AS nerr, sum(ntest) AS ntest FROM script } { }
puts "$nerr errors from $ntest tests."
if {$nerr>0} {
db eval { SELECT config, filename FROM script WHERE nerr>0 } {
lappend errlist [r_testname $config $filename]
}
puts "Errors in: $errlist"
set errcode 1
}
# Check if any scripts were not run or did not finish. Print out a
# line identifying them if there are any.
set errlist [list]
db eval { SELECT config, filename FROM script WHERE state!='done' } {
lappend errlist [r_testname $config $filename]
}
if {$errlist!=[list]} {
puts "Tests DID NOT FINISH (crashed?): $errlist"
set errcode 1
}
set bLeak 0
db eval {
SELECT id, nmalloc, nbyte, leaker FROM malloc
WHERE nmalloc>0 OR nbyte>0
} {
if {$id==0} {
set line "This process "
} else {
set line "Helper $id "
}
append line "leaked $nbyte byte in $nmalloc allocations"
if {$leaker!=""} { append line " (perhaps in [file tail $leaker])" }
puts $line
set bLeak 1
}
if {$bLeak==0} {
puts "No leaks - all allocations freed."
}
db close
puts "Test database is $R(dbname)"
puts "Test log file is $R(logname)"
if {$errcode} {
puts "This test has FAILED."
}
return $errcode
}
if {$R(helper)==0} {
make_new_testset
}
set R(nHelperRunning) 0
if {$R(helper)==0 && $R(nJob)>1} {
cd $cmdlinearg(TESTFIXTURE_HOME)
for {set ii 1} {$ii <= $R(nJob)} {incr ii} {
set cmd "[info nameofexec] $R(info_script) helper $ii 2>@1"
puts "Launching helper $ii ($cmd)"
set chan [open "|$cmd" r]
fconfigure $chan -blocking false
fileevent $chan readable [list r_helper_readable $ii $chan]
incr R(nHelperRunning)
}
cd $cmdlinearg(testdir)
}
proc r_helper_readable {id chan} {
set data [gets $chan]
if {$data!=""} { puts "helper $id:$data" }
if {[eof $chan]} {
puts "helper $id is finished"
incr ::R(nHelperRunning) -1
close $chan
}
}
if {$R(nHelperRunning)==0} {
while { ""!=[set t [get_next_test]] } {
set R(output) ""
set TC(count) 0
set TC(errors) 0
foreach {config filename} $t {}
array set O $::testspec($config)
set ::G(perm:name) $config
set ::G(perm:prefix) $O(-prefix)
set ::G(isquick) 1
set ::G(perm:dbconfig) $O(-dbconfig)
set ::G(perm:presql) $O(-presql)
eval $O(-initialize)
set ms [slave_test_file $filename]
eval $O(-shutdown)
unset -nocomplain ::G(perm:sqlite3_args)
unset ::G(perm:name)
unset ::G(perm:prefix)
unset ::G(perm:dbconfig)
unset ::G(perm:presql)
r_set_test_result $config $filename $ms $TC(errors) $TC(count) $R(output)
if {$R(helper)==0} {
foreach msg [r_get_messages] { puts $msg }
}
}
# Tests are finished - write a record into testrunner.db describing
# any memory leaks.
r_memory_report
} else {
set TTT 0
sqlite3 db $R(dbname)
db timeout $R(timeout)
while {$R(nHelperRunning)>0} {
after 250 { incr TTT }
vwait TTT
foreach msg [r_get_messages db] { puts $msg }
}
db close
}
set errcode 0
if {$R(helper)==0} {
set errcode [r_final_report]
}
exit $errcode
|