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

RegistryTest.pudu

Pudu78 lines4.6 KB

GitHub ↗
1/** @Test.Registry.Suite — building, sharing, reloading, and naming pipelines */2module PuduLangResilience.RegistryTest34import Std.Io as Io5import Std.Option as Option6import Std.Sync as Sync7import Std.Test as Test8import PuduLangResilience.Context as Context9import PuduLangResilience.Pipeline as Pipeline10import PuduLangResilience.Registry as Registry11import PuduLangResilience as Resilience12import PuduLangResilience.Retry as Retry13import PuduLangResilience.Strategy as Strategy1415/// A builder counting how many pipelines it built and naming them after the key.16fn counting(builds: &Sync.Counter) -> Registry.Builder[Int, Str] {17  let counter = *builds18  fn(context: Registry.BuilderContext) -> Array[Strategy.Strategy[Int, Str]] {19    let _counted = Sync.increment(&counter, 1)20    [Retry.strategy(Retry.Options{..Retry.defaults(), name: Some(context.builderName + "-retry"), delay: 0})]21  }22}2324/// Runs the suite.25fn main() -> Int {26  let registry: Registry.Registry[Int, Str] = Registry.create()27  let builds = Sync.counter(0)28  let added = Registry.tryAddBuilder(&registry, "orders", counting(&builds))29  let addedAgain = Registry.tryAddBuilder(&registry, "orders", counting(&builds))30  let first = Registry.get(&registry, "orders")31  let second = Registry.get(&registry, "orders")32  let buildsAfterTwoGets = Sync.count(&builds)33  let reloaded = Registry.reload(&registry, "orders")34  let buildsAfterReload = Sync.count(&builds)35  let missing = Registry.get(&registry, "payments")36  let viaGetOrAdd = Registry.getOrAdd(&registry, "payments", counting(&builds))37  let invalidAdded = Registry.tryAddBuilder(&registry, "broken", fn(_context: Registry.BuilderContext) -> Array[Strategy.Strategy[Int, Str]] {38      [Retry.strategy(Retry.Options{..Retry.defaults(), maxRetryAttempts: 0})]39    })40  let broken = Registry.get(&registry, "broken")4142  let named: Registry.Registry[Int, Str] = Registry.createWith(Registry.Options{..Registry.defaults(), builderName: fn(key: Str) -> Str { "svc-" + key }, instanceName: fn(key: Str) -> Option[Str] { Some(key + "-1") }})43  let namedPipeline = Registry.getOrAdd(&named, "users", counting(&Sync.counter(0)))44  let description = match namedPipeline { case Ok(pipeline) => Some(Pipeline.describe(&pipeline)) case Err(_) => None }4546  let calls = Sync.counter(0)47  let ran = match first {48    case Ok(pipeline) => Pipeline.execute(&pipeline, fn(_context: Context.Context) -> Resilience.Outcome[Int, Str] {49        let made = match Sync.increment(&calls, 1) { case Ok(n) => n case Err(_) => 0 }50        if made < 2 { Resilience.raise("once") } else { Ok(made) }51      })52    case Err(problem) => Resilience.raise(Registry.explain(&problem))53  }5455  let checks = Test.suite("Registry", &[56      Test.that("a builder is added for a new key", added),57      Test.not("a second builder for the same key is refused", addedAgain),58      Test.that("the pipeline is found", match (first, second) { case (Ok(_), Ok(_)) => true case _ => false }),59      Test.equals("the pipeline is built once and shared", &buildsAfterTwoGets, &Ok(1)),60      Test.that("a reload builds a new pipeline", match reloaded { case Ok(_) => true case Err(_) => false }),61      Test.equals("a reload runs the builder again", &buildsAfterReload, &Ok(2)),62      Test.equals("an unknown key is not found", &match missing { case Err(problem) => Registry.explain(&problem) case Ok(_) => "" }, &"No pipeline builder is registered for 'payments'."),63      Test.that("getOrAdd registers and builds", match viaGetOrAdd { case Ok(_) => true case Err(_) => false }),64      Test.equals("a registered pipeline runs its strategies", &ran, &Ok(2)),65      Test.that("an invalid builder can be registered", invalidAdded),66      Test.equals("an invalid pipeline is explained", &match broken { case Err(problem) => Registry.explain(&problem) case Ok(_) => "" }, &"The pipeline for 'broken' is invalid: Retry: maxRetryAttempts must be at least 1"),67      Test.equals("tryGet answers nothing for an invalid pipeline", &(Registry.tryGet(&registry, "broken") == None), &true),68      Test.equals("every key with a builder is listed", &Registry.keys(&registry), &["broken", "orders", "payments"]),69      Test.equals("keys become builder and instance names", &Option.map(description, |found: Pipeline.Descriptor| (found.name, found.instance)), &Some((Some("svc-users"), Some("users-1")))),70      Test.equals("the builder sees its names", &Option.map(description, |found: Pipeline.Descriptor| found.strategies.map(|layer: Pipeline.Described| layer.name)), &Some([Some("svc-users-retry")]))71    ])72  let report = Test.run(&checks)73  for failure in Test.failuresOf(&report) {74    let _reported = Io.writeErrorLine(failure)75  }76  Test.report(&report)77}78