Skip to content

Commit ab1e68a

Browse files
committed
improve the way we terminate subprocess during test runs
1 parent faaaaf4 commit ab1e68a

3 files changed

Lines changed: 36 additions & 24 deletions

File tree

cmd/meeseeks/main_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ func TestMainCommands(t *testing.T) {
1616
name: "no command",
1717
test: func(t *testing.T) {
1818
var stdoutBuf, stderrBuf bytes.Buffer
19-
exitCode := runCLICommand(t, []string{}, &stdoutBuf, &stderrBuf, 5*time.Second)
19+
exitCode := runCLICommand([]string{}, &stdoutBuf, &stderrBuf, 5*time.Second)
2020
output := stdoutBuf.String() + stderrBuf.String()
2121

2222
if exitCode != 1 {
@@ -43,7 +43,7 @@ func TestMainCommands(t *testing.T) {
4343
name: "unknown command",
4444
test: func(t *testing.T) {
4545
var stdoutBuf, stderrBuf bytes.Buffer
46-
exitCode := runCLICommand(t, []string{"unknown"}, &stdoutBuf, &stderrBuf, 5*time.Second)
46+
exitCode := runCLICommand([]string{"unknown"}, &stdoutBuf, &stderrBuf, 5*time.Second)
4747
output := stdoutBuf.String() + stderrBuf.String()
4848

4949
if exitCode != 1 {
@@ -59,7 +59,7 @@ func TestMainCommands(t *testing.T) {
5959
name: "version command",
6060
test: func(t *testing.T) {
6161
var stdoutBuf, stderrBuf bytes.Buffer
62-
exitCode := runCLICommand(t, []string{"version"}, &stdoutBuf, &stderrBuf, 5*time.Second)
62+
exitCode := runCLICommand([]string{"version"}, &stdoutBuf, &stderrBuf, 5*time.Second)
6363
output := stdoutBuf.String() + stderrBuf.String()
6464

6565
if exitCode != 0 {

cmd/meeseeks/run_test.go

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func TestRunCommand_ConfigValidation(t *testing.T) {
3131
for _, tt := range tests {
3232
t.Run(tt.name, func(t *testing.T) {
3333
var stdoutBuf, stderrBuf bytes.Buffer
34-
exitCode := runCLICommand(t, tt.args, &stdoutBuf, &stderrBuf, 5*time.Second)
34+
exitCode := runCLICommand(tt.args, &stdoutBuf, &stderrBuf, 5*time.Second)
3535
output := stdoutBuf.String() + stderrBuf.String()
3636

3737
if exitCode != tt.expectedExit {
@@ -69,12 +69,20 @@ func TestRunCommand_Foreground(t *testing.T) {
6969
t.Fatalf("Failed to create test config file: %v", err)
7070
}
7171

72-
ctx, cancel := context.WithTimeout(t.Context(), 3*time.Second)
72+
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
7373
defer cancel()
7474

7575
cmd := exec.CommandContext(ctx, "go", "run", ".")
7676
cmd.Args = append(cmd.Args, []string{"run", "-config", configFile}...)
7777

78+
// Set process group to ensure we can kill child processes
79+
// Running test creates a chain of processes:
80+
// 1. go run . (parent)
81+
// 2. The compiled meeseeks binary (child)
82+
// 3. Potentially other child processes started by meeseeks
83+
// By using syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL), we kill the entire process group
84+
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
85+
7886
// Use pipes instead of bytes.Buffer to avoid deadlock.
7987
// When using bytes.Buffer directly with cmd.Stdout/Stderr, the process can block
8088
// writing to the buffer if nothing is reading from it. This causes the test to hang
@@ -112,13 +120,21 @@ func TestRunCommand_Foreground(t *testing.T) {
112120
stderr.ReadFrom(stderrPipe)
113121
}()
114122

115-
// Send SIGTERM after a short delay to gracefully stop the daemon
116-
go func() {
117-
time.Sleep(500 * time.Millisecond)
123+
// Send SIGTERM after allowing time for startup message
124+
terminateProcess := func() {
125+
time.Sleep(1 * time.Second) // Give more time for startup
118126
if cmd.Process != nil {
119-
cmd.Process.Signal(syscall.SIGTERM)
127+
// First try SIGTERM to the process group
128+
syscall.Kill(-cmd.Process.Pid, syscall.SIGTERM)
129+
130+
// Give it time to exit gracefully, then force kill the process group
131+
time.Sleep(300 * time.Millisecond)
132+
if cmd.Process != nil {
133+
syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
134+
}
120135
}
121-
}()
136+
}
137+
go terminateProcess()
122138

123139
err = cmd.Wait()
124140

@@ -129,7 +145,7 @@ func TestRunCommand_Foreground(t *testing.T) {
129145
if err != nil && !strings.Contains(err.Error(), "signal") &&
130146
!strings.Contains(err.Error(), "interrupt") &&
131147
!strings.Contains(err.Error(), "context deadline") {
132-
t.Fatalf("Unexpected error (ignoring interrupt/timeout): %v", err)
148+
t.Fatalf("Unexpected error (ignoring interrupt/timeout/killed): %v", err)
133149
}
134150

135151
output := stdout.String() + stderr.String()
@@ -161,7 +177,6 @@ func TestRunCommand_Detached(t *testing.T) {
161177

162178
var stdout, stderr bytes.Buffer
163179
exitCode := runCLICommand(
164-
t,
165180
[]string{"run", "-d", "-config", configFile},
166181
&stdout,
167182
&stderr,
@@ -194,7 +209,6 @@ func TestRunCommand_Detached(t *testing.T) {
194209

195210
var stdoutBuf, stderrBuf bytes.Buffer
196211
exitCode = runCLICommand(
197-
t,
198212
[]string{"status"},
199213
&stdoutBuf,
200214
&stderrBuf,
@@ -209,7 +223,7 @@ func TestRunCommand_Detached(t *testing.T) {
209223
t.Fatalf("Status command could not connect to daemon: %q", statusOutput)
210224
}
211225

212-
exitCode = runCLICommand(t, []string{"exit"}, nil, nil, 5*time.Second)
226+
exitCode = runCLICommand([]string{"exit"}, nil, nil, 5*time.Second)
213227
if exitCode != 0 {
214228
t.Fatalf("Expected exit code %d, got %d", 0, exitCode)
215229
}
@@ -224,7 +238,6 @@ func TestRunCommand_Detached(t *testing.T) {
224238

225239
var stdoutBuf2, stderrBuf2 bytes.Buffer
226240
exitCode = runCLICommand(
227-
t,
228241
[]string{"status"},
229242
&stdoutBuf2,
230243
&stderrBuf2,

cmd/meeseeks/test_helpers.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,12 @@ import (
1515

1616
// runCLICommand runs CLI commands as subprocess.
1717
func runCLICommand(
18-
t *testing.T,
1918
args []string,
2019
stdoutBuf io.Writer,
2120
stderrBuf io.Writer,
2221
timeout time.Duration,
2322
) int {
24-
ctx, cancel := context.WithTimeout(t.Context(), timeout)
23+
ctx, cancel := context.WithTimeout(context.Background(), timeout)
2524
defer cancel()
2625

2726
cmd := exec.CommandContext(ctx, "go", "run", ".")
@@ -83,7 +82,6 @@ func (d *testDetachedDaemon) start() {
8382

8483
var stdout, stderr bytes.Buffer
8584
exitCode := runCLICommand(
86-
d.t,
8785
[]string{"run", "-d", "-config", d.configFile},
8886
&stdout,
8987
&stderr,
@@ -121,14 +119,15 @@ func (d *testDetachedDaemon) stop() {
121119
}
122120

123121
// Try to exit gracefully first
124-
exitCode := runCLICommand(d.t, []string{"exit"}, nil, nil, 5*time.Second)
125-
if exitCode != 0 && exitCode != 1 { // exit might return 1 if already stopped
126-
d.t.Logf("Unexpected exit code during daemon cleanup: %d", exitCode)
122+
exitCode := runCLICommand([]string{"exit"}, nil, nil, 5*time.Second)
123+
if exitCode != 0 {
124+
d.t.Errorf("Unexpected exit code during daemon cleanup: %d", exitCode)
127125
}
128126

129-
// Ensure cleanup by removing PID and socket files
127+
// Force cleanup by removing PID and socket files
130128
expectedPidFile := getPidFile()
131129
expectedSocketPath := getSocketPath()
130+
132131
os.Remove(expectedPidFile)
133132
os.Remove(expectedSocketPath)
134133

@@ -162,7 +161,7 @@ func runCommandTests(t *testing.T, tests []commandTestCase) {
162161
for _, tt := range tests {
163162
t.Run(tt.name, func(t *testing.T) {
164163
var stdoutBuf, stderrBuf bytes.Buffer
165-
exitCode := runCLICommand(t, tt.args, &stdoutBuf, &stderrBuf, 5*time.Second)
164+
exitCode := runCLICommand(tt.args, &stdoutBuf, &stderrBuf, 5*time.Second)
166165
output := stdoutBuf.String() + stderrBuf.String()
167166

168167
if exitCode != tt.expectedExit {
@@ -179,7 +178,7 @@ func runCommandTests(t *testing.T, tests []commandTestCase) {
179178
// testCommandHelp tests the help functionality for a command.
180179
func testCommandHelp(t *testing.T, command string, expectedMessages []string) {
181180
var stdoutBuf, stderrBuf bytes.Buffer
182-
exitCode := runCLICommand(t, []string{command, "-h"}, &stdoutBuf, &stderrBuf, 5*time.Second)
181+
exitCode := runCLICommand([]string{command, "-h"}, &stdoutBuf, &stderrBuf, 5*time.Second)
183182
output := stdoutBuf.String() + stderrBuf.String()
184183

185184
if exitCode != 0 {

0 commit comments

Comments
 (0)