You've already forked agentic-coding-workflow
66 lines
1.6 KiB
Go
66 lines
1.6 KiB
Go
//go:build !windows
|
|
|
|
package proc
|
|
|
|
import (
|
|
"fmt"
|
|
"os/exec"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestUnixGroup_KillsChildProcess 验证整树清理:父 shell 派生一个后台 sleep
|
|
// 子进程并打印其 PID,TerminateGroup 后该子进程(孙级)也应被 kill(-pgid) 回收,
|
|
// 证明终止覆盖整个进程组而非仅组长。
|
|
func TestUnixGroup_KillsChildProcess(t *testing.T) {
|
|
g := NewGroup()
|
|
// `sleep 30 &` 后台子进程;`echo $!` 打印其 PID;`wait` 让 shell 阻塞存活。
|
|
cmd := exec.Command("sh", "-c", "sleep 30 & echo $!; wait")
|
|
g.Prepare(cmd)
|
|
stdout, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
t.Fatalf("stdout pipe: %v", err)
|
|
}
|
|
if err := cmd.Start(); err != nil {
|
|
t.Fatalf("start: %v", err)
|
|
}
|
|
if err := g.Adopt(cmd); err != nil {
|
|
t.Fatalf("adopt: %v", err)
|
|
}
|
|
defer g.Close()
|
|
|
|
var childPID int
|
|
if _, err := fmt.Fscan(stdout, &childPID); err != nil {
|
|
t.Fatalf("read child pid: %v", err)
|
|
}
|
|
if childPID <= 0 {
|
|
t.Fatalf("invalid child pid: %d", childPID)
|
|
}
|
|
|
|
done := make(chan struct{})
|
|
go func() {
|
|
_ = cmd.Wait()
|
|
close(done)
|
|
}()
|
|
|
|
g.TerminateGroup(done, 2*time.Second)
|
|
select {
|
|
case <-done:
|
|
case <-time.After(10 * time.Second):
|
|
t.Fatal("parent not terminated")
|
|
}
|
|
|
|
// 给内核一点时间回收子进程,然后用 kill(pid, 0) 探测:返回错误(ESRCH)=已死。
|
|
deadline := time.Now().Add(5 * time.Second)
|
|
for {
|
|
if err := syscall.Kill(childPID, 0); err != nil {
|
|
return // 子进程已死,符合预期。
|
|
}
|
|
if time.Now().After(deadline) {
|
|
t.Fatalf("child process %d still alive after group terminate", childPID)
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
}
|