Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-resilience

Resilience pipelines for Pudu: retry, circuit breaker, timeout, fallback, hedging, rate limiting, and chaos injection

0.1.0Apache-2.01

InstallClose

BackoffTest.pudu

Pudu58 lines3.6 KB

GitHub ↗
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 Numeric89/// A plan with the given shape and base delay, uncapped and without jitter.10fn plan(shape: Backoff.Shape, delay: Int) -> Backoff.Plan {11  Backoff.Plan{shape: shape, delay: delay, maxDelay: None, useJitter: false}12}1314/// The first `count` delays of a plan, drawing `draw` every time.15fn 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}2728/// Runs the suite.29fn 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