26f897c1ec
release / release-please (push) Failing after 1m49s
docs / build (push) Failing after 6m34s
release / build-and-upload (arm64, linux) (push) Has been cancelled
release / build-and-upload (arm64, windows) (push) Has been cancelled
release / build-darwin (amd64, darwin) (push) Has been cancelled
release / checksums (push) Has been cancelled
release / finalize (push) Has been cancelled
release / build-darwin (arm64, darwin) (push) Has been cancelled
release / build-and-upload (amd64, linux) (push) Has been cancelled
release / build-and-upload (amd64, windows) (push) Has been cancelled
docs / deploy (push) Has been cancelled
89 lines
1.9 KiB
Go
89 lines
1.9 KiB
Go
//go:build !windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestTerminateCmdStopsProcessGroup(t *testing.T) {
|
|
t.Helper()
|
|
|
|
cmd := exec.Command("sh", "-c", "sleep 30 & echo $! && wait")
|
|
cmd.SysProcAttr = newProcAttr()
|
|
stdout, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
t.Fatalf("stdout pipe: %v", err)
|
|
}
|
|
if err := cmd.Start(); err != nil {
|
|
t.Fatalf("start process group: %v", err)
|
|
}
|
|
|
|
line, err := bufio.NewReader(stdout).ReadString('\n')
|
|
if err != nil {
|
|
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
|
_, _ = cmd.Process.Wait()
|
|
t.Fatalf("read child pid: %v", err)
|
|
}
|
|
childPID, err := strconv.Atoi(strings.TrimSpace(line))
|
|
if err != nil {
|
|
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
|
_, _ = cmd.Process.Wait()
|
|
t.Fatalf("parse child pid %q: %v", line, err)
|
|
}
|
|
|
|
if err := terminateCmd(cmd, 500*time.Millisecond); err != nil {
|
|
t.Fatalf("terminateCmd: %v", err)
|
|
}
|
|
if waitForProcessExit(childPID, time.Second) {
|
|
t.Fatalf("child process %d still running after group termination", childPID)
|
|
}
|
|
}
|
|
|
|
func waitForProcessExit(pid int, timeout time.Duration) bool {
|
|
deadline := time.Now().Add(timeout)
|
|
for time.Now().Before(deadline) {
|
|
running, err := processRunning(pid)
|
|
if err != nil {
|
|
return true
|
|
}
|
|
if !running {
|
|
return false
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
running, err := processRunning(pid)
|
|
return err != nil || running
|
|
}
|
|
|
|
func processRunning(pid int) (bool, error) {
|
|
err := syscall.Kill(pid, 0)
|
|
if err != nil {
|
|
if errors.Is(err, syscall.ESRCH) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
cmd := exec.Command("ps", "-o", "stat=", "-p", strconv.Itoa(pid))
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
var exitErr *exec.ExitError
|
|
if errors.As(err, &exitErr) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
state := strings.TrimSpace(string(out))
|
|
if state == "" || strings.HasPrefix(state, "Z") {
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
}
|