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
157 lines
3.2 KiB
Go
157 lines
3.2 KiB
Go
package events
|
|
|
|
import (
|
|
"context"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestSubscribeOnceClosesAfterFirstEvent(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
bus := NewBus()
|
|
defer closeBus(t, bus)
|
|
|
|
var handled atomic.Uint64
|
|
sub, err := bus.Channel().SubscribeOnce(
|
|
context.Background(),
|
|
SubscribeOptions{Name: "once", Buffer: 2},
|
|
func(context.Context, Event) error {
|
|
handled.Add(1)
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("SubscribeOnce failed: %v", err)
|
|
}
|
|
|
|
bus.Publish(context.Background(), Event{Kind: KindAgentTurnStart})
|
|
waitForSubscriptionDone(t, sub)
|
|
bus.Publish(context.Background(), Event{Kind: KindAgentTurnEnd})
|
|
|
|
if got := handled.Load(); got != 1 {
|
|
t.Fatalf("handled = %d, want 1", got)
|
|
}
|
|
if got := sub.Stats().Handled; got != 1 {
|
|
t.Fatalf("subscription handled = %d, want 1", got)
|
|
}
|
|
}
|
|
|
|
func TestUnsubscribeClosesChannel(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
bus := NewBus()
|
|
defer closeBus(t, bus)
|
|
|
|
sub, ch, err := bus.Channel().SubscribeChan(context.Background(), SubscribeOptions{Name: "chan"})
|
|
if err != nil {
|
|
t.Fatalf("SubscribeChan failed: %v", err)
|
|
}
|
|
if err := sub.Close(); err != nil {
|
|
t.Fatalf("Close failed: %v", err)
|
|
}
|
|
|
|
select {
|
|
case _, ok := <-ch:
|
|
if ok {
|
|
t.Fatal("channel is open, want closed")
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("timed out waiting for channel close")
|
|
}
|
|
waitForSubscriptionDone(t, sub)
|
|
}
|
|
|
|
func TestHandlerPanicRecovered(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
bus := NewBus()
|
|
defer closeBus(t, bus)
|
|
|
|
sub, err := bus.Channel().Subscribe(
|
|
context.Background(),
|
|
SubscribeOptions{Name: "panic", Buffer: 1},
|
|
func(context.Context, Event) error {
|
|
panic("boom")
|
|
},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Subscribe failed: %v", err)
|
|
}
|
|
|
|
bus.Publish(context.Background(), Event{Kind: KindAgentError})
|
|
waitForStat(t, func() uint64 {
|
|
return sub.Stats().Panicked
|
|
}, 1)
|
|
}
|
|
|
|
func TestLockedHandlerProcessesSequentially(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
bus := NewBus()
|
|
defer closeBus(t, bus)
|
|
|
|
var active atomic.Int64
|
|
var maxActive atomic.Int64
|
|
sub, err := bus.Channel().Subscribe(
|
|
context.Background(),
|
|
SubscribeOptions{Name: "locked", Buffer: 8, Concurrency: Locked},
|
|
func(context.Context, Event) error {
|
|
current := active.Add(1)
|
|
for {
|
|
currentMax := maxActive.Load()
|
|
if current <= currentMax || maxActive.CompareAndSwap(currentMax, current) {
|
|
break
|
|
}
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
active.Add(-1)
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Subscribe failed: %v", err)
|
|
}
|
|
|
|
for i := 0; i < 5; i++ {
|
|
bus.Publish(context.Background(), Event{Kind: KindAgentLLMDelta})
|
|
}
|
|
waitForStat(t, func() uint64 {
|
|
return sub.Stats().Handled
|
|
}, 5)
|
|
|
|
if got := maxActive.Load(); got != 1 {
|
|
t.Fatalf("max active handlers = %d, want 1", got)
|
|
}
|
|
}
|
|
|
|
func waitForSubscriptionDone(t *testing.T, sub Subscription) {
|
|
t.Helper()
|
|
|
|
select {
|
|
case <-sub.Done():
|
|
case <-time.After(time.Second):
|
|
t.Fatal("timed out waiting for subscription to stop")
|
|
}
|
|
}
|
|
|
|
func waitForStat(t *testing.T, stat func() uint64, want uint64) {
|
|
t.Helper()
|
|
|
|
deadline := time.After(time.Second)
|
|
ticker := time.NewTicker(time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
if got := stat(); got >= want {
|
|
return
|
|
}
|
|
select {
|
|
case <-ticker.C:
|
|
case <-deadline:
|
|
t.Fatalf("timed out waiting for stat >= %d", want)
|
|
}
|
|
}
|
|
}
|