Skip to content

Commit

Permalink
testscript: add kill command
Browse files Browse the repository at this point in the history
This allows sending a termination signal to backgrounded commands.
  • Loading branch information
kortschak committed Feb 12, 2024
1 parent 2c88e7f commit ecfcbae
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 1 deletion.
65 changes: 64 additions & 1 deletion testscript/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,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 @@ -492,7 +493,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
7 changes: 7 additions & 0 deletions testscript/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,13 @@ 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.
- mkdir path...
Create the listed directories, if they do not already exists.
Expand Down
13 changes: 13 additions & 0 deletions testscript/testdata/kill.txt
Original file line number Diff line number Diff line change
@@ -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 still be running
# before the test exits and so the test will fail when it completes.
! exec sleep 5 &

kill -KILL test_sleep
wait test_sleep

0 comments on commit ecfcbae

Please sign in to comment.