//! Timer / sleep tests. These are time-sensitive and use generous //! tolerances — we care about ordering and "didn't return instantly / //! didn't take forever," not microsecond-precise scheduling. use smarm::{run, sleep, spawn}; use std::sync::Arc; use std::sync::Mutex; use std::time::{Duration, Instant}; #[test] fn sleep_returns_after_at_least_the_requested_duration() { run(|| { let t0 = Instant::now(); sleep(Duration::from_millis(50)); let elapsed = t0.elapsed(); assert!( elapsed >= Duration::from_millis(45), "slept only {:?}, expected ≥ ~50ms", elapsed ); // Loose upper bound — anything wildly slow indicates a bug. assert!( elapsed < Duration::from_millis(500), "slept {:?}, far longer than the 50ms request", elapsed ); }); } #[test] fn shorter_sleep_wakes_first() { let log: Arc>> = Arc::new(Mutex::new(Vec::new())); let l1 = log.clone(); let l2 = log.clone(); run(move || { let h1 = spawn(move || { sleep(Duration::from_millis(60)); l1.lock().unwrap().push(1); }); let h2 = spawn(move || { sleep(Duration::from_millis(20)); l2.lock().unwrap().push(2); }); h1.join().unwrap(); h2.join().unwrap(); }); // 2 (shorter sleep) wakes before 1. assert_eq!(*log.lock().unwrap(), vec![2, 1]); } #[test] fn one_sleeping_actor_does_not_block_other_runnable_actors() { let log: Arc>> = Arc::new(Mutex::new(Vec::new())); let l1 = log.clone(); let l2 = log.clone(); run(move || { let h1 = spawn(move || { sleep(Duration::from_millis(100)); l1.lock().unwrap().push(1); }); let h2 = spawn(move || { // Doesn't sleep. Should be able to run while h1 is parked. for _ in 0..3 { l2.lock().unwrap().push(2); smarm::yield_now(); } }); h2.join().unwrap(); h1.join().unwrap(); }); let v = log.lock().unwrap(); // h2 finishes long before h1's 100ms timer. let h2_count = v.iter().filter(|&&x| x == 2).count(); let h1_pos = v.iter().position(|&x| x == 1); assert_eq!(h2_count, 3); // h1's push should land after h2 is fully done. if let Some(p) = h1_pos { assert!(p >= h2_count, "h1 woke before h2 finished: log = {:?}", *v); } } #[test] fn zero_duration_sleep_yields_but_does_not_park_forever() { // A zero-duration sleep should behave like yield_now: control returns // promptly without hanging. run(|| { let t0 = Instant::now(); sleep(Duration::from_millis(0)); assert!(t0.elapsed() < Duration::from_millis(100)); }); } #[test] fn many_concurrent_sleepers_all_wake() { let counter = Arc::new(std::sync::atomic::AtomicU32::new(0)); let c = counter.clone(); run(move || { let mut handles = Vec::new(); for i in 0..20u64 { let cc = c.clone(); handles.push(spawn(move || { // Stagger so they don't all coalesce to the same wake. sleep(Duration::from_millis(5 + i * 2)); cc.fetch_add(1, std::sync::atomic::Ordering::SeqCst); })); } for h in handles { h.join().unwrap(); } }); assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 20); }