package pacer import ( "context" "testing" "time" ) func TestPacer_Instant(t *testing.T) { p := New(100, 0) // 100/s, no ramp ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel() ch, stop := p.Tick(ctx) defer stop() var count int for range ch { count++ if count > 25 { // Should fire ~20 times in 200ms at 100/s break } } if count < 15 { t.Errorf("instant pacer: expected ≥15 ticks in 200ms, got %d", count) } } func TestPacer_RampLinear(t *testing.T) { // This test uses a mock clock that advances with each call. // Total wall clock needed: 100ms (ramp) + 200ms (steady) = 300ms. p := New(100, 100*time.Millisecond) // 100/s, 100ms ramp clockMs := 0 p.Clock = func() time.Time { clockMs += 10 // each p.Clock call advances 10ms return time.Date(2000, 1, 1, 0, 0, 0, clockMs*1_000_000, time.UTC) } ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() ch, stop := p.Tick(ctx) defer stop() var count int for range ch { count++ if count > 50 { break } } // With mock clock: ramp phase (100ms) + steady phase (~200ms of ticks) // At 100/s, 1 tick per 10ms. In 300ms mock time, should get ~30 ticks. if count < 20 { t.Errorf("ramp pacer: expected ≥20 ticks in 300ms mock time, got %d", count) } } func TestPacer_Overshoot(t *testing.T) { // rate=0 should return a ticker that essentially never fires. p := New(0, 0) ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) defer cancel() ch, stop := p.Tick(ctx) defer stop() select { case _, ok := <-ch: if ok { t.Error("channel should be closed for rate=0") } case <-time.After(60 * time.Millisecond): // expected — no ticks fired } } func TestPacer_ContextCancel(t *testing.T) { p := New(10_000, 0) // fast rate ctx, cancel := context.WithCancel(context.Background()) ch, stop := p.Tick(ctx) // Cancel immediately cancel() stop() // Channel should close (no tick sent after cancel) select { case _, ok := <-ch: if ok { t.Error("channel should be closed after context cancel") } case <-time.After(50 * time.Millisecond): // ok — closed within 50ms } }