iop/packages/go/agentruntime/emitter_test.go

102 lines
2.6 KiB
Go

package agentruntime
import (
"context"
"sync"
"testing"
)
type eventRecorder struct {
events []RuntimeEvent
}
func (r *eventRecorder) Emit(_ context.Context, event RuntimeEvent) error {
r.events = append(r.events, event)
return nil
}
func TestTerminalEmitterForwardsExactlyOneTerminal(t *testing.T) {
recorder := &eventRecorder{}
emitter := NewTerminalEmitter(recorder)
events := []RuntimeEvent{
{RunID: "run-1", Type: EventTypeStart},
{RunID: "run-1", Type: EventTypeDelta, Delta: "ok"},
{RunID: "run-1", Type: EventTypeComplete},
{RunID: "run-1", Type: EventTypeError, Error: "late"},
{RunID: "run-1", Type: EventTypeDelta, Delta: "late"},
}
for _, event := range events {
if err := emitter.Emit(context.Background(), event); err != nil {
t.Fatalf("Emit() error = %v", err)
}
}
if !emitter.TerminalObserved() {
t.Fatal("terminal was not observed")
}
if len(recorder.events) != 3 {
t.Fatalf("event count = %d, want 3: %#v", len(recorder.events), recorder.events)
}
if recorder.events[2].Type != EventTypeComplete {
t.Fatalf("terminal = %q, want %q", recorder.events[2].Type, EventTypeComplete)
}
}
type blockingSink struct {
mu sync.Mutex
events []RuntimeEvent
firstEntered chan struct{}
releaseFirst chan struct{}
}
func (s *blockingSink) Emit(_ context.Context, event RuntimeEvent) error {
s.mu.Lock()
first := len(s.events) == 0
s.events = append(s.events, event)
s.mu.Unlock()
if first {
close(s.firstEntered)
<-s.releaseFirst
}
return nil
}
func TestTerminalEmitterPreservesConcurrentAcceptedOrder(t *testing.T) {
sink := &blockingSink{
firstEntered: make(chan struct{}),
releaseFirst: make(chan struct{}),
}
emitter := NewTerminalEmitter(sink)
deltaDone := make(chan error, 1)
go func() {
deltaDone <- emitter.Emit(context.Background(), RuntimeEvent{RunID: "run-1", Type: EventTypeDelta, Delta: "first"})
}()
<-sink.firstEntered // Delta has entered sink.Emit and is blocked.
completeDone := make(chan error, 1)
go func() {
completeDone <- emitter.Emit(context.Background(), RuntimeEvent{RunID: "run-1", Type: EventTypeComplete})
}()
close(sink.releaseFirst)
if err := <-deltaDone; err != nil {
t.Fatalf("delta Emit failed: %v", err)
}
if err := <-completeDone; err != nil {
t.Fatalf("complete Emit failed: %v", err)
}
sink.mu.Lock()
defer sink.mu.Unlock()
if len(sink.events) != 2 {
t.Fatalf("events count = %d, want 2: %#v", len(sink.events), sink.events)
}
if sink.events[0].Type != EventTypeDelta || sink.events[1].Type != EventTypeComplete {
t.Fatalf("events order = [%v, %v], want [EventTypeDelta, EventTypeComplete]", sink.events[0].Type, sink.events[1].Type)
}
}