mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
eedebabbea
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
156 lines
3.9 KiB
Go
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)
|
|
}
|
|
}
|