
BackoffTest.pudu
Pudu58 lines3.6 KB
1/** @Test.Domain.Backoff.Suite — delay growth, caps, jitter, and saturation */2module PuduLangResilience.Domain.BackoffTest34import Std.Io as Io5import Std.Test as Test6import PuduLangResilience.Domain.Backoff as Backoff7import PuduLangResilience.Utils.Numeric as Numeric8910fn plan(shape: Backoff.Shape, delay: Int) -> Backoff.Plan {11 Backoff.Plan{shape: shape, delay: delay, maxDelay: None, useJitter: false}12}131415fn series(given: &Backoff.Plan, count: Int, draw: Float) -> Array[Int] {16 var delays: Array[Int] = []17 var state = 0.018 var attempt = 019 while attempt < count {20 let next = Backoff.next(given, attempt, state, draw)21 delays = delays.push(next[0])22 state = next[1]23 attempt = attempt + 124 }25 delays26}272829fn main() -> Int {30 let jittered = Backoff.Plan{..plan(Backoff.Exponential, 1000), useJitter: true}31 let decorrelated = series(&jittered, 6, 0.5)32 let capped = Backoff.Plan{..jittered, maxDelay: Some(3000)}33 let checks = Test.suite("Domain.Backoff", &[34 Test.equals("a zero delay keeps the jitter state", &Backoff.next(&Backoff.Plan{..jittered, delay: 0}, 3, 1.5, 0.5), &(0, 1.5)),35 Test.equals("exponential growth stops at saturation", &Backoff.steady(&plan(Backoff.Exponential, Numeric.LARGEST), 3), &Numeric.LARGEST),36 Test.equals("constant delays do not grow", &series(&plan(Backoff.Constant, 1000), 4, 0.0), &[1000, 1000, 1000, 1000]),37 Test.equals("linear delays grow by the base", &series(&plan(Backoff.Linear, 1000), 4, 0.0), &[1000, 2000, 3000, 4000]),38 Test.equals("exponential delays double", &series(&plan(Backoff.Exponential, 1000), 5, 0.0), &[1000, 2000, 4000, 8000, 16000]),39 Test.equals("a zero base delay is always zero", &series(&Backoff.Plan{..plan(Backoff.Exponential, 0), useJitter: true}, 3, 0.9), &[0, 0, 0]),40 Test.equals("the ceiling caps every delay", &series(&Backoff.Plan{..plan(Backoff.Exponential, 1000), maxDelay: Some(4500)}, 5, 0.0), &[1000, 2000, 4000, 4500, 4500]),41 Test.equals("constant jitter spans three quarters to five quarters", &[series(&Backoff.Plan{..plan(Backoff.Constant, 1000), useJitter: true}, 1, 0.0), series(&Backoff.Plan{..plan(Backoff.Constant, 1000), useJitter: true}, 1, 0.999)], &[[750], [1249]]),42 Test.equals("linear jitter spreads around the linear delay", &series(&Backoff.Plan{..plan(Backoff.Linear, 1000), useJitter: true}, 2, 0.5), &[1000, 2000]),43 Test.that("decorrelated jitter grows overall", decorrelated[5] > decorrelated[0]),44 Test.all("decorrelated jitter is never negative", &decorrelated, |delay: Int| delay >= 0),45 Test.all("decorrelated jitter respects the ceiling", &series(&capped, 8, 0.7), |delay: Int| delay <= 3000),46 Test.equals("the first decorrelated delay follows the curve", &decorrelated[0], &897),47 Test.equals("exponential growth saturates instead of overflowing", &Backoff.steady(&plan(Backoff.Exponential, 1000), 200), &Numeric.LARGEST),48 Test.equals("linear growth saturates instead of overflowing", &Backoff.steady(&plan(Backoff.Linear, Numeric.LARGEST / 2), 5), &Numeric.LARGEST),49 Test.equals("a curve that is no longer finite saturates", &Backoff.next(&jittered, 2000, 0.0, 0.5)[0], &Numeric.LARGEST),50 Test.equals("saturated delays still meet the ceiling", &Backoff.next(&Backoff.Plan{..jittered, maxDelay: Some(86400000)}, 2000, 0.0, 0.5)[0], &86400000)51 ])52 let ran = Test.run(&checks)53 for failure in Test.failuresOf(&ran) {54 let _reported = Io.writeErrorLine(failure)55 }56 Test.report(&ran)57}58