ca243a2d50
executus CI / test (push) Failing after 24s
Batteries-included agent-harness base, extracted from mort's agent layer. This first cut establishes the module + the zero-coupling core primitives: - lane, dispatchguard, pendingattach, run/progress.go: moved verbatim from mort - config: host config Source seam + env-var default (nil-safe helpers) - deliver: output-egress seam + Discard/Stdout defaults - identity: AdminPolicy + MemberResolver seams (nil-safe) - fanout: programmatic N×M swarm (bounded global + per-key concurrency) - README/CLAUDE.md with the vibe-coded banner; CI with Go gates + the "core stays majordomo+stdlib only" invariant Core builds with stdlib only today; majordomo enters at P1 (model/structured). go build/vet/test -race all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
114 lines
3.0 KiB
Go
114 lines
3.0 KiB
Go
package lane
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// recordingSink captures every RecordLaneSample call.
|
|
type recordingSink struct {
|
|
mu sync.Mutex
|
|
samples []sampleRow
|
|
}
|
|
|
|
type sampleRow struct {
|
|
lane string
|
|
running int
|
|
queued int
|
|
sampledAt time.Time
|
|
}
|
|
|
|
func (r *recordingSink) RecordLaneSample(_ context.Context, lane string, running, queued int, sampledAt time.Time) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.samples = append(r.samples, sampleRow{lane, running, queued, sampledAt})
|
|
return nil
|
|
}
|
|
|
|
// recordingPurger captures PurgeLaneSamples cutoffs.
|
|
type recordingPurger struct {
|
|
mu sync.Mutex
|
|
cutoffs []time.Time
|
|
}
|
|
|
|
func (r *recordingPurger) PurgeLaneSamples(_ context.Context, olderThan time.Time) (int64, error) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.cutoffs = append(r.cutoffs, olderThan)
|
|
return 0, nil
|
|
}
|
|
|
|
func TestSampler_SamplesAllLanes(t *testing.T) {
|
|
reg := NewRegistry(nil)
|
|
reg.GetOrCreate(context.Background(), "ollama")
|
|
reg.GetOrCreate(context.Background(), "anthropic-default")
|
|
|
|
sink := &recordingSink{}
|
|
now := time.Date(2026, 5, 4, 12, 0, 0, 0, time.UTC)
|
|
clock := func() time.Time { return now }
|
|
s := NewSampler(reg, sink, nil, 30*time.Second, 7*24*time.Hour, clock)
|
|
|
|
s.Sample(context.Background())
|
|
|
|
sink.mu.Lock()
|
|
defer sink.mu.Unlock()
|
|
if len(sink.samples) != 2 {
|
|
t.Fatalf("expected 2 samples (one per lane), got %d", len(sink.samples))
|
|
}
|
|
seen := map[string]bool{}
|
|
for _, sm := range sink.samples {
|
|
seen[sm.lane] = true
|
|
if !sm.sampledAt.Equal(now) {
|
|
t.Errorf("expected sampledAt=%v, got %v", now, sm.sampledAt)
|
|
}
|
|
}
|
|
if !seen["ollama"] || !seen["anthropic-default"] {
|
|
t.Fatalf("missing lane: %+v", seen)
|
|
}
|
|
}
|
|
|
|
func TestSampler_PurgeOnceUsesRetentionWindow(t *testing.T) {
|
|
reg := NewRegistry(nil)
|
|
purger := &recordingPurger{}
|
|
now := time.Date(2026, 5, 4, 12, 0, 0, 0, time.UTC)
|
|
clock := func() time.Time { return now }
|
|
s := NewSampler(reg, nil, purger, 30*time.Second, 7*24*time.Hour, clock)
|
|
|
|
s.PurgeOnce(context.Background())
|
|
|
|
purger.mu.Lock()
|
|
defer purger.mu.Unlock()
|
|
if len(purger.cutoffs) != 1 {
|
|
t.Fatalf("expected 1 purge call, got %d", len(purger.cutoffs))
|
|
}
|
|
want := now.Add(-7 * 24 * time.Hour)
|
|
if !purger.cutoffs[0].Equal(want) {
|
|
t.Fatalf("cutoff: want %v, got %v", want, purger.cutoffs[0])
|
|
}
|
|
}
|
|
|
|
func TestSampler_NilSinkOrRegistryIsSafe(t *testing.T) {
|
|
// nil registry — no-op, no panic.
|
|
s := NewSampler(nil, &recordingSink{}, nil, 30*time.Second, 7*24*time.Hour, nil)
|
|
s.Sample(context.Background())
|
|
|
|
// nil sink — no-op.
|
|
reg := NewRegistry(nil)
|
|
reg.GetOrCreate(context.Background(), "ollama")
|
|
s2 := NewSampler(reg, nil, nil, 30*time.Second, 7*24*time.Hour, nil)
|
|
s2.Sample(context.Background())
|
|
}
|
|
|
|
func TestSampler_StartStopIdempotent(t *testing.T) {
|
|
reg := NewRegistry(nil)
|
|
sink := &recordingSink{}
|
|
s := NewSampler(reg, sink, nil, 30*time.Second, 7*24*time.Hour, nil)
|
|
ctx := context.Background()
|
|
s.Start(ctx)
|
|
s.Start(ctx) // second Start is a no-op
|
|
s.Stop()
|
|
s.Stop() // second Stop is a no-op
|
|
}
|