Skip to content

Commit 2af95f2

Browse files
authored
testscript: add kill command (rogpeppe#243)
This allows sending a termination signal to backgrounded commands. Fixes rogpeppe#242.
1 parent 31b9365 commit 2af95f2

File tree

4 files changed

+105
-3
lines changed

4 files changed

+105
-3
lines changed

testscript/cmd.go

+64-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ var scriptCmds = map[string]func(*TestScript, bool, []string){
3535
"exec": (*TestScript).cmdExec,
3636
"exists": (*TestScript).cmdExists,
3737
"grep": (*TestScript).cmdGrep,
38+
"kill": (*TestScript).cmdKill,
3839
"mkdir": (*TestScript).cmdMkdir,
3940
"mv": (*TestScript).cmdMv,
4041
"rm": (*TestScript).cmdRm,
@@ -484,7 +485,69 @@ func (ts *TestScript) cmdUNIX2DOS(neg bool, args []string) {
484485
}
485486
}
486487

487-
// Tait waits for background commands to exit, setting stderr and stdout to their result.
488+
// cmdKill kills background commands.
489+
func (ts *TestScript) cmdKill(neg bool, args []string) {
490+
signals := map[string]os.Signal{
491+
"INT": os.Interrupt,
492+
"KILL": os.Kill,
493+
}
494+
var (
495+
name string
496+
signal os.Signal
497+
)
498+
switch len(args) {
499+
case 0:
500+
case 1, 2:
501+
sig, ok := strings.CutPrefix(args[0], "-")
502+
if ok {
503+
signal, ok = signals[sig]
504+
if !ok {
505+
ts.Fatalf("unknown signal: %s", sig)
506+
}
507+
} else {
508+
name = args[0]
509+
break
510+
}
511+
if len(args) == 2 {
512+
name = args[1]
513+
}
514+
default:
515+
ts.Fatalf("usage: kill [-SIGNAL] [name]")
516+
}
517+
if neg {
518+
ts.Fatalf("unsupported: ! kill")
519+
}
520+
if signal == nil {
521+
signal = os.Kill
522+
}
523+
if name != "" {
524+
ts.killBackgroundOne(name, signal)
525+
} else {
526+
ts.killBackground(signal)
527+
}
528+
}
529+
530+
func (ts *TestScript) killBackgroundOne(bgName string, signal os.Signal) {
531+
bg := ts.findBackground(bgName)
532+
if bg == nil {
533+
ts.Fatalf("unknown background process %q", bgName)
534+
}
535+
err := bg.cmd.Process.Signal(signal)
536+
if err != nil {
537+
ts.Fatalf("unexpected error terminating background command %q: %v", bgName, err)
538+
}
539+
}
540+
541+
func (ts *TestScript) killBackground(signal os.Signal) {
542+
for bgName, bg := range ts.background {
543+
err := bg.cmd.Process.Signal(signal)
544+
if err != nil {
545+
ts.Fatalf("unexpected error terminating background command %q: %v", bgName, err)
546+
}
547+
}
548+
}
549+
550+
// cmdWait waits for background commands to exit, setting stderr and stdout to their result.
488551
func (ts *TestScript) cmdWait(neg bool, args []string) {
489552
if len(args) > 1 {
490553
ts.Fatalf("usage: wait [name]")

testscript/doc.go

+14-2
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,8 @@ The predefined commands are:
166166
167167
If the last token is '&word&` (where "word" is alphanumeric), the
168168
command runs in the background but has a name, and can be waited
169-
for specifically by passing the word to 'wait'.
169+
for specifically by passing the word to 'wait', or used to terminate
170+
the process by invoking 'kill' with the word passed to it.
170171
171172
Standard input can be provided using the stdin command; this will be
172173
cleared after exec has been called.
@@ -179,6 +180,15 @@ The predefined commands are:
179180
The file's content must (or must not) match the regular expression pattern.
180181
For positive matches, -count=N specifies an exact number of matches to require.
181182
183+
- kill [-SIGNAL] [command]
184+
Terminate all 'exec' and 'go' commands started in the background (with the '&'
185+
token) by sending an termination signal. Recognized signals are KILL and INT.
186+
If no signal is specified, KILL is sent.
187+
188+
If a command argument is specified, it terminates only that command, which
189+
must have been started with the final token '&command&` as described for the
190+
exec command.
191+
182192
- mkdir path...
183193
Create the listed directories, if they do not already exists.
184194
@@ -234,7 +244,9 @@ The predefined commands are:
234244
concatenation of the corresponding streams of the background commands,
235245
in the order in which those commands were started.
236246
237-
If an argument is specified, it waits for just that command.
247+
If an argument is specified, it waits for just that command, which
248+
must have been started with the final token '&command&` as described for the
249+
exec command.
238250
239251
When TestScript runs a script and the script fails, by default TestScript shows
240252
the execution of the most recent phase of the script (since the last # comment)

testscript/testdata/kill.txt

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[!exec:sleep] skip
2+
3+
# This test depends on sleep exiting with a non-success status when being
4+
# terminated by an interrupt (kill on Windows) signal.
5+
6+
! exec sleep 10 &test_sleep&
7+
8+
# Set a timeout. If the kill below fails, this sleep will have terminated
9+
# before the test exits and so the test will fail when it completes.
10+
! exec sleep 5 &
11+
12+
kill -KILL test_sleep
13+
wait test_sleep

testscript/testdata/kill_unnamed.txt

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[!exec:sleep] skip
2+
3+
# This test depends on sleep exiting with a non-success status when being
4+
# terminated by an interrupt (kill on Windows) signal.
5+
6+
! exec sleep 10 &
7+
! exec sleep 10 &
8+
9+
# Set a timeout. If the kill below fails, this sleep will have terminated
10+
# before the test exits and so the test will fail when it completes.
11+
! exec sleep 5 &
12+
13+
kill -KILL
14+
wait

0 commit comments

Comments
 (0)