@@ -87,25 +87,43 @@ func TestTokenBucket_ThrottlesAfterBurst(t *testing.T) {
8787// --- SlidingWindowLimiter tests ---
8888
8989func TestSlidingWindow_RateWithinWindow (t * testing.T ) {
90- // 5 RPS, 1-second window: in any 1-second slice, at most 5 grants.
91- // We measure over 2 seconds and expect ~10 total (±4 for timing).
92- l := NewSlidingWindow (5 , 1 )
90+ // Test the core invariant: no more than `allowed` grants in any window.
91+ // Request a fixed number of grants and verify timestamps.
92+ const (
93+ rps = 5
94+ windowSec = 1
95+ totalGrants = 12 // enough to span multiple windows
96+ )
97+
98+ l := NewSlidingWindow (rps , windowSec )
9399 defer l .Stop ()
94100
95- ctx , cancel := context .WithTimeout (context .Background (), 2 * time .Second )
101+ ctx , cancel := context .WithTimeout (context .Background (), 10 * time .Second )
96102 defer cancel ()
97103
98- count := 0
99- for {
104+ grants := make ([]time. Time , 0 , totalGrants )
105+ for i := 0 ; i < totalGrants ; i ++ {
100106 if err := l .Wait (ctx ); err != nil {
101- break
107+ t .Fatalf ("grant %d timed out: %v" , i , err )
108+ }
109+ grants = append (grants , time .Now ())
110+ }
111+
112+ // Check invariant: in any 1-second sliding window, at most `allowed` grants.
113+ allowed := rps * windowSec
114+ for i , ts := range grants {
115+ windowEnd := ts
116+ windowStart := windowEnd .Add (- time .Duration (windowSec ) * time .Second )
117+ count := 0
118+ for _ , g := range grants {
119+ if g .After (windowStart ) && ! g .After (windowEnd ) {
120+ count ++
121+ }
122+ }
123+ if count > allowed + 1 { // +1 tolerance for boundary timing
124+ t .Errorf ("grant %d: %d grants in 1s window ending at %v, want ≤%d" ,
125+ i , count , windowEnd , allowed )
102126 }
103- count ++
104- }
105-
106- // Expect 6–14 grants (5 RPS × 2s = 10, ±40% tolerance for CI timing)
107- if count < 6 || count > 14 {
108- t .Errorf ("sliding window 5 RPS/1s over 2s: got %d, want ~10" , count )
109127 }
110128}
111129
0 commit comments