Files
tutti/services/tuttid/service/agent/codex_appserver_process_unix_test.go
T
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

98 lines
2.6 KiB
Go

//go:build !windows
package agent
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
"testing"
"time"
)
const codexAppServerProcessTreeTestTimeout = 3 * time.Second
func TestCodexCLIModelListerStopsProcessTreeOnTimeout(t *testing.T) {
childPIDPath := filepath.Join(t.TempDir(), "model-child.pid")
scriptPath := writeHangingCodexAppServer(t, childPIDPath)
startedAt := time.Now()
_, err := (CodexCLIModelLister{
Command: scriptPath,
Timeout: codexAppServerProcessTreeTestTimeout,
}).ListModels(context.Background())
if err == nil || !strings.Contains(err.Error(), "model/list timed out") {
t.Fatalf("ListModels error = %v, want model/list timeout", err)
}
if elapsed := time.Since(startedAt); elapsed > 5*time.Second {
t.Fatalf("ListModels returned after %s, want bounded process cleanup", elapsed)
}
assertProcessExited(t, readChildPID(t, childPIDPath))
}
func TestCodexCLICapabilityListerStopsProcessTreeOnTimeout(t *testing.T) {
childPIDPath := filepath.Join(t.TempDir(), "capability-child.pid")
scriptPath := writeHangingCodexAppServer(t, childPIDPath)
startedAt := time.Now()
_, err := (CodexCLICapabilityLister{
Command: scriptPath,
Timeout: codexAppServerProcessTreeTestTimeout,
}).List(context.Background(), "")
if err == nil || !strings.Contains(err.Error(), "capability discovery timed out") {
t.Fatalf("List error = %v, want capability discovery timeout", err)
}
if elapsed := time.Since(startedAt); elapsed > 5*time.Second {
t.Fatalf("List returned after %s, want bounded process cleanup", elapsed)
}
assertProcessExited(t, readChildPID(t, childPIDPath))
}
func writeHangingCodexAppServer(t *testing.T, childPIDPath string) string {
t.Helper()
scriptPath := filepath.Join(t.TempDir(), "codex")
script := fmt.Sprintf(`#!/bin/sh
sleep 5 &
child_pid=$!
printf '%%s\n' "$child_pid" > %q
wait "$child_pid"
`, childPIDPath)
if err := os.WriteFile(scriptPath, []byte(script), 0o755); err != nil {
t.Fatalf("write fake codex app-server: %v", err)
}
return scriptPath
}
func readChildPID(t *testing.T, path string) int {
t.Helper()
raw, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read child pid: %v", err)
}
pid, err := strconv.Atoi(strings.TrimSpace(string(raw)))
if err != nil {
t.Fatalf("parse child pid %q: %v", raw, err)
}
return pid
}
func assertProcessExited(t *testing.T, pid int) {
t.Helper()
deadline := time.Now().Add(2 * time.Second)
for {
err := syscall.Kill(pid, 0)
if errors.Is(err, syscall.ESRCH) {
return
}
if time.Now().After(deadline) {
t.Fatalf("child process %d is still alive after app-server timeout", pid)
}
time.Sleep(10 * time.Millisecond)
}
}