commit - ad438e39091f0b135bb9d964b2a92e8afb158f8e
commit + 15f1a49a5f1b34c6d571fe2d4561b3005c418592
blob - 6412d3ac001eec975c236cf75e58896b406b7507
blob + ebd39c4f42ec24c30364eb06cbd987c5e20c9f57
--- exercise
+++ exercise
# config options
set opts(-B) . ;# basedir
+set opts(-j) 1 ;# number of concurrent jobs
set opts(-pd) {*} ;# pattern for test search directories
set opts(-ps) {*.ts} ;# pattern for test suite executable files
set opts(-pt) {*.t} ;# pattern for test executable files
# executing tests
-proc execute_test {countervar basedir path} {
- global opts
- upvar $countervar counters
+# A coroutine that starts the given test and yields a channel representing its
+# stdin/stdout. The initial invocation may also return the string "error" if
+# execution failed and no channel could be created. Unless an error occured,
+# subsequent invocations yield the empty string when awaiting more input or a
+# symbolic result (ok, error, or fail) to signify that the test has completed.
+# The channel initially remains in blocking mode and may be ignored for
+# sequential operation. If set to non-blocking, an event handler should be set
+# up that resumes the coroutine whenever the channel becomes readable. When
+# the coroutine ends, the channel is closed and any event handler vanishes.
+proc execute_test {job basedir path} {
+ global opts counters
# Should the test result be integrated as a nested test suite?
set suite [string match $opts(-ps) [file tail $path]]
} else {
# Capture stderr in a temporary file.
# Unfortunately, 'chan pipe' only appears in Tcl 8.6. :(
- set errfile "/tmp/exercise.testerr.[pid]"
+ set errfile "/tmp/exercise.[pid].$job.err"
}
set scounters {}
if {[catch {
# Execute the test, attaching pipes to stdin and stdout.
set pipe [open "|$exe 2>$errfile" r+]
+ yield $pipe
# Consume the test's stdout. Ignore it for normal tests.
# Continually update the progress report for test suites.
- while {[gets $pipe out] >= 0} {
+ while true {
+ if {[gets $pipe out] < 0} {
+ if {[eof $pipe]} break
+ yield ;# await further input and
+ continue ;# try again
+ }
+
if {$suite} {
set scounters [sanitize_suite_counters $out]
print_progress [add_suite_counters $counters $scounters]
if {$suite} {check_suite_counters $scounters}
# Wait for the subprocess to exit.
+ fconfigure $pipe -blocking true ;# make sure we get CHILDSTATUS
close $pipe
} res opt]
} {
} else {
dict incr counters $what
}
+
+ return $what
}
}
proc main {argc argv} {
- global opts env
+ global opts env counters J
set counters {run 0 ok 0 fail 0 error 0}
# handle environment variables
if {[info exists env(EXERCISEDIR)]} {set opts(-D) $env(EXERCISEDIR)}
# handle command line options
- set u {[-q] [-B dir] [-C dir] [-D dir] [-p k=pattern] [path ...]}
+ set u {[-q] [-B dir] [-C dir] [-D dir] [-j n] [-p k=pattern] [path ...]}
set prog [file tail $::argv0]
- while {[getopt argv {B:C:D:p:q} opt arg]} {
+ while {[getopt argv {B:C:D:j:p:q} opt arg]} {
if {$opt eq "?"} {
puts stderr "usage: $prog $u"
exit 1
set opt p$k
set arg $v
}
+ if {$opt eq "j"} {
+ if {![string is integer -strict $arg] || $arg < 1} {
+ puts stderr "$prog: -j expects positive integer argument"
+ exit 1
+ }
+ }
set opts(-$opt) $arg
}
set argc [llength $argv]
# run the tests
dict set counters run [llength $files] ;# tentative total run count
;# might adjust when suites run
+ set J 0 ;# number of active jobs
+ set i 0 ;# running job id
foreach file $files {
print_progress $counters
- execute_test counters $basedir $file
+
+ # wait for a slot to become available
+ while {$J >= $opts(-j)} {vwait J}
+
+ # start job
+ set job job[incr i]
+ set chan [coroutine $job execute_test $job $basedir $file]
+ if {$chan eq "error"} continue ;# did not execute
+
+ # enter coroutine whenever chan becomes readable
+ fconfigure $chan -blocking false
+ fileevent $chan readable "if {\[$job\] ne {}} {incr J -1}"
+
+ incr J
}
+ while {$J > 0} {vwait J} ;# wait for all jobs to finish
+
+ # print final result summary
if {!$opts(-q)} {puts [summary $counters]}
# sanity-check counters, exit 127 if mismatched (should never happen)
blob - 3bf7865c8862a126c753341fbf3efd657fad3ea1
blob + 0a3258df7bafd9b42ec1f410698aca7f953052ab
--- exercise.1
+++ exercise.1
.Op Fl B Ar dir
.Op Fl C Ar dir
.Op Fl D Ar dir
+.Op Fl j Ar n
.Op Fl p Ar k Ns Sy = Ns Ar pattern
.Op Ar path ...
.
This is useful, for example, when
.Ar dir
contains common helper or data files for the tests to find.
+.It Fl j Ar n
+Execute up to
+.Ar n
+test jobs concurrently.
.It Fl p Sy d= Ns Ar pattern
When searching for tests,
only descend into directories that match the given pattern.