Skip to content

Commit

Permalink
testscript: add kill command (#243)
Browse files Browse the repository at this point in the history
This allows sending a termination signal to backgrounded commands.

Fixes #242.
  • Loading branch information
kortschak committed Apr 15, 2024
1 parent 31b9365 commit 2af95f2
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 3 deletions.
65 changes: 64 additions & 1 deletion testscript/cmd.go
Expand Up @@ -35,6 +35,7 @@ var scriptCmds = map[string]func(*TestScript, bool, []string){
"exec": (*TestScript).cmdExec,
"exists": (*TestScript).cmdExists,
"grep": (*TestScript).cmdGrep,
"kill": (*TestScript).cmdKill,
"mkdir": (*TestScript).cmdMkdir,
"mv": (*TestScript).cmdMv,
"rm": (*TestScript).cmdRm,
Expand Down Expand Up @@ -484,7 +485,69 @@ func (ts *TestScript) cmdUNIX2DOS(neg bool, args []string) {
}
}

// Tait waits for background commands to exit, setting stderr and stdout to their result.
// cmdKill kills background commands.
func (ts *TestScript) cmdKill(neg bool, args []string) {
signals := map[string]os.Signal{
"INT": os.Interrupt,
"KILL": os.Kill,
}
var (
name string
signal os.Signal
)
switch len(args) {
case 0:
case 1, 2:
sig, ok := strings.CutPrefix(args[0], "-")
if ok {
signal, ok = signals[sig]
if !ok {
ts.Fatalf("unknown signal: %s", sig)
}
} else {
name = args[0]
break
}
if len(args) == 2 {
name = args[1]
}
default:
ts.Fatalf("usage: kill [-SIGNAL] [name]")
}
if neg {
ts.Fatalf("unsupported: ! kill")
}
if signal == nil {
signal = os.Kill
}
if name != "" {
ts.killBackgroundOne(name, signal)
} else {
ts.killBackground(signal)
}
}

func (ts *TestScript) killBackgroundOne(bgName string, signal os.Signal) {
bg := ts.findBackground(bgName)
if bg == nil {
ts.Fatalf("unknown background process %q", bgName)
}
err := bg.cmd.Process.Signal(signal)
if err != nil {
ts.Fatalf("unexpected error terminating background command %q: %v", bgName, err)
}
}

func (ts *TestScript) killBackground(signal os.Signal) {
for bgName, bg := range ts.background {
err := bg.cmd.Process.Signal(signal)
if err != nil {
ts.Fatalf("unexpected error terminating background command %q: %v", bgName, err)
}
}
}

// cmdWait waits for background commands to exit, setting stderr and stdout to their result.
func (ts *TestScript) cmdWait(neg bool, args []string) {
if len(args) > 1 {
ts.Fatalf("usage: wait [name]")
Expand Down
16 changes: 14 additions & 2 deletions testscript/doc.go
Expand Up @@ -166,7 +166,8 @@ The predefined commands are:
If the last token is '&word&` (where "word" is alphanumeric), the
command runs in the background but has a name, and can be waited
for specifically by passing the word to 'wait'.
for specifically by passing the word to 'wait', or used to terminate
the process by invoking 'kill' with the word passed to it.
Standard input can be provided using the stdin command; this will be
cleared after exec has been called.
Expand All @@ -179,6 +180,15 @@ The predefined commands are:
The file's content must (or must not) match the regular expression pattern.
For positive matches, -count=N specifies an exact number of matches to require.
- kill [-SIGNAL] [command]
Terminate all 'exec' and 'go' commands started in the background (with the '&'
token) by sending an termination signal. Recognized signals are KILL and INT.
If no signal is specified, KILL is sent.
If a command argument is specified, it terminates only that command, which
must have been started with the final token '&command&` as described for the
exec command.
- mkdir path...
Create the listed directories, if they do not already exists.
Expand Down Expand Up @@ -234,7 +244,9 @@ The predefined commands are:
concatenation of the corresponding streams of the background commands,
in the order in which those commands were started.
If an argument is specified, it waits for just that command.
If an argument is specified, it waits for just that command, which
must have been started with the final token '&command&` as described for the
exec command.
When TestScript runs a script and the script fails, by default TestScript shows
the execution of the most recent phase of the script (since the last # comment)
Expand Down
13 changes: 13 additions & 0 deletions testscript/testdata/kill.txt
@@ -0,0 +1,13 @@
[!exec:sleep] skip

# This test depends on sleep exiting with a non-success status when being
# terminated by an interrupt (kill on Windows) signal.

! exec sleep 10 &test_sleep&

# Set a timeout. If the kill below fails, this sleep will have terminated
# before the test exits and so the test will fail when it completes.
! exec sleep 5 &

kill -KILL test_sleep
wait test_sleep
14 changes: 14 additions & 0 deletions testscript/testdata/kill_unnamed.txt
@@ -0,0 +1,14 @@
[!exec:sleep] skip

# This test depends on sleep exiting with a non-success status when being
# terminated by an interrupt (kill on Windows) signal.

! exec sleep 10 &
! exec sleep 10 &

# Set a timeout. If the kill below fails, this sleep will have terminated
# before the test exits and so the test will fail when it completes.
! exec sleep 5 &

kill -KILL
wait

0 comments on commit 2af95f2

Please sign in to comment.