741fdcee24
Process spawn/wait/kill functions for subprocess management with output capture, timeout, and process group cleanup. DagRun and DagStepResult types for SQLite execution persistence. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
108 lines
2.7 KiB
Go
108 lines
2.7 KiB
Go
package infra
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestProcessSpawn(t *testing.T) {
|
|
t.Run("spawn and wait echo", func(t *testing.T) {
|
|
h, err := ProcessSpawn("echo hello", "", nil, "")
|
|
if err != nil {
|
|
t.Fatalf("spawn: %v", err)
|
|
}
|
|
res, err := ProcessWait(h, 10)
|
|
if err != nil {
|
|
t.Fatalf("wait: %v", err)
|
|
}
|
|
if res.ExitCode != 0 {
|
|
t.Errorf("exit code: got %d, want 0", res.ExitCode)
|
|
}
|
|
if !strings.Contains(res.Stdout, "hello") {
|
|
t.Errorf("stdout: got %q, want it to contain 'hello'", res.Stdout)
|
|
}
|
|
})
|
|
|
|
t.Run("spawn with timeout kills", func(t *testing.T) {
|
|
h, err := ProcessSpawn("sleep 60", "", nil, "")
|
|
if err != nil {
|
|
t.Fatalf("spawn: %v", err)
|
|
}
|
|
res, err := ProcessWait(h, 2)
|
|
if err != nil {
|
|
t.Fatalf("wait: %v", err)
|
|
}
|
|
if !res.Killed {
|
|
t.Errorf("killed: got false, want true")
|
|
}
|
|
if res.ExitCode == 0 {
|
|
t.Errorf("exit code: got 0, want != 0 after kill")
|
|
}
|
|
})
|
|
|
|
t.Run("spawn with env", func(t *testing.T) {
|
|
h, err := ProcessSpawn("echo $TEST_VAR", "", []string{"PATH=/usr/bin:/bin", "TEST_VAR=hello123"}, "")
|
|
if err != nil {
|
|
t.Fatalf("spawn: %v", err)
|
|
}
|
|
res, err := ProcessWait(h, 10)
|
|
if err != nil {
|
|
t.Fatalf("wait: %v", err)
|
|
}
|
|
if !strings.Contains(res.Stdout, "hello123") {
|
|
t.Errorf("stdout: got %q, want it to contain 'hello123'", res.Stdout)
|
|
}
|
|
})
|
|
|
|
t.Run("spawn script", func(t *testing.T) {
|
|
script := "#!/bin/sh\necho line1\necho line2"
|
|
h, err := ProcessSpawn(script, "", nil, "")
|
|
if err != nil {
|
|
t.Fatalf("spawn: %v", err)
|
|
}
|
|
res, err := ProcessWait(h, 10)
|
|
if err != nil {
|
|
t.Fatalf("wait: %v", err)
|
|
}
|
|
if !strings.Contains(res.Stdout, "line1") {
|
|
t.Errorf("stdout: got %q, want it to contain 'line1'", res.Stdout)
|
|
}
|
|
if !strings.Contains(res.Stdout, "line2") {
|
|
t.Errorf("stdout: got %q, want it to contain 'line2'", res.Stdout)
|
|
}
|
|
})
|
|
|
|
t.Run("spawn with working dir", func(t *testing.T) {
|
|
h, err := ProcessSpawn("pwd", "/tmp", nil, "")
|
|
if err != nil {
|
|
t.Fatalf("spawn: %v", err)
|
|
}
|
|
res, err := ProcessWait(h, 10)
|
|
if err != nil {
|
|
t.Fatalf("wait: %v", err)
|
|
}
|
|
if !strings.Contains(res.Stdout, "/tmp") {
|
|
t.Errorf("stdout: got %q, want it to contain '/tmp'", res.Stdout)
|
|
}
|
|
})
|
|
|
|
t.Run("kill process", func(t *testing.T) {
|
|
h, err := ProcessSpawn("sleep 60", "", nil, "")
|
|
if err != nil {
|
|
t.Fatalf("spawn: %v", err)
|
|
}
|
|
if err := ProcessKill(h, 1); err != nil {
|
|
t.Fatalf("kill: %v", err)
|
|
}
|
|
// After kill, Wait should unblock quickly.
|
|
_ = h.Cmd.Wait()
|
|
state := h.Cmd.ProcessState
|
|
if state == nil {
|
|
t.Fatal("process state is nil after kill+wait")
|
|
}
|
|
if state.ExitCode() == 0 {
|
|
t.Errorf("exit code: got 0 after kill, want non-zero")
|
|
}
|
|
})
|
|
}
|