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