3 set testdir
[file normalize
[file dirname
$argv0]]
6 source [file join $testdir testrunner_data.tcl
]
7 source [file join $testdir permutations.test
]
11 # This script requires an interpreter that supports [package require sqlite3]
12 # to run. If this is not such an intepreter, see if there is a [testfixture]
13 # in the current directory. If so, run the command using it. If not,
14 # recommend that the user build one.
16 proc find_interpreter
{} {
17 set interpreter
[file tail
[info nameofexec
]]
18 set rc
[catch { package require sqlite3
}]
20 if { [string match
-nocase testfixture
* $interpreter]==0
21 && [file executable .
/testfixture
]
23 puts "Failed to find tcl package sqlite3. Restarting with ./testfixture.."
25 exec .
/testfixture
[info script
] {*}$::argv >@ stdout
31 puts stderr
"Failed to find tcl package sqlite3"
32 puts stderr
"Run \"make testfixture\" and then try again..."
38 # Usually this script is run by [testfixture]. But it can also be run
39 # by a regular [tclsh]. For these cases, emulate the [clock_milliseconds]
41 if {[info commands clock_milliseconds
]==""} {
42 proc clock_milliseconds
{} {
47 #-------------------------------------------------------------------------
51 set a0
[file tail
$::argv0]
53 puts stderr
[string trim
[subst -nocommands {
55 $a0 ?SWITCHES? ?PERMUTATION? ?PATTERNS?
63 --zipvfs ZIPVFS-SOURCE-DIR
65 Interesting values
for PERMUTATION are
:
67 veryquick
- a fast subset of the tcl test scripts. This is the
default.
68 full
- all tcl test scripts.
69 all
- all tcl test scripts
, plus a subset of test scripts rerun
70 with various permutations.
71 release
- full release test with various builds.
73 If no PATTERN arguments are present
, all tests specified by the PERMUTATION
74 are run. Otherwise
, each pattern is interpreted as a
glob pattern. Only
75 those tcl tests
for which the final component of the
filename matches at
76 least one specified pattern are run.
78 If no PATTERN arguments are present
, then various fuzztest
, threadtest
79 and other tests are run as part of the
"release" permutation. These are
80 omitted
if any PATTERN arguments are specified on the command line.
82 If a PERMUTATION is specified and is followed by the path to a
Tcl script
83 instead of a
list of patterns
, then that single
Tcl test script is run
84 with the specified permutation.
86 The
--fuzztest option is ignored
if the PERMUTATION is
"release". Otherwise
,
87 if it is present
, then
"make -C <dir> fuzztest" is run as part of the tests
,
88 where
<dir
> is the directory containing the testfixture
binary used to
91 The
"status" and
"njob" commands are designed to be run from the same
92 directory as a running testrunner.tcl script that is running tests. The
93 "status" command prints a report describing the current state and progress
94 of the tests. The
"njob" command may be used to query or modify the number
95 of sub-processes the test script uses to run tests.
100 #-------------------------------------------------------------------------
102 #-------------------------------------------------------------------------
103 # Try to estimate a the number of processes to use.
105 # Command [guess_number_of_cores] attempts to glean the number of logical
106 # cores. Command [default_njob] returns the default value for the --jobs
109 proc guess_number_of_cores
{} {
110 if {[catch {number_of_cores
} ret
]} {
113 if {$::tcl_platform(platform
)=="windows"} {
114 catch { set ret
$::env(NUMBER_OF_PROCESSORS
) }
116 if {$::tcl_platform(os
)=="Darwin"} {
117 set cmd
"sysctl -n hw.logicalcpu"
122 set fd
[open "|$cmd" r
]
132 proc default_njob
{} {
133 set nCore
[guess_number_of_cores
]
137 set nHelper
[expr int
($nCore*0.5)]
141 #-------------------------------------------------------------------------
143 #-------------------------------------------------------------------------
144 # Setup various default values in the global TRG() array.
146 set TRG
(dbname
) [file normalize testrunner.db
]
147 set TRG
(logname
) [file normalize testrunner.log
]
148 set TRG
(build.logname
) [file normalize testrunner_build.log
]
149 set TRG
(info_script
) [file normalize
[info script
]]
150 set TRG
(timeout
) 10000 ;# Default busy-timeout for testrunner.db
151 set TRG
(nJob
) [default_njob
] ;# Default number of helper processes
152 set TRG
(patternlist
) [list]
153 set TRG
(cmdline
) $argv
154 set TRG
(reporttime
) 2000
155 set TRG
(fuzztest
) 0 ;# is the fuzztest option present.
156 set TRG
(zipvfs
) "" ;# -zipvfs option, if any
158 switch -nocase -glob -- $tcl_platform(os
) {
160 set TRG
(platform
) osx
161 set TRG
(make
) make.sh
162 set TRG
(makecmd
) "bash make.sh"
165 set TRG
(platform
) linux
166 set TRG
(make
) make.sh
167 set TRG
(makecmd
) "bash make.sh"
170 set TRG
(platform
) win
171 set TRG
(make
) make.bat
172 set TRG
(makecmd
) make.bat
175 error "cannot determine platform!"
178 #-------------------------------------------------------------------------
180 #-------------------------------------------------------------------------
181 # The database schema used by the testrunner.db database.
184 DROP TABLE IF EXISTS script
;
185 DROP TABLE IF EXISTS config
;
188 build TEXT DEFAULT ''
,
190 filename TEXT
, -- full path to test script
191 slow BOOLEAN
, -- true
if script is
"slow"
192 state TEXT CHECK
( state IN
(''
, 'ready'
, 'running'
, 'done'
, 'failed'
) ),
193 time INTEGER
, -- Time in ms
194 output TEXT
, -- full output of test script
196 jobtype TEXT CHECK
( jobtype IN
('script'
, 'build'
, 'make'
) ),
197 PRIMARY KEY
(build
, config
, filename)
201 name TEXT COLLATE nocase PRIMARY KEY
,
205 CREATE INDEX i1 ON script
(state
, jobtype
);
206 CREATE INDEX i2 ON script
(state
, priority
);
208 #-------------------------------------------------------------------------
210 #--------------------------------------------------------------------------
211 # Check if this script is being invoked to run a single file. If so,
214 if {[llength $argv]==2
215 && ([lindex $argv 0]=="" ||
[info exists
::testspec([lindex $argv 0])])
216 && [file exists
[lindex $argv 1]]
218 set permutation
[lindex $argv 0]
219 set script
[file normalize
[lindex $argv 1]]
222 set testdir
[file dirname
$argv0]
223 source $::testdir/tester.tcl
225 if {$permutation=="full"} {
227 unset -nocomplain ::G(isquick
)
230 } elseif
{$permutation!="default" && $permutation!=""} {
232 if {[info exists
::testspec($permutation)]==0} {
233 error "no such permutation: $permutation"
236 array set O
$::testspec($permutation)
237 set ::G(perm
:name
) $permutation
238 set ::G(perm
:prefix
) $O(-prefix)
240 set ::G(perm
:dbconfig
) $O(-dbconfig)
241 set ::G(perm
:presql
) $O(-presql)
243 rename finish_test helper_finish_test
244 proc finish_test
{} "
258 #--------------------------------------------------------------------------
260 #--------------------------------------------------------------------------
261 # Check if this is the "njob" command:
263 if {([llength $argv]==2 ||
[llength $argv]==1)
264 && [string compare
-nocase njob
[lindex $argv 0]]==0
266 sqlite3 mydb
$TRG(dbname
)
267 if {[llength $argv]==2} {
268 set param
[lindex $argv 1]
269 if {[string is integer
$param]==0 ||
$param<1 ||
$param>128} {
270 puts stderr
"parameter must be an integer between 1 and 128"
274 mydb
eval { REPLACE INTO config VALUES
('njob'
, $param); }
276 set res
[mydb one
{ SELECT value FROM config WHERE name
='njob'
}]
281 #--------------------------------------------------------------------------
283 #--------------------------------------------------------------------------
284 # Check if this is the "script" command:
286 if {[string compare
-nocase script
[lindex $argv 0]]==0} {
287 if {[llength $argv]!=2 && !([llength $argv]==3&&[lindex $argv 1]=="-msvc")} {
291 set bMsvc
[expr ([llength $argv]==3)]
292 set config
[lindex $argv [expr [llength $argv]-1]]
294 puts [trd_buildscript
$config [file dirname
$testdir] $bMsvc]
299 #--------------------------------------------------------------------------
300 # Check if this is the "status" command:
302 if {[llength $argv]==1
303 && [string compare
-nocase status
[lindex $argv 0]]==0
306 proc display_job
{build config
filename {tm
""}} {
307 if {$config=="build"} {
308 set fname
"build: $filename"
310 } elseif
{$config=="make"} {
311 set fname
"make: $filename"
314 set fname
[file normalize
$filename]
315 if {[string first
$::srcdir $fname]==0} {
316 set fname
[string range
$fname [string length
$::srcdir]+1 end
]
319 set dfname
[format %-33s $fname]
325 if {$build!=""} { set dbuild
$build }
326 if {$config!="" && $config!="full"} { set dconfig
$config }
327 if {$dbuild!="" ||
$dconfig!=""} {
329 if {$dbuild!=""} {append dparams
"build=$dbuild"}
330 if {$dbuild!="" && $dconfig!=""} {append dparams
" "}
331 if {$dconfig!=""} {append dparams
"config=$dconfig"}
333 set dparams
[format %-33s $dparams]
336 set dtm
"\[${tm}ms\]"
338 puts " $dfname $dparams $dtm"
341 sqlite3 mydb
$TRG(dbname
)
345 set cmdline
[mydb one
{ SELECT value FROM config WHERE name
='cmdline'
}]
346 set nJob
[mydb one
{ SELECT value FROM config WHERE name
='njob'
}]
348 set now
[clock_milliseconds
]
351 COALESCE
((SELECT value FROM config WHERE name
='end'
), $now) -
352 (SELECT value FROM config WHERE name
='start'
)
356 foreach s
{"" ready running done failed
} { set S
($s) 0 }
358 SELECT state
, count
(*) AS cnt FROM script GROUP BY
1
363 set fin
[expr $S(done
)+$S(failed
)]
364 if {$cmdline!=""} {set cmdline
" $cmdline"}
368 set f
"$S(failed) FAILED, "
370 puts "Command line: \[testrunner.tcl$cmdline\]"
372 puts "Summary: ${tm}ms, ($fin/$total) finished, ${f}$S(running) running"
374 set srcdir
[file dirname
[file dirname
$TRG(info_script
)]]
378 SELECT build
, config
, filename, time FROM script WHERE state
='running'
381 display_job
$build $config $filename [expr $now-$time]
387 SELECT build
, config
, filename FROM script WHERE state
='failed'
390 display_job
$build $config $filename
398 #-------------------------------------------------------------------------
399 # Parse the command line.
401 for {set ii
0} {$ii < [llength $argv]} {incr ii
} {
402 set isLast
[expr $ii==([llength $argv]-1)]
403 set a
[lindex $argv $ii]
404 set n
[string length
$a]
406 if {[string range
$a 0 0]=="-"} {
407 if {($n>2 && [string match
"$a*" --jobs]) ||
$a=="-j"} {
409 set TRG
(nJob
) [lindex $argv $ii]
410 if {$isLast} { usage
}
411 } elseif
{($n>2 && [string match
"$a*" --fuzztest]) ||
$a=="-f"} {
413 } elseif
{($n>2 && [string match
"$a*" --zipvfs]) ||
$a=="-z"} {
415 set TRG
(zipvfs
) [lindex $argv $ii]
416 if {$isLast} { usage
}
421 lappend TRG
(patternlist
) [string map
{% *} $a]
428 # This script runs individual tests - tcl scripts or [make xyz] commands -
429 # in directories named "testdir$N", where $N is an integer. This variable
430 # contains a list of integers indicating the directories in use.
432 # This variable is accessed only via the following commands:
435 # Return the number of entries currently in the list.
438 # Remove value IDIR from the list. It is an error if it is not present.
441 # Select a value that is not already in the list. Add it to the list
444 set TRG
(dirs_in_use
) [list]
446 proc dirs_nHelper
{} {
448 llength $TRG(dirs_in_use
)
450 proc dirs_freeDir
{iDir
} {
453 foreach d
$TRG(dirs_in_use
) {
454 if {$iDir!=$d} { lappend out
$d }
456 if {[llength $out]!=[llength $TRG(dirs_in_use
)]-1} {
457 error "dirs_freeDir could not find $iDir"
459 set TRG
(dirs_in_use
) $out
461 proc dirs_allocDir
{} {
463 array set inuse
[list]
464 foreach d
$TRG(dirs_in_use
) {
467 for {set iRet
0} {[info exists inuse
($iRet)]} {incr iRet
} { }
468 lappend TRG
(dirs_in_use
) $iRet
472 # Check that directory $dir exists. If it does not, create it. If
473 # it does, delete its contents.
475 proc create_or_clear_dir
{dir
} {
476 set dir
[file normalize
$dir]
477 catch { file mkdir
$dir }
478 foreach f
[glob -nocomplain [file join $dir *]] {
479 catch { file delete
-force $f }
483 proc copy_dir
{from to
} {
484 foreach f
[glob -nocomplain [file join $from *]] {
485 catch { file copy
-force $f $to }
489 proc build_to_dirname
{bname
} {
490 set fold
[string tolower
[string map
{- _
} $bname]]
491 return "testrunner_build_$fold"
494 #-------------------------------------------------------------------------
495 # Return a list of tests to run. Each element of the list is itself a
496 # list of two elements - the name of a permuations.test configuration
497 # followed by the full path to a test script. i.e.:
499 # {BUILD CONFIG FILENAME} {BUILD CONFIG FILENAME} ...
501 proc testset_patternlist
{patternlist
} {
504 set testset
[list] ;# return value
506 set first
[lindex $patternlist 0]
508 if {$first=="sdevtest" ||
$first=="mdevtest"} {
509 set CONFIGS
(sdevtest
) {All-Debug All-Sanitize
}
510 set CONFIGS
(mdevtest
) {All-Debug All-O0
}
512 set patternlist
[lrange $patternlist 1 end
]
514 foreach b
$CONFIGS($first) {
515 lappend testset
[list $b build testfixture
]
516 lappend testset
[list $b make fuzztest
]
517 testset_append testset
$b veryquick
$patternlist
519 } elseif
{$first=="release"} {
520 set platform
$::TRG(platform
)
522 set patternlist
[lrange $patternlist 1 end
]
523 foreach b
[trd_builds
$platform] {
524 foreach c
[trd_configs
$platform $b] {
525 testset_append testset
$b $c $patternlist
528 if {[llength $patternlist]==0 ||
$b=="User-Auth"} {
529 set target testfixture
531 set target coretestprogs
533 lappend testset
[list $b build
$target]
536 if {[llength $patternlist]==0} {
537 foreach b
[trd_builds
$platform] {
538 foreach e
[trd_extras
$platform $b] {
539 lappend testset
[list $b make
$e]
544 set TRG
(fuzztest
) 0 ;# ignore --fuzztest option in this case
546 } elseif
{$first=="all"} {
548 set clist
[trd_all_configs
]
549 set patternlist
[lrange $patternlist 1 end
]
551 testset_append testset
"" $c $patternlist
554 } elseif
{[info exists
::testspec($first)]} {
556 testset_append testset
"" $first [lrange $patternlist 1 end
]
557 } elseif
{ [llength $patternlist]==0 } {
558 testset_append testset
"" veryquick
$patternlist
560 testset_append testset
"" full
$patternlist
562 if {$TRG(fuzztest
)} {
563 if {$TRG(platform
)=="win"} { error "todo" }
564 lappend testset
[list "" make fuzztest
]
570 proc testset_append
{listvar build config patternlist
} {
573 catch { array unset O
}
574 array set O
$::testspec($config)
576 foreach f
$O(-files) {
577 if {[llength $patternlist]>0} {
579 foreach p
$patternlist {
580 if {[string match
$p [file tail
$f]]} {
585 if {$bMatch==0} continue
588 if {[file pathtype
$f]!="absolute"} {
589 set f
[file join $::testdir $f]
591 lappend lvar
[list $build $config $f]
595 #--------------------------------------------------------------------------
598 proc r_write_db
{tcl
} {
599 trdb
eval { BEGIN EXCLUSIVE
}
604 # Obtain a new job to be run by worker $iJob (an integer). A job is
605 # returned as a three element list:
607 # {$build $config $file}
609 proc r_get_next_job
{iJob
} {
613 set orderby
"ORDER BY priority ASC"
615 set orderby
"ORDER BY priority DESC"
622 SELECT build, config, filename
632 set tm
[clock_milliseconds
]
635 UPDATE script SET state
='running'
, time=$tm
636 WHERE
(build
, config
, filename) = ($b, $c, $f)
641 if {$f==""} { return "" }
645 #rename r_get_next_job r_get_next_job_r
646 #proc r_get_next_job {iJob} {
647 # puts [time { set res [r_get_next_job_r $iJob] }]
651 proc make_new_testset
{} {
654 set tests
[testset_patternlist
$TRG(patternlist
)]
656 if {$TRG(zipvfs
)!=""} {
657 source [file join $TRG(zipvfs
) test zipvfs_testrunner.tcl
]
658 set tests
[concat $tests [zipvfs_testrunner_testset
]]
663 trdb
eval $TRG(schema
)
665 set cmdline
$TRG(cmdline
)
666 set tm
[clock_milliseconds
]
667 trdb
eval { REPLACE INTO config VALUES
('njob'
, $nJob ); }
668 trdb
eval { REPLACE INTO config VALUES
('cmdline'
, $cmdline ); }
669 trdb
eval { REPLACE INTO config VALUES
('start'
, $tm ); }
672 foreach {b c s
} $t {}
675 if {$c!="make" && $c!="build"} {
677 for {set ii
0} {$ii<100 && ![eof $fd]} {incr ii
} {
679 if {[string match
-nocase *testrunner
:* $line]} {
680 regexp -nocase {.
*testrunner
:(.
*)} $line -> properties
681 foreach p
$properties {
682 if {$p=="slow"} { set slow
1 }
683 if {$p=="superslow"} { set slow
2 }
690 if {$c=="make" && $b==""} {
695 if {$c=="veryquick"} {
700 if {$b!="" && $c!="build"} {
704 set priority
[expr {$slow*2}]
705 if {$c=="make"} { incr priority
3 }
706 if {$c=="build"} { incr priority
1 }
708 if {$c=="make" ||
$c=="build"} {
716 (build
, config
, filename, slow
, state
, priority
, jobtype
)
717 VALUES
($b, $c, $s, $slow, $state, $priority, $jobtype)
723 proc script_input_ready
{fd iJob b c f
} {
730 fconfigure $fd -blocking 1
732 set rc
[catch { close $fd } msg
]
734 puts "FAILED: $b $c $f"
738 set tm
[expr [clock_milliseconds
] - $T($iJob)]
740 puts $TRG(log
) "### $b ### $c ### $f ${tm}ms ($state)"
741 puts $TRG(log
) [string trim
$O($iJob)]
746 UPDATE script SET output
= $output, state
=$state, time=$tm
747 WHERE
(build
, config
, filename) = ($b, $c, $f)
749 if {$state=="done" && $c=="build"} {
751 UPDATE script SET state
= 'ready' WHERE
(build
, state
)==($b, ''
)
760 set rc
[catch { gets $fd line
} res
]
765 append O
($iJob) "$line\n"
775 proc launch_another_job
{iJob
} {
780 set testfixture
[info nameofexec
]
781 set script
$TRG(info_script
)
783 set dir
[dirname
$iJob]
784 create_or_clear_dir
$dir
788 set job
[r_get_next_job
$iJob]
789 if {$job==""} { return 0 }
791 foreach {b c f
} $job {}
794 set testdir
[file dirname
$TRG(info_script
)]
795 set srcdir
[file dirname
$testdir]
796 set builddir
[build_to_dirname
$b]
797 create_or_clear_dir
$builddir
800 set script
[zipvfs_testrunner_script
]
802 set script
[trd_buildscript
$b $srcdir [expr {$TRG(platform
)=="win"}]]
805 set fd
[open [file join $builddir $TRG(make
)] w
]
809 puts "Launching build \"$b\" in directory $builddir..."
810 set target coretestprogs
811 if {$b=="User-Auth"} { set target testfixture
}
813 set cmd
"$TRG(makecmd) $target"
816 } elseif
{$c=="make"} {
818 if {$f!="fuzztest"} { error "corruption in testrunner.db!" }
819 # Special case - run [make fuzztest]
820 set makedir
[file dirname
$testfixture]
821 if {$TRG(platform
)=="win"} {
824 set cmd
[list make
-C $makedir fuzztest
]
827 set builddir
[build_to_dirname
$b]
828 copy_dir
$builddir $dir
829 set cmd
"$TRG(makecmd) $f"
833 set testfixture
[info nameofexec
]
836 if {$TRG(platform
)=="win"} { set tail testfixture.exe
}
837 set testfixture
[file normalize
[file join [build_to_dirname
$b] $tail]]
840 if {$c=="valgrind"} {
841 set testfixture
"valgrind -v --error-exitcode=1 $testfixture"
842 set ::env(OMIT_MISUSE
) 1
844 set cmd
[concat $testfixture [list $script $c $f]]
849 set fd
[open "|$cmd 2>@1" r
]
853 fconfigure $fd -blocking false
854 fileevent $fd readable
[list script_input_ready
$fd $iJob $b $c $f]
855 unset -nocomplain ::env(OMIT_MISUSE
)
860 proc one_line_report
{} {
863 set tm
[expr [clock_milliseconds
] - $TRG(starttime
)]
864 set tm
[format "%d" [expr int
($tm/1000.0 + 0.5)]]
866 foreach s
{ready running done failed
} {
874 SELECT state
, jobtype
, count
(*) AS cnt
876 GROUP BY state
, jobtype
878 set v
($state,$jobtype) $cnt
879 if {[info exists t
($jobtype)]} {
880 incr t
($jobtype) $cnt
888 foreach j
[array names t
] {
889 set fin
[expr $v(done
,$j) + $v(failed
,$j)]
890 lappend text "$j ($fin/$t($j)) f=$v(failed,$j) r=$v(running,$j)"
893 if {[info exists TRG
(reportlength
)]} {
894 puts -nonewline "[string repeat " " $TRG(reportlength)]\r"
896 set report
"${tm}s: [join $text { }]"
897 set TRG
(reportlength
) [string length
$report]
898 if {[string length
$report]<80} {
899 puts -nonewline "$report\r"
905 after $TRG(reporttime
) one_line_report
908 proc launch_some_jobs
{} {
911 set nJob
[trdb one
{ SELECT value FROM config WHERE name
='njob'
}]
913 while {[dirs_nHelper
]<$nJob} {
914 set iDir
[dirs_allocDir
]
915 if {0==[launch_another_job
$iDir]} {
922 proc run_testset
{} {
926 set TRG
(starttime
) [clock_milliseconds
]
927 set TRG
(log
) [open $TRG(logname
) w
]
930 # launch_another_job $ii
933 while {[dirs_nHelper
]>0} {
934 after 500 {incr ::wakeup}
941 set tm
[clock_milliseconds
]
942 trdb
eval { REPLACE INTO config VALUES
('end'
, $tm ); }
943 set nErr
[trdb one
{SELECT count
(*) FROM script WHERE state
='failed'
}]
945 puts "$nErr failures:"
947 SELECT build
, config
, filename FROM script WHERE state
='failed'
949 puts "FAILED: $build $config $filename"
954 puts "\nTest database is $TRG(dbname)"
955 puts "Test log is $TRG(logname)"
959 sqlite3 trdb
$TRG(dbname
)
960 trdb timeout
$TRG(timeout
)
961 set tm
[lindex [time { make_new_testset
}] 0]
963 puts "splitting work across $TRG(nJob) cores"
965 puts "built testset in [expr $tm/1000]ms.."