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