3 set testdir
[file dirname
$argv0]
6 source [file join $testdir testrunner_data.tcl
]
7 source [file join $testdir permutations.test
]
11 #-------------------------------------------------------------------------
15 set a0
[file tail
$::argv0]
17 puts stderr
[string trim
[subst -nocommands {
19 $a0 ?SWITCHES? ?PERMUTATION? ?PATTERNS?
27 --zipvfs ZIPVFS-SOURCE-DIR
29 Interesting values
for PERMUTATION are
:
31 veryquick
- a fast subset of the tcl test scripts. This is the
default.
32 full
- all tcl test scripts.
33 all
- all tcl test scripts
, plus a subset of test scripts rerun
34 with various permutations.
35 release
- full release test with various builds.
37 If no PATTERN arguments are present
, all tests specified by the PERMUTATION
38 are run. Otherwise
, each pattern is interpreted as a
glob pattern. Only
39 those tcl tests
for which the final component of the
filename matches at
40 least one specified pattern are run.
42 If no PATTERN arguments are present
, then various fuzztest
, threadtest
43 and other tests are run as part of the
"release" permutation. These are
44 omitted
if any PATTERN arguments are specified on the command line.
46 If a PERMUTATION is specified and is followed by the path to a
Tcl script
47 instead of a
list of patterns
, then that single
Tcl test script is run
48 with the specified permutation.
50 The
--fuzztest option is ignored
if the PERMUTATION is
"release". Otherwise
,
51 if it is present
, then
"make -C <dir> fuzztest" is run as part of the tests
,
52 where
<dir
> is the directory containing the testfixture
binary used to
55 The
"status" and
"njob" commands are designed to be run from the same
56 directory as a running testrunner.tcl script that is running tests. The
57 "status" command prints a report describing the current state and progress
58 of the tests. The
"njob" command may be used to query or modify the number
59 of sub-processes the test script uses to run tests.
64 #-------------------------------------------------------------------------
66 #-------------------------------------------------------------------------
67 # Try to estimate a the number of processes to use.
69 # Command [guess_number_of_cores] attempts to glean the number of logical
70 # cores. Command [default_njob] returns the default value for the --jobs
73 proc guess_number_of_cores
{} {
74 if {[catch {number_of_cores
} ret
]} {
77 if {$::tcl_platform(os
)=="Darwin"} {
78 set cmd
"sysctl -n hw.logicalcpu"
83 set fd
[open "|$cmd" r
]
92 proc default_njob
{} {
93 set nCore
[guess_number_of_cores
]
97 set nHelper
[expr int
($nCore*0.5)]
101 #-------------------------------------------------------------------------
103 #-------------------------------------------------------------------------
104 # Setup various default values in the global TRG() array.
106 set TRG
(dbname
) [file normalize testrunner.db
]
107 set TRG
(logname
) [file normalize testrunner.log
]
108 set TRG
(build.logname
) [file normalize testrunner_build.log
]
109 set TRG
(info_script
) [file normalize
[info script
]]
110 set TRG
(timeout
) 10000 ;# Default busy-timeout for testrunner.db
111 set TRG
(nJob
) [default_njob
] ;# Default number of helper processes
112 set TRG
(patternlist
) [list]
113 set TRG
(cmdline
) $argv
114 set TRG
(reporttime
) 2000
115 set TRG
(fuzztest
) 0 ;# is the fuzztest option present.
116 set TRG
(zipvfs
) "" ;# -zipvfs option, if any
118 switch -nocase -glob -- $tcl_platform(os
) {
120 set TRG
(platform
) osx
121 set TRG
(make
) make.sh
122 set TRG
(makecmd
) "bash make.sh"
125 set TRG
(platform
) linux
126 set TRG
(make
) make.sh
127 set TRG
(makecmd
) "bash make.sh"
130 set TRG
(platform
) win
131 set TRG
(make
) make.bat
132 set TRG
(makecmd
) make.bat
135 error "cannot determine platform!"
138 #-------------------------------------------------------------------------
140 #-------------------------------------------------------------------------
141 # The database schema used by the testrunner.db database.
144 DROP TABLE IF EXISTS script
;
145 DROP TABLE IF EXISTS config
;
148 build TEXT DEFAULT ''
,
150 filename TEXT
, -- full path to test script
151 slow BOOLEAN
, -- true
if script is
"slow"
152 state TEXT CHECK
( state IN
(''
, 'ready'
, 'running'
, 'done'
, 'failed'
) ),
153 time INTEGER
, -- Time in ms
154 output TEXT
, -- full output of test script
155 priority AS
((config
='make'
) + ((config
='build'
)*2) + (slow
*4)),
157 CASE WHEN config IN
('build'
, 'make'
) THEN config ELSE 'script' END
159 PRIMARY KEY
(build
, config
, filename)
163 name TEXT COLLATE nocase PRIMARY KEY
,
167 CREATE INDEX i1 ON script
(state
, jobtype
);
168 CREATE INDEX i2 ON script
(state
, priority
);
170 #-------------------------------------------------------------------------
172 #--------------------------------------------------------------------------
173 # Check if this script is being invoked to run a single file. If so,
176 if {[llength $argv]==2
177 && ([lindex $argv 0]=="" ||
[info exists
::testspec([lindex $argv 0])])
178 && [file exists
[lindex $argv 1]]
180 set permutation
[lindex $argv 0]
181 set script
[file normalize
[lindex $argv 1]]
184 if {$permutation=="full"} {
186 set testdir
[file dirname
$argv0]
187 source $::testdir/tester.tcl
188 unset -nocomplain ::G(isquick
)
191 } elseif
{$permutation!="default" && $permutation!=""} {
193 if {[info exists
::testspec($permutation)]==0} {
194 error "no such permutation: $permutation"
197 array set O
$::testspec($permutation)
198 set ::G(perm
:name
) $permutation
199 set ::G(perm
:prefix
) $O(-prefix)
201 set ::G(perm
:dbconfig
) $O(-dbconfig)
202 set ::G(perm
:presql
) $O(-presql)
204 rename finish_test helper_finish_test
205 proc finish_test
{} "
219 #--------------------------------------------------------------------------
221 #--------------------------------------------------------------------------
222 # Check if this is the "njob" command:
224 if {([llength $argv]==2 ||
[llength $argv]==1)
225 && [string compare
-nocase njob
[lindex $argv 0]]==0
227 sqlite3 mydb
$TRG(dbname
)
228 if {[llength $argv]==2} {
229 set param
[lindex $argv 1]
230 if {[string is integer
$param]==0 ||
$param<1 ||
$param>128} {
231 puts stderr
"parameter must be an integer between 1 and 128"
235 mydb
eval { REPLACE INTO config VALUES
('njob'
, $param); }
237 set res
[mydb one
{ SELECT value FROM config WHERE name
='njob'
}]
242 #--------------------------------------------------------------------------
244 #--------------------------------------------------------------------------
245 # Check if this is the "status" command:
247 if {[llength $argv]==1
248 && [string compare
-nocase status
[lindex $argv 0]]==0
251 proc display_job
{build config
filename {tm
""}} {
252 if {$config=="build"} {
253 set fname
"build: $filename"
255 } elseif
{$config=="make"} {
256 set fname
"make: $filename"
259 set fname
[file normalize
$filename]
260 if {[string first
$::srcdir $fname]==0} {
261 set fname
[string range
$fname [string length
$::srcdir]+1 end
]
264 set dfname
[format %-33s $fname]
270 if {$build!=""} { set dbuild
$build }
271 if {$config!="" && $config!="full"} { set dconfig
$config }
272 if {$dbuild!="" ||
$dconfig!=""} {
274 if {$dbuild!=""} {append dparams
"build=$dbuild"}
275 if {$dbuild!="" && $dconfig!=""} {append dparams
" "}
276 if {$dconfig!=""} {append dparams
"config=$dconfig"}
278 set dparams
[format %-33s $dparams]
281 set dtm
"\[${tm}ms\]"
283 puts " $dfname $dparams $dtm"
286 sqlite3 mydb
$TRG(dbname
)
290 set cmdline
[mydb one
{ SELECT value FROM config WHERE name
='cmdline'
}]
291 set nJob
[mydb one
{ SELECT value FROM config WHERE name
='njob'
}]
292 set tm
[expr [clock_milliseconds
] - [mydb one
{
293 SELECT value FROM config WHERE name
='start'
297 foreach s
{"" ready running done failed
} { set S
($s) 0 }
299 SELECT state
, count
(*) AS cnt FROM script GROUP BY
1
304 set fin
[expr $S(done
)+$S(failed
)]
305 if {$cmdline!=""} {set cmdline
" $cmdline"}
309 set f
"$S(failed) FAILED, "
311 puts "Command line: \[testrunner.tcl$cmdline\]"
313 puts "Summary: ${tm}ms, ($fin/$total) finished, ${f}$S(running) running"
315 set srcdir
[file dirname
[file dirname
$TRG(info_script
)]]
318 set now
[clock_milliseconds
]
320 SELECT build
, config
, filename, time FROM script WHERE state
='running'
323 display_job
$build $config $filename [expr $now-$time]
329 SELECT build
, config
, filename FROM script WHERE state
='failed'
332 display_job
$build $config $filename
340 #-------------------------------------------------------------------------
341 # Parse the command line.
343 for {set ii
0} {$ii < [llength $argv]} {incr ii
} {
344 set isLast
[expr $ii==([llength $argv]-1)]
345 set a
[lindex $argv $ii]
346 set n
[string length
$a]
348 if {[string range
$a 0 0]=="-"} {
349 if {($n>2 && [string match
"$a*" --jobs]) ||
$a=="-j"} {
351 set TRG
(nJob
) [lindex $argv $ii]
352 if {$isLast} { usage
}
353 } elseif
{($n>2 && [string match
"$a*" --fuzztest]) ||
$a=="-f"} {
355 } elseif
{($n>2 && [string match
"$a*" --zipvfs]) ||
$a=="-z"} {
357 set TRG
(zipvfs
) [lindex $argv $ii]
358 if {$isLast} { usage
}
363 lappend TRG
(patternlist
) [string map
{% *} $a]
370 # This script runs individual tests - tcl scripts or [make xyz] commands -
371 # in directories named "testdir$N", where $N is an integer. This variable
372 # contains a list of integers indicating the directories in use.
374 # This variable is accessed only via the following commands:
377 # Return the number of entries currently in the list.
380 # Remove value IDIR from the list. It is an error if it is not present.
383 # Select a value that is not already in the list. Add it to the list
386 set TRG
(dirs_in_use
) [list]
388 proc dirs_nHelper
{} {
390 llength $TRG(dirs_in_use
)
392 proc dirs_freeDir
{iDir
} {
395 foreach d
$TRG(dirs_in_use
) {
396 if {$iDir!=$d} { lappend out
$d }
398 if {[llength $out]!=[llength $TRG(dirs_in_use
)]-1} {
399 error "dirs_freeDir could not find $iDir"
401 set TRG
(dirs_in_use
) $out
403 proc dirs_allocDir
{} {
405 array set inuse
[list]
406 foreach d
$TRG(dirs_in_use
) {
409 for {set iRet
0} {[info exists inuse
($iRet)]} {incr iRet
} { }
410 lappend TRG
(dirs_in_use
) $iRet
414 set testdir
[file dirname
$argv0]
416 # Check that directory $dir exists. If it does not, create it. If
417 # it does, delete its contents.
419 proc create_or_clear_dir
{dir
} {
420 set dir
[file normalize
$dir]
421 catch { file mkdir
$dir }
422 foreach f
[glob -nocomplain [file join $dir *]] {
423 catch { file delete
-force $f }
427 proc copy_dir
{from to
} {
428 foreach f
[glob -nocomplain [file join $from *]] {
429 catch { file copy
-force $f $to }
433 proc build_to_dirname
{bname
} {
434 set fold
[string tolower
[string map
{- _
} $bname]]
435 return "testrunner_build_$fold"
438 #-------------------------------------------------------------------------
439 # Return a list of tests to run. Each element of the list is itself a
440 # list of two elements - the name of a permuations.test configuration
441 # followed by the full path to a test script. i.e.:
443 # {BUILD CONFIG FILENAME} {BUILD CONFIG FILENAME} ...
445 proc testset_patternlist
{patternlist
} {
448 set testset
[list] ;# return value
450 set first
[lindex $patternlist 0]
452 if {$first=="release"} {
453 set platform
$::TRG(platform
)
455 set patternlist
[lrange $patternlist 1 end
]
456 foreach b
[trd_builds
$platform] {
457 foreach c
[trd_configs
$platform $b] {
458 testset_append testset
$b $c $patternlist
461 if {[llength $patternlist]==0 ||
$b=="User-Auth"} {
462 set target testfixture
464 set target coretestprogs
466 lappend testset
[list $b build
$target]
469 if {[llength $patternlist]==0} {
470 foreach b
[trd_builds
$platform] {
471 foreach e
[trd_extras
$platform $b] {
472 lappend testset
[list $b make
$e]
477 set TRG
(fuzztest
) 0 ;# ignore --fuzztest option in this case
479 } elseif
{$first=="all"} {
481 set clist
[trd_all_configs
]
482 set patternlist
[lrange $patternlist 1 end
]
484 testset_append testset
"" $c $patternlist
487 } elseif
{[info exists
::testspec($first)]} {
489 testset_append testset
"" $first [lrange $patternlist 1 end
]
490 } elseif
{ [llength $patternlist]==0 } {
491 testset_append testset
"" veryquick
$patternlist
493 testset_append testset
"" full
$patternlist
495 if {$TRG(fuzztest
)} {
496 if {$TRG(platform
)=="win"} { error "todo" }
497 lappend testset
[list "" make fuzztest
]
503 proc testset_append
{listvar build config patternlist
} {
506 catch { array unset O
}
507 array set O
$::testspec($config)
509 foreach f
$O(-files) {
510 if {[llength $patternlist]>0} {
512 foreach p
$patternlist {
513 if {[string match
$p [file tail
$f]]} {
518 if {$bMatch==0} continue
521 if {[file pathtype
$f]!="absolute"} {
522 set f
[file join $::testdir $f]
524 lappend lvar
[list $build $config $f]
528 #--------------------------------------------------------------------------
531 proc r_write_db
{tcl
} {
532 trdb
eval { BEGIN EXCLUSIVE
}
537 # Obtain a new job to be run by worker $iJob (an integer). A job is
538 # returned as a three element list:
540 # {$build $config $file}
542 proc r_get_next_job
{iJob
} {
546 set orderby
"ORDER BY priority ASC"
548 set orderby
"ORDER BY priority DESC"
555 SELECT build, config, filename
565 set tm
[clock_milliseconds
]
568 UPDATE script SET state
='running'
, time=$tm
569 WHERE
(build
, config
, filename) = ($b, $c, $f)
574 if {$f==""} { return "" }
578 #rename r_get_next_job r_get_next_job_r
579 #proc r_get_next_job {iJob} {
580 # puts [time { set res [r_get_next_job_r $iJob] }]
584 proc make_new_testset
{} {
587 set tests
[testset_patternlist
$TRG(patternlist
)]
589 if {$TRG(zipvfs
)!=""} {
590 source [file join $TRG(zipvfs
) test zipvfs_testrunner.tcl
]
591 set tests
[concat $tests [zipvfs_testrunner_testset
]]
596 trdb
eval $TRG(schema
)
598 set cmdline
$TRG(cmdline
)
599 set tm
[clock_milliseconds
]
600 trdb
eval { REPLACE INTO config VALUES
('njob'
, $nJob ); }
601 trdb
eval { REPLACE INTO config VALUES
('cmdline'
, $cmdline ); }
602 trdb
eval { REPLACE INTO config VALUES
('start'
, $tm ); }
605 foreach {b c s
} $t {}
608 if {$c!="make" && $c!="build"} {
610 for {set ii
0} {$ii<100 && ![eof $fd]} {incr ii
} {
612 if {[string match
-nocase *testrunner
:* $line]} {
613 regexp -nocase {.
*testrunner
:(.
*)} $line -> properties
614 foreach p
$properties {
615 if {$p=="slow"} { set slow
1 }
616 if {$p=="superslow"} { set slow
2 }
623 if {$c=="make" && $b==""} {
628 if {$c=="veryquick"} {
633 if {$b!="" && $c!="build"} {
638 INSERT INTO script
(build
, config
, filename, slow
, state
)
639 VALUES
($b, $c, $s, $slow, $state)
645 proc script_input_ready
{fd iJob b c f
} {
652 fconfigure $fd -blocking 1
654 set rc
[catch { close $fd } msg
]
656 puts "FAILED: $b $c $f"
660 set tm
[expr [clock_milliseconds
] - $T($iJob)]
662 puts $TRG(log
) "### $b ### $c ### $f ${tm}ms ($state)"
663 puts $TRG(log
) [string trim
$O($iJob)]
668 UPDATE script SET output
= $output, state
=$state, time=$tm
669 WHERE
(build
, config
, filename) = ($b, $c, $f)
671 if {$state=="done" && $c=="build"} {
673 UPDATE script SET state
= 'ready' WHERE
(build
, state
)==($b, ''
)
682 set rc
[catch { gets $fd line
} res
]
687 append O
($iJob) "$line\n"
697 proc launch_another_job
{iJob
} {
702 set testfixture
[info nameofexec
]
703 set script
$TRG(info_script
)
705 set dir
[dirname
$iJob]
706 create_or_clear_dir
$dir
710 set job
[r_get_next_job
$iJob]
711 if {$job==""} { return 0 }
713 foreach {b c f
} $job {}
716 set testdir
[file dirname
$TRG(info_script
)]
717 set srcdir
[file dirname
$testdir]
718 set builddir
[build_to_dirname
$b]
719 create_or_clear_dir
$builddir
722 set script
[zipvfs_testrunner_script
]
724 set cmd
[info nameofexec
]
725 lappend cmd
[file join $testdir releasetest_data.tcl
]
727 if {$TRG(platform
)=="win"} { lappend cmd
-msvc }
728 lappend cmd
$b $srcdir
729 set script
[exec {*}$cmd]
732 set fd
[open [file join $builddir $TRG(make
)] w
]
736 puts "Launching build \"$b\" in directory $builddir..."
737 set target coretestprogs
738 if {$b=="User-Auth"} { set target testfixture
}
740 set cmd
"$TRG(makecmd) $target"
743 } elseif
{$c=="make"} {
745 if {$f!="fuzztest"} { error "corruption in testrunner.db!" }
746 # Special case - run [make fuzztest]
747 set makedir
[file dirname
$testfixture]
748 if {$TRG(platform
)=="win"} {
751 set cmd
[list make
-C $makedir fuzztest
]
754 set builddir
[build_to_dirname
$b]
755 copy_dir
$builddir $dir
756 set cmd
"$TRG(makecmd) $f"
760 set testfixture
[info nameofexec
]
763 if {$TRG(platform
)=="win"} { set tail testfixture.exe
}
764 set testfixture
[file normalize
[file join [build_to_dirname
$b] $tail]]
767 if {$c=="valgrind"} {
768 set testfixture
"valgrind -v --error-exitcode=1 $testfixture"
769 set ::env(OMIT_MISUSE
) 1
771 set cmd
[concat $testfixture [list $script $c $f]]
776 set fd
[open "|$cmd 2>@1" r
]
780 fconfigure $fd -blocking false
781 fileevent $fd readable
[list script_input_ready
$fd $iJob $b $c $f]
782 unset -nocomplain ::env(OMIT_MISUSE
)
787 proc one_line_report
{} {
790 set tm
[expr [clock_milliseconds
] - $TRG(starttime
)]
791 set tm
[format "%d" [expr int
($tm/1000.0 + 0.5)]]
793 foreach s
{ready running done failed
} {
801 SELECT state
, jobtype
, count
(*) AS cnt
803 GROUP BY state
, jobtype
805 set v
($state,$jobtype) $cnt
806 if {[info exists t
($jobtype)]} {
807 incr t
($jobtype) $cnt
815 foreach j
[array names t
] {
816 set fin
[expr $v(done
,$j) + $v(failed
,$j)]
817 lappend text "$j ($fin/$t($j)) f=$v(failed,$j) r=$v(running,$j)"
820 if {[info exists TRG
(reportlength
)]} {
821 puts -nonewline "[string repeat " " $TRG(reportlength)]\r"
823 set report
"${tm}s: [join $text { }]"
824 set TRG
(reportlength
) [string length
$report]
825 if {[string length
$report]<80} {
826 puts -nonewline "$report\r"
832 after $TRG(reporttime
) one_line_report
835 proc launch_some_jobs
{} {
838 set nJob
[trdb one
{ SELECT value FROM config WHERE name
='njob'
}]
840 while {[dirs_nHelper
]<$nJob} {
841 set iDir
[dirs_allocDir
]
842 if {0==[launch_another_job
$iDir]} {
849 proc run_testset
{} {
853 set TRG
(starttime
) [clock_milliseconds
]
854 set TRG
(log
) [open $TRG(logname
) w
]
857 # launch_another_job $ii
860 while {[dirs_nHelper
]>0} {
861 after 500 {incr ::wakeup}
868 set nErr
[trdb one
{SELECT count
(*) FROM script WHERE state
='failed'
}]
870 puts "$nErr failures:"
872 SELECT build
, config
, filename FROM script WHERE state
='failed'
874 puts "FAILED: $build $config $filename"
879 puts "\nTest database is $TRG(dbname)"
880 puts "Test log is $TRG(logname)"
884 sqlite3 trdb
$TRG(dbname
)
885 trdb timeout
$TRG(timeout
)
886 set tm
[lindex [time { make_new_testset
}] 0]
888 puts "splitting work across $TRG(nJob) cores"
890 puts "built testset in [expr $tm/1000]ms.."