Files
picoclaw/pkg/events/events_test.go
T
Hoshina eedebabbea feat(events): add runtime event bus
Introduce pkg/events with filtered channels, subscription policies, backpressure, and stats. Wire AgentLoop to dual-publish legacy agent events into runtime events while preserving old event APIs.

Validation: go test ./pkg/events/... ./pkg/agent; go test -race ./pkg/events/...; make lint
2026-04-26 15:36:03 +08:00

156 lines
3.9 KiB
Go

package events
import (
"context"
"testing"
"time"
)
func TestPublishDeliversToMatchingSubscriber(t *testing.T) {
t.Parallel()
bus := NewBus()
defer closeBus(t, bus)
_, ch, err := bus.Channel().OfKind(KindAgentTurnStart).SubscribeChan(
context.Background(),
SubscribeOptions{Name: "turn-starts", Buffer: 1},
)
if err != nil {
t.Fatalf("SubscribeChan failed: %v", err)
}
unmatched := bus.Publish(context.Background(), Event{Kind: KindAgentTurnEnd})
if unmatched.Matched != 0 || unmatched.Delivered != 0 {
t.Fatalf("unmatched Publish = %+v, want no delivery", unmatched)
}
result := bus.Publish(context.Background(), Event{Kind: KindAgentTurnStart})
if result.Matched != 1 || result.Delivered != 1 || result.Dropped != 0 {
t.Fatalf("Publish = %+v, want one delivered event", result)
}
evt := receiveEvent(t, ch)
if evt.Kind != KindAgentTurnStart {
t.Fatalf("event kind = %q, want %q", evt.Kind, KindAgentTurnStart)
}
if evt.ID == "" {
t.Fatal("event ID is empty")
}
if evt.Time.IsZero() {
t.Fatal("event Time is zero")
}
}
func TestDropNewestIncrementsStats(t *testing.T) {
t.Parallel()
bus := NewBus()
defer closeBus(t, bus)
sub, _, err := bus.Channel().SubscribeChan(
context.Background(),
SubscribeOptions{Name: "drop-newest", Buffer: 1, Backpressure: DropNewest},
)
if err != nil {
t.Fatalf("SubscribeChan failed: %v", err)
}
first := bus.Publish(context.Background(), Event{Kind: KindAgentTurnStart})
if first.Delivered != 1 || first.Dropped != 0 {
t.Fatalf("first Publish = %+v, want one delivered event", first)
}
second := bus.Publish(context.Background(), Event{Kind: KindAgentTurnEnd})
if second.Delivered != 0 || second.Dropped != 1 {
t.Fatalf("second Publish = %+v, want one dropped event", second)
}
if got := sub.Stats().Dropped; got != 1 {
t.Fatalf("subscription dropped = %d, want 1", got)
}
if got := bus.Stats().Dropped; got != 1 {
t.Fatalf("bus dropped = %d, want 1", got)
}
}
func TestDropOldestKeepsNewestEvent(t *testing.T) {
t.Parallel()
bus := NewBus()
defer closeBus(t, bus)
sub, ch, err := bus.Channel().SubscribeChan(
context.Background(),
SubscribeOptions{Name: "drop-oldest", Buffer: 1, Backpressure: DropOldest},
)
if err != nil {
t.Fatalf("SubscribeChan failed: %v", err)
}
bus.Publish(context.Background(), Event{Kind: Kind("test.old"), Payload: "old"})
result := bus.Publish(context.Background(), Event{Kind: Kind("test.new"), Payload: "new"})
if result.Delivered != 1 || result.Dropped != 1 {
t.Fatalf("Publish = %+v, want replacement delivery", result)
}
evt := receiveEvent(t, ch)
if evt.Payload != "new" {
t.Fatalf("payload = %v, want new", evt.Payload)
}
if got := sub.Stats().Dropped; got != 1 {
t.Fatalf("subscription dropped = %d, want 1", got)
}
}
func TestBlockRespectsContext(t *testing.T) {
t.Parallel()
bus := NewBus()
defer closeBus(t, bus)
_, _, err := bus.Channel().SubscribeChan(
context.Background(),
SubscribeOptions{Name: "block", Buffer: 1, Backpressure: Block},
)
if err != nil {
t.Fatalf("SubscribeChan failed: %v", err)
}
first := bus.Publish(context.Background(), Event{Kind: Kind("test.first")})
if first.Delivered != 1 {
t.Fatalf("first Publish = %+v, want one delivered event", first)
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
defer cancel()
second := bus.Publish(ctx, Event{Kind: Kind("test.second")})
if second.Blocked != 1 || second.Dropped != 1 || second.Delivered != 0 {
t.Fatalf("second Publish = %+v, want one blocked drop", second)
}
}
func receiveEvent(t *testing.T, ch <-chan Event) Event {
t.Helper()
select {
case evt, ok := <-ch:
if !ok {
t.Fatal("event channel closed before receive")
}
return evt
case <-time.After(time.Second):
t.Fatal("timed out waiting for event")
return Event{}
}
}
func closeBus(t *testing.T, bus *EventBus) {
t.Helper()
if err := bus.Close(); err != nil {
t.Fatalf("Close failed: %v", err)
}
}