]> git.ipfire.org Git - thirdparty/sqlite.git/blob - test/testrunner.tcl
05f7d4cc98dfdad4ba2558e4ffd57147534a3898
[thirdparty/sqlite.git] / test / testrunner.tcl
1
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
10
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.
15 #
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
28 }
29 }
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
34 }
35 }
36 find_interpreter
37
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
44 }
45 }
46
47 #-------------------------------------------------------------------------
48 # Usage:
49 #
50 proc usage {} {
51 set a0 [file tail $::argv0]
52
53 puts stderr [string trim [subst -nocommands {
54 Usage:
55 $a0 ?SWITCHES? ?PERMUTATION? ?PATTERNS?
56 $a0 PERMUTATION FILE
57 $a0 help
58 $a0 njob ?NJOB?
59 $a0 script ?-msvc? CONFIG
60 $a0 status
61
62 where SWITCHES are:
63 --buildonly Build test exes but do not run tests
64 --config CONFIGS Only use configs on comma-separate list CONFIGS
65 --dryrun Write what would have happened to testrunner.log
66 --explain Write summary to stdout
67 --jobs NUM Run tests using NUM separate processes
68 --omit CONFIGS Omit configs on comma-separated list CONFIGS
69 --stop-on-coredump Stop running if any test segfaults
70 --stop-on-error Stop running after any reported error
71 --zipvfs ZIPVFSDIR ZIPVFS source directory
72
73 Special values for PERMUTATION that work with plain tclsh:
74
75 list - show all allowed PERMUTATION arguments.
76 mdevtest - tests recommended prior to normal development check-ins.
77 release - full release test with various builds.
78 sdevtest - like mdevtest but using ASAN and UBSAN.
79
80 Other PERMUTATION arguments must be run using testfixture, not tclsh:
81
82 all - all tcl test scripts, plus a subset of test scripts rerun
83 with various permutations.
84 full - all tcl test scripts.
85 veryquick - a fast subset of the tcl test scripts. This is the default.
86
87 If no PATTERN arguments are present, all tests specified by the PERMUTATION
88 are run. Otherwise, each pattern is interpreted as a glob pattern. Only
89 those tcl tests for which the final component of the filename matches at
90 least one specified pattern are run.
91
92 If no PATTERN arguments are present, then various fuzztest, threadtest
93 and other tests are run as part of the "release" permutation. These are
94 omitted if any PATTERN arguments are specified on the command line.
95
96 If a PERMUTATION is specified and is followed by the path to a Tcl script
97 instead of a list of patterns, then that single Tcl test script is run
98 with the specified permutation.
99
100 The "status" and "njob" commands are designed to be run from the same
101 directory as a running testrunner.tcl script that is running tests. The
102 "status" command prints a report describing the current state and progress
103 of the tests. The "njob" command may be used to query or modify the number
104 of sub-processes the test script uses to run tests.
105
106 The "script" command outputs the script used to build a configuration.
107 Add the "-msvc" option for a Windows-compatible script. For a list of
108 available configurations enter "$a0 script help".
109
110 Full documentation here: https://sqlite.org/src/doc/trunk/doc/testrunner.md
111 }]]
112
113 exit 1
114 }
115 #-------------------------------------------------------------------------
116
117 #-------------------------------------------------------------------------
118 # Try to estimate a the number of processes to use.
119 #
120 # Command [guess_number_of_cores] attempts to glean the number of logical
121 # cores. Command [default_njob] returns the default value for the --jobs
122 # switch.
123 #
124 proc guess_number_of_cores {} {
125 if {[catch {number_of_cores} ret]} {
126 set ret 4
127
128 if {$::tcl_platform(platform)=="windows"} {
129 catch { set ret $::env(NUMBER_OF_PROCESSORS) }
130 } else {
131 if {$::tcl_platform(os)=="Darwin"} {
132 set cmd "sysctl -n hw.logicalcpu"
133 } else {
134 set cmd "nproc"
135 }
136 catch {
137 set fd [open "|$cmd" r]
138 set ret [gets $fd]
139 close $fd
140 set ret [expr $ret]
141 }
142 }
143 }
144 return $ret
145 }
146
147 proc default_njob {} {
148 global env
149 if {[info exists env(NJOB)] && $env(NJOB)>=1} {
150 return $env(NJOB)
151 }
152 set nCore [guess_number_of_cores]
153 if {$nCore<=2} {
154 set nHelper 1
155 } else {
156 set nHelper [expr int($nCore*0.5)]
157 }
158 return $nHelper
159 }
160 #-------------------------------------------------------------------------
161
162 #-------------------------------------------------------------------------
163 # Setup various default values in the global TRG() array.
164 #
165 set TRG(dbname) [file normalize testrunner.db]
166 set TRG(logname) [file normalize testrunner.log]
167 set TRG(build.logname) [file normalize testrunner_build.log]
168 set TRG(info_script) [file normalize [info script]]
169 set TRG(timeout) 10000 ;# Default busy-timeout for testrunner.db
170 set TRG(nJob) [default_njob] ;# Default number of helper processes
171 set TRG(patternlist) [list]
172 set TRG(cmdline) $argv
173 set TRG(reporttime) 2000
174 set TRG(fuzztest) 0 ;# is the fuzztest option present.
175 set TRG(zipvfs) "" ;# -zipvfs option, if any
176 set TRG(buildonly) 0 ;# True if --buildonly option
177 set TRG(config) {} ;# Only build the named configurations
178 set TRG(omitconfig) {} ;# Do not build these configurations
179 set TRG(dryrun) 0 ;# True if --dryrun option
180 set TRG(explain) 0 ;# True for the --explain option
181 set TRG(stopOnError) 0 ;# Stop running at first failure
182 set TRG(stopOnCore) 0 ;# Stop on a core-dump
183
184 switch -nocase -glob -- $tcl_platform(os) {
185 *darwin* {
186 set TRG(platform) osx
187 set TRG(make) make.sh
188 set TRG(makecmd) "bash make.sh"
189 set TRG(testfixture) testfixture
190 set TRG(shell) sqlite3
191 set TRG(run) run.sh
192 set TRG(runcmd) "bash run.sh"
193 }
194 *linux* {
195 set TRG(platform) linux
196 set TRG(make) make.sh
197 set TRG(makecmd) "bash make.sh"
198 set TRG(testfixture) testfixture
199 set TRG(shell) sqlite3
200 set TRG(run) run.sh
201 set TRG(runcmd) "bash run.sh"
202 }
203 *win* {
204 set TRG(platform) win
205 set TRG(make) make.bat
206 set TRG(makecmd) "call make.bat"
207 set TRG(testfixture) testfixture.exe
208 set TRG(shell) sqlite3.exe
209 set TRG(run) run.bat
210 set TRG(runcmd) "run.bat"
211 }
212 default {
213 error "cannot determine platform!"
214 }
215 }
216 #-------------------------------------------------------------------------
217
218 #-------------------------------------------------------------------------
219 # The database schema used by the testrunner.db database.
220 #
221 set TRG(schema) {
222 DROP TABLE IF EXISTS jobs;
223 DROP TABLE IF EXISTS config;
224
225 /*
226 ** This table contains one row for each job that testrunner.tcl must run
227 ** before the entire test run is finished.
228 **
229 ** jobid:
230 ** Unique identifier for each job. Must be a +ve non-zero number.
231 **
232 ** displaytype:
233 ** 3 or 4 letter mnemonic for the class of tests this belongs to e.g.
234 ** "fuzz", "tcl", "make" etc.
235 **
236 ** displayname:
237 ** Name/description of job. For display purposes.
238 **
239 ** build:
240 ** If the job requires a make.bat/make.sh make wrapper (i.e. to build
241 ** something), the name of the build configuration it uses. See
242 ** testrunner_data.tcl for a list of build configs. e.g. "Win32-MemDebug".
243 **
244 ** dirname:
245 ** If the job should use a well-known directory name for its
246 ** sub-directory instead of an anonymous "testdir[1234...]" sub-dir
247 ** that is deleted after the job is finished.
248 **
249 ** cmd:
250 ** Bash or batch script to run the job.
251 **
252 ** depid:
253 ** The jobid value of a job that this job depends on. This job may not
254 ** be run before its depid job has finished successfully.
255 **
256 ** priority:
257 ** Higher values run first. Sometimes.
258 */
259 CREATE TABLE jobs(
260 /* Fields populated when db is initialized */
261 jobid INTEGER PRIMARY KEY, -- id to identify job
262 displaytype TEXT NOT NULL, -- Type of test (for one line report)
263 displayname TEXT NOT NULL, -- Human readable job name
264 build TEXT NOT NULL DEFAULT '', -- make.sh/make.bat file request, if any
265 dirname TEXT NOT NULL DEFAULT '', -- directory name, if required
266 cmd TEXT NOT NULL, -- shell command to run
267 depid INTEGER, -- identifier of dependency (or '')
268 priority INTEGER NOT NULL, -- higher priority jobs may run earlier
269
270 /* Fields updated as jobs run */
271 starttime INTEGER,
272 endtime INTEGER,
273 state TEXT CHECK( state IN ('', 'ready', 'running', 'done', 'failed') ),
274 output TEXT
275 );
276
277 CREATE TABLE config(
278 name TEXT COLLATE nocase PRIMARY KEY,
279 value
280 ) WITHOUT ROWID;
281
282 CREATE INDEX i1 ON jobs(state, priority);
283 CREATE INDEX i2 ON jobs(depid);
284 }
285 #-------------------------------------------------------------------------
286
287 #--------------------------------------------------------------------------
288 # Check if this script is being invoked to run a single file. If so,
289 # run it.
290 #
291 if {[llength $argv]==2
292 && ([lindex $argv 0]=="" || [info exists ::testspec([lindex $argv 0])])
293 && [file exists [lindex $argv 1]]
294 } {
295 set permutation [lindex $argv 0]
296 set script [file normalize [lindex $argv 1]]
297 set ::argv [list]
298
299 set testdir [file dirname $argv0]
300 source $::testdir/tester.tcl
301
302 if {$permutation=="full"} {
303
304 unset -nocomplain ::G(isquick)
305 reset_db
306
307 } elseif {$permutation!="default" && $permutation!=""} {
308
309 if {[info exists ::testspec($permutation)]==0} {
310 error "no such permutation: $permutation"
311 }
312
313 array set O $::testspec($permutation)
314 set ::G(perm:name) $permutation
315 set ::G(perm:prefix) $O(-prefix)
316 set ::G(isquick) 1
317 set ::G(perm:dbconfig) $O(-dbconfig)
318 set ::G(perm:presql) $O(-presql)
319
320 rename finish_test helper_finish_test
321 proc finish_test {} "
322 uplevel {
323 $O(-shutdown)
324 }
325 helper_finish_test
326 "
327
328 eval $O(-initialize)
329 }
330
331 reset_db
332 source $script
333 exit
334 }
335 #--------------------------------------------------------------------------
336
337 #--------------------------------------------------------------------------
338 # Check if this is the "njob" command:
339 #
340 if {([llength $argv]==2 || [llength $argv]==1)
341 && [string compare -nocase njob [lindex $argv 0]]==0
342 } {
343 sqlite3 mydb $TRG(dbname)
344 if {[llength $argv]==2} {
345 set param [lindex $argv 1]
346 if {[string is integer $param]==0 || $param<1 || $param>128} {
347 puts stderr "parameter must be an integer between 1 and 128"
348 exit 1
349 }
350
351 mydb eval { REPLACE INTO config VALUES('njob', $param); }
352 }
353 set res [mydb one { SELECT value FROM config WHERE name='njob' }]
354 mydb close
355 puts "$res"
356 exit
357 }
358 #--------------------------------------------------------------------------
359
360 #--------------------------------------------------------------------------
361 # Check if this is the "help" command:
362 #
363 if {[string compare -nocase help [lindex $argv 0]]==0} {
364 usage
365 }
366 #--------------------------------------------------------------------------
367
368 #--------------------------------------------------------------------------
369 # Check if this is the "script" command:
370 #
371 if {[string compare -nocase script [lindex $argv 0]]==0} {
372 if {[llength $argv]!=2 && !([llength $argv]==3&&[lindex $argv 1]=="-msvc")} {
373 usage
374 }
375
376 set bMsvc [expr ([llength $argv]==3)]
377 set config [lindex $argv [expr [llength $argv]-1]]
378
379 puts [trd_buildscript $config [file dirname $testdir] $bMsvc]
380 exit
381 }
382
383
384 #--------------------------------------------------------------------------
385 # Check if this is the "status" command:
386 #
387 if {[llength $argv]==1
388 && [string compare -nocase status [lindex $argv 0]]==0
389 } {
390
391 proc display_job {jobdict {tm ""}} {
392 array set job $jobdict
393
394 set dfname [format %-60s $job(displayname)]
395
396 set dtm ""
397 if {$tm!=""} { set dtm "\[[expr {$tm-$job(starttime)}]ms\]" }
398 puts " $dfname $dtm"
399 }
400
401 sqlite3 mydb $TRG(dbname)
402 mydb timeout 1000
403 mydb eval BEGIN
404
405 set cmdline [mydb one { SELECT value FROM config WHERE name='cmdline' }]
406 set nJob [mydb one { SELECT value FROM config WHERE name='njob' }]
407
408 set now [clock_milliseconds]
409 set tm [mydb one {
410 SELECT
411 COALESCE((SELECT value FROM config WHERE name='end'), $now) -
412 (SELECT value FROM config WHERE name='start')
413 }]
414
415 set total 0
416 foreach s {"" ready running done failed} { set S($s) 0 }
417 mydb eval {
418 SELECT state, count(*) AS cnt FROM jobs GROUP BY 1
419 } {
420 incr S($state) $cnt
421 incr total $cnt
422 }
423 set fin [expr $S(done)+$S(failed)]
424 if {$cmdline!=""} {set cmdline " $cmdline"}
425
426 set f ""
427 if {$S(failed)>0} {
428 set f "$S(failed) FAILED, "
429 }
430 puts "Command line: \[testrunner.tcl$cmdline\]"
431 puts "Jobs: $nJob"
432 puts "Summary: ${tm}ms, ($fin/$total) finished, ${f}$S(running) running"
433
434 set srcdir [file dirname [file dirname $TRG(info_script)]]
435 if {$S(running)>0} {
436 puts "Running: "
437 mydb eval {
438 SELECT * FROM jobs WHERE state='running' ORDER BY starttime
439 } job {
440 display_job [array get job] $now
441 }
442 }
443 if {$S(failed)>0} {
444 puts "Failures: "
445 mydb eval {
446 SELECT * FROM jobs WHERE state='failed' ORDER BY starttime
447 } job {
448 display_job [array get job]
449 }
450 }
451
452 mydb close
453 exit
454 }
455
456 #-------------------------------------------------------------------------
457 # Parse the command line.
458 #
459 for {set ii 0} {$ii < [llength $argv]} {incr ii} {
460 set isLast [expr $ii==([llength $argv]-1)]
461 set a [lindex $argv $ii]
462 set n [string length $a]
463
464 if {[string range $a 0 0]=="-"} {
465 if {($n>2 && [string match "$a*" --jobs]) || $a=="-j"} {
466 incr ii
467 set TRG(nJob) [lindex $argv $ii]
468 if {$isLast} { usage }
469 } elseif {($n>2 && [string match "$a*" --zipvfs]) || $a=="-z"} {
470 incr ii
471 set TRG(zipvfs) [file normalize [lindex $argv $ii]]
472 if {$isLast} { usage }
473 } elseif {($n>2 && [string match "$a*" --buildonly]) || $a=="-b"} {
474 set TRG(buildonly) 1
475 } elseif {($n>2 && [string match "$a*" --config]) || $a=="-c"} {
476 incr ii
477 set TRG(config) [lindex $argv $ii]
478 } elseif {($n>2 && [string match "$a*" --dryrun]) || $a=="-d"} {
479 set TRG(dryrun) 1
480 } elseif {($n>2 && [string match "$a*" --explain]) || $a=="-e"} {
481 set TRG(explain) 1
482 } elseif {($n>2 && [string match "$a*" --omit]) || $a=="-c"} {
483 incr ii
484 set TRG(omitconfig) [lindex $argv $ii]
485 } elseif {[string match "$a*" --stop-on-error]} {
486 set TRG(stopOnError) 1
487 } elseif {[string match "$a*" --stop-on-coredump]} {
488 set TRG(stopOnCore) 1
489 } else {
490 usage
491 }
492 } else {
493 lappend TRG(patternlist) [string map {% *} $a]
494 }
495 }
496 set argv [list]
497
498 # This script runs individual tests - tcl scripts or [make xyz] commands -
499 # in directories named "testdir$N", where $N is an integer. This variable
500 # contains a list of integers indicating the directories in use.
501 #
502 # This variable is accessed only via the following commands:
503 #
504 # dirs_nHelper
505 # Return the number of entries currently in the list.
506 #
507 # dirs_freeDir IDIR
508 # Remove value IDIR from the list. It is an error if it is not present.
509 #
510 # dirs_allocDir
511 # Select a value that is not already in the list. Add it to the list
512 # and return it.
513 #
514 set TRG(dirs_in_use) [list]
515
516 proc dirs_nHelper {} {
517 global TRG
518 llength $TRG(dirs_in_use)
519 }
520 proc dirs_freeDir {iDir} {
521 global TRG
522 set out [list]
523 foreach d $TRG(dirs_in_use) {
524 if {$iDir!=$d} { lappend out $d }
525 }
526 if {[llength $out]!=[llength $TRG(dirs_in_use)]-1} {
527 error "dirs_freeDir could not find $iDir"
528 }
529 set TRG(dirs_in_use) $out
530 }
531 proc dirs_allocDir {} {
532 global TRG
533 array set inuse [list]
534 foreach d $TRG(dirs_in_use) {
535 set inuse($d) 1
536 }
537 for {set iRet 0} {[info exists inuse($iRet)]} {incr iRet} { }
538 lappend TRG(dirs_in_use) $iRet
539 return $iRet
540 }
541
542 # Check that directory $dir exists. If it does not, create it. If
543 # it does, delete its contents.
544 #
545 proc create_or_clear_dir {dir} {
546 set dir [file normalize $dir]
547 catch { file mkdir $dir }
548 foreach f [glob -nocomplain [file join $dir *]] {
549 catch { file delete -force $f }
550 }
551 }
552
553 proc build_to_dirname {bname} {
554 set fold [string tolower [string map {- _} $bname]]
555 return "testrunner_build_$fold"
556 }
557
558 #-------------------------------------------------------------------------
559
560 proc r_write_db {tcl} {
561 trdb eval { BEGIN EXCLUSIVE }
562 uplevel $tcl
563 trdb eval { COMMIT }
564 }
565
566 # Obtain a new job to be run by worker $iJob (an integer). A job is
567 # returned as a three element list:
568 #
569 # {$build $config $file}
570 #
571 proc r_get_next_job {iJob} {
572 global T
573
574 if {($iJob%2)} {
575 set orderby "ORDER BY priority ASC"
576 } else {
577 set orderby "ORDER BY priority DESC"
578 }
579
580 set ret [list]
581
582 r_write_db {
583 set query "
584 SELECT * FROM jobs AS j WHERE state='ready' $orderby LIMIT 1
585 "
586 trdb eval $query job {
587 set tm [clock_milliseconds]
588 set T($iJob) $tm
589 set jobid $job(jobid)
590
591 trdb eval {
592 UPDATE jobs SET starttime=$tm, state='running' WHERE jobid=$jobid
593 }
594
595 set ret [array get job]
596 }
597 }
598
599 return $ret
600 }
601
602 #rename r_get_next_job r_get_next_job_r
603 #proc r_get_next_job {iJob} {
604 #puts [time { set res [r_get_next_job_r $iJob] }]
605 #set res
606 #}
607
608 # Usage:
609 #
610 # add_job OPTION ARG OPTION ARG...
611 #
612 # where available OPTIONS are:
613 #
614 # -displaytype
615 # -displayname
616 # -build
617 # -dirname
618 # -cmd
619 # -depid
620 # -priority
621 #
622 # Returns the jobid value for the new job.
623 #
624 proc add_job {args} {
625
626 set options {
627 -displaytype -displayname -build -dirname
628 -cmd -depid -priority
629 }
630
631 # Set default values of options.
632 set A(-dirname) ""
633 set A(-depid) ""
634 set A(-priority) 0
635 set A(-build) ""
636
637 array set A $args
638
639 # Check all required options are present. And that no extras are present.
640 foreach o $options {
641 if {[info exists A($o)]==0} { error "missing required option $o" }
642 }
643 foreach o [array names A] {
644 if {[lsearch -exact $options $o]<0} { error "unrecognized option: $o" }
645 }
646
647 set state ""
648 if {$A(-depid)==""} { set state ready }
649
650 trdb eval {
651 INSERT INTO jobs(
652 displaytype, displayname, build, dirname, cmd, depid, priority,
653 state
654 ) VALUES (
655 $A(-displaytype),
656 $A(-displayname),
657 $A(-build),
658 $A(-dirname),
659 $A(-cmd),
660 $A(-depid),
661 $A(-priority),
662 $state
663 )
664 }
665
666 trdb last_insert_rowid
667 }
668
669 # Argument $build is either an empty string, or else a list of length 3
670 # describing the job to build testfixture. In the usual form:
671 #
672 # {ID DIRNAME DISPLAYNAME}
673 #
674 # e.g
675 #
676 # {1 /home/user/sqlite/test/testrunner_bld_xyz All-Debug}
677 #
678 proc add_tcl_jobs {build config patternlist {shelldepid ""}} {
679 global TRG
680
681 set topdir [file dirname $::testdir]
682 set testrunner_tcl [file normalize [info script]]
683
684 if {$build==""} {
685 set testfixture [info nameofexec]
686 } else {
687 set testfixture [file join [lindex $build 1] $TRG(testfixture)]
688 }
689 if {[lindex $build 2]=="Valgrind"} {
690 set setvar "export OMIT_MISUSE=1\n"
691 set testfixture "${setvar}valgrind -v --error-exitcode=1 $testfixture"
692 }
693
694 # The ::testspec array is populated by permutations.test
695 foreach f [dict get $::testspec($config) -files] {
696
697 if {[llength $patternlist]>0} {
698 set bMatch 0
699 foreach p $patternlist {
700 if {[string match $p [file tail $f]]} {
701 set bMatch 1
702 break
703 }
704 }
705 if {$bMatch==0} continue
706 }
707
708 if {[file pathtype $f]!="absolute"} { set f [file join $::testdir $f] }
709 set f [file normalize $f]
710
711 set displayname [string map [list $topdir/ {}] $f]
712 if {$config=="full" || $config=="veryquick"} {
713 set cmd "$testfixture $f"
714 } else {
715 set cmd "$testfixture $testrunner_tcl $config $f"
716 set displayname "config=$config $displayname"
717 }
718 if {$build!=""} {
719 set displayname "[lindex $build 2] $displayname"
720 }
721
722 set lProp [trd_test_script_properties $f]
723 set priority 0
724 if {[lsearch $lProp slow]>=0} { set priority 2 }
725 if {[lsearch $lProp superslow]>=0} { set priority 4 }
726
727 set depid [lindex $build 0]
728 if {$shelldepid!="" && [lsearch $lProp shell]>=0} { set depid $shelldepid }
729
730 add_job \
731 -displaytype tcl \
732 -displayname $displayname \
733 -cmd $cmd \
734 -depid $depid \
735 -priority $priority
736 }
737 }
738
739 proc add_build_job {buildname target {postcmd ""} {depid ""}} {
740 global TRG
741
742 set dirname "[string tolower [string map {- _} $buildname]]_$target"
743 set dirname "testrunner_bld_$dirname"
744
745 set cmd "$TRG(makecmd) $target"
746 if {$postcmd!=""} {
747 append cmd "\n"
748 append cmd $postcmd
749 }
750
751 set id [add_job \
752 -displaytype bld \
753 -displayname "Build $buildname ($target)" \
754 -dirname $dirname \
755 -build $buildname \
756 -cmd $cmd \
757 -depid $depid \
758 -priority 3
759 ]
760
761 list $id [file normalize $dirname] $buildname
762 }
763
764 proc add_shell_build_job {buildname dirname depid} {
765 global TRG
766
767 if {$TRG(platform)=="win"} {
768 set path [string map {/ \\} "$dirname/"]
769 set copycmd "xcopy $TRG(shell) $path"
770 } else {
771 set copycmd "cp $TRG(shell) $dirname/"
772 }
773
774 return [
775 add_build_job $buildname $TRG(shell) $copycmd $depid
776 ]
777 }
778
779
780 proc add_make_job {bld target} {
781 global TRG
782
783 if {$TRG(platform)=="win"} {
784 set path [string map {/ \\} [lindex $bld 1]]
785 set cmd "xcopy /S $path\\* ."
786 } else {
787 set cmd "cp -r [lindex $bld 1]/* ."
788 }
789 append cmd "\n$TRG(makecmd) $target"
790
791 add_job \
792 -displaytype make \
793 -displayname "[lindex $bld 2] make $target" \
794 -cmd $cmd \
795 -depid [lindex $bld 0] \
796 -priority 1
797 }
798
799 proc add_fuzztest_jobs {buildname} {
800
801 foreach {interpreter scripts} [trd_fuzztest_data] {
802 set subcmd [lrange $interpreter 1 end]
803 set interpreter [lindex $interpreter 0]
804
805 set bld [add_build_job $buildname $interpreter]
806 foreach {depid dirname displayname} $bld {}
807
808 foreach s $scripts {
809
810 # Fuzz data files fuzzdata1.db and fuzzdata2.db are larger than
811 # the others. So ensure that these are run as a higher priority.
812 set tail [file tail $s]
813 if {$tail=="fuzzdata1.db" || $tail=="fuzzdata2.db"} {
814 set priority 5
815 } else {
816 set priority 1
817 }
818
819 add_job \
820 -displaytype fuzz \
821 -displayname "$buildname $interpreter $tail" \
822 -depid $depid \
823 -cmd "[file join $dirname $interpreter] $subcmd $s" \
824 -priority $priority
825 }
826 }
827 }
828
829 proc add_zipvfs_jobs {} {
830 global TRG
831 source [file join $TRG(zipvfs) test zipvfs_testrunner.tcl]
832
833 set bld [add_build_job Zipvfs $TRG(testfixture)]
834 foreach s [zipvfs_testrunner_files] {
835 set cmd "[file join [lindex $bld 1] $TRG(testfixture)] $s"
836 add_job \
837 -displaytype tcl \
838 -displayname "Zipvfs [file tail $s]" \
839 -cmd $cmd \
840 -depid [lindex $bld 0]
841 }
842
843 set ::env(SQLITE_TEST_DIR) $::testdir
844 }
845
846 # Used to add jobs for "mdevtest" and "sdevtest".
847 #
848 proc add_devtest_jobs {lBld patternlist} {
849 global TRG
850
851 foreach b $lBld {
852 set bld [add_build_job $b $TRG(testfixture)]
853 add_tcl_jobs $bld veryquick $patternlist SHELL
854 if {$patternlist==""} {
855 add_fuzztest_jobs $b
856 }
857
858 if {[trdb one "SELECT EXISTS (SELECT 1 FROM jobs WHERE depid='SHELL')"]} {
859 set sbld [add_shell_build_job $b [lindex $bld 1] [lindex $bld 0]]
860 set sbldid [lindex $sbld 0]
861 trdb eval {
862 UPDATE jobs SET depid=$sbldid WHERE depid='SHELL'
863 }
864 }
865
866 }
867 }
868
869 # Check to ensure that the interpreter is a full-blown "testfixture"
870 # build and not just a "tclsh". If this is not the case, issue an
871 # error message and exit.
872 #
873 proc must_be_testfixture {} {
874 if {[lsearch [info commands] sqlite3_soft_heap_limit]<0} {
875 puts "Use testfixture, not tclsh, for these arguments."
876 exit 1
877 }
878 }
879
880 proc add_jobs_from_cmdline {patternlist} {
881 global TRG
882
883 if {$TRG(zipvfs)!=""} {
884 add_zipvfs_jobs
885 if {[llength $patternlist]==0} return
886 }
887
888 if {[llength $patternlist]==0} {
889 set patternlist [list veryquick]
890 }
891
892 set first [lindex $patternlist 0]
893 switch -- $first {
894 all {
895 must_be_testfixture
896 set patternlist [lrange $patternlist 1 end]
897 set clist [trd_all_configs]
898 foreach c $clist {
899 add_tcl_jobs "" $c $patternlist
900 }
901 }
902
903 mdevtest {
904 set config_set {
905 All-O0
906 All-Debug
907 }
908 add_devtest_jobs $config_set [lrange $patternlist 1 end]
909 }
910
911 sdevtest {
912 set config_set {
913 All-Sanitize
914 All-Debug
915 }
916 add_devtest_jobs $config_set [lrange $patternlist 1 end]
917 }
918
919 release {
920 set patternlist [lrange $patternlist 1 end]
921 foreach b [trd_builds $TRG(platform)] {
922 if {$TRG(config)!="" && ![regexp "\\y$b\\y" $TRG(config)]} continue
923 if {[regexp "\\y$b\\y" $TRG(omitconfig)]} continue
924 set bld [add_build_job $b $TRG(testfixture)]
925 foreach c [trd_configs $TRG(platform) $b] {
926 add_tcl_jobs $bld $c $patternlist
927 }
928
929 if {$patternlist==""} {
930 foreach e [trd_extras $TRG(platform) $b] {
931 if {$e=="fuzztest"} {
932 add_fuzztest_jobs $b
933 } else {
934 add_make_job $bld $e
935 }
936 }
937 }
938 }
939 }
940
941 list {
942 set allperm [array names ::testspec]
943 lappend allperm all mdevtest sdevtest release list
944 puts "Allowed values for the PERMUTATION argument: [lsort $allperm]"
945 exit 0
946 }
947
948 default {
949 must_be_testfixture
950 if {[info exists ::testspec($first)]} {
951 add_tcl_jobs "" $first [lrange $patternlist 1 end]
952 } else {
953 add_tcl_jobs "" full $patternlist
954 }
955 }
956 }
957 }
958
959 proc make_new_testset {} {
960 global TRG
961
962 r_write_db {
963 trdb eval $TRG(schema)
964 set nJob $TRG(nJob)
965 set cmdline $TRG(cmdline)
966 set tm [clock_milliseconds]
967 trdb eval { REPLACE INTO config VALUES('njob', $nJob ); }
968 trdb eval { REPLACE INTO config VALUES('cmdline', $cmdline ); }
969 trdb eval { REPLACE INTO config VALUES('start', $tm ); }
970
971 add_jobs_from_cmdline $TRG(patternlist)
972 }
973
974 }
975
976 proc mark_job_as_finished {jobid output state endtm} {
977 r_write_db {
978 trdb eval {
979 UPDATE jobs
980 SET output=$output, state=$state, endtime=$endtm
981 WHERE jobid=$jobid;
982 UPDATE jobs SET state='ready' WHERE depid=$jobid;
983 }
984 }
985 }
986
987 proc script_input_ready {fd iJob jobid} {
988 global TRG
989 global O
990 global T
991
992 if {[eof $fd]} {
993 trdb eval { SELECT * FROM jobs WHERE jobid=$jobid } job {}
994
995 # If this job specified a directory name, then delete the run.sh/run.bat
996 # file from it before continuing. This is because the contents of this
997 # directory might be copied by some other job, and we don't want to copy
998 # the run.sh file in this case.
999 if {$job(dirname)!=""} {
1000 file delete -force [file join $job(dirname) $TRG(run)]
1001 }
1002
1003 set ::done 1
1004 fconfigure $fd -blocking 1
1005 set state "done"
1006 set rc [catch { close $fd } msg]
1007 if {$rc} {
1008 if {[info exists TRG(reportlength)]} {
1009 puts -nonewline "[string repeat " " $TRG(reportlength)]\r"
1010 }
1011 puts "FAILED: $job(displayname) ($iJob)"
1012 set state "failed"
1013 if {$TRG(stopOnError)} {
1014 puts "OUTPUT: $O($iJob)"
1015 exit 1
1016 }
1017 if {$TRG(stopOnCore) && [string first {core dumped} $O($iJob)]>0} {
1018 puts "OUTPUT: $O($iJob)"
1019 exit 1
1020 }
1021 }
1022
1023 set tm [clock_milliseconds]
1024 set jobtm [expr {$tm - $job(starttime)}]
1025
1026 puts $TRG(log) "### $job(displayname) ${jobtm}ms ($state)"
1027 puts $TRG(log) [string trim $O($iJob)]
1028
1029 mark_job_as_finished $jobid $O($iJob) $state $tm
1030
1031 dirs_freeDir $iJob
1032 launch_some_jobs
1033 incr ::wakeup
1034 } else {
1035 set rc [catch { gets $fd line } res]
1036 if {$rc} {
1037 puts "ERROR $res"
1038 }
1039 if {$res>=0} {
1040 append O($iJob) "$line\n"
1041 }
1042 }
1043
1044 }
1045
1046 proc dirname {ii} {
1047 return "testdir$ii"
1048 }
1049
1050 proc launch_another_job {iJob} {
1051 global TRG
1052 global O
1053 global T
1054
1055 set testfixture [info nameofexec]
1056 set script $TRG(info_script)
1057
1058 set O($iJob) ""
1059
1060 set jobdict [r_get_next_job $iJob]
1061 if {$jobdict==""} { return 0 }
1062 array set job $jobdict
1063
1064 set dir $job(dirname)
1065 if {$dir==""} { set dir [dirname $iJob] }
1066 create_or_clear_dir $dir
1067
1068 if {$job(build)!=""} {
1069 set srcdir [file dirname $::testdir]
1070 if {$job(build)=="Zipvfs"} {
1071 set script [zipvfs_testrunner_script]
1072 } else {
1073 set bWin [expr {$TRG(platform)=="win"}]
1074 set script [trd_buildscript $job(build) $srcdir $bWin]
1075 }
1076 set fd [open [file join $dir $TRG(make)] w]
1077 puts $fd $script
1078 close $fd
1079 }
1080
1081 # Add a batch/shell file command to set the directory used for temp
1082 # files to the test's working directory. Otherwise, tests that use
1083 # large numbers of temp files (e.g. zipvfs), might generate temp
1084 # filename collisions.
1085 if {$TRG(platform)=="win"} {
1086 set set_tmp_dir "SET SQLITE_TMPDIR=[file normalize $dir]"
1087 } else {
1088 set set_tmp_dir "export SQLITE_TMPDIR=\"[file normalize $dir]\""
1089 }
1090
1091 if { $TRG(dryrun) } {
1092
1093 mark_job_as_finished $job(jobid) "" done 0
1094 dirs_freeDir $iJob
1095 if {$job(build)!=""} {
1096 puts $TRG(log) "(cd $dir ; $job(cmd) )"
1097 } else {
1098 puts $TRG(log) "$job(cmd)"
1099 }
1100
1101 } else {
1102 set pwd [pwd]
1103 cd $dir
1104 set fd [open $TRG(run) w]
1105 puts $fd $set_tmp_dir
1106 puts $fd $job(cmd)
1107 close $fd
1108 set fd [open "|$TRG(runcmd) 2>@1" r]
1109 cd $pwd
1110
1111 fconfigure $fd -blocking false
1112 fileevent $fd readable [list script_input_ready $fd $iJob $job(jobid)]
1113 }
1114
1115 return 1
1116 }
1117
1118 proc one_line_report {} {
1119 global TRG
1120
1121 set tm [expr [clock_milliseconds] - $TRG(starttime)]
1122 set tm [format "%d" [expr int($tm/1000.0 + 0.5)]]
1123
1124 r_write_db {
1125 trdb eval {
1126 SELECT displaytype, state, count(*) AS cnt
1127 FROM jobs
1128 GROUP BY 1, 2
1129 } {
1130 set v($state,$displaytype) $cnt
1131 incr t($displaytype) $cnt
1132 }
1133 }
1134
1135 set text ""
1136 foreach j [lsort [array names t]] {
1137 foreach k {done failed running} { incr v($k,$j) 0 }
1138 set fin [expr $v(done,$j) + $v(failed,$j)]
1139 lappend text "${j}($fin/$t($j))"
1140 if {$v(failed,$j)>0} {
1141 lappend text "f$v(failed,$j)"
1142 }
1143 if {$v(running,$j)>0} {
1144 lappend text "r$v(running,$j)"
1145 }
1146 }
1147
1148 if {[info exists TRG(reportlength)]} {
1149 puts -nonewline "[string repeat " " $TRG(reportlength)]\r"
1150 }
1151 set report "${tm} [join $text { }]"
1152 set TRG(reportlength) [string length $report]
1153 if {[string length $report]<100} {
1154 puts -nonewline "$report\r"
1155 flush stdout
1156 } else {
1157 puts $report
1158 }
1159
1160 after $TRG(reporttime) one_line_report
1161 }
1162
1163 proc launch_some_jobs {} {
1164 global TRG
1165 set nJob [trdb one { SELECT value FROM config WHERE name='njob' }]
1166
1167 while {[dirs_nHelper]<$nJob} {
1168 set iDir [dirs_allocDir]
1169 if {0==[launch_another_job $iDir]} {
1170 dirs_freeDir $iDir
1171 break;
1172 }
1173 }
1174 }
1175
1176 proc run_testset {} {
1177 global TRG
1178 set ii 0
1179
1180 set TRG(starttime) [clock_milliseconds]
1181 set TRG(log) [open $TRG(logname) w]
1182
1183 launch_some_jobs
1184
1185 one_line_report
1186 while {[dirs_nHelper]>0} {
1187 after 500 {incr ::wakeup}
1188 vwait ::wakeup
1189 }
1190 close $TRG(log)
1191 one_line_report
1192
1193 r_write_db {
1194 set tm [clock_milliseconds]
1195 trdb eval { REPLACE INTO config VALUES('end', $tm ); }
1196 set nErr [trdb one {SELECT count(*) FROM jobs WHERE state='failed'}]
1197 if {$nErr>0} {
1198 puts "$nErr failures:"
1199 trdb eval {
1200 SELECT displayname FROM jobs WHERE state='failed'
1201 } {
1202 puts "FAILED: $displayname"
1203 }
1204 }
1205 }
1206
1207 puts "\nTest database is $TRG(dbname)"
1208 puts "Test log is $TRG(logname)"
1209 }
1210
1211 # Handle the --buildonly option, if it was specified.
1212 #
1213 proc handle_buildonly {} {
1214 global TRG
1215 if {$TRG(buildonly)} {
1216 r_write_db {
1217 trdb eval { DELETE FROM jobs WHERE displaytype!='bld' }
1218 }
1219 }
1220 }
1221
1222 # Handle the --explain option. Provide a human-readable
1223 # explanation of all the tests that are in the trdb database jobs
1224 # table.
1225 #
1226 proc explain_layer {indent depid} {
1227 global TRG
1228 if {$TRG(buildonly)} {
1229 set showtests 0
1230 } else {
1231 set showtests 1
1232 }
1233 trdb eval {SELECT jobid, displayname, displaytype, dirname
1234 FROM jobs WHERE depid=$depid ORDER BY displayname} {
1235 if {$displaytype=="bld"} {
1236 puts "${indent}$displayname in $dirname"
1237 explain_layer "${indent} " $jobid
1238 } elseif {$showtests} {
1239 puts "${indent}[lindex $displayname end]"
1240 }
1241 }
1242 }
1243 proc explain_tests {} {
1244 explain_layer "" ""
1245 }
1246
1247 sqlite3 trdb $TRG(dbname)
1248 trdb timeout $TRG(timeout)
1249 set tm [lindex [time { make_new_testset }] 0]
1250 if {$TRG(explain)} {
1251 explain_tests
1252 } else {
1253 if {$TRG(nJob)>1} {
1254 puts "splitting work across $TRG(nJob) jobs"
1255 }
1256 puts "built testset in [expr $tm/1000]ms.."
1257 handle_buildonly
1258 run_testset
1259 }
1260 trdb close