Add the json_pretty(J) function for pretty-printing of JSON. An optional
[sqlite.git] / test / testrunner.tcl
blob0c704daf211199f182d4392c2e783c9d55560552
2 set dir [pwd]
3 set testdir [file normalize [file dirname $argv0]]
4 set saved $argv
5 set argv [list]
6 source [file join $testdir testrunner_data.tcl]
7 source [file join $testdir permutations.test]
8 set argv $saved
9 cd $dir
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 }]
19 if {$rc} {
20 if { [string match -nocase testfixture* $interpreter]==0
21 && [file executable ./testfixture]
22 } {
23 puts "Failed to find tcl package sqlite3. Restarting with ./testfixture.."
24 set status [catch {
25 exec ./testfixture [info script] {*}$::argv >@ stdout
26 } msg]
27 exit $status
30 if {$rc} {
31 puts stderr "Failed to find tcl package sqlite3"
32 puts stderr "Run \"make testfixture\" and then try again..."
33 exit 1
36 find_interpreter
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]
40 # command.
41 if {[info commands clock_milliseconds]==""} {
42 proc clock_milliseconds {} {
43 clock milliseconds
47 #-------------------------------------------------------------------------
48 # Usage:
50 proc usage {} {
51 set a0 [file tail $::argv0]
53 puts stderr [string trim [subst -nocommands {
54 Usage:
55 $a0 ?SWITCHES? ?PERMUTATION? ?PATTERNS?
56 $a0 PERMUTATION FILE
57 $a0 njob ?NJOB?
58 $a0 status
60 where SWITCHES are:
61 --buildonly
62 --dryrun
63 --jobs NUMBER-OF-JOBS
64 --zipvfs ZIPVFS-SOURCE-DIR
66 Interesting values for PERMUTATION are:
68 veryquick - a fast subset of the tcl test scripts. This is the default.
69 full - all tcl test scripts.
70 all - all tcl test scripts, plus a subset of test scripts rerun
71 with various permutations.
72 release - full release test with various builds.
74 If no PATTERN arguments are present, all tests specified by the PERMUTATION
75 are run. Otherwise, each pattern is interpreted as a glob pattern. Only
76 those tcl tests for which the final component of the filename matches at
77 least one specified pattern are run.
79 If no PATTERN arguments are present, then various fuzztest, threadtest
80 and other tests are run as part of the "release" permutation. These are
81 omitted if any PATTERN arguments are specified on the command line.
83 If a PERMUTATION is specified and is followed by the path to a Tcl script
84 instead of a list of patterns, then that single Tcl test script is run
85 with the specified permutation.
87 The "status" and "njob" commands are designed to be run from the same
88 directory as a running testrunner.tcl script that is running tests. The
89 "status" command prints a report describing the current state and progress
90 of the tests. The "njob" command may be used to query or modify the number
91 of sub-processes the test script uses to run tests.
92 }]]
94 exit 1
96 #-------------------------------------------------------------------------
98 #-------------------------------------------------------------------------
99 # Try to estimate a the number of processes to use.
101 # Command [guess_number_of_cores] attempts to glean the number of logical
102 # cores. Command [default_njob] returns the default value for the --jobs
103 # switch.
105 proc guess_number_of_cores {} {
106 if {[catch {number_of_cores} ret]} {
107 set ret 4
109 if {$::tcl_platform(platform)=="windows"} {
110 catch { set ret $::env(NUMBER_OF_PROCESSORS) }
111 } else {
112 if {$::tcl_platform(os)=="Darwin"} {
113 set cmd "sysctl -n hw.logicalcpu"
114 } else {
115 set cmd "nproc"
117 catch {
118 set fd [open "|$cmd" r]
119 set ret [gets $fd]
120 close $fd
121 set ret [expr $ret]
125 return $ret
128 proc default_njob {} {
129 set nCore [guess_number_of_cores]
130 if {$nCore<=2} {
131 set nHelper 1
132 } else {
133 set nHelper [expr int($nCore*0.5)]
135 return $nHelper
137 #-------------------------------------------------------------------------
139 #-------------------------------------------------------------------------
140 # Setup various default values in the global TRG() array.
142 set TRG(dbname) [file normalize testrunner.db]
143 set TRG(logname) [file normalize testrunner.log]
144 set TRG(build.logname) [file normalize testrunner_build.log]
145 set TRG(info_script) [file normalize [info script]]
146 set TRG(timeout) 10000 ;# Default busy-timeout for testrunner.db
147 set TRG(nJob) [default_njob] ;# Default number of helper processes
148 set TRG(patternlist) [list]
149 set TRG(cmdline) $argv
150 set TRG(reporttime) 2000
151 set TRG(fuzztest) 0 ;# is the fuzztest option present.
152 set TRG(zipvfs) "" ;# -zipvfs option, if any
153 set TRG(buildonly) 0 ;# True if --buildonly option
154 set TRG(dryrun) 0 ;# True if --dryrun option
156 switch -nocase -glob -- $tcl_platform(os) {
157 *darwin* {
158 set TRG(platform) osx
159 set TRG(make) make.sh
160 set TRG(makecmd) "bash make.sh"
161 set TRG(testfixture) testfixture
162 set TRG(run) run.sh
163 set TRG(runcmd) "bash run.sh"
165 *linux* {
166 set TRG(platform) linux
167 set TRG(make) make.sh
168 set TRG(makecmd) "bash make.sh"
169 set TRG(testfixture) testfixture
170 set TRG(run) run.sh
171 set TRG(runcmd) "bash run.sh"
173 *win* {
174 set TRG(platform) win
175 set TRG(make) make.bat
176 set TRG(makecmd) make.bat
177 set TRG(testfixture) testfixture.exe
178 set TRG(run) run.bat
179 set TRG(runcmd) "run.bat"
181 default {
182 error "cannot determine platform!"
185 #-------------------------------------------------------------------------
187 #-------------------------------------------------------------------------
188 # The database schema used by the testrunner.db database.
190 set TRG(schema) {
191 DROP TABLE IF EXISTS jobs;
192 DROP TABLE IF EXISTS config;
195 ** This table contains one row for each job that testrunner.tcl must run
196 ** before the entire test run is finished.
198 ** jobid:
199 ** Unique identifier for each job. Must be a +ve non-zero number.
201 ** displaytype:
202 ** 3 or 4 letter mnemonic for the class of tests this belongs to e.g.
203 ** "fuzz", "tcl", "make" etc.
205 ** displayname:
206 ** Name/description of job. For display purposes.
208 ** build:
209 ** If the job requires a make.bat/make.sh make wrapper (i.e. to build
210 ** something), the name of the build configuration it uses. See
211 ** testrunner_data.tcl for a list of build configs. e.g. "Win32-MemDebug".
213 ** dirname:
214 ** If the job should use a well-known directory name for its
215 ** sub-directory instead of an anonymous "testdir[1234...]" sub-dir
216 ** that is deleted after the job is finished.
218 ** cmd:
219 ** Bash or batch script to run the job.
221 ** depid:
222 ** The jobid value of a job that this job depends on. This job may not
223 ** be run before its depid job has finished successfully.
225 ** priority:
226 ** Higher values run first. Sometimes.
228 CREATE TABLE jobs(
229 /* Fields populated when db is initialized */
230 jobid INTEGER PRIMARY KEY, -- id to identify job
231 displaytype TEXT NOT NULL, -- Type of test (for one line report)
232 displayname TEXT NOT NULL, -- Human readable job name
233 build TEXT NOT NULL DEFAULT '', -- make.sh/make.bat file request, if any
234 dirname TEXT NOT NULL DEFAULT '', -- directory name, if required
235 cmd TEXT NOT NULL, -- shell command to run
236 depid INTEGER, -- identifier of dependency (or '')
237 priority INTEGER NOT NULL, -- higher priority jobs may run earlier
239 /* Fields updated as jobs run */
240 starttime INTEGER,
241 endtime INTEGER,
242 state TEXT CHECK( state IN ('', 'ready', 'running', 'done', 'failed') ),
243 output TEXT
246 CREATE TABLE config(
247 name TEXT COLLATE nocase PRIMARY KEY,
248 value
249 ) WITHOUT ROWID;
251 CREATE INDEX i1 ON jobs(state, priority);
252 CREATE INDEX i2 ON jobs(depid);
254 #-------------------------------------------------------------------------
256 #--------------------------------------------------------------------------
257 # Check if this script is being invoked to run a single file. If so,
258 # run it.
260 if {[llength $argv]==2
261 && ([lindex $argv 0]=="" || [info exists ::testspec([lindex $argv 0])])
262 && [file exists [lindex $argv 1]]
264 set permutation [lindex $argv 0]
265 set script [file normalize [lindex $argv 1]]
266 set ::argv [list]
268 set testdir [file dirname $argv0]
269 source $::testdir/tester.tcl
271 if {$permutation=="full"} {
273 unset -nocomplain ::G(isquick)
274 reset_db
276 } elseif {$permutation!="default" && $permutation!=""} {
278 if {[info exists ::testspec($permutation)]==0} {
279 error "no such permutation: $permutation"
282 array set O $::testspec($permutation)
283 set ::G(perm:name) $permutation
284 set ::G(perm:prefix) $O(-prefix)
285 set ::G(isquick) 1
286 set ::G(perm:dbconfig) $O(-dbconfig)
287 set ::G(perm:presql) $O(-presql)
289 rename finish_test helper_finish_test
290 proc finish_test {} "
291 uplevel {
292 $O(-shutdown)
294 helper_finish_test
297 eval $O(-initialize)
300 reset_db
301 source $script
302 exit
304 #--------------------------------------------------------------------------
306 #--------------------------------------------------------------------------
307 # Check if this is the "njob" command:
309 if {([llength $argv]==2 || [llength $argv]==1)
310 && [string compare -nocase njob [lindex $argv 0]]==0
312 sqlite3 mydb $TRG(dbname)
313 if {[llength $argv]==2} {
314 set param [lindex $argv 1]
315 if {[string is integer $param]==0 || $param<1 || $param>128} {
316 puts stderr "parameter must be an integer between 1 and 128"
317 exit 1
320 mydb eval { REPLACE INTO config VALUES('njob', $param); }
322 set res [mydb one { SELECT value FROM config WHERE name='njob' }]
323 mydb close
324 puts "$res"
325 exit
327 #--------------------------------------------------------------------------
329 #--------------------------------------------------------------------------
330 # Check if this is the "script" command:
332 if {[string compare -nocase script [lindex $argv 0]]==0} {
333 if {[llength $argv]!=2 && !([llength $argv]==3&&[lindex $argv 1]=="-msvc")} {
334 usage
337 set bMsvc [expr ([llength $argv]==3)]
338 set config [lindex $argv [expr [llength $argv]-1]]
340 puts [trd_buildscript $config [file dirname $testdir] $bMsvc]
341 exit
345 #--------------------------------------------------------------------------
346 # Check if this is the "status" command:
348 if {[llength $argv]==1
349 && [string compare -nocase status [lindex $argv 0]]==0
352 proc display_job {jobdict {tm ""}} {
353 array set job $jobdict
355 set dfname [format %-60s $job(displayname)]
357 set dtm ""
358 if {$tm!=""} { set dtm "\[[expr {$tm-$job(starttime)}]ms\]" }
359 puts " $dfname $dtm"
362 sqlite3 mydb $TRG(dbname)
363 mydb timeout 1000
364 mydb eval BEGIN
366 set cmdline [mydb one { SELECT value FROM config WHERE name='cmdline' }]
367 set nJob [mydb one { SELECT value FROM config WHERE name='njob' }]
369 set now [clock_milliseconds]
370 set tm [mydb one {
371 SELECT
372 COALESCE((SELECT value FROM config WHERE name='end'), $now) -
373 (SELECT value FROM config WHERE name='start')
376 set total 0
377 foreach s {"" ready running done failed} { set S($s) 0 }
378 mydb eval {
379 SELECT state, count(*) AS cnt FROM jobs GROUP BY 1
381 incr S($state) $cnt
382 incr total $cnt
384 set fin [expr $S(done)+$S(failed)]
385 if {$cmdline!=""} {set cmdline " $cmdline"}
387 set f ""
388 if {$S(failed)>0} {
389 set f "$S(failed) FAILED, "
391 puts "Command line: \[testrunner.tcl$cmdline\]"
392 puts "Jobs: $nJob"
393 puts "Summary: ${tm}ms, ($fin/$total) finished, ${f}$S(running) running"
395 set srcdir [file dirname [file dirname $TRG(info_script)]]
396 if {$S(running)>0} {
397 puts "Running: "
398 mydb eval {
399 SELECT * FROM jobs WHERE state='running' ORDER BY starttime
400 } job {
401 display_job [array get job] $now
404 if {$S(failed)>0} {
405 puts "Failures: "
406 mydb eval {
407 SELECT * FROM jobs WHERE state='failed' ORDER BY starttime
408 } job {
409 display_job [array get job]
413 mydb close
414 exit
417 #-------------------------------------------------------------------------
418 # Parse the command line.
420 for {set ii 0} {$ii < [llength $argv]} {incr ii} {
421 set isLast [expr $ii==([llength $argv]-1)]
422 set a [lindex $argv $ii]
423 set n [string length $a]
425 if {[string range $a 0 0]=="-"} {
426 if {($n>2 && [string match "$a*" --jobs]) || $a=="-j"} {
427 incr ii
428 set TRG(nJob) [lindex $argv $ii]
429 if {$isLast} { usage }
430 } elseif {($n>2 && [string match "$a*" --zipvfs]) || $a=="-z"} {
431 incr ii
432 set TRG(zipvfs) [file normalize [lindex $argv $ii]]
433 if {$isLast} { usage }
434 } elseif {($n>2 && [string match "$a*" --buildonly]) || $a=="-b"} {
435 set TRG(buildonly) 1
436 } elseif {($n>2 && [string match "$a*" --dryrun]) || $a=="-d"} {
437 set TRG(dryrun) 1
438 } else {
439 usage
441 } else {
442 lappend TRG(patternlist) [string map {% *} $a]
445 set argv [list]
449 # This script runs individual tests - tcl scripts or [make xyz] commands -
450 # in directories named "testdir$N", where $N is an integer. This variable
451 # contains a list of integers indicating the directories in use.
453 # This variable is accessed only via the following commands:
455 # dirs_nHelper
456 # Return the number of entries currently in the list.
458 # dirs_freeDir IDIR
459 # Remove value IDIR from the list. It is an error if it is not present.
461 # dirs_allocDir
462 # Select a value that is not already in the list. Add it to the list
463 # and return it.
465 set TRG(dirs_in_use) [list]
467 proc dirs_nHelper {} {
468 global TRG
469 llength $TRG(dirs_in_use)
471 proc dirs_freeDir {iDir} {
472 global TRG
473 set out [list]
474 foreach d $TRG(dirs_in_use) {
475 if {$iDir!=$d} { lappend out $d }
477 if {[llength $out]!=[llength $TRG(dirs_in_use)]-1} {
478 error "dirs_freeDir could not find $iDir"
480 set TRG(dirs_in_use) $out
482 proc dirs_allocDir {} {
483 global TRG
484 array set inuse [list]
485 foreach d $TRG(dirs_in_use) {
486 set inuse($d) 1
488 for {set iRet 0} {[info exists inuse($iRet)]} {incr iRet} { }
489 lappend TRG(dirs_in_use) $iRet
490 return $iRet
493 # Check that directory $dir exists. If it does not, create it. If
494 # it does, delete its contents.
496 proc create_or_clear_dir {dir} {
497 set dir [file normalize $dir]
498 catch { file mkdir $dir }
499 foreach f [glob -nocomplain [file join $dir *]] {
500 catch { file delete -force $f }
504 proc build_to_dirname {bname} {
505 set fold [string tolower [string map {- _} $bname]]
506 return "testrunner_build_$fold"
509 #-------------------------------------------------------------------------
511 proc r_write_db {tcl} {
512 trdb eval { BEGIN EXCLUSIVE }
513 uplevel $tcl
514 trdb eval { COMMIT }
517 # Obtain a new job to be run by worker $iJob (an integer). A job is
518 # returned as a three element list:
520 # {$build $config $file}
522 proc r_get_next_job {iJob} {
523 global T
525 if {($iJob%2)} {
526 set orderby "ORDER BY priority ASC"
527 } else {
528 set orderby "ORDER BY priority DESC"
531 set ret [list]
533 r_write_db {
534 set query "
535 SELECT * FROM jobs AS j WHERE state='ready' $orderby LIMIT 1
537 trdb eval $query job {
538 set tm [clock_milliseconds]
539 set T($iJob) $tm
540 set jobid $job(jobid)
542 trdb eval {
543 UPDATE jobs SET starttime=$tm, state='running' WHERE jobid=$jobid
546 set ret [array get job]
550 return $ret
553 #rename r_get_next_job r_get_next_job_r
554 #proc r_get_next_job {iJob} {
555 #puts [time { set res [r_get_next_job_r $iJob] }]
556 #set res
559 # Usage:
561 # add_job OPTION ARG OPTION ARG...
563 # where available OPTIONS are:
565 # -displaytype
566 # -displayname
567 # -build
568 # -dirname
569 # -cmd
570 # -depid
571 # -priority
573 # Returns the jobid value for the new job.
575 proc add_job {args} {
577 set options {
578 -displaytype -displayname -build -dirname
579 -cmd -depid -priority
582 # Set default values of options.
583 set A(-dirname) ""
584 set A(-depid) ""
585 set A(-priority) 0
586 set A(-build) ""
588 array set A $args
590 # Check all required options are present. And that no extras are present.
591 foreach o $options {
592 if {[info exists A($o)]==0} { error "missing required option $o" }
594 foreach o [array names A] {
595 if {[lsearch -exact $options $o]<0} { error "unrecognized option: $o" }
598 set state ""
599 if {$A(-depid)==""} { set state ready }
601 trdb eval {
602 INSERT INTO jobs(
603 displaytype, displayname, build, dirname, cmd, depid, priority,
604 state
605 ) VALUES (
606 $A(-displaytype),
607 $A(-displayname),
608 $A(-build),
609 $A(-dirname),
610 $A(-cmd),
611 $A(-depid),
612 $A(-priority),
613 $state
617 trdb last_insert_rowid
620 proc add_tcl_jobs {build config patternlist} {
621 global TRG
623 set topdir [file dirname $::testdir]
624 set testrunner_tcl [file normalize [info script]]
626 if {$build==""} {
627 set testfixture [info nameofexec]
628 } else {
629 set testfixture [file join [lindex $build 1] $TRG(testfixture)]
631 if {[lindex $build 2]=="Valgrind"} {
632 set setvar "export OMIT_MISUSE=1\n"
633 set testfixture "${setvar}valgrind -v --error-exitcode=1 $testfixture"
636 # The ::testspec array is populated by permutations.test
637 foreach f [dict get $::testspec($config) -files] {
639 if {[llength $patternlist]>0} {
640 set bMatch 0
641 foreach p $patternlist {
642 if {[string match $p [file tail $f]]} {
643 set bMatch 1
644 break
647 if {$bMatch==0} continue
650 if {[file pathtype $f]!="absolute"} { set f [file join $::testdir $f] }
651 set f [file normalize $f]
653 set displayname [string map [list $topdir/ {}] $f]
654 if {$config=="full" || $config=="veryquick"} {
655 set cmd "$testfixture $f"
656 } else {
657 set cmd "$testfixture $testrunner_tcl $config $f"
658 set displayname "config=$config $displayname"
660 if {$build!=""} {
661 set displayname "[lindex $build 2] $displayname"
664 set lProp [trd_test_script_properties $f]
665 set priority 0
666 if {[lsearch $lProp slow]>=0} { set priority 2 }
667 if {[lsearch $lProp superslow]>=0} { set priority 4 }
669 add_job \
670 -displaytype tcl \
671 -displayname $displayname \
672 -cmd $cmd \
673 -depid [lindex $build 0] \
674 -priority $priority
679 proc add_build_job {buildname target} {
680 global TRG
682 set dirname "[string tolower [string map {- _} $buildname]]_$target"
683 set dirname "testrunner_bld_$dirname"
685 set id [add_job \
686 -displaytype bld \
687 -displayname "Build $buildname ($target)" \
688 -dirname $dirname \
689 -build $buildname \
690 -cmd "$TRG(makecmd) $target" \
691 -priority 3
694 list $id [file normalize $dirname] $buildname
697 proc add_make_job {bld target} {
698 global TRG
700 if {$TRG(platform)=="win"} {
701 set path [string map {/ \\} [lindex $bld 1]]
702 set cmd "xcopy /S $path\\* ."
703 } else {
704 set cmd "cp -r [lindex $bld 1]/* ."
706 append cmd "\n$TRG(makecmd) $target"
708 add_job \
709 -displaytype make \
710 -displayname "[lindex $bld 2] make $target" \
711 -cmd $cmd \
712 -depid [lindex $bld 0] \
713 -priority 1
716 proc add_fuzztest_jobs {buildname} {
718 foreach {interpreter scripts} [trd_fuzztest_data] {
719 set subcmd [lrange $interpreter 1 end]
720 set interpreter [lindex $interpreter 0]
722 set bld [add_build_job $buildname $interpreter]
723 foreach {depid dirname displayname} $bld {}
725 foreach s $scripts {
727 # Fuzz data files fuzzdata1.db and fuzzdata2.db are larger than
728 # the others. So ensure that these are run as a higher priority.
729 set tail [file tail $s]
730 if {$tail=="fuzzdata1.db" || $tail=="fuzzdata2.db"} {
731 set priority 5
732 } else {
733 set priority 1
736 add_job \
737 -displaytype fuzz \
738 -displayname "$buildname $interpreter $tail" \
739 -depid $depid \
740 -cmd "[file join $dirname $interpreter] $subcmd $s" \
741 -priority $priority
746 proc add_zipvfs_jobs {} {
747 global TRG
748 source [file join $TRG(zipvfs) test zipvfs_testrunner.tcl]
750 set bld [add_build_job Zipvfs $TRG(testfixture)]
751 foreach s [zipvfs_testrunner_files] {
752 set cmd "[file join [lindex $bld 1] $TRG(testfixture)] $s"
753 add_job \
754 -displaytype tcl \
755 -displayname "Zipvfs [file tail $s]" \
756 -cmd $cmd \
757 -depid [lindex $bld 0]
760 set ::env(SQLITE_TEST_DIR) $::testdir
763 # Used to add jobs for "mdevtest" and "sdevtest".
765 proc add_devtest_jobs {lBld patternlist} {
766 global TRG
768 foreach b $lBld {
769 set bld [add_build_job $b $TRG(testfixture)]
770 add_tcl_jobs $bld veryquick $patternlist
771 if {$patternlist==""} {
772 add_fuzztest_jobs $b
777 proc add_jobs_from_cmdline {patternlist} {
778 global TRG
780 if {$TRG(zipvfs)!=""} {
781 add_zipvfs_jobs
782 if {[llength $patternlist]==0} return
785 if {[llength $patternlist]==0} {
786 set patternlist [list veryquick]
789 set first [lindex $patternlist 0]
790 switch -- $first {
791 all {
792 set patternlist [lrange $patternlist 1 end]
793 set clist [trd_all_configs]
794 foreach c $clist {
795 add_tcl_jobs "" $c $patternlist
799 mdevtest {
800 add_devtest_jobs {All-O0 All-Debug} [lrange $patternlist 1 end]
803 sdevtest {
804 add_devtest_jobs {All-Sanitize All-Debug} [lrange $patternlist 1 end]
807 release {
808 set patternlist [lrange $patternlist 1 end]
809 foreach b [trd_builds $TRG(platform)] {
810 set bld [add_build_job $b $TRG(testfixture)]
811 foreach c [trd_configs $TRG(platform) $b] {
812 add_tcl_jobs $bld $c $patternlist
815 if {$patternlist==""} {
816 foreach e [trd_extras $TRG(platform) $b] {
817 if {$e=="fuzztest"} {
818 add_fuzztest_jobs $b
819 } else {
820 add_make_job $bld $e
827 default {
828 if {[info exists ::testspec($first)]} {
829 add_tcl_jobs "" $first [lrange $patternlist 1 end]
830 } else {
831 add_tcl_jobs "" full $patternlist
837 proc make_new_testset {} {
838 global TRG
840 r_write_db {
841 trdb eval $TRG(schema)
842 set nJob $TRG(nJob)
843 set cmdline $TRG(cmdline)
844 set tm [clock_milliseconds]
845 trdb eval { REPLACE INTO config VALUES('njob', $nJob ); }
846 trdb eval { REPLACE INTO config VALUES('cmdline', $cmdline ); }
847 trdb eval { REPLACE INTO config VALUES('start', $tm ); }
849 add_jobs_from_cmdline $TRG(patternlist)
854 proc mark_job_as_finished {jobid output state endtm} {
855 r_write_db {
856 trdb eval {
857 UPDATE jobs
858 SET output=$output, state=$state, endtime=$endtm
859 WHERE jobid=$jobid;
860 UPDATE jobs SET state='ready' WHERE depid=$jobid;
865 proc script_input_ready {fd iJob jobid} {
866 global TRG
867 global O
868 global T
870 if {[eof $fd]} {
871 trdb eval { SELECT * FROM jobs WHERE jobid=$jobid } job {}
873 # If this job specified a directory name, then delete the run.sh/run.bat
874 # file from it before continuing. This is because the contents of this
875 # directory might be copied by some other job, and we don't want to copy
876 # the run.sh file in this case.
877 if {$job(dirname)!=""} {
878 file delete -force [file join $job(dirname) $TRG(run)]
881 set ::done 1
882 fconfigure $fd -blocking 1
883 set state "done"
884 set rc [catch { close $fd } msg]
885 if {$rc} {
886 if {[info exists TRG(reportlength)]} {
887 puts -nonewline "[string repeat " " $TRG(reportlength)]\r"
889 puts "FAILED: $job(displayname) ($iJob)"
890 set state "failed"
893 set tm [clock_milliseconds]
894 set jobtm [expr {$tm - $job(starttime)}]
896 puts $TRG(log) "### $job(displayname) ${jobtm}ms ($state)"
897 puts $TRG(log) [string trim $O($iJob)]
899 mark_job_as_finished $jobid $O($iJob) $state $tm
901 dirs_freeDir $iJob
902 launch_some_jobs
903 incr ::wakeup
904 } else {
905 set rc [catch { gets $fd line } res]
906 if {$rc} {
907 puts "ERROR $res"
909 if {$res>=0} {
910 append O($iJob) "$line\n"
916 proc dirname {ii} {
917 return "testdir$ii"
920 proc launch_another_job {iJob} {
921 global TRG
922 global O
923 global T
925 set testfixture [info nameofexec]
926 set script $TRG(info_script)
928 set O($iJob) ""
930 set jobdict [r_get_next_job $iJob]
931 if {$jobdict==""} { return 0 }
932 array set job $jobdict
934 set dir $job(dirname)
935 if {$dir==""} { set dir [dirname $iJob] }
936 create_or_clear_dir $dir
938 if {$job(build)!=""} {
939 set srcdir [file dirname $::testdir]
940 if {$job(build)=="Zipvfs"} {
941 set script [zipvfs_testrunner_script]
942 } else {
943 set bWin [expr {$TRG(platform)=="win"}]
944 set script [trd_buildscript $job(build) $srcdir $bWin]
946 set fd [open [file join $dir $TRG(make)] w]
947 puts $fd $script
948 close $fd
951 if { $TRG(dryrun) } {
953 mark_job_as_finished $job(jobid) "" done 0
954 dirs_freeDir $iJob
955 if {$job(build)!=""} {
956 puts $TRG(log) "(cd $dir ; $job(cmd) )"
957 } else {
958 puts $TRG(log) "$job(cmd)"
961 } else {
962 set pwd [pwd]
963 cd $dir
964 set fd [open $TRG(run) w]
965 puts $fd $job(cmd)
966 close $fd
967 set fd [open "|$TRG(runcmd) 2>@1" r]
968 cd $pwd
970 fconfigure $fd -blocking false
971 fileevent $fd readable [list script_input_ready $fd $iJob $job(jobid)]
974 return 1
977 proc one_line_report {} {
978 global TRG
980 set tm [expr [clock_milliseconds] - $TRG(starttime)]
981 set tm [format "%d" [expr int($tm/1000.0 + 0.5)]]
983 r_write_db {
984 trdb eval {
985 SELECT displaytype, state, count(*) AS cnt
986 FROM jobs
987 GROUP BY 1, 2
989 set v($state,$displaytype) $cnt
990 incr t($displaytype) $cnt
994 set text ""
995 foreach j [lsort [array names t]] {
996 foreach k {done failed running} { incr v($k,$j) 0 }
997 set fin [expr $v(done,$j) + $v(failed,$j)]
998 lappend text "${j}($fin/$t($j))"
999 if {$v(failed,$j)>0} {
1000 lappend text "f$v(failed,$j)"
1002 if {$v(running,$j)>0} {
1003 lappend text "r$v(running,$j)"
1007 if {[info exists TRG(reportlength)]} {
1008 puts -nonewline "[string repeat " " $TRG(reportlength)]\r"
1010 set report "${tm} [join $text { }]"
1011 set TRG(reportlength) [string length $report]
1012 if {[string length $report]<100} {
1013 puts -nonewline "$report\r"
1014 flush stdout
1015 } else {
1016 puts $report
1019 after $TRG(reporttime) one_line_report
1022 proc launch_some_jobs {} {
1023 global TRG
1024 set nJob [trdb one { SELECT value FROM config WHERE name='njob' }]
1026 while {[dirs_nHelper]<$nJob} {
1027 set iDir [dirs_allocDir]
1028 if {0==[launch_another_job $iDir]} {
1029 dirs_freeDir $iDir
1030 break;
1035 proc run_testset {} {
1036 global TRG
1037 set ii 0
1039 set TRG(starttime) [clock_milliseconds]
1040 set TRG(log) [open $TRG(logname) w]
1042 launch_some_jobs
1044 one_line_report
1045 while {[dirs_nHelper]>0} {
1046 after 500 {incr ::wakeup}
1047 vwait ::wakeup
1049 close $TRG(log)
1050 one_line_report
1052 r_write_db {
1053 set tm [clock_milliseconds]
1054 trdb eval { REPLACE INTO config VALUES('end', $tm ); }
1055 set nErr [trdb one {SELECT count(*) FROM jobs WHERE state='failed'}]
1056 if {$nErr>0} {
1057 puts "$nErr failures:"
1058 trdb eval {
1059 SELECT displayname FROM jobs WHERE state='failed'
1061 puts "FAILED: $displayname"
1066 puts "\nTest database is $TRG(dbname)"
1067 puts "Test log is $TRG(logname)"
1070 # Handle the --buildonly option, if it was specified.
1072 proc handle_buildonly {} {
1073 global TRG
1074 if {$TRG(buildonly)} {
1075 r_write_db {
1076 trdb eval { DELETE FROM jobs WHERE displaytype!='bld' }
1081 sqlite3 trdb $TRG(dbname)
1082 trdb timeout $TRG(timeout)
1083 set tm [lindex [time { make_new_testset }] 0]
1084 if {$TRG(nJob)>1} {
1085 puts "splitting work across $TRG(nJob) jobs"
1087 puts "built testset in [expr $tm/1000]ms.."
1089 handle_buildonly
1090 run_testset
1091 trdb close
1092 #puts [pwd]