diff --git a/asynchronix/tests/model_scheduling.rs b/asynchronix/tests/model_scheduling.rs new file mode 100644 index 0000000..642a7e8 --- /dev/null +++ b/asynchronix/tests/model_scheduling.rs @@ -0,0 +1,233 @@ +//! Event scheduling within `Model` input methods. + +use std::time::Duration; + +use asynchronix::model::{Model, Output}; +use asynchronix::simulation::{Mailbox, SimInit}; +use asynchronix::time::{EventKey, MonotonicTime, Scheduler}; + +#[test] +fn model_schedule_event() { + #[derive(Default)] + struct TestModel { + output: Output<()>, + } + impl TestModel { + fn trigger(&mut self, _: (), scheduler: &Scheduler) { + scheduler + .schedule_event_at(scheduler.time() + Duration::from_secs(2), Self::action, ()) + .unwrap(); + } + async fn action(&mut self) { + self.output.send(()).await; + } + } + impl Model for TestModel {} + + let mut model = TestModel::default(); + let mbox = Mailbox::new(); + + let mut output = model.output.connect_stream().0; + let addr = mbox.address(); + + let t0 = MonotonicTime::EPOCH; + let mut simu = SimInit::new().add_model(model, mbox).init(t0); + + simu.send_event(TestModel::trigger, (), addr); + simu.step(); + assert_eq!(simu.time(), t0 + Duration::from_secs(2)); + assert!(output.next().is_some()); + simu.step(); + assert!(output.next().is_none()); +} + +#[test] +fn model_cancel_future_keyed_event() { + #[derive(Default)] + struct TestModel { + output: Output, + key: Option, + } + impl TestModel { + fn trigger(&mut self, _: (), scheduler: &Scheduler) { + scheduler + .schedule_event_at(scheduler.time() + Duration::from_secs(1), Self::action1, ()) + .unwrap(); + self.key = scheduler + .schedule_keyed_event_at( + scheduler.time() + Duration::from_secs(2), + Self::action2, + (), + ) + .ok(); + } + async fn action1(&mut self) { + self.output.send(1).await; + // Cancel the call to `action2`. + self.key.take().unwrap().cancel(); + } + async fn action2(&mut self) { + self.output.send(2).await; + } + } + impl Model for TestModel {} + + let mut model = TestModel::default(); + let mbox = Mailbox::new(); + + let mut output = model.output.connect_stream().0; + let addr = mbox.address(); + + let t0 = MonotonicTime::EPOCH; + let mut simu = SimInit::new().add_model(model, mbox).init(t0); + + simu.send_event(TestModel::trigger, (), addr); + simu.step(); + assert_eq!(simu.time(), t0 + Duration::from_secs(1)); + assert_eq!(output.next(), Some(1)); + simu.step(); + assert_eq!(simu.time(), t0 + Duration::from_secs(1)); + assert!(output.next().is_none()); +} + +#[test] +fn model_cancel_same_time_keyed_event() { + #[derive(Default)] + struct TestModel { + output: Output, + key: Option, + } + impl TestModel { + fn trigger(&mut self, _: (), scheduler: &Scheduler) { + scheduler + .schedule_event_at(scheduler.time() + Duration::from_secs(2), Self::action1, ()) + .unwrap(); + self.key = scheduler + .schedule_keyed_event_at( + scheduler.time() + Duration::from_secs(2), + Self::action2, + (), + ) + .ok(); + } + async fn action1(&mut self) { + self.output.send(1).await; + // Cancel the call to `action2`. + self.key.take().unwrap().cancel(); + } + async fn action2(&mut self) { + self.output.send(2).await; + } + } + impl Model for TestModel {} + + let mut model = TestModel::default(); + let mbox = Mailbox::new(); + + let mut output = model.output.connect_stream().0; + let addr = mbox.address(); + + let t0 = MonotonicTime::EPOCH; + let mut simu = SimInit::new().add_model(model, mbox).init(t0); + + simu.send_event(TestModel::trigger, (), addr); + simu.step(); + assert_eq!(simu.time(), t0 + Duration::from_secs(2)); + assert_eq!(output.next(), Some(1)); + assert!(output.next().is_none()); + simu.step(); + assert!(output.next().is_none()); +} + +#[test] +fn model_schedule_periodic_event() { + #[derive(Default)] + struct TestModel { + output: Output, + } + impl TestModel { + fn trigger(&mut self, _: (), scheduler: &Scheduler) { + scheduler + .schedule_periodic_event_at( + scheduler.time() + Duration::from_secs(2), + Duration::from_secs(3), + Self::action, + 42, + ) + .unwrap(); + } + async fn action(&mut self, payload: i32) { + self.output.send(payload).await; + } + } + impl Model for TestModel {} + + let mut model = TestModel::default(); + let mbox = Mailbox::new(); + + let mut output = model.output.connect_stream().0; + let addr = mbox.address(); + + let t0 = MonotonicTime::EPOCH; + let mut simu = SimInit::new().add_model(model, mbox).init(t0); + + simu.send_event(TestModel::trigger, (), addr); + + // Move to the next events at t0 + 2s + k*3s. + for k in 0..10 { + simu.step(); + assert_eq!( + simu.time(), + t0 + Duration::from_secs(2) + k * Duration::from_secs(3) + ); + assert_eq!(output.next(), Some(42)); + assert!(output.next().is_none()); + } +} + +#[test] +fn model_cancel_periodic_event() { + #[derive(Default)] + struct TestModel { + output: Output<()>, + key: Option, + } + impl TestModel { + fn trigger(&mut self, _: (), scheduler: &Scheduler) { + self.key = scheduler + .schedule_periodic_keyed_event_at( + scheduler.time() + Duration::from_secs(2), + Duration::from_secs(3), + Self::action, + (), + ) + .ok(); + } + async fn action(&mut self) { + self.output.send(()).await; + // Cancel the next events. + self.key.take().unwrap().cancel(); + } + } + impl Model for TestModel {} + + let mut model = TestModel::default(); + let mbox = Mailbox::new(); + + let mut output = model.output.connect_stream().0; + let addr = mbox.address(); + + let t0 = MonotonicTime::EPOCH; + let mut simu = SimInit::new().add_model(model, mbox).init(t0); + + simu.send_event(TestModel::trigger, (), addr); + + simu.step(); + assert_eq!(simu.time(), t0 + Duration::from_secs(2)); + assert!(output.next().is_some()); + assert!(output.next().is_none()); + + simu.step(); + assert_eq!(simu.time(), t0 + Duration::from_secs(2)); + assert!(output.next().is_none()); +} diff --git a/asynchronix/tests/simulation_scheduling.rs b/asynchronix/tests/simulation_scheduling.rs new file mode 100644 index 0000000..5df71f9 --- /dev/null +++ b/asynchronix/tests/simulation_scheduling.rs @@ -0,0 +1,199 @@ +//! Event scheduling from a `Simulation` instance. + +use std::time::Duration; + +use asynchronix::model::{Model, Output}; +use asynchronix::simulation::{Address, EventStream, Mailbox, SimInit, Simulation}; +use asynchronix::time::MonotonicTime; + +// Simple input-to-output pass-through model. +struct PassThroughModel { + pub output: Output, +} +impl PassThroughModel { + pub fn new() -> Self { + Self { + output: Output::default(), + } + } + pub async fn input(&mut self, arg: T) { + self.output.send(arg).await; + } +} +impl Model for PassThroughModel {} + +/// A simple bench containing a single pass-through model (input forwarded to +/// output). +fn simple_bench() -> ( + Simulation, + MonotonicTime, + Address>, + EventStream, +) { + // Bench assembly. + let mut model = PassThroughModel::new(); + let mbox = Mailbox::new(); + + let out_stream = model.output.connect_stream().0; + let addr = mbox.address(); + + let t0 = MonotonicTime::EPOCH; + + let simu = SimInit::new().add_model(model, mbox).init(t0); + + (simu, t0, addr, out_stream) +} + +#[test] +fn simulation_schedule_events() { + let (mut simu, t0, addr, mut output) = simple_bench(); + + // Queue 2 events at t0+3s and t0+2s, in reverse order. + simu.schedule_event_in(Duration::from_secs(3), PassThroughModel::input, (), &addr) + .unwrap(); + simu.schedule_event_at( + t0 + Duration::from_secs(2), + PassThroughModel::input, + (), + &addr, + ) + .unwrap(); + + // Move to the 1st event at t0+2s. + simu.step(); + assert_eq!(simu.time(), t0 + Duration::from_secs(2)); + assert!(output.next().is_some()); + + // Schedule another event in 4s (at t0+6s). + simu.schedule_event_in(Duration::from_secs(4), PassThroughModel::input, (), &addr) + .unwrap(); + + // Move to the 2nd event at t0+3s. + simu.step(); + assert_eq!(simu.time(), t0 + Duration::from_secs(3)); + assert!(output.next().is_some()); + + // Move to the 3rd event at t0+6s. + simu.step(); + assert_eq!(simu.time(), t0 + Duration::from_secs(6)); + assert!(output.next().is_some()); + assert!(output.next().is_none()); +} + +#[test] +fn simulation_schedule_keyed_events() { + let (mut simu, t0, addr, mut output) = simple_bench(); + + let event_t1 = simu + .schedule_keyed_event_at( + t0 + Duration::from_secs(1), + PassThroughModel::input, + 1, + &addr, + ) + .unwrap(); + + let event_t2_1 = simu + .schedule_keyed_event_in(Duration::from_secs(2), PassThroughModel::input, 21, &addr) + .unwrap(); + + simu.schedule_event_in(Duration::from_secs(2), PassThroughModel::input, 22, &addr) + .unwrap(); + + // Move to the 1st event at t0+1. + simu.step(); + + // Try to cancel the 1st event after it has already taken place and check + // that the cancellation had no effect. + event_t1.cancel(); + assert_eq!(simu.time(), t0 + Duration::from_secs(1)); + assert_eq!(output.next(), Some(1)); + + // Cancel the second event (t0+2) before it is meant to takes place and + // check that we move directly to the 3rd event. + event_t2_1.cancel(); + simu.step(); + assert_eq!(simu.time(), t0 + Duration::from_secs(2)); + assert_eq!(output.next(), Some(22)); + assert!(output.next().is_none()); +} + +#[test] +fn simulation_schedule_periodic_events() { + let (mut simu, t0, addr, mut output) = simple_bench(); + + // Queue 2 periodic events at t0 + 3s + k*2s. + simu.schedule_periodic_event_in( + Duration::from_secs(3), + Duration::from_secs(2), + PassThroughModel::input, + 1, + &addr, + ) + .unwrap(); + simu.schedule_periodic_event_at( + t0 + Duration::from_secs(3), + Duration::from_secs(2), + PassThroughModel::input, + 2, + &addr, + ) + .unwrap(); + + // Move to the next events at t0 + 3s + k*2s. + for k in 0..10 { + simu.step(); + assert_eq!( + simu.time(), + t0 + Duration::from_secs(3) + k * Duration::from_secs(2) + ); + assert_eq!(output.next(), Some(1)); + assert_eq!(output.next(), Some(2)); + assert!(output.next().is_none()); + } +} + +#[test] +fn simulation_schedule_periodic_keyed_events() { + let (mut simu, t0, addr, mut output) = simple_bench(); + + // Queue 2 periodic events at t0 + 3s + k*2s. + simu.schedule_periodic_event_in( + Duration::from_secs(3), + Duration::from_secs(2), + PassThroughModel::input, + 1, + &addr, + ) + .unwrap(); + let event2_key = simu + .schedule_periodic_keyed_event_at( + t0 + Duration::from_secs(3), + Duration::from_secs(2), + PassThroughModel::input, + 2, + &addr, + ) + .unwrap(); + + // Move to the next event at t0+3s. + simu.step(); + assert_eq!(simu.time(), t0 + Duration::from_secs(3)); + assert_eq!(output.next(), Some(1)); + assert_eq!(output.next(), Some(2)); + assert!(output.next().is_none()); + + // Cancel the second event. + event2_key.cancel(); + + // Move to the next events at t0 + 3s + k*2s. + for k in 1..10 { + simu.step(); + assert_eq!( + simu.time(), + t0 + Duration::from_secs(3) + k * Duration::from_secs(2) + ); + assert_eq!(output.next(), Some(1)); + assert!(output.next().is_none()); + } +}