
TimeoutTest.pudu
Pudu80 lines4.6 KB
1/** @Test.Timeout.Suite — cooperative cancellation at the timeout */2module PuduLangResilience.TimeoutTest34import Std.Concurrent.Cancel as Cancel5import Std.Concurrent as Concurrent6import Std.Io as Io7import Std.Result as Result8import Std.Sync as Sync9import Std.Test as Test10import PuduLangResilience.Context as Context11import PuduLangResilience.Pipeline as Pipeline12import PuduLangResilience as Resilience13import PuduLangResilience.Telemetry as Telemetry14import PuduLangResilience.Timeout as Timeout151617fn bounded(options: Timeout.Options, events: &Sync.Cell[Array[Str]]) -> Pipeline.Pipeline[Int, Str] {18 let sink = *events19 let settings = Pipeline.Options{..Pipeline.defaults(), listeners: [fn(event: Telemetry.Event) -> () {20 let held = match Sync.get(&sink) { case Ok(list) => list case Err(_) => [] }21 let _kept = Sync.set(&sink, held.push(event.name))22 }] }23 match Pipeline.buildWith(&settings, [Timeout.strategy(options)]) {24 case Ok(pipeline) => pipeline25 case Err(invalid) => panic(Pipeline.explain(&invalid))26 }27}282930fn patient(millis: Int) -> fn(Context.Context) -> Resilience.Outcome[Int, Str] {31 fn(context: Context.Context) -> Resilience.Outcome[Int, Str] {32 Context.pause(&context, millis) ?33 Ok(1)34 }35}363738fn main() -> Int {39 let events: Sync.Cell[Array[Str]] = Sync.cell([])40 let noticed: Sync.Cell[Int] = Sync.cell(0)41 let watched = Timeout.Options{..Timeout.after(30), onTimeout: Some(fn(elapsed: Timeout.Elapsed) -> () { let _kept = Sync.set(¬iced, elapsed.timeout) })}42 let slow = Pipeline.execute(&bounded(watched, &events), patient(2000))43 let fast = Pipeline.execute(&bounded(Timeout.after(1000), &Sync.cell([])), patient(1))44 let ignoring = Pipeline.execute(&bounded(Timeout.after(20), &Sync.cell([])), fn(_context: Context.Context) -> Resilience.Outcome[Int, Str] {45 let _slept = Concurrent.sleep(60)46 Ok(2)47 })48 let outer = Cancel.token()49 let _cancelled = Cancel.cancel(&outer, "caller left")50 let byCaller = Pipeline.executeWith(&bounded(Timeout.after(1000), &Sync.cell([])), Context.cancellable(outer), patient(2000))51 let parentDeadline = Pipeline.executeWith(&bounded(Timeout.after(1000), &Sync.cell([])), Context.cancellable(Cancel.expiring(20)), patient(2000))52 let unbounded = Timeout.Options{..Timeout.after(10), generator: Some(fn(_context: Context.Context) -> Int { 0 })}53 let generatedNone = Pipeline.execute(&bounded(unbounded, &Sync.cell([])), patient(40))54 let generatedShort = Timeout.Options{..Timeout.defaults(), generator: Some(fn(_context: Context.Context) -> Int { 15 })}55 let generatedFires = Pipeline.execute(&bounded(generatedShort, &Sync.cell([])), patient(2000))56 let failing = Pipeline.execute(&bounded(Timeout.after(1000), &Sync.cell([])), fn(_context: Context.Context) -> Resilience.Outcome[Int, Str] { Resilience.raise("bad") })57 let tooShort = Pipeline.build([Timeout.strategy(Timeout.after(9))])58 let tooLong = Pipeline.build([Timeout.strategy(Timeout.after(86400001))])59 let checks = Test.suite("Timeout", &[60 Test.equals("a cooperative callback past its timeout is answered as timed out", &slow, &Err(Resilience.TimedOut(30))),61 Test.equals("onTimeout sees the timeout", &Sync.get(¬iced), &Ok(30)),62 Test.equals("the timeout is reported between the pipeline events", &Sync.get(&events), &Ok(["PipelineExecuting", "OnTimeout", "PipelineExecuted"])),63 Test.equals("a callback within its timeout answers its value", &fast, &Ok(1)),64 Test.equals("a callback ignoring its token keeps its value", &ignoring, &Ok(2)),65 Test.equals("a cancellation by the caller is not a timeout", &byCaller, &Err(Resilience.Cancelled("cancelled: caller left"))),66 Test.equals("an earlier deadline of the caller is not a timeout", &parentDeadline, &Err(Resilience.Cancelled("the deadline passed"))),67 Test.equals("a generated timeout of zero runs without one", &generatedNone, &Ok(1)),68 Test.equals("a generated timeout replaces the fixed one", &generatedFires, &Err(Resilience.TimedOut(15))),69 Test.equals("the callback's own failure passes through", &failing, &Resilience.raise("bad")),70 Test.that("a timeout below 10 ms is refused", Result.isErr(&tooShort)),71 Test.that("a timeout above one day is refused", Result.isErr(&tooLong)),72 Test.that("the bounds themselves are accepted", Timeout.validate(&Timeout.after(10)).isEmpty() && Timeout.validate(&Timeout.after(86400000)).isEmpty())73 ])74 let ran = Test.run(&checks)75 for failure in Test.failuresOf(&ran) {76 let _reported = Io.writeErrorLine(failure)77 }78 Test.report(&ran)79}80