diff --git a/cmd/picoclaw-launcher-tui/internal/ui/gateway_posix.go b/cmd/picoclaw-launcher-tui/internal/ui/gateway_posix.go index bc874f7f2..143e5558a 100644 --- a/cmd/picoclaw-launcher-tui/internal/ui/gateway_posix.go +++ b/cmd/picoclaw-launcher-tui/internal/ui/gateway_posix.go @@ -5,12 +5,14 @@ package ui import "os/exec" +var execCommand = exec.Command + func isGatewayProcessRunning() bool { - cmd := exec.Command("sh", "-c", "pgrep -f 'picoclaw\\s+gateway' >/dev/null 2>&1") + cmd := execCommand("sh", "-c", "pgrep -f 'picoclaw\\s+gateway' >/dev/null 2>&1") return cmd.Run() == nil } func stopGatewayProcess() error { - cmd := exec.Command("sh", "-c", "pkill -f 'picoclaw\\s+gateway' >/dev/null 2>&1") + cmd := execCommand("sh", "-c", "pkill -f 'picoclaw\\s+gateway' >/dev/null 2>&1") return cmd.Run() } diff --git a/cmd/picoclaw-launcher-tui/internal/ui/gateway_windows.go b/cmd/picoclaw-launcher-tui/internal/ui/gateway_windows.go index 7067a5c13..8b4c12096 100644 --- a/cmd/picoclaw-launcher-tui/internal/ui/gateway_windows.go +++ b/cmd/picoclaw-launcher-tui/internal/ui/gateway_windows.go @@ -5,12 +5,14 @@ package ui import "os/exec" +var execCommand = exec.Command + func isGatewayProcessRunning() bool { - cmd := exec.Command("tasklist", "/FI", "IMAGENAME eq picoclaw.exe") + cmd := execCommand("tasklist", "/FI", "IMAGENAME eq picoclaw.exe") return cmd.Run() == nil } func stopGatewayProcess() error { - cmd := exec.Command("taskkill", "/F", "/IM", "picoclaw.exe") + cmd := execCommand("taskkill", "/F", "/IM", "picoclaw.exe") return cmd.Run() } diff --git a/cmd/picoclaw-launcher-tui/internal/ui/gateway_windows_test.go b/cmd/picoclaw-launcher-tui/internal/ui/gateway_windows_test.go new file mode 100644 index 000000000..645ae602b --- /dev/null +++ b/cmd/picoclaw-launcher-tui/internal/ui/gateway_windows_test.go @@ -0,0 +1,131 @@ +//go:build windows +// +build windows + +package ui + +import ( + "os" + "os/exec" + "reflect" + "strconv" + "testing" +) + +func TestIsGatewayProcessRunning(t *testing.T) { + origExecCommand := execCommand + defer func() { execCommand = origExecCommand }() + + tests := []struct { + name string + exitCode int + wantResult bool + }{ + { + name: "running", + exitCode: 0, + wantResult: true, + }, + { + name: "not running", + exitCode: 1, + wantResult: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var gotName string + var gotArgs []string + + execCommand = func(name string, args ...string) *exec.Cmd { + gotName = name + gotArgs = args + + cmd := exec.Command(os.Args[0], "-test.run=TestHelperProcess", "--") + cmd.Env = append(os.Environ(), + "GO_WANT_HELPER_PROCESS=1", + "GO_WANT_HELPER_PROCESS_EXIT_CODE="+strconv.Itoa(tt.exitCode), + ) + return cmd + } + + got := isGatewayProcessRunning() + if got != tt.wantResult { + t.Errorf("isGatewayProcessRunning() = %v, want %v", got, tt.wantResult) + } + if gotName != "tasklist" { + t.Errorf("expected command name tasklist, got %s", gotName) + } + expectedArgs := []string{"/FI", "IMAGENAME eq picoclaw.exe"} + if !reflect.DeepEqual(gotArgs, expectedArgs) { + t.Errorf("expected args %v, got %v", expectedArgs, gotArgs) + } + }) + } +} + +func TestStopGatewayProcess(t *testing.T) { + origExecCommand := execCommand + defer func() { execCommand = origExecCommand }() + + tests := []struct { + name string + exitCode int + wantErr bool + }{ + { + name: "success", + exitCode: 0, + wantErr: false, + }, + { + name: "failure", + exitCode: 1, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var gotName string + var gotArgs []string + + execCommand = func(name string, args ...string) *exec.Cmd { + gotName = name + gotArgs = args + + cmd := exec.Command(os.Args[0], "-test.run=TestHelperProcess", "--") + cmd.Env = append(os.Environ(), + "GO_WANT_HELPER_PROCESS=1", + "GO_WANT_HELPER_PROCESS_EXIT_CODE="+strconv.Itoa(tt.exitCode), + ) + return cmd + } + + err := stopGatewayProcess() + if (err != nil) != tt.wantErr { + t.Errorf("stopGatewayProcess() error = %v, wantErr %v", err, tt.wantErr) + } + if gotName != "taskkill" { + t.Errorf("expected command name taskkill, got %s", gotName) + } + expectedArgs := []string{"/F", "/IM", "picoclaw.exe"} + if !reflect.DeepEqual(gotArgs, expectedArgs) { + t.Errorf("expected args %v, got %v", expectedArgs, gotArgs) + } + }) + } +} + +func TestHelperProcess(t *testing.T) { + if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { + return + } + defer os.Exit(0) + + if code := os.Getenv("GO_WANT_HELPER_PROCESS_EXIT_CODE"); code != "" { + c, _ := strconv.Atoi(code) + os.Exit(c) + } + os.Exit(0) +}