102 lines
2.6 KiB
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)
|
|
}
|
|
}
|