Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

testscript: add kill command #243

Merged
merged 1 commit into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
16 changes: 14 additions & 2 deletions testscript/doc.go
Original file line number Diff line number Diff line change
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
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 have terminated
# before the test exits and so the test will fail when it completes.
mvdan marked this conversation as resolved.
Show resolved Hide resolved
! exec sleep 5 &

kill -KILL test_sleep
wait test_sleep
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please add failure test cases? in particular:

  • trying to kill a running background process that was started with & rather than &word& (i.e. using a background process name that doesn't exist)
  • trying to kill a background process that already stopped or was already killed

Please also cover the kill and kill name forms, as otherwise we can't be sure they will keep working in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These negated tests are not currently possible since the kill command is not negatable. This is consistent with wait.

The positive tests for without name are possible.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding more tests!

I see your point about consistency with wait not being negatable. I personally think both should be, and both should fail if given an unknown/exited process, but we can always add that later - especially once we have ? to ignore the result of a command.

14 changes: 14 additions & 0 deletions testscript/testdata/kill_unnamed.txt
Original file line number Diff line number Diff line change
@@ -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
Loading