Testrunner.tcl enhancements: (1) Attempt to build the SQLite tcl extension
[sqlite.git] / test / testrunner.tcl
blob09704c03d396a1744def69f9bd0f21d304061296
1 #!/bin/sh
2 # Script to runs tests for SQLite. Run with option "help" for more info. \
3 exec tclsh "$0" "$@"
5 set dir [pwd]
6 set testdir [file normalize [file dirname $argv0]]
7 set saved $argv
8 set argv [list]
9 source [file join $testdir testrunner_data.tcl]
10 source [file join $testdir permutations.test]
11 set argv $saved
12 cd $dir
14 # This script requires an interpreter that supports [package require sqlite3]
15 # to run. If this is not such an intepreter, see if there is a [testfixture]
16 # in the current directory. If so, run the command using it. If not,
17 # recommend that the user build one.
19 proc find_interpreter {} {
20 global dir
21 set interpreter [file tail [info nameofexec]]
22 set rc [catch { package require sqlite3 }]
23 if {$rc} {
24 if {[file readable pkgIndex.tcl] && [catch {source pkgIndex.tcl}]==0} {
25 set rc [catch { package require sqlite3 }]
28 if {$rc} {
29 if { [string match -nocase testfixture* $interpreter]==0
30 && [file executable ./testfixture]
31 } {
32 puts "Failed to find tcl package sqlite3. Restarting with ./testfixture.."
33 set status [catch {
34 exec ./testfixture [info script] {*}$::argv >@ stdout
35 } msg]
36 exit $status
39 if {$rc} {
40 puts "Cannot find tcl package sqlite3: Trying to build it now..."
41 if {$::tcl_platform(platform)=="windows"} {
42 set bat [open make-tcl-extension.bat w]
43 puts $bat "nmake /f Makefile.msc tclextension"
44 close $bat
45 catch {exec -ignorestderr -- make-tcl-extension.bat}
46 } else {
47 catch {exec make tclextension}
49 if {[file readable pkgIndex.tcl] && [catch {source pkgIndex.tcl}]==0} {
50 set rc [catch { package require sqlite3 }]
52 if {$rc==0} {
53 puts "The SQLite tcl extension was successfully built and loaded."
54 puts "Run \"make tclextension-install\" to avoid having to rebuild\
55 it in the future."
56 } else {
57 puts "Unable to build the SQLite tcl extension"
60 if {$rc} {
61 puts stderr "Cannot find a working instance of the SQLite tcl extension."
62 puts stderr "Run \"make tclextension\" or \"make testfixture\" and\
63 try again..."
64 exit 1
67 find_interpreter
69 # Usually this script is run by [testfixture]. But it can also be run
70 # by a regular [tclsh]. For these cases, emulate the [clock_milliseconds]
71 # command.
72 if {[info commands clock_milliseconds]==""} {
73 proc clock_milliseconds {} {
74 clock milliseconds
78 #-------------------------------------------------------------------------
79 # Usage:
81 proc usage {} {
82 set a0 [file tail $::argv0]
84 puts stderr [string trim [subst -nocommands {
85 Usage:
86 $a0 ?SWITCHES? ?PERMUTATION? ?PATTERNS?
87 $a0 PERMUTATION FILE
88 $a0 errors ?-v|--verbose? ?-s|--summary? ?PATTERN?
89 $a0 help
90 $a0 joblist ?PATTERN?
91 $a0 njob ?NJOB?
92 $a0 script ?-msvc? CONFIG
93 $a0 status ?-d SECS? ?--cls?
95 where SWITCHES are:
96 --buildonly Build test exes but do not run tests
97 --config CONFIGS Only use configs on comma-separate list CONFIGS
98 --dryrun Write what would have happened to testrunner.log
99 --explain Write summary to stdout
100 --jobs NUM Run tests using NUM separate processes
101 --omit CONFIGS Omit configs on comma-separated list CONFIGS
102 --status Show the full "status" report while running
103 --stop-on-coredump Stop running if any test segfaults
104 --stop-on-error Stop running after any reported error
105 --zipvfs ZIPVFSDIR ZIPVFS source directory
107 Special values for PERMUTATION that work with plain tclsh:
109 list - show all allowed PERMUTATION arguments.
110 mdevtest - tests recommended prior to normal development check-ins.
111 release - full release test with various builds.
112 sdevtest - like mdevtest but using ASAN and UBSAN.
114 Other PERMUTATION arguments must be run using testfixture, not tclsh:
116 all - all tcl test scripts, plus a subset of test scripts rerun
117 with various permutations.
118 full - all tcl test scripts.
119 veryquick - a fast subset of the tcl test scripts. This is the default.
121 If no PATTERN arguments are present, all tests specified by the PERMUTATION
122 are run. Otherwise, each pattern is interpreted as a glob pattern. Only
123 those tcl tests for which the final component of the filename matches at
124 least one specified pattern are run. The glob wildcard '*' is prepended
125 to the pattern if it does not start with '^' and appended to every
126 pattern that does not end with '$'.
128 If no PATTERN arguments are present, then various fuzztest, threadtest
129 and other tests are run as part of the "release" permutation. These are
130 omitted if any PATTERN arguments are specified on the command line.
132 If a PERMUTATION is specified and is followed by the path to a Tcl script
133 instead of a list of patterns, then that single Tcl test script is run
134 with the specified permutation.
136 The "status" and "njob" commands are designed to be run from the same
137 directory as a running testrunner.tcl script that is running tests. The
138 "status" command prints a report describing the current state and progress
139 of the tests. Use the "-d N" option to have the status display clear the
140 screen and repeat every N seconds. The "njob" command may be used to query
141 or modify the number of sub-processes the test script uses to run tests.
143 The "script" command outputs the script used to build a configuration.
144 Add the "-msvc" option for a Windows-compatible script. For a list of
145 available configurations enter "$a0 script help".
147 The "errors" commands shows the output of tests that failed in the
148 most recent run. Complete output is shown if the -v or --verbose options
149 are used. Otherwise, an attempt is made to minimize the output to show
150 only the parts that contain the error messages. The --summary option just
151 shows the jobs that failed. If PATTERN are provided, the error information
152 is only provided for jobs that match PATTERN.
154 Full documentation here: https://sqlite.org/src/doc/trunk/doc/testrunner.md
157 exit 1
159 #-------------------------------------------------------------------------
161 #-------------------------------------------------------------------------
162 # Try to estimate a the number of processes to use.
164 # Command [guess_number_of_cores] attempts to glean the number of logical
165 # cores. Command [default_njob] returns the default value for the --jobs
166 # switch.
168 proc guess_number_of_cores {} {
169 if {[catch {number_of_cores} ret]} {
170 set ret 4
172 if {$::tcl_platform(platform)=="windows"} {
173 catch { set ret $::env(NUMBER_OF_PROCESSORS) }
174 } else {
175 if {$::tcl_platform(os)=="Darwin"} {
176 set cmd "sysctl -n hw.logicalcpu"
177 } else {
178 set cmd "nproc"
180 catch {
181 set fd [open "|$cmd" r]
182 set ret [gets $fd]
183 close $fd
184 set ret [expr $ret]
188 return $ret
191 proc default_njob {} {
192 global env
193 if {[info exists env(NJOB)] && $env(NJOB)>=1} {
194 return $env(NJOB)
196 set nCore [guess_number_of_cores]
197 if {$nCore<=2} {
198 set nHelper 1
199 } else {
200 set nHelper [expr int($nCore*0.5)]
202 return $nHelper
204 #-------------------------------------------------------------------------
206 #-------------------------------------------------------------------------
207 # Setup various default values in the global TRG() array.
209 set TRG(dbname) [file normalize testrunner.db]
210 set TRG(logname) [file normalize testrunner.log]
211 set TRG(build.logname) [file normalize testrunner_build.log]
212 set TRG(info_script) [file normalize [info script]]
213 set TRG(timeout) 10000 ;# Default busy-timeout for testrunner.db
214 set TRG(nJob) [default_njob] ;# Default number of helper processes
215 set TRG(patternlist) [list]
216 set TRG(cmdline) $argv
217 set TRG(reporttime) 2000
218 set TRG(fuzztest) 0 ;# is the fuzztest option present.
219 set TRG(zipvfs) "" ;# -zipvfs option, if any
220 set TRG(buildonly) 0 ;# True if --buildonly option
221 set TRG(config) {} ;# Only build the named configurations
222 set TRG(omitconfig) {} ;# Do not build these configurations
223 set TRG(dryrun) 0 ;# True if --dryrun option
224 set TRG(explain) 0 ;# True for the --explain option
225 set TRG(stopOnError) 0 ;# Stop running at first failure
226 set TRG(stopOnCore) 0 ;# Stop on a core-dump
227 set TRG(fullstatus) 0 ;# Full "status" report while running
229 switch -nocase -glob -- $tcl_platform(os) {
230 *darwin* {
231 set TRG(platform) osx
232 set TRG(make) make.sh
233 set TRG(makecmd) "bash make.sh"
234 set TRG(testfixture) testfixture
235 set TRG(shell) sqlite3
236 set TRG(run) run.sh
237 set TRG(runcmd) "bash run.sh"
239 *linux* {
240 set TRG(platform) linux
241 set TRG(make) make.sh
242 set TRG(makecmd) "bash make.sh"
243 set TRG(testfixture) testfixture
244 set TRG(shell) sqlite3
245 set TRG(run) run.sh
246 set TRG(runcmd) "bash run.sh"
248 *win* {
249 set TRG(platform) win
250 set TRG(make) make.bat
251 set TRG(makecmd) "call make.bat"
252 set TRG(testfixture) testfixture.exe
253 set TRG(shell) sqlite3.exe
254 set TRG(run) run.bat
255 set TRG(runcmd) "run.bat"
257 default {
258 error "cannot determine platform!"
261 #-------------------------------------------------------------------------
263 #-------------------------------------------------------------------------
264 # The database schema used by the testrunner.db database.
266 set TRG(schema) {
267 DROP TABLE IF EXISTS jobs;
268 DROP TABLE IF EXISTS config;
271 ** This table contains one row for each job that testrunner.tcl must run
272 ** before the entire test run is finished.
274 ** jobid:
275 ** Unique identifier for each job. Must be a +ve non-zero number.
277 ** displaytype:
278 ** 3 or 4 letter mnemonic for the class of tests this belongs to e.g.
279 ** "fuzz", "tcl", "make" etc.
281 ** displayname:
282 ** Name/description of job. For display purposes.
284 ** build:
285 ** If the job requires a make.bat/make.sh make wrapper (i.e. to build
286 ** something), the name of the build configuration it uses. See
287 ** testrunner_data.tcl for a list of build configs. e.g. "Win32-MemDebug".
289 ** dirname:
290 ** If the job should use a well-known directory name for its
291 ** sub-directory instead of an anonymous "testdir[1234...]" sub-dir
292 ** that is deleted after the job is finished.
294 ** cmd:
295 ** Bash or batch script to run the job.
297 ** depid:
298 ** The jobid value of a job that this job depends on. This job may not
299 ** be run before its depid job has finished successfully.
301 ** priority:
302 ** Higher values run first. Sometimes.
304 CREATE TABLE jobs(
305 /* Fields populated when db is initialized */
306 jobid INTEGER PRIMARY KEY, -- id to identify job
307 displaytype TEXT NOT NULL, -- Type of test (for one line report)
308 displayname TEXT NOT NULL, -- Human readable job name
309 build TEXT NOT NULL DEFAULT '', -- make.sh/make.bat file request, if any
310 dirname TEXT NOT NULL DEFAULT '', -- directory name, if required
311 cmd TEXT NOT NULL, -- shell command to run
312 depid INTEGER, -- identifier of dependency (or '')
313 priority INTEGER NOT NULL, -- higher priority jobs may run earlier
315 /* Fields updated as jobs run */
316 starttime INTEGER, -- Start time (milliseconds since 1970)
317 endtime INTEGER, -- End time
318 state TEXT CHECK( state IN ('','ready','running','done','failed','omit') ),
319 ntest INT, -- Number of test cases run
320 nerr INT, -- Number of errors reported
321 svers TEXT, -- Reported SQLite version
322 pltfm TEXT, -- Host platform reported
323 output TEXT -- test output
326 CREATE TABLE config(
327 name TEXT COLLATE nocase PRIMARY KEY,
328 value
329 ) WITHOUT ROWID;
331 CREATE INDEX i1 ON jobs(state, priority);
332 CREATE INDEX i2 ON jobs(depid);
334 #-------------------------------------------------------------------------
336 #--------------------------------------------------------------------------
337 # Check if this script is being invoked to run a single file. If so,
338 # run it.
340 if {[llength $argv]==2
341 && ([lindex $argv 0]=="" || [info exists ::testspec([lindex $argv 0])])
342 && [file exists [lindex $argv 1]]
344 set permutation [lindex $argv 0]
345 set script [file normalize [lindex $argv 1]]
346 set ::argv [list]
348 set testdir [file dirname $argv0]
349 source $::testdir/tester.tcl
351 if {$permutation=="full"} {
353 unset -nocomplain ::G(isquick)
354 reset_db
356 } elseif {$permutation!="default" && $permutation!=""} {
358 if {[info exists ::testspec($permutation)]==0} {
359 error "no such permutation: $permutation"
362 array set O $::testspec($permutation)
363 set ::G(perm:name) $permutation
364 set ::G(perm:prefix) $O(-prefix)
365 set ::G(isquick) 1
366 set ::G(perm:dbconfig) $O(-dbconfig)
367 set ::G(perm:presql) $O(-presql)
369 rename finish_test helper_finish_test
370 proc finish_test {} "
371 uplevel {
372 $O(-shutdown)
374 helper_finish_test
377 eval $O(-initialize)
380 reset_db
381 source $script
382 exit
384 #--------------------------------------------------------------------------
386 #--------------------------------------------------------------------------
387 # Check if this is the "njob" command:
389 if {([llength $argv]==2 || [llength $argv]==1)
390 && [string compare -nocase njob [lindex $argv 0]]==0
392 sqlite3 mydb $TRG(dbname)
393 if {[llength $argv]==2} {
394 set param [lindex $argv 1]
395 if {[string is integer $param]==0 || $param<0 || $param>128} {
396 puts stderr "parameter must be an integer between 0 and 128"
397 exit 1
400 mydb eval { REPLACE INTO config VALUES('njob', $param); }
402 set res [mydb one { SELECT value FROM config WHERE name='njob' }]
403 mydb close
404 puts "$res"
405 exit
407 #--------------------------------------------------------------------------
409 #--------------------------------------------------------------------------
410 # Check if this is the "help" command:
412 if {[string compare -nocase help [lindex $argv 0]]==0} {
413 usage
415 #--------------------------------------------------------------------------
417 #--------------------------------------------------------------------------
418 # Check if this is the "script" command:
420 if {[string compare -nocase script [lindex $argv 0]]==0} {
421 if {[llength $argv]!=2 && !([llength $argv]==3&&[lindex $argv 1]=="-msvc")} {
422 usage
425 set bMsvc [expr ([llength $argv]==3)]
426 set config [lindex $argv [expr [llength $argv]-1]]
428 puts [trd_buildscript $config [file dirname $testdir] $bMsvc]
429 exit
432 # Compute an elapse time string MM:SS or HH:MM:SS based on the
433 # number of milliseconds in the argument.
435 proc elapsetime {ms} {
436 set s [expr {int(($ms+500.0)*0.001)}]
437 set hr [expr {$s/3600}]
438 set mn [expr {($s/60)%60}]
439 set sc [expr {$s%60}]
440 if {$hr>0} {
441 return [format %02d:%02d:%02d $hr $mn $sc]
442 } else {
443 return [format %02d:%02d $mn $sc]
447 # Helper routine for show_status
449 proc display_job {jobdict {tm ""}} {
450 array set job $jobdict
451 if {[string length $job(displayname)]>65} {
452 set dfname [format %.65s... $job(displayname)]
453 } else {
454 set dfname [format %-68s $job(displayname)]
456 set dtm ""
457 if {$tm!=""} {
458 set dtm [expr {$tm-$job(starttime)}]
459 set dtm [format %8s [elapsetime $dtm]]
460 } else {
461 set dtm [format %8s ""]
463 puts " $dfname $dtm"
466 # This procedure shows the "status" page. It uses the database
467 # connect passed in as the "db" parameter. If the "cls" parameter
468 # is true, then VT100 escape codes are used to format the display.
470 proc show_status {db cls} {
471 global TRG
472 $db eval BEGIN
473 if {[catch {
474 set cmdline [$db one { SELECT value FROM config WHERE name='cmdline' }]
475 set nJob [$db one { SELECT value FROM config WHERE name='njob' }]
476 } msg]} {
477 if {$cls} {puts "\033\[H\033\[2J"}
478 puts "Cannot read database: $TRG(dbname)"
479 return
481 set now [clock_milliseconds]
482 set tm [$db one {
483 SELECT
484 COALESCE((SELECT value FROM config WHERE name='end'), $now) -
485 (SELECT value FROM config WHERE name='start')
488 set total 0
489 foreach s {"" ready running done failed} { set S($s) 0 }
490 $db eval {
491 SELECT state, count(*) AS cnt FROM jobs GROUP BY 1
493 incr S($state) $cnt
494 incr total $cnt
496 set nt 0
497 set ne 0
498 $db eval {
499 SELECT sum(ntest) AS nt, sum(nerr) AS ne FROM jobs HAVING nt>0
500 } break
501 set fin [expr $S(done)+$S(failed)]
502 if {$cmdline!=""} {set cmdline " $cmdline"}
504 if {$cls} {
505 # Move the cursor to the top-left corner. Each iteration will simply
506 # overwrite.
507 puts -nonewline "\033\[H"
508 flush stdout
510 puts [format %-79.79s "Command: \[testrunner.tcl$cmdline\]"]
511 puts [format %-79.79s "Summary: [elapsetime $tm], $fin/$total jobs,\
512 $ne errors, $nt tests"]
514 set srcdir [file dirname [file dirname $TRG(info_script)]]
515 set line "Running: $S(running) (max: $nJob)"
516 if {$S(running)>0 && $fin>100 && $fin>0.05*$total} {
517 # Only estimate the time remaining after completing at least 100
518 # jobs amounting to 10% of the total. Never estimate less than
519 # 2% of the total time used so far.
520 set tmleft [expr {($tm/$fin)*($total-$fin)}]
521 if {$tmleft<0.02*$tm} {
522 set tmleft [expr {$tm*0.02}]
524 append line " est time left [elapsetime $tmleft]"
526 puts [format %-79.79s $line]
527 if {$S(running)>0} {
528 $db eval {
529 SELECT * FROM jobs WHERE state='running' ORDER BY starttime
530 } job {
531 display_job [array get job] $now
534 if {$S(failed)>0} {
535 # $toshow is the number of failures to report. In $cls mode,
536 # status tries to limit the number of failure reported so that
537 # the status display does not overflow a 24-line terminal. It will
538 # always show at least the most recent 4 failures, even if an overflow
539 # is needed. No limit is imposed for a status within $cls.
541 if {$cls && $S(failed)>18-$S(running)} {
542 set toshow [expr {18-$S(running)}]
543 if {$toshow<4} {set toshow 4}
544 set shown " (must recent $toshow shown)"
545 } else {
546 set toshow $S(failed)
547 set shown ""
549 puts [format %-79s "Failed: $S(failed) $shown"]
550 $db eval {
551 SELECT * FROM jobs WHERE state='failed'
552 ORDER BY endtime DESC LIMIT $toshow
553 } job {
554 display_job [array get job]
556 set nOmit [$db one {SELECT count(*) FROM jobs WHERE state='omit'}]
557 if {$nOmit} {
558 puts [format %-79s " ... $nOmit jobs omitted due to failures"]
561 if {$cls} {
562 # Clear everything else to the bottom of the screen
563 puts -nonewline "\033\[0J"
564 flush stdout
566 $db eval COMMIT
571 #--------------------------------------------------------------------------
572 # Check if this is the "status" command:
574 if {[llength $argv]>=1
575 && [string compare -nocase status [lindex $argv 0]]==0
577 set delay 0
578 set cls 0
579 for {set ii 1} {$ii<[llength $argv]} {incr ii} {
580 set a0 [lindex $argv $ii]
581 if {$a0=="-d" && $ii+1<[llength $argv]} {
582 incr ii
583 set delay [lindex $argv $ii]
584 if {![string is integer -strict $delay]} {
585 puts "Argument to -d should be an integer"
586 exit 1
588 } elseif {$a0=="-cls" || $a0=="--cls"} {
589 set cls 1
590 } else {
591 puts "unknown option: \"$a0\""
592 exit 1
596 if {![file readable $TRG(dbname)]} {
597 puts "Database missing: $TRG(dbname)"
598 exit
600 sqlite3 mydb $TRG(dbname)
601 mydb timeout 2000
603 # Clear the whole screen initially.
605 if {$delay>0 || $cls} {puts -nonewline "\033\[2J"}
607 while {1} {
608 show_status mydb [expr {$delay>0 || $cls}]
609 if {$delay<=0} break
610 after [expr {$delay*1000}]
612 mydb close
613 exit
616 #--------------------------------------------------------------------------
617 # Check if this is the "joblist" command:
619 if {[llength $argv]>=1
620 && [string compare -nocase "joblist" [lindex $argv 0]]==0
622 set pattern {}
623 for {set ii 1} {$ii<[llength $argv]} {incr ii} {
624 set a0 [lindex $argv $ii]
625 if {$pattern==""} {
626 set pattern [string trim $a0 *]
627 } else {
628 puts "unknown option: \"$a0\""
629 exit 1
632 set SQL {SELECT displaytype, displayname, state FROM jobs}
633 if {$pattern!=""} {
634 regsub -all {[^a-zA-Z0-9*.-/]} $pattern ? pattern
635 append SQL " WHERE displayname GLOB '*$pattern*'"
637 append SQL " ORDER BY starttime"
639 if {![file readable $TRG(dbname)]} {
640 puts "Database missing: $TRG(dbname)"
641 exit
643 sqlite3 mydb $TRG(dbname)
644 mydb timeout 2000
646 mydb eval $SQL {
647 set label UNKNOWN
648 switch -- $state {
649 ready {set label READY}
650 done {set label DONE}
651 failed {set label FAILED}
652 omit {set label OMIT}
653 running {set label RUNNING}
655 puts [format {%-7s %-5s %s} $label $displaytype $displayname]
657 mydb close
658 exit
661 # Scan the output of all jobs looking for the summary lines that
662 # report the number of test cases and the number of errors.
663 # Aggregate these numbers and return them.
665 proc aggregate_test_counts {db} {
666 set ne 0
667 set nt 0
668 $db eval {SELECT sum(nerr) AS ne, sum(ntest) as nt FROM jobs} break
669 return [list $ne $nt]
672 #--------------------------------------------------------------------------
673 # Check if this is the "errors" command:
675 if {[llength $argv]>=1
676 && ([string compare -nocase errors [lindex $argv 0]]==0 ||
677 [string match err* [lindex $argv 0]]==1)
679 set verbose 0
680 set pattern {}
681 set summary 0
682 for {set ii 1} {$ii<[llength $argv]} {incr ii} {
683 set a0 [lindex $argv $ii]
684 if {$a0=="-v" || $a0=="--verbose" || $a0=="-verbose"} {
685 set verbose 1
686 } elseif {$a0=="-s" || $a0=="--summary" || $a0=="-summary"} {
687 set summary 1
688 } elseif {$pattern==""} {
689 set pattern *[string trim $a0 *]*
690 } else {
691 puts "unknown option: \"$a0\"". Use --help for more info."
692 exit 1
695 set cnt 0
696 sqlite3 mydb $TRG(dbname)
697 mydb timeout 5000
698 if {$summary} {
699 set sql "SELECT displayname FROM jobs WHERE state='failed'"
700 } else {
701 set sql "SELECT displaytype, displayname, output FROM jobs \
702 WHERE state='failed'"
704 if {$pattern!=""} {
705 regsub -all {[^a-zA-Z0-9*/ ?]} $pattern . pattern
706 append sql " AND displayname GLOB '$pattern'"
708 mydb eval $sql {
709 if {$summary} {
710 puts "FAILED: $displayname"
711 continue
713 puts "**** $displayname ****"
714 if {$verbose || $displaytype!="tcl"} {
715 puts $output
716 } else {
717 foreach line [split $output \n] {
718 if {[string match {!*} $line] || [string match *failed* $line]} {
719 puts $line
723 incr cnt
725 if {$pattern==""} {
726 set summary [aggregate_test_counts mydb]
727 mydb close
728 puts "Total [lindex $summary 0] errors out of [lindex $summary 1] tests"
729 } else {
730 mydb close
732 exit
735 #-------------------------------------------------------------------------
736 # Parse the command line.
738 for {set ii 0} {$ii < [llength $argv]} {incr ii} {
739 set isLast [expr $ii==([llength $argv]-1)]
740 set a [lindex $argv $ii]
741 set n [string length $a]
743 if {[string range $a 0 0]=="-"} {
744 if {($n>2 && [string match "$a*" --jobs]) || $a=="-j"} {
745 incr ii
746 set TRG(nJob) [lindex $argv $ii]
747 if {$isLast} { usage }
748 } elseif {($n>2 && [string match "$a*" --zipvfs]) || $a=="-z"} {
749 incr ii
750 set TRG(zipvfs) [file normalize [lindex $argv $ii]]
751 if {$isLast} { usage }
752 } elseif {($n>2 && [string match "$a*" --buildonly]) || $a=="-b"} {
753 set TRG(buildonly) 1
754 } elseif {($n>2 && [string match "$a*" --config]) || $a=="-c"} {
755 incr ii
756 set TRG(config) [lindex $argv $ii]
757 } elseif {($n>2 && [string match "$a*" --dryrun]) || $a=="-d"} {
758 set TRG(dryrun) 1
759 } elseif {($n>2 && [string match "$a*" --explain]) || $a=="-e"} {
760 set TRG(explain) 1
761 } elseif {($n>2 && [string match "$a*" --omit]) || $a=="-c"} {
762 incr ii
763 set TRG(omitconfig) [lindex $argv $ii]
764 } elseif {[string match "$a*" --stop-on-error]} {
765 set TRG(stopOnError) 1
766 } elseif {[string match "$a*" --stop-on-coredump]} {
767 set TRG(stopOnCore) 1
768 } elseif {[string match "$a*" --status]} {
769 if {$tcl_platform(platform)=="windows"} {
770 puts stdout \
771 "The --status option is not available on Windows. A suggested work-around"
772 puts stdout \
773 "is to run the following command in a separate window:\n"
774 puts stdout " [info nameofexe] $argv0 status -d 2\n"
775 } else {
776 set TRG(fullstatus) 1
778 } else {
779 usage
781 } else {
782 lappend TRG(patternlist) [string map {% *} $a]
785 set argv [list]
787 # This script runs individual tests - tcl scripts or [make xyz] commands -
788 # in directories named "testdir$N", where $N is an integer. This variable
789 # contains a list of integers indicating the directories in use.
791 # This variable is accessed only via the following commands:
793 # dirs_nHelper
794 # Return the number of entries currently in the list.
796 # dirs_freeDir IDIR
797 # Remove value IDIR from the list. It is an error if it is not present.
799 # dirs_allocDir
800 # Select a value that is not already in the list. Add it to the list
801 # and return it.
803 set TRG(dirs_in_use) [list]
805 proc dirs_nHelper {} {
806 global TRG
807 llength $TRG(dirs_in_use)
809 proc dirs_freeDir {iDir} {
810 global TRG
811 set out [list]
812 foreach d $TRG(dirs_in_use) {
813 if {$iDir!=$d} { lappend out $d }
815 if {[llength $out]!=[llength $TRG(dirs_in_use)]-1} {
816 error "dirs_freeDir could not find $iDir"
818 set TRG(dirs_in_use) $out
820 proc dirs_allocDir {} {
821 global TRG
822 array set inuse [list]
823 foreach d $TRG(dirs_in_use) {
824 set inuse($d) 1
826 for {set iRet 0} {[info exists inuse($iRet)]} {incr iRet} { }
827 lappend TRG(dirs_in_use) $iRet
828 return $iRet
831 # Check that directory $dir exists. If it does not, create it. If
832 # it does, delete its contents.
834 proc create_or_clear_dir {dir} {
835 set dir [file normalize $dir]
836 catch { file mkdir $dir }
837 foreach f [glob -nocomplain [file join $dir *]] {
838 catch { file delete -force $f }
842 proc build_to_dirname {bname} {
843 set fold [string tolower [string map {- _} $bname]]
844 return "testrunner_build_$fold"
847 #-------------------------------------------------------------------------
849 proc r_write_db {tcl} {
850 trdb eval { BEGIN EXCLUSIVE }
851 uplevel $tcl
852 trdb eval { COMMIT }
855 # Obtain a new job to be run by worker $iJob (an integer). A job is
856 # returned as a three element list:
858 # {$build $config $file}
860 proc r_get_next_job {iJob} {
861 global T
863 if {($iJob%2)} {
864 set orderby "ORDER BY priority ASC"
865 } else {
866 set orderby "ORDER BY priority DESC"
869 set ret [list]
871 r_write_db {
872 set query "
873 SELECT * FROM jobs AS j WHERE state='ready' $orderby LIMIT 1
875 trdb eval $query job {
876 set tm [clock_milliseconds]
877 set T($iJob) $tm
878 set jobid $job(jobid)
880 trdb eval {
881 UPDATE jobs SET starttime=$tm, state='running' WHERE jobid=$jobid
884 set ret [array get job]
888 return $ret
891 # Usage:
893 # add_job OPTION ARG OPTION ARG...
895 # where available OPTIONS are:
897 # -displaytype
898 # -displayname
899 # -build
900 # -dirname
901 # -cmd
902 # -depid
903 # -priority
905 # Returns the jobid value for the new job.
907 proc add_job {args} {
909 set options {
910 -displaytype -displayname -build -dirname
911 -cmd -depid -priority
914 # Set default values of options.
915 set A(-dirname) ""
916 set A(-depid) ""
917 set A(-priority) 0
918 set A(-build) ""
920 array set A $args
922 # Check all required options are present. And that no extras are present.
923 foreach o $options {
924 if {[info exists A($o)]==0} { error "missing required option $o" }
926 foreach o [array names A] {
927 if {[lsearch -exact $options $o]<0} { error "unrecognized option: $o" }
930 set state ""
931 if {$A(-depid)==""} { set state ready }
933 trdb eval {
934 INSERT INTO jobs(
935 displaytype, displayname, build, dirname, cmd, depid, priority,
936 state
937 ) VALUES (
938 $A(-displaytype),
939 $A(-displayname),
940 $A(-build),
941 $A(-dirname),
942 $A(-cmd),
943 $A(-depid),
944 $A(-priority),
945 $state
949 trdb last_insert_rowid
952 # Argument $build is either an empty string, or else a list of length 3
953 # describing the job to build testfixture. In the usual form:
955 # {ID DIRNAME DISPLAYNAME}
957 # e.g
959 # {1 /home/user/sqlite/test/testrunner_bld_xyz All-Debug}
961 proc add_tcl_jobs {build config patternlist {shelldepid ""}} {
962 global TRG
964 set topdir [file dirname $::testdir]
965 set testrunner_tcl [file normalize [info script]]
967 if {$build==""} {
968 set testfixture [info nameofexec]
969 } else {
970 set testfixture [file join [lindex $build 1] $TRG(testfixture)]
972 if {[lindex $build 2]=="Valgrind"} {
973 set setvar "export OMIT_MISUSE=1\n"
974 set testfixture "${setvar}valgrind -v --error-exitcode=1 $testfixture"
977 # The ::testspec array is populated by permutations.test
978 foreach f [dict get $::testspec($config) -files] {
980 if {[llength $patternlist]>0} {
981 set bMatch 0
982 foreach p $patternlist {
983 set p [string trim $p *]
984 if {[string index $p 0]=="^"} {
985 set p [string range $p 1 end]
986 } else {
987 set p "*$p"
989 if {[string index $p end]=="\$"} {
990 set p [string range $p 0 end-1]
991 } else {
992 set p "$p*"
994 if {[string match $p "$config [file tail $f]"]} {
995 set bMatch 1
996 break
999 if {$bMatch==0} continue
1002 if {[file pathtype $f]!="absolute"} { set f [file join $::testdir $f] }
1003 set f [file normalize $f]
1005 set displayname [string map [list $topdir/ {}] $f]
1006 if {$config=="full" || $config=="veryquick"} {
1007 set cmd "$testfixture $f"
1008 } else {
1009 set cmd "$testfixture $testrunner_tcl $config $f"
1010 set displayname "config=$config $displayname"
1012 if {$build!=""} {
1013 set displayname "[lindex $build 2] $displayname"
1016 set lProp [trd_test_script_properties $f]
1017 set priority 0
1018 if {[lsearch $lProp slow]>=0} { set priority 2 }
1019 if {[lsearch $lProp superslow]>=0} { set priority 4 }
1021 set depid [lindex $build 0]
1022 if {$shelldepid!="" && [lsearch $lProp shell]>=0} { set depid $shelldepid }
1024 add_job \
1025 -displaytype tcl \
1026 -displayname $displayname \
1027 -cmd $cmd \
1028 -depid $depid \
1029 -priority $priority
1033 proc add_build_job {buildname target {postcmd ""} {depid ""}} {
1034 global TRG
1036 set dirname "[string tolower [string map {- _} $buildname]]_$target"
1037 set dirname "testrunner_bld_$dirname"
1039 set cmd "$TRG(makecmd) $target"
1040 if {$postcmd!=""} {
1041 append cmd "\n"
1042 append cmd $postcmd
1045 set id [add_job \
1046 -displaytype bld \
1047 -displayname "Build $buildname ($target)" \
1048 -dirname $dirname \
1049 -build $buildname \
1050 -cmd $cmd \
1051 -depid $depid \
1052 -priority 3
1055 list $id [file normalize $dirname] $buildname
1058 proc add_shell_build_job {buildname dirname depid} {
1059 global TRG
1061 if {$TRG(platform)=="win"} {
1062 set path [string map {/ \\} "$dirname/"]
1063 set copycmd "xcopy $TRG(shell) $path"
1064 } else {
1065 set copycmd "cp $TRG(shell) $dirname/"
1068 return [
1069 add_build_job $buildname $TRG(shell) $copycmd $depid
1074 proc add_make_job {bld target} {
1075 global TRG
1077 if {$TRG(platform)=="win"} {
1078 set path [string map {/ \\} [lindex $bld 1]]
1079 set cmd "xcopy /S $path\\* ."
1080 } else {
1081 set cmd "cp -r [lindex $bld 1]/* ."
1083 append cmd "\n$TRG(makecmd) $target"
1085 add_job \
1086 -displaytype make \
1087 -displayname "[lindex $bld 2] make $target" \
1088 -cmd $cmd \
1089 -depid [lindex $bld 0] \
1090 -priority 1
1093 proc add_fuzztest_jobs {buildname} {
1095 foreach {interpreter scripts} [trd_fuzztest_data] {
1096 set subcmd [lrange $interpreter 1 end]
1097 set interpreter [lindex $interpreter 0]
1099 set bld [add_build_job $buildname $interpreter]
1100 foreach {depid dirname displayname} $bld {}
1102 foreach s $scripts {
1104 # Fuzz data files fuzzdata1.db and fuzzdata2.db are larger than
1105 # the others. So ensure that these are run as a higher priority.
1106 set tail [file tail $s]
1107 if {$tail=="fuzzdata1.db" || $tail=="fuzzdata2.db"} {
1108 set priority 5
1109 } else {
1110 set priority 1
1113 add_job \
1114 -displaytype fuzz \
1115 -displayname "$buildname $interpreter $tail" \
1116 -depid $depid \
1117 -cmd "[file join $dirname $interpreter] $subcmd $s" \
1118 -priority $priority
1123 proc add_zipvfs_jobs {} {
1124 global TRG
1125 source [file join $TRG(zipvfs) test zipvfs_testrunner.tcl]
1127 set bld [add_build_job Zipvfs $TRG(testfixture)]
1128 foreach s [zipvfs_testrunner_files] {
1129 set cmd "[file join [lindex $bld 1] $TRG(testfixture)] $s"
1130 add_job \
1131 -displaytype tcl \
1132 -displayname "Zipvfs [file tail $s]" \
1133 -cmd $cmd \
1134 -depid [lindex $bld 0]
1137 set ::env(SQLITE_TEST_DIR) $::testdir
1140 # Used to add jobs for "mdevtest" and "sdevtest".
1142 proc add_devtest_jobs {lBld patternlist} {
1143 global TRG
1145 foreach b $lBld {
1146 set bld [add_build_job $b $TRG(testfixture)]
1147 add_tcl_jobs $bld veryquick $patternlist SHELL
1148 if {$patternlist==""} {
1149 add_fuzztest_jobs $b
1152 if {[trdb one "SELECT EXISTS (SELECT 1 FROM jobs WHERE depid='SHELL')"]} {
1153 set sbld [add_shell_build_job $b [lindex $bld 1] [lindex $bld 0]]
1154 set sbldid [lindex $sbld 0]
1155 trdb eval {
1156 UPDATE jobs SET depid=$sbldid WHERE depid='SHELL'
1163 # Check to ensure that the interpreter is a full-blown "testfixture"
1164 # build and not just a "tclsh". If this is not the case, issue an
1165 # error message and exit.
1167 proc must_be_testfixture {} {
1168 if {[lsearch [info commands] sqlite3_soft_heap_limit]<0} {
1169 puts "Use testfixture, not tclsh, for these arguments."
1170 exit 1
1174 proc add_jobs_from_cmdline {patternlist} {
1175 global TRG
1177 if {$TRG(zipvfs)!=""} {
1178 add_zipvfs_jobs
1179 if {[llength $patternlist]==0} return
1182 if {[llength $patternlist]==0} {
1183 set patternlist [list veryquick]
1186 set first [lindex $patternlist 0]
1187 switch -- $first {
1188 all {
1189 must_be_testfixture
1190 set patternlist [lrange $patternlist 1 end]
1191 set clist [trd_all_configs]
1192 foreach c $clist {
1193 add_tcl_jobs "" $c $patternlist
1197 devtest -
1198 mdevtest {
1199 set config_set {
1200 All-O0
1201 All-Debug
1203 add_devtest_jobs $config_set [lrange $patternlist 1 end]
1206 sdevtest {
1207 set config_set {
1208 All-Sanitize
1209 All-Debug
1211 add_devtest_jobs $config_set [lrange $patternlist 1 end]
1214 release {
1215 set patternlist [lrange $patternlist 1 end]
1216 foreach b [trd_builds $TRG(platform)] {
1217 if {$TRG(config)!="" && ![regexp "\\y$b\\y" $TRG(config)]} continue
1218 if {[regexp "\\y$b\\y" $TRG(omitconfig)]} continue
1219 set bld [add_build_job $b $TRG(testfixture)]
1220 foreach c [trd_configs $TRG(platform) $b] {
1221 add_tcl_jobs $bld $c $patternlist SHELL
1224 if {$patternlist==""} {
1225 foreach e [trd_extras $TRG(platform) $b] {
1226 if {$e=="fuzztest"} {
1227 add_fuzztest_jobs $b
1228 } else {
1229 add_make_job $bld $e
1234 if {[trdb one "SELECT EXISTS(SELECT 1
1235 FROM jobs WHERE depid='SHELL')"]} {
1236 set sbld [add_shell_build_job $b [lindex $bld 1] [lindex $bld 0]]
1237 set sbldid [lindex $sbld 0]
1238 trdb eval {
1239 UPDATE jobs SET depid=$sbldid WHERE depid='SHELL'
1245 list {
1246 set allperm [array names ::testspec]
1247 lappend allperm all mdevtest sdevtest release list
1248 puts "Allowed values for the PERMUTATION argument: [lsort $allperm]"
1249 exit 0
1252 default {
1253 must_be_testfixture
1254 if {[info exists ::testspec($first)]} {
1255 add_tcl_jobs "" $first [lrange $patternlist 1 end]
1256 } else {
1257 add_tcl_jobs "" full $patternlist
1263 proc make_new_testset {} {
1264 global TRG
1266 trdb eval {PRAGMA journal_mode=WAL;}
1267 r_write_db {
1268 trdb eval $TRG(schema)
1269 set nJob $TRG(nJob)
1270 set cmdline $TRG(cmdline)
1271 set tm [clock_milliseconds]
1272 trdb eval { REPLACE INTO config VALUES('njob', $nJob ); }
1273 trdb eval { REPLACE INTO config VALUES('cmdline', $cmdline ); }
1274 trdb eval { REPLACE INTO config VALUES('start', $tm ); }
1276 add_jobs_from_cmdline $TRG(patternlist)
1281 proc mark_job_as_finished {jobid output state endtm} {
1282 set ntest 1
1283 set nerr 0
1284 if {$endtm>0} {
1285 if {[regexp {\y(\d+) errors out of (\d+) tests} $output all a b]} {
1286 set nerr $a
1287 set ntest $b
1289 regexp {\y\d+ errors out of \d+ tests (on [^\n]+-bit \S+-endian)} \
1290 $output all pltfm
1291 regexp {\ySQLite \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d [0-9a-fA-F]+} \
1292 $output svers
1294 r_write_db {
1295 if {$state=="failed"} {
1296 set childstate omit
1297 if {$nerr<=0} {set nerr 1}
1298 } else {
1299 set childstate ready
1301 trdb eval {
1302 UPDATE jobs
1303 SET output=$output, state=$state, endtime=$endtm,
1304 ntest=$ntest, nerr=$nerr, svers=$svers, pltfm=$pltfm
1305 WHERE jobid=$jobid;
1306 UPDATE jobs SET state=$childstate WHERE depid=$jobid;
1311 proc script_input_ready {fd iJob jobid} {
1312 global TRG
1313 global O
1314 global T
1316 if {[eof $fd]} {
1317 trdb eval { SELECT * FROM jobs WHERE jobid=$jobid } job {}
1319 # If this job specified a directory name, then delete the run.sh/run.bat
1320 # file from it before continuing. This is because the contents of this
1321 # directory might be copied by some other job, and we don't want to copy
1322 # the run.sh file in this case.
1323 if {$job(dirname)!=""} {
1324 file delete -force [file join $job(dirname) $TRG(run)]
1327 set ::done 1
1328 fconfigure $fd -blocking 1
1329 set state "done"
1330 set rc [catch { close $fd } msg]
1331 if {$rc} {
1332 if {[info exists TRG(reportlength)]} {
1333 puts -nonewline "[string repeat " " $TRG(reportlength)]\r"
1335 puts "FAILED: $job(displayname) ($iJob)"
1336 set state "failed"
1337 if {$TRG(stopOnError)} {
1338 puts "OUTPUT: $O($iJob)"
1339 exit 1
1341 if {$TRG(stopOnCore) && [string first {core dumped} $O($iJob)]>0} {
1342 puts "OUTPUT: $O($iJob)"
1343 exit 1
1347 set tm [clock_milliseconds]
1348 set jobtm [expr {$tm - $job(starttime)}]
1350 puts $TRG(log) "### $job(displayname) ${jobtm}ms ($state)"
1351 puts $TRG(log) [string trim $O($iJob)]
1353 mark_job_as_finished $jobid $O($iJob) $state $tm
1355 dirs_freeDir $iJob
1356 launch_some_jobs
1357 incr ::wakeup
1358 } else {
1359 set rc [catch { gets $fd line } res]
1360 if {$rc} {
1361 puts "ERROR $res"
1363 if {$res>=0} {
1364 append O($iJob) "$line\n"
1370 proc dirname {ii} {
1371 return "testdir$ii"
1374 proc launch_another_job {iJob} {
1375 global TRG
1376 global O
1377 global T
1379 set testfixture [info nameofexec]
1380 set script $TRG(info_script)
1382 set O($iJob) ""
1384 set jobdict [r_get_next_job $iJob]
1385 if {$jobdict==""} { return 0 }
1386 array set job $jobdict
1388 set dir $job(dirname)
1389 if {$dir==""} { set dir [dirname $iJob] }
1390 create_or_clear_dir $dir
1392 if {$job(build)!=""} {
1393 set srcdir [file dirname $::testdir]
1394 if {$job(build)=="Zipvfs"} {
1395 set script [zipvfs_testrunner_script]
1396 } else {
1397 set bWin [expr {$TRG(platform)=="win"}]
1398 set script [trd_buildscript $job(build) $srcdir $bWin]
1400 set fd [open [file join $dir $TRG(make)] w]
1401 puts $fd $script
1402 close $fd
1405 # Add a batch/shell file command to set the directory used for temp
1406 # files to the test's working directory. Otherwise, tests that use
1407 # large numbers of temp files (e.g. zipvfs), might generate temp
1408 # filename collisions.
1409 if {$TRG(platform)=="win"} {
1410 set set_tmp_dir "SET SQLITE_TMPDIR=[file normalize $dir]"
1411 } else {
1412 set set_tmp_dir "export SQLITE_TMPDIR=\"[file normalize $dir]\""
1415 if { $TRG(dryrun) } {
1417 mark_job_as_finished $job(jobid) "" done 0
1418 dirs_freeDir $iJob
1419 if {$job(build)!=""} {
1420 puts $TRG(log) "(cd $dir ; $job(cmd) )"
1421 } else {
1422 puts $TRG(log) "$job(cmd)"
1425 } else {
1426 set pwd [pwd]
1427 cd $dir
1428 set fd [open $TRG(run) w]
1429 puts $fd $set_tmp_dir
1430 puts $fd $job(cmd)
1431 close $fd
1432 set fd [open "|$TRG(runcmd) 2>@1" r]
1433 cd $pwd
1435 fconfigure $fd -blocking false -translation binary
1436 fileevent $fd readable [list script_input_ready $fd $iJob $job(jobid)]
1439 return 1
1442 # Show the testing progress report
1444 proc progress_report {} {
1445 global TRG
1447 if {$TRG(fullstatus)} {
1448 if {$::tcl_platform(platform)=="windows"} {
1449 exec [info nameofexe] $::argv0 status --cls
1450 } else {
1451 show_status trdb 1
1453 } else {
1454 set tm [expr [clock_milliseconds] - $TRG(starttime)]
1455 set tm [format "%d" [expr int($tm/1000.0 + 0.5)]]
1457 r_write_db {
1458 trdb eval {
1459 SELECT displaytype, state, count(*) AS cnt
1460 FROM jobs
1461 GROUP BY 1, 2
1463 set v($state,$displaytype) $cnt
1464 incr t($displaytype) $cnt
1468 set text ""
1469 foreach j [lsort [array names t]] {
1470 foreach k {done failed running} { incr v($k,$j) 0 }
1471 set fin [expr $v(done,$j) + $v(failed,$j)]
1472 lappend text "${j}($fin/$t($j))"
1473 if {$v(failed,$j)>0} {
1474 lappend text "f$v(failed,$j)"
1476 if {$v(running,$j)>0} {
1477 lappend text "r$v(running,$j)"
1481 if {[info exists TRG(reportlength)]} {
1482 puts -nonewline "[string repeat " " $TRG(reportlength)]\r"
1484 set report "${tm} [join $text { }]"
1485 set TRG(reportlength) [string length $report]
1486 if {[string length $report]<100} {
1487 puts -nonewline "$report\r"
1488 flush stdout
1489 } else {
1490 puts $report
1493 after $TRG(reporttime) progress_report
1496 proc launch_some_jobs {} {
1497 global TRG
1498 set nJob [trdb one { SELECT value FROM config WHERE name='njob' }]
1500 while {[dirs_nHelper]<$nJob} {
1501 set iDir [dirs_allocDir]
1502 if {0==[launch_another_job $iDir]} {
1503 dirs_freeDir $iDir
1504 break;
1509 proc run_testset {} {
1510 global TRG
1511 set ii 0
1513 set TRG(starttime) [clock_milliseconds]
1514 set TRG(log) [open $TRG(logname) w]
1516 launch_some_jobs
1518 if {$TRG(fullstatus)} {puts "\033\[2J"}
1519 progress_report
1520 while {[dirs_nHelper]>0} {
1521 after 500 {incr ::wakeup}
1522 vwait ::wakeup
1524 close $TRG(log)
1525 progress_report
1527 r_write_db {
1528 set tm [clock_milliseconds]
1529 trdb eval { REPLACE INTO config VALUES('end', $tm ); }
1530 set nErr [trdb one {SELECT count(*) FROM jobs WHERE state='failed'}]
1531 if {$nErr>0} {
1532 puts "$nErr failures:"
1533 trdb eval {
1534 SELECT displayname FROM jobs WHERE state='failed'
1536 puts "FAILED: $displayname"
1539 set nOmit [trdb one {SELECT count(*) FROM jobs WHERE state='omit'}]
1540 if {$nOmit>0} {
1541 puts "$nOmit jobs skipped due to prior failures"
1545 puts "\nTest database is $TRG(dbname)"
1546 puts "Test log is $TRG(logname)"
1547 trdb eval {
1548 SELECT sum(ntest) AS totaltest,
1549 sum(nerr) AS totalerr
1550 FROM jobs
1551 } break
1552 trdb eval {
1553 SELECT max(endtime)-min(starttime) AS totaltime
1554 FROM jobs WHERE endtime>0
1555 } break;
1556 set et [elapsetime $totaltime]
1557 set pltfm {}
1558 trdb eval {
1559 SELECT pltfm, count(*) FROM jobs WHERE pltfm IS NOT NULL
1560 ORDER BY 2 DESC LIMIT 1
1561 } {puts $pltfm}
1562 puts "$totalerr errors out of $totaltest tests in $et $pltfm"
1563 trdb eval {
1564 SELECT DISTINCT substr(svers,1,80) FROM jobs WHERE svers IS NOT NULL
1565 } {puts $svers}
1569 # Handle the --buildonly option, if it was specified.
1571 proc handle_buildonly {} {
1572 global TRG
1573 if {$TRG(buildonly)} {
1574 r_write_db {
1575 trdb eval { DELETE FROM jobs WHERE displaytype!='bld' }
1580 # Handle the --explain option. Provide a human-readable
1581 # explanation of all the tests that are in the trdb database jobs
1582 # table.
1584 proc explain_layer {indent depid} {
1585 global TRG
1586 if {$TRG(buildonly)} {
1587 set showtests 0
1588 } else {
1589 set showtests 1
1591 trdb eval {SELECT jobid, displayname, displaytype, dirname
1592 FROM jobs WHERE depid=$depid ORDER BY displayname} {
1593 if {$displaytype=="bld"} {
1594 puts "${indent}$displayname in $dirname"
1595 explain_layer "${indent} " $jobid
1596 } elseif {$showtests} {
1597 set tail [lindex $displayname end]
1598 set e1 [lindex $displayname 1]
1599 if {[string match config=* $e1]} {
1600 set cfg [string range $e1 7 end]
1601 puts "${indent}($cfg) $tail"
1602 } else {
1603 puts "${indent}$tail"
1608 proc explain_tests {} {
1609 explain_layer "" ""
1612 sqlite3 trdb $TRG(dbname)
1613 trdb timeout $TRG(timeout)
1614 set tm [lindex [time { make_new_testset }] 0]
1615 if {$TRG(explain)} {
1616 explain_tests
1617 } else {
1618 if {$TRG(nJob)>1} {
1619 puts "splitting work across $TRG(nJob) cores"
1621 puts "built testset in [expr $tm/1000]ms.."
1622 handle_buildonly
1623 run_testset
1625 trdb close