package testutil import ( "context" "os" "runtime" "strings" "sync" "testing" "time" noderuntime "iop/packages/go/agentruntime" ) type FakeSink struct { mu sync.Mutex events []noderuntime.RuntimeEvent } func (f *FakeSink) Emit(_ context.Context, e noderuntime.RuntimeEvent) error { f.mu.Lock() defer f.mu.Unlock() f.events = append(f.events, e) return nil } func (f *FakeSink) Events() []noderuntime.RuntimeEvent { f.mu.Lock() defer f.mu.Unlock() cp := make([]noderuntime.RuntimeEvent, len(f.events)) copy(cp, f.events) return cp } func RequireUnixShell(t *testing.T) { t.Helper() if runtime.GOOS == "windows" { t.Skip("Unix shell required") } } func RequirePTYSupport(t *testing.T) { t.Helper() if runtime.GOOS == "windows" { t.Skip("PTY not supported on Windows") } } func CollectDeltas(events []noderuntime.RuntimeEvent) string { deltas := make([]string, 0, len(events)) for _, e := range events { if e.Type == noderuntime.EventTypeDelta { deltas = append(deltas, e.Delta) } } return strings.Join(deltas, "") } func RequireEventually(t *testing.T, timeout, interval time.Duration, fn func() bool) { t.Helper() timer := time.NewTimer(timeout) defer timer.Stop() ticker := time.NewTicker(interval) defer ticker.Stop() for { if fn() { return } select { case <-timer.C: t.Fatal("condition not satisfied within timeout") case <-ticker.C: } } } func ReadMarker(path string) string { data, err := os.ReadFile(path) if err != nil { return "" } return strings.TrimSpace(string(data)) }