Skip to content

Commit f3cb5c2

Browse files
authored
testscript: support named background commands (#152)
This lets us wait for an individual background command rather than all of them at once.
1 parent dc66b32 commit f3cb5c2

File tree

4 files changed

+88
-7
lines changed

4 files changed

+88
-7
lines changed

testscript/cmd.go

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -234,14 +234,20 @@ func (ts *TestScript) cmdEnv(neg bool, args []string) {
234234
}
235235
}
236236

237+
var backgroundSpecifier = regexp.MustCompile(`^&([a-zA-Z_0-9]+&)?$`)
238+
237239
// exec runs the given command.
238240
func (ts *TestScript) cmdExec(neg bool, args []string) {
239241
if len(args) < 1 || (len(args) == 1 && args[0] == "&") {
240242
ts.Fatalf("usage: exec program [args...] [&]")
241243
}
242244

243245
var err error
244-
if len(args) > 0 && args[len(args)-1] == "&" {
246+
if len(args) > 0 && backgroundSpecifier.MatchString(args[len(args)-1]) {
247+
bgName := strings.TrimSuffix(strings.TrimPrefix(args[len(args)-1], "&"), "&")
248+
if ts.findBackground(bgName) != nil {
249+
ts.Fatalf("duplicate background process name %q", bgName)
250+
}
245251
var cmd *exec.Cmd
246252
cmd, err = ts.execBackground(args[0], args[1:len(args)-1]...)
247253
if err == nil {
@@ -250,7 +256,7 @@ func (ts *TestScript) cmdExec(neg bool, args []string) {
250256
ctxWait(ts.ctxt, cmd)
251257
close(wait)
252258
}()
253-
ts.background = append(ts.background, backgroundCmd{cmd, wait, neg})
259+
ts.background = append(ts.background, backgroundCmd{bgName, cmd, wait, neg})
254260
}
255261
ts.stdout, ts.stderr = "", ""
256262
} else {
@@ -449,16 +455,69 @@ func (ts *TestScript) cmdUNIX2DOS(neg bool, args []string) {
449455

450456
// Tait waits for background commands to exit, setting stderr and stdout to their result.
451457
func (ts *TestScript) cmdWait(neg bool, args []string) {
458+
if len(args) > 1 {
459+
ts.Fatalf("usage: wait [name]")
460+
}
452461
if neg {
453462
ts.Fatalf("unsupported: ! wait")
454463
}
455464
if len(args) > 0 {
456-
ts.Fatalf("usage: wait")
465+
ts.waitBackgroundOne(args[0])
466+
} else {
467+
ts.waitBackground(true)
468+
}
469+
}
470+
471+
func (ts *TestScript) waitBackgroundOne(bgName string) {
472+
bg := ts.findBackground(bgName)
473+
if bg == nil {
474+
ts.Fatalf("unknown background process %q", bgName)
475+
}
476+
<-bg.wait
477+
ts.stdout = bg.cmd.Stdout.(*strings.Builder).String()
478+
ts.stderr = bg.cmd.Stderr.(*strings.Builder).String()
479+
if ts.stdout != "" {
480+
fmt.Fprintf(&ts.log, "[stdout]\n%s", ts.stdout)
481+
}
482+
if ts.stderr != "" {
483+
fmt.Fprintf(&ts.log, "[stderr]\n%s", ts.stderr)
484+
}
485+
// Note: ignore bg.neg, which only takes effect on the non-specific
486+
// wait command.
487+
if bg.cmd.ProcessState.Success() {
488+
if bg.neg {
489+
ts.Fatalf("unexpected command success")
490+
}
491+
} else {
492+
if ts.ctxt.Err() != nil {
493+
ts.Fatalf("test timed out while running command")
494+
} else if !bg.neg {
495+
ts.Fatalf("unexpected command failure")
496+
}
497+
}
498+
// Remove this process from the list of running background processes.
499+
for i := range ts.background {
500+
if bg == &ts.background[i] {
501+
ts.background = append(ts.background[:i], ts.background[i+1:]...)
502+
break
503+
}
504+
}
505+
}
506+
507+
func (ts *TestScript) findBackground(bgName string) *backgroundCmd {
508+
if bgName == "" {
509+
return nil
510+
}
511+
for i := range ts.background {
512+
bg := &ts.background[i]
513+
if bg.name == bgName {
514+
return bg
515+
}
457516
}
458-
ts.waitBackground(true, neg)
517+
return nil
459518
}
460519

461-
func (ts *TestScript) waitBackground(checkStatus bool, neg bool) {
520+
func (ts *TestScript) waitBackground(checkStatus bool) {
462521
var stdouts, stderrs []string
463522
for _, bg := range ts.background {
464523
<-bg.wait

testscript/doc.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ The predefined commands are:
152152
test. At the end of the test, any remaining background processes are
153153
terminated using os.Interrupt (if supported) or os.Kill.
154154
155+
If the last token is '&word&` (where "word" is alphanumeric), the
156+
command runs in the background but has a name, and can be waited
157+
for specifically by passing the word to 'wait'.
158+
155159
Standard input can be provided using the stdin command; this will be
156160
cleared after exec has been called.
157161
@@ -197,13 +201,15 @@ The predefined commands are:
197201
- symlink file -> target
198202
Create file as a symlink to target. The -> (like in ls -l output) is required.
199203
200-
- wait
204+
- wait [command]
201205
Wait for all 'exec' and 'go' commands started in the background (with the '&'
202206
token) to exit, and display success or failure status for them.
203207
After a call to wait, the 'stderr' and 'stdout' commands will apply to the
204208
concatenation of the corresponding streams of the background commands,
205209
in the order in which those commands were started.
206210
211+
If an argument is specified, it waits for just that command.
212+
207213
When TestScript runs a script and the script fails, by default TestScript shows
208214
the execution of the most recent phase of the script (since the last # comment)
209215
and only shows the # comments for earlier phases. For example, here is a

testscript/testdata/wait.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,21 @@ exec echo bar &
2020
wait
2121
stdout 'foo\nbar'
2222

23+
exec echo bg1 &b1&
24+
exec echo bg2 &b2&
25+
exec echo bg3 &b3&
26+
exec echo bg4 &b4&
27+
28+
wait b3
29+
stdout bg3
30+
wait b2
31+
stdout bg2
32+
wait
33+
stdout 'bg1\nbg4'
34+
35+
# We should be able to start several background processes and wait for them
36+
# individually.
37+
2338
# The end of the test should interrupt or kill any remaining background
2439
# programs.
2540
[!exec:sleep] skip

testscript/testscript.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ type TestScript struct {
286286
}
287287

288288
type backgroundCmd struct {
289+
name string
289290
cmd *exec.Cmd
290291
wait <-chan struct{}
291292
neg bool // if true, cmd should fail
@@ -396,7 +397,7 @@ func (ts *TestScript) run() {
396397
if ts.t.Verbose() || hasFailed(ts.t) {
397398
// In verbose mode or on test failure, we want to see what happened in the background
398399
// processes too.
399-
ts.waitBackground(false, false)
400+
ts.waitBackground(false)
400401
} else {
401402
for _, bg := range ts.background {
402403
<-bg.wait

0 commit comments

Comments
 (0)