termtap/internal/process/signal_unix_test.go
Hayden Hargreaves 002773e77f test: AI generated all of these tests
Just for the MVP of course. Need to validate the idea.
2026-04-23 19:47:04 -07:00

148 lines
3.4 KiB
Go

//go:build unix
package process
import (
"errors"
"os"
"os/exec"
"syscall"
"testing"
"time"
)
type customSignal struct{}
func (customSignal) String() string { return "custom" }
func (customSignal) Signal() {}
// NOTE: Run these tests with -race in CI for signal/process safety.
func TestConfigureProcessForSignals(t *testing.T) {
t.Parallel()
cmd := exec.Command("sh", "-c", "sleep 0.1")
configureProcessForSignals(cmd)
if cmd.SysProcAttr == nil {
t.Fatal("SysProcAttr is nil")
}
if !cmd.SysProcAttr.Setpgid {
t.Fatal("Setpgid = false, want true")
}
}
func TestSignalProcess_NilSafe(t *testing.T) {
t.Parallel()
if err := SignalProcess(nil, syscall.SIGTERM); err != nil {
t.Fatalf("SignalProcess(nil) error = %v, want nil", err)
}
cmd := &exec.Cmd{}
if err := SignalProcess(cmd, syscall.SIGTERM); err != nil {
t.Fatalf("SignalProcess(cmd without process) error = %v, want nil", err)
}
cmd.Process = &os.Process{Pid: 0}
if err := SignalProcess(cmd, syscall.SIGTERM); err != nil {
t.Fatalf("SignalProcess(pid<=0) error = %v, want nil", err)
}
}
func TestSignalProcess_ESRCHIsTreatedAsSuccess(t *testing.T) {
t.Parallel()
cmd := &exec.Cmd{Process: &os.Process{Pid: 999999}}
if err := SignalProcess(cmd, syscall.SIGTERM); err != nil {
t.Fatalf("SignalProcess() error = %v, want nil when process group not found", err)
}
}
func TestSignalProcess_UsesFallbackOnKillError(t *testing.T) {
t.Parallel()
cmd := exec.Command("sh", "-c", "sleep 5")
configureProcessForSignals(cmd)
if err := cmd.Start(); err != nil {
t.Fatalf("start command error = %v", err)
}
t.Cleanup(func() {
_ = cmd.Process.Kill()
_, _ = cmd.Process.Wait()
})
// Invalid signal causes syscall.Kill to fail with EINVAL, then fallback to cmd.Process.Signal.
err := SignalProcess(cmd, syscall.Signal(9999))
if err == nil {
t.Fatal("SignalProcess() error = nil, want non-nil for invalid signal")
}
if !(errors.Is(err, syscall.EINVAL) || errors.Is(err, os.ErrProcessDone)) {
// OS/process timing can vary; ensure we at least failed predictably.
t.Fatalf("SignalProcess() unexpected error: %v", err)
}
}
func TestSignalProcess_NonSyscallSignalUsesProcessSignal(t *testing.T) {
t.Parallel()
cmd := exec.Command("sh", "-c", "sleep 1")
configureProcessForSignals(cmd)
if err := cmd.Start(); err != nil {
t.Fatalf("start command error = %v", err)
}
t.Cleanup(func() {
_ = cmd.Process.Kill()
_, _ = cmd.Process.Wait()
})
err := SignalProcess(cmd, customSignal{})
if err == nil {
t.Fatal("SignalProcess(custom signal) error = nil, want non-nil")
}
if errors.Is(err, os.ErrProcessDone) {
return
}
if msg := err.Error(); msg == "" {
t.Fatalf("unexpected empty error for custom signal: %v", err)
}
}
func TestProcessAlive(t *testing.T) {
t.Parallel()
if ProcessAlive(nil) {
t.Fatal("ProcessAlive(nil) = true, want false")
}
if ProcessAlive(&exec.Cmd{}) {
t.Fatal("ProcessAlive(cmd without process) = true, want false")
}
cmd := exec.Command("sh", "-c", "sleep 0.2")
configureProcessForSignals(cmd)
if err := cmd.Start(); err != nil {
t.Fatalf("start command error = %v", err)
}
if !ProcessAlive(cmd) {
_ = cmd.Process.Kill()
_, _ = cmd.Process.Wait()
t.Fatal("ProcessAlive(running) = false, want true")
}
_ = cmd.Process.Kill()
_, _ = cmd.Process.Wait()
deadline := time.After(time.Second)
for {
if !ProcessAlive(cmd) {
return
}
select {
case <-deadline:
t.Fatal("ProcessAlive(exited) stayed true")
default:
}
}
}