返回 DeepSeek-Reasonix
kill_windows_test.go
根目录 / internal / proc / kill_windows_test.go
1 //go:build windows
2
3 package proc
4
5 import (
6 "errors"
7 "io"
8 "os/exec"
9 "testing"
10 "time"
11 )
12
13 // A launcher (cmd.exe) that spawns a long-lived grandchild (ping) which inherits
14 // the stdout pipe: killing only the direct child leaves the grandchild holding
15 // the pipe, so cmd.Wait blocks until the grandchild exits. KillTree must take
16 // the whole tree down so Wait returns promptly.
17 func TestKillTreeUnblocksWaitOnSurvivingGrandchild(t *testing.T) {
18 cmd := exec.Command("cmd", "/c", "ping", "-n", "30", "127.0.0.1")
19 HideWindow(cmd)
20 stdout, err := cmd.StdoutPipe()
21 if err != nil {
22 t.Fatalf("StdoutPipe: %v", err)
23 }
24 if err := cmd.Start(); err != nil {
25 t.Fatalf("Start: %v", err)
26 }
27 go func() { _, _ = io.Copy(io.Discard, stdout) }()
28 time.Sleep(500 * time.Millisecond) // let cmd.exe exec the ping grandchild
29
30 KillTree(cmd)
31
32 done := make(chan error, 1)
33 go func() { done <- cmd.Wait() }()
34 select {
35 case <-done:
36 case <-time.After(8 * time.Second):
37 t.Fatal("cmd.Wait blocked after KillTree — grandchild survived holding the pipe")
38 }
39 }
40
41 // StartTracked must create a Job Object for the started process, and KillTracked
42 // must take the whole tracked tree down, including descendants a plain taskkill
43 // /T would miss.
44 func TestKillTrackedReapsTrackedTree(t *testing.T) {
45 cmd := exec.Command("cmd", "/c", "ping", "-n", "30", "127.0.0.1")
46 HideWindow(cmd)
47 stdout, err := cmd.StdoutPipe()
48 if err != nil {
49 t.Fatalf("StdoutPipe: %v", err)
50 }
51 job, err := StartTracked(cmd)
52 if err != nil {
53 t.Fatalf("StartTracked: %v", err)
54 }
55 if job == 0 {
56 t.Fatal("StartTracked returned 0 — job object not created")
57 }
58 go func() { _, _ = io.Copy(io.Discard, stdout) }()
59 time.Sleep(500 * time.Millisecond) // let cmd.exe exec the ping grandchild into the job
60
61 KillTracked(cmd, job)
62
63 done := make(chan error, 1)
64 go func() { done <- cmd.Wait() }()
65 select {
66 case <-done:
67 case <-time.After(8 * time.Second):
68 t.Fatal("cmd.Wait blocked after KillTracked — tracked tree survived")
69 }
70 }
71
72 // StartTracked creates the child suspended to win the job-assignment race, so it
73 // must resume it or the process hangs forever. A child that exits with a known
74 // code proves the resume fired: Wait returns that code instead of timing out.
75 func TestStartTrackedResumesSuspendedChild(t *testing.T) {
76 cmd := exec.Command("cmd", "/c", "exit", "7")
77 HideWindow(cmd)
78 if _, err := StartTracked(cmd); err != nil {
79 t.Fatalf("StartTracked: %v", err)
80 }
81
82 done := make(chan error, 1)
83 go func() { done <- cmd.Wait() }()
84 select {
85 case err := <-done:
86 var ee *exec.ExitError
87 if !errors.As(err, &ee) || ee.ExitCode() != 7 {
88 t.Fatalf("exit = %v, want exit status 7", err)
89 }
90 case <-time.After(8 * time.Second):
91 t.Fatal("cmd.Wait blocked — StartTracked left the child suspended")
92 }
93 }
94
94 lines GO