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

PermitsTest.pudu

Pudu116 lines9.2 KB

GitHub ↗
1/** @Test.Domain.Permits.Suite — grants, queueing, eviction, and each algorithm */2module PuduLangResilience.Domain.PermitsTest34import Std.Io as Io5import Std.Test as Test6import PuduLangResilience.Domain.Algorithms as Algorithms7import PuduLangResilience.Domain.Permits as Permits89/// Runs the suite.10fn main() -> Int {11  let counting = Algorithms.concurrency(2)12  let oldest = Permits.open(2, 2, 2, Permits.Oldest)13  let first = Permits.request(&oldest, &counting, 0, 2, true)14  let queuedOne = Permits.request(&first[0], &counting, 0, 1, true)15  let queuedTwo = Permits.request(&queuedOne[0], &counting, 0, 1, true)16  let overflow = Permits.request(&queuedTwo[0], &counting, 0, 1, true)17  let blockedTurn = Permits.poll(&queuedTwo[0], &counting, 0, 2)18  let returned = Permits.giveBack(&queuedTwo[0], &counting, 2)19  let secondFirst = Permits.poll(&returned, &counting, 0, 2)20  let oldestTurn = Permits.poll(&returned, &counting, 0, 1)21  let newest = Permits.open(0, 2, 2, Permits.Newest)22  let n1 = Permits.request(&newest, &counting, 0, 1, true)23  let n2 = Permits.request(&n1[0], &counting, 0, 1, true)24  let n3 = Permits.request(&n2[0], &counting, 0, 1, true)25  let evictedTurn = Permits.poll(&n3[0], &counting, 0, 1)26  let refilled = Permits.giveBack(&n3[0], &counting, 1)27  let newestTurn = Permits.poll(&refilled, &counting, 0, 3)28  let withdrawn = Permits.withdraw(&n3[0], 3)2930  let bucket = Algorithms.tokenBucket(10, 5, 100, true)31  let fullBucket = Permits.open(Algorithms.Bucket{tokens: 10, stamp: 0}, 10, 0, Permits.Oldest)32  let drained = Permits.request(&fullBucket, &bucket, 0, 10, false)33  let dry = Permits.request(&drained[0], &bucket, 30, 7, false)34  let manualBucket = Algorithms.tokenBucket(10, 4, 100, false)35  let empty = Permits.open(Algorithms.Bucket{tokens: 0, stamp: 0}, 10, 0, Permits.Oldest)3637  let fixed = Algorithms.fixedWindow(3, 1000, true)38  let windowBook = Permits.open(Algorithms.Window{used: 0, start: 0}, 3, 0, Permits.Oldest)39  let usedUp = Permits.request(&windowBook, &fixed, 100, 3, false)40  let overWindow = Permits.request(&usedUp[0], &fixed, 400, 1, false)41  let nextWindow = Permits.request(&usedUp[0], &fixed, 2500, 1, false)4243  let sliding = Algorithms.slidingWindow(4, 1000, 4, true)44  let slideBook = Permits.open(Algorithms.Segments{counts: [0, 0, 0, 0], start: 0}, 4, 0, Permits.Oldest)45  let early = Permits.request(&slideBook, &sliding, 0, 3, false)46  let late = Permits.request(&early[0], &sliding, 600, 1, false)47  let full = Permits.request(&late[0], &sliding, 700, 1, false)4849  let twoEvicted = Permits.request(&Permits.request(&n3[0], &counting, 0, 1, true)[0], &counting, 0, 1, true)50  let firstEvictedSeen = Permits.poll(&twoEvicted[0], &counting, 0, 1)51  let secondEvictedSeen = Permits.poll(&firstEvictedSeen[0], &counting, 0, 2)52  let withdrawnKeepsOthers = Permits.poll(&withdrawn, &counting, 0, 1)53  let newestFree = Permits.open(1, 2, 2, Permits.Newest)54  let newestWaiting = Permits.request(&Permits.request(&newestFree, &counting, 0, 2, true)[0], &counting, 0, 1, true)55  let newestPasses = Permits.request(&Permits.giveBack(&newestWaiting[0], &counting, 1), &counting, 0, 1, true)56  let oldestFree = Permits.open(1, 2, 3, Permits.Oldest)57  let oldestWaiting = Permits.request(&oldestFree, &counting, 0, 2, true)58  let oldestQueues = Permits.request(&Permits.giveBack(&oldestWaiting[0], &counting, 1), &counting, 0, 1, true)59  let manualWindow = Algorithms.fixedWindow(3, 1000, false)60  let spentWindow = Permits.request(&Permits.open(Algorithms.Window{used: 0, start: 0}, 3, 0, Permits.Oldest), &manualWindow, 0, 3, false)[0]61  let manualSliding = Algorithms.slidingWindow(2, 1000, 2, false)62  let spentSliding = Permits.request(&Permits.open(Algorithms.Segments{counts: [0, 0], start: 0}, 2, 0, Permits.Oldest), &manualSliding, 0, 2, false)[0]63  let checks = Test.suite("Domain.Permits", &[64      Test.not("a concurrency limiter refuses replenishment by hand", Permits.replenish(&oldest, &counting)[1]),65      Test.not("an automatic window refuses replenishment by hand", Permits.replenish(&windowBook, &fixed)[1]),66      Test.equals("a window replenished by hand does not move with time", &Permits.available(&spentWindow, &manualWindow, 99999), &0),67      Test.equals("replenishing a window by hand empties it", &(Permits.replenish(&spentWindow, &manualWindow)[1], Permits.available(&Permits.replenish(&spentWindow, &manualWindow)[0], &manualWindow, 0)), &(true, 3)),68      Test.not("an automatic sliding window refuses replenishment by hand", Permits.replenish(&slideBook, &sliding)[1]),69      Test.equals("a sliding window replenished by hand does not move with time", &Permits.available(&spentSliding, &manualSliding, 99999), &0),70      Test.equals("replenishing a sliding window by hand moves it one segment", &(Permits.replenish(&spentSliding, &manualSliding)[1], Permits.available(&Permits.replenish(&spentSliding, &manualSliding)[0], &manualSliding, 0)), &(true, 0)),71      Test.equals("a second hand replenishment frees the oldest segment", &Permits.available(&Permits.replenish(&Permits.replenish(&spentSliding, &manualSliding)[0], &manualSliding)[0], &manualSliding, 0), &2),72      Test.equals("a manual window gives no retry time", &Permits.request(&spentWindow, &manualWindow, 0, 1, false)[1], &Permits.Deny(None)),73      Test.equals("each evicted waiter learns of it separately", &(firstEvictedSeen[1], secondEvictedSeen[1]), &(Permits.Evicted, Permits.Evicted)),74      Test.equals("withdrawing one waiter keeps other evictions", &withdrawnKeepsOthers[1], &Permits.Evicted),75      Test.equals("newest first grants a new request past older waiters", &newestPasses[1], &Permits.Grant),76      Test.equals("oldest first queues a new request behind older waiters", &oldestQueues[1], &Permits.Queue(2)),77      Test.equals("available permits are granted", &first[1], &Permits.Grant),78      Test.equals("a request without permits joins the queue", &(queuedOne[1], queuedTwo[1]), &(Permits.Queue(1), Permits.Queue(2))),79      Test.equals("a full queue denies with no retry time", &overflow[1], &Permits.Deny(None)),80      Test.equals("queued permits are counted", &Permits.queued(&queuedTwo[0]), &2),81      Test.equals("a waiter without permits keeps waiting", &blockedTurn[1], &Permits.Waiting),82      Test.equals("a later waiter may not pass the oldest", &secondFirst[1], &Permits.Waiting),83      Test.equals("the oldest waiter is served first", &oldestTurn[1], &Permits.Taken),84      Test.equals("a request above the permit limit is denied", &Permits.request(&oldest, &counting, 0, 3, true)[1], &Permits.Deny(None)),85      Test.equals("a negative request is denied", &Permits.request(&oldest, &counting, 0, -1, true)[1], &Permits.Deny(None)),86      Test.equals("a request for no permits is granted while any is free", &Permits.request(&oldest, &counting, 0, 0, false)[1], &Permits.Grant),87      Test.equals("a request for no permits is denied when none is free", &Permits.request(&first[0], &counting, 0, 0, false)[1], &Permits.Deny(None)),88      Test.equals("a request that may not queue is denied", &Permits.request(&first[0], &counting, 0, 1, false)[1], &Permits.Deny(None)),89      Test.equals("newest first evicts the oldest waiter", &n3[1], &Permits.Queue(3)),90      Test.equals("an evicted waiter learns it on its next turn", &evictedTurn[1], &Permits.Evicted),91      Test.equals("an eviction counts as a failure", &n3[0].failed, &1),92      Test.equals("the newest waiter is served first", &newestTurn[1], &Permits.Taken),93      Test.equals("a withdrawn waiter leaves the queue and counts as a failure", &(Permits.queued(&withdrawn), withdrawn.failed), &(1, 2)),94      Test.equals("returned concurrency permits never exceed the limit", &Permits.available(&Permits.giveBack(&oldest, &counting, 5), &counting, 0), &2),95      Test.equals("grants are counted", &drained[0].succeeded, &1),96      Test.equals("an empty bucket tells when enough tokens return", &dry[1], &Permits.Deny(Some(170))),97      Test.equals("tokens return per whole period", &Permits.available(&drained[0], &bucket, 250), &10),98      Test.equals("tokens never exceed the limit", &Permits.available(&fullBucket, &bucket, 100000), &10),99      Test.equals("a bucket replenished by hand does not refill with time", &Permits.available(&empty, &manualBucket, 100000), &0),100      Test.equals("a bucket replenished by hand refills one period at a time", &Permits.available(&Permits.replenish(&empty, &manualBucket)[0], &manualBucket, 0), &4),101      Test.equals("a bucket replenished by hand gives no retry time", &Permits.request(&empty, &manualBucket, 0, 1, false)[1], &Permits.Deny(None)),102      Test.not("an automatic bucket refuses replenishment by hand", Permits.replenish(&fullBucket, &bucket)[1]),103      Test.equals("a used-up window denies until it ends", &overWindow[1], &Permits.Deny(Some(600))),104      Test.equals("a new window grants again", &nextWindow[1], &Permits.Grant),105      Test.equals("a sliding window counts every segment still inside it", &full[1], &Permits.Deny(Some(300))),106      Test.equals("permits in the newest segment were taken", &late[1], &Permits.Grant),107      Test.equals("shifting past every segment empties them", &Algorithms.shifted(&[1, 2, 3], 7), &[0, 0, 0]),108      Test.equals("shifting drops the oldest segments", &Algorithms.shifted(&[1, 2, 3], 1), &[2, 3, 0])109    ])110  let ran = Test.run(&checks)111  for failure in Test.failuresOf(&ran) {112    let _reported = Io.writeErrorLine(failure)113  }114  Test.report(&ran)115}116