diff --git a/testscript/cmd.go b/testscript/cmd.go index 446245a1..e04d26e4 100644 --- a/testscript/cmd.go +++ b/testscript/cmd.go @@ -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, @@ -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]") diff --git a/testscript/doc.go b/testscript/doc.go index 57b3c101..f75d3479 100644 --- a/testscript/doc.go +++ b/testscript/doc.go @@ -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. @@ -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. @@ -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) diff --git a/testscript/testdata/kill.txt b/testscript/testdata/kill.txt new file mode 100644 index 00000000..da713ca2 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/testscript/testdata/kill_unnamed.txt b/testscript/testdata/kill_unnamed.txt new file mode 100644 index 00000000..cd113abe --- /dev/null +++ b/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 \ No newline at end of file