From 126c05273da98027ed092827752693e963638083 Mon Sep 17 00:00:00 2001 From: Peter Taylor Date: Thu, 25 Jun 2020 13:13:53 -0600 Subject: [PATCH 1/5] Add `Timer` type --- CHANGELOG.md | 6 +- src/clock.rs | 8 +- src/lib.rs | 4 + src/timer.rs | 257 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 272 insertions(+), 3 deletions(-) create mode 100644 src/timer.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 11fc3a1..dcde591 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ ### Added -- Add fallibility to `Clock` methods +- A `Timer` type supporting one-shot and periodic software timers utilizing a `Clock` implementation +- `Timer` unit tests +- Fallibility to `Clock` methods - `Instant::duration_until()` with order checking - Order checking to `Instant::duration_since()` - Bounds checking on `Instant` impls of Add/Sub @@ -12,7 +14,7 @@ ### Changed -- Add `&mut self` to `Clock` functions (make stateful, or at least allow stateful implementations) +- Add `&self` to `Clock` functions (make stateful, or at least allow stateful implementations) - All time-type inner types from signed to unsigned - `Instant::duration_since()` return type to `Result` - Refactor `examples/nrf52_dk` diff --git a/src/clock.rs b/src/clock.rs index 4fcc263..4ceb23c 100644 --- a/src/clock.rs +++ b/src/clock.rs @@ -1,6 +1,7 @@ //! The `Clock` trait can be implemented over hardware timers or other time-keeping device -use crate::{time_int::TimeInt, Duration, Instant, Period}; +use crate::timer::param; +use crate::{time_int::TimeInt, Duration, Instant, Period, Timer}; use core::convert::TryFrom; /// Potential `Clock` errors @@ -41,4 +42,9 @@ pub trait Clock: Sized { while self.now()? < end {} Ok(()) } + + /// Spawn a new, `OneShot` [`Timer`] from this clock + fn new_timer(&self) -> Timer { + Timer::::new(&self) + } } diff --git a/src/lib.rs b/src/lib.rs index e8c9f1e..83c2e5f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,8 @@ //! **Wrapping Clock**: A clock that when at its maximum value, the next count is the minimum //! value. //! +//! **Timer**: An entity that counts toward an expiration. +//! //! **Instant**: A specific instant in time ("time-point") read from a clock. //! //! **Duration**: The difference of two instances. The time that has elapsed since an instant. A @@ -77,6 +79,7 @@ mod frequency; mod instant; mod period; mod time_int; +mod timer; pub use clock::Clock; use core::{convert::Infallible, fmt}; @@ -84,6 +87,7 @@ pub use duration::Duration; pub use instant::Instant; pub use period::Period; pub use time_int::TimeInt; +pub use timer::Timer; /// Public _traits_ /// diff --git a/src/timer.rs b/src/timer.rs new file mode 100644 index 0000000..6a83c3c --- /dev/null +++ b/src/timer.rs @@ -0,0 +1,257 @@ +use crate::timer::param::*; +use crate::{traits::*, Duration, Instant}; +use core::{marker::PhantomData, ops::Add, prelude::v1::*}; + +pub(crate) mod param { + #[derive(Debug)] + pub struct None; + + #[derive(Debug)] + pub struct Disarmed; + + #[derive(Debug)] + pub struct Armed; + + #[derive(Debug)] + pub struct Running; + + #[derive(Debug)] + pub struct Periodic; + + #[derive(Debug)] + pub struct OneShot; +} + +/// A `Timer` counts toward an expiration, can be polled for elapsed and remaining time, as +/// well as optionally execute a task upon expiration. +#[derive(Debug)] +pub struct Timer<'a, Type, State, Clock: crate::Clock, Dur: Duration> { + clock: &'a Clock, + duration: Option, + expiration: Option>, + _type: PhantomData, + _state: PhantomData, +} + +impl<'a, Clock: crate::Clock, Dur: Duration> Timer<'_, param::None, param::None, Clock, Dur> { + /// Construct a new, `OneShot` `Timer` + #[allow(clippy::new_ret_no_self)] + pub fn new(clock: &Clock) -> Timer { + Timer:: { + clock, + duration: Option::None, + expiration: Option::None, + _type: PhantomData, + _state: PhantomData, + } + } +} + +impl<'a, Type, State, Clock: crate::Clock, Dur: Duration> Timer<'a, Type, State, Clock, Dur> { + /// Change timer type to one-shot + pub fn into_oneshot(self) -> Timer<'a, OneShot, State, Clock, Dur> { + Timer:: { + clock: self.clock, + duration: self.duration, + expiration: self.expiration, + _type: PhantomData, + _state: PhantomData, + } + } + + /// Change timer type into periodic + pub fn into_periodic(self) -> Timer<'a, Periodic, State, Clock, Dur> { + Timer:: { + clock: self.clock, + duration: self.duration, + expiration: self.expiration, + _type: PhantomData, + _state: PhantomData, + } + } +} + +impl<'a, Type, Clock: crate::Clock, Dur: Duration> Timer<'a, Type, Disarmed, Clock, Dur> { + /// Set the [`Duration`](trait.Duration.html) of the timer + /// + /// This _arms_ the timer (makes it ready to run). + pub fn set_duration(self, duration: Dur) -> Timer<'a, Type, Armed, Clock, Dur> { + Timer:: { + clock: self.clock, + duration: Some(duration), + expiration: Option::None, + _type: PhantomData, + _state: PhantomData, + } + } +} +impl<'a, Type, Clock: crate::Clock, Dur: Duration> Timer<'a, Type, Armed, Clock, Dur> { + /// Start the _armed_ timer from this instant + pub fn start(self) -> Timer<'a, Type, Running, Clock, Dur> + where + Instant: Add>, + { + Timer:: { + clock: self.clock, + duration: self.duration, + expiration: Some(self.clock.now().unwrap() + self.duration.unwrap()), + _type: PhantomData, + _state: PhantomData, + } + } +} + +impl Timer<'_, Type, Running, Clock, Dur> { + fn _is_expired(&self) -> bool { + self.clock.now().unwrap() >= self.expiration.unwrap() + } +} + +impl<'a, Clock: crate::Clock, Dur: Duration> Timer<'a, OneShot, Running, Clock, Dur> { + /// Block until the timer has expired + pub fn wait(self) { + // since the timer is running, _is_expired() will return a value + while !self._is_expired() {} + } + + /// Check whether the timer has expired + /// + /// The timer is not restarted + pub fn is_expired(&self) -> bool { + self._is_expired() + } +} + +impl Timer<'_, Periodic, Running, Clock, Dur> { + /// Block until the timer has expired + /// + /// The timer is restarted + pub fn wait(self) -> Self + where + Instant: Add>, + { + // since the timer is running, _is_expired() will return a value + while !self._is_expired() {} + + Self { + clock: self.clock, + duration: self.duration, + expiration: self + .expiration + .map(|expiration| expiration + self.duration.unwrap()), + _type: PhantomData, + _state: PhantomData, + } + } + + /// Check whether a _periodic_ timer has elapsed + /// + /// The timer is restarted if it has elapsed. + pub fn period_complete(&mut self) -> bool + where + Instant: Add>, + { + // since the timer is running, _is_expired() will return a value + if self._is_expired() { + self.expiration = Some(self.expiration.unwrap() + self.duration.unwrap()); + + true + } else { + false + } + } +} + +#[cfg(test)] +#[allow(unused_imports)] +#[allow(unsafe_code)] +mod test { + use crate::{traits::*, units::*, Duration, Error, Instant, Period}; + use std::convert::{Infallible, TryFrom, TryInto}; + + #[derive(Debug, Ord, PartialOrd, Eq, PartialEq)] + struct Clock; + + static mut START: Option = None; + + impl crate::Clock for Clock { + type Rep = u64; + const PERIOD: Period = ::new(1, 64_000_000); + type ImplError = Infallible; + + fn now(&self) -> Result, crate::clock::Error> { + let since_start = unsafe { START.unwrap() }.elapsed(); + let ticks = Nanoseconds::::try_from(since_start) + .unwrap() + .into_ticks(Self::PERIOD) + .unwrap(); + Ok(Instant::new(ticks)) + } + } + + fn init_start_time() { + unsafe { + START = Some(std::time::Instant::now()); + } + } + + #[test] + fn oneshot_wait() { + init_start_time(); + let clock = Clock; + + clock + .new_timer() + .set_duration(1_u32.seconds()) + .start() + .wait(); + + unsafe { + assert!(Seconds::::try_from(START.unwrap().elapsed()).unwrap() == 1_u32.seconds()); + } + } + + #[test] + fn periodic_wait() { + init_start_time(); + let clock = Clock; + + let timer = clock + .new_timer() + .into_periodic() + .set_duration(1_u32.seconds()) + .start() + .wait(); + + unsafe { + assert!(Seconds::::try_from(START.unwrap().elapsed()).unwrap() == 1_u32.seconds()); + } + + let timer = timer.wait(); + unsafe { + assert!(Seconds::::try_from(START.unwrap().elapsed()).unwrap() == 2_u32.seconds()); + } + + let timer = timer.wait(); + unsafe { + assert!(Seconds::::try_from(START.unwrap().elapsed()).unwrap() == 3_u32.seconds()); + } + } + + #[test] + fn periodic_expiration() { + init_start_time(); + let clock = Clock; + + let mut timer = clock + .new_timer() + .into_periodic() + .set_duration(1_u32.seconds()) + .start(); + + std::thread::sleep(std::time::Duration::from_secs(2)); + + assert!(timer.period_complete()); + assert!(timer.period_complete()); + } +} From 24029d783867e9026843a58a6ebf178529953eb9 Mon Sep 17 00:00:00 2001 From: Peter Taylor Date: Sun, 28 Jun 2020 21:01:05 -0600 Subject: [PATCH 2/5] refactor(Clock): Remove `Clock::delay()` This functionality is now handled by `Timer`. BREAKING CHANGE: `Clock::delay()` replaced with `Timer::wait()` --- examples/nrf52_dk/main.rs | 12 ++++++++++-- src/clock.rs | 15 --------------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/examples/nrf52_dk/main.rs b/examples/nrf52_dk/main.rs index a450522..e85144e 100644 --- a/examples/nrf52_dk/main.rs +++ b/examples/nrf52_dk/main.rs @@ -152,12 +152,20 @@ where led2.set_high()?; led3.set_high()?; led4.set_low()?; - clock.delay(250_u32.milliseconds()).unwrap(); + clock + .new_timer() + .set_duration(250_u32.milliseconds()) + .start() + .wait(); led1.set_high()?; led2.set_low()?; led3.set_low()?; led4.set_high()?; - clock.delay(250_u32.milliseconds()).unwrap(); + clock + .new_timer() + .set_duration(250_u32.milliseconds()) + .start() + .wait(); } } diff --git a/src/clock.rs b/src/clock.rs index 4ceb23c..fd3e5e1 100644 --- a/src/clock.rs +++ b/src/clock.rs @@ -2,7 +2,6 @@ use crate::timer::param; use crate::{time_int::TimeInt, Duration, Instant, Period, Timer}; -use core::convert::TryFrom; /// Potential `Clock` errors #[non_exhaustive] @@ -29,20 +28,6 @@ pub trait Clock: Sized { /// Implementation-specific error returned through [`Error::Other(ImplError)`] fn now(&self) -> Result, Error>; - /// Blocking delay - /// - /// # Errors - /// Implementation-specific error returned through [`Error::Other(ImplError)`] - fn delay(&self, dur: Dur) -> Result<(), Error> - where - Self::Rep: TryFrom, - { - let start = self.now()?; - let end = start + dur; - while self.now()? < end {} - Ok(()) - } - /// Spawn a new, `OneShot` [`Timer`] from this clock fn new_timer(&self) -> Timer { Timer::::new(&self) From 30d17ea1033d550da1cf2f94ee8018fafce59bf3 Mon Sep 17 00:00:00 2001 From: Peter Taylor Date: Sun, 28 Jun 2020 21:03:08 -0600 Subject: [PATCH 3/5] feat(Timer): Add functions to read elapsed and remaining time from a timer docs(Clock,Timer): Remove links to private items --- src/timer.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 9 deletions(-) diff --git a/src/timer.rs b/src/timer.rs index 6a83c3c..6e97cbb 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -1,6 +1,5 @@ -use crate::timer::param::*; -use crate::{traits::*, Duration, Instant}; -use core::{marker::PhantomData, ops::Add, prelude::v1::*}; +use crate::{duration::TryConvertFrom, timer::param::*, traits::*, units::*, Duration, Instant}; +use core::{convert::TryFrom, marker::PhantomData, ops::Add, prelude::v1::*}; pub(crate) mod param { #[derive(Debug)] @@ -105,13 +104,53 @@ impl Timer<'_, Type, Running, Clock, D fn _is_expired(&self) -> bool { self.clock.now().unwrap() >= self.expiration.unwrap() } + + /// Returns the [`Duration`](trait.Duration.html) of time elapsed since it was started + /// + /// The units of the [`Duration`](trait.Duration.html) are the same as that used with + /// [`set_duration()`](struct.Timer.html#method.set_duration). + pub fn elapsed(&self) -> Dur + where + Dur::Rep: TryFrom, + Clock::Rep: TryFrom, + { + self.clock + .now() + .unwrap() + .duration_since(&(self.expiration.unwrap() - self.duration.unwrap())) + .unwrap() + } + + /// Returns the [`Duration`](trait.Duration.html) until the expiration of the timer + /// + /// The units of the [`Duration`](trait.Duration.html) are the same as that used with + /// [`set_duration()`](struct.Timer.html#method.set_duration). + pub fn remaining(&self) -> Dur + where + Dur::Rep: TryFrom, + Clock::Rep: TryFrom, + Dur: TryConvertFrom>, + { + if let Ok(duration) = self + .expiration + .unwrap() + .duration_since(&self.clock.now().unwrap()) + { + duration + } else { + 0.seconds().try_convert_into().unwrap() + } + } } impl<'a, Clock: crate::Clock, Dur: Duration> Timer<'a, OneShot, Running, Clock, Dur> { /// Block until the timer has expired - pub fn wait(self) { + pub fn wait(self) -> Timer<'a, OneShot, Armed, Clock, Dur> { // since the timer is running, _is_expired() will return a value while !self._is_expired() {} + + Timer::::new(self.clock) + .set_duration(self.duration.unwrap()) } /// Check whether the timer has expired @@ -207,7 +246,7 @@ mod test { .wait(); unsafe { - assert!(Seconds::::try_from(START.unwrap().elapsed()).unwrap() == 1_u32.seconds()); + assert!(Seconds::::try_from(START.unwrap().elapsed()).unwrap() >= 1_u32.seconds()); } } @@ -224,17 +263,17 @@ mod test { .wait(); unsafe { - assert!(Seconds::::try_from(START.unwrap().elapsed()).unwrap() == 1_u32.seconds()); + assert!(Seconds::::try_from(START.unwrap().elapsed()).unwrap() >= 1_u32.seconds()); } let timer = timer.wait(); unsafe { - assert!(Seconds::::try_from(START.unwrap().elapsed()).unwrap() == 2_u32.seconds()); + assert!(Seconds::::try_from(START.unwrap().elapsed()).unwrap() >= 2_u32.seconds()); } - let timer = timer.wait(); + timer.wait(); unsafe { - assert!(Seconds::::try_from(START.unwrap().elapsed()).unwrap() == 3_u32.seconds()); + assert!(Seconds::::try_from(START.unwrap().elapsed()).unwrap() >= 3_u32.seconds()); } } @@ -254,4 +293,30 @@ mod test { assert!(timer.period_complete()); assert!(timer.period_complete()); } + + #[test] + fn read_timer() { + init_start_time(); + let clock = Clock; + + let timer = clock.new_timer().set_duration(2_u32.seconds()).start(); + + assert_eq!(timer.elapsed(), 0_u32.seconds()); + assert_eq!(timer.remaining(), 1_u32.seconds()); + + std::thread::sleep(std::time::Duration::from_secs(1)); + + assert_eq!(timer.elapsed(), 1_u32.seconds()); + assert_eq!(timer.remaining(), 0_u32.seconds()); + + std::thread::sleep(std::time::Duration::from_secs(1)); + + assert_eq!(timer.elapsed(), 2_u32.seconds()); + assert_eq!(timer.remaining(), 0_u32.seconds()); + + std::thread::sleep(std::time::Duration::from_secs(1)); + + assert_eq!(timer.elapsed(), 3_u32.seconds()); + assert_eq!(timer.remaining(), 0_u32.seconds()); + } } From 230c755928fe381e6b712a55bf66404e8ac1e8ac Mon Sep 17 00:00:00 2001 From: Peter Taylor Date: Mon, 29 Jun 2020 14:46:47 -0600 Subject: [PATCH 4/5] feat(Timer): Add return from OneShot Timer::wait() Return a TimerBuilder --- src/timer.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/timer.rs b/src/timer.rs index 6e97cbb..2c4997d 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -239,7 +239,7 @@ mod test { init_start_time(); let clock = Clock; - clock + let timer = clock .new_timer() .set_duration(1_u32.seconds()) .start() @@ -248,6 +248,14 @@ mod test { unsafe { assert!(Seconds::::try_from(START.unwrap().elapsed()).unwrap() >= 1_u32.seconds()); } + + // WHEN blocking on a timer + timer.start().wait(); + + // THEN the block occurs for _at least_ the given duration + unsafe { + assert!(Seconds::::try_from(START.unwrap().elapsed()).unwrap() >= 2_u32.seconds()); + } } #[test] From f063f1f08751e94874d7b97143d36e6116a9e363 Mon Sep 17 00:00:00 2001 From: Peter Taylor Date: Mon, 29 Jun 2020 17:08:21 -0600 Subject: [PATCH 5/5] test(Timer): Stabilize tests Switch from using system clock for delays to manually advancing clock ticks using multiple threads --- CHANGELOG.md | 1 + Cargo.lock | 24 +++++++++++ Cargo.toml | 3 ++ src/timer.rs | 114 +++++++++++++++++++++++++++++---------------------- 4 files changed, 92 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcde591..602f454 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Order checking to `Instant::duration_since()` - Bounds checking on `Instant` impls of Add/Sub - Changelog back to v0.5.0 release +- [`crossbeam-utils`](https://crates.io/crates/crossbeam-utils) dev-dependency for scoped threads in tests ### Changed diff --git a/Cargo.lock b/Cargo.lock index 050557d..0d34995 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,12 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cortex-m" version = "0.6.2" @@ -62,6 +68,17 @@ dependencies = [ "syn", ] +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if", + "lazy_static", +] + [[package]] name = "embedded-hal" version = "0.2.4" @@ -78,6 +95,7 @@ version = "0.5.2" dependencies = [ "cortex-m", "cortex-m-rt", + "crossbeam-utils", "mutex-trait", "nrf52832-hal", "num", @@ -100,6 +118,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f6af40154f64bd7095b5245d6933fef99b0a28c432b38a676cfafaa0aedfc2" +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "mutex-trait" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 91319f3..437bcce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,5 +22,8 @@ panic_rtt = "0.3.0" nrf52832-hal = { version = "0.10.0", default-features = false, features = ["rt", "xxAA-package"] } mutex-trait = "0.2.0" +[target.'cfg(not(target_arch = "arm"))'.dev-dependencies] +crossbeam-utils = "0.7.2" + [patch.crates-io] cortex-m = { git = "https://github.com/rust-embedded/cortex-m", branch = "mutex_add" } diff --git a/src/timer.rs b/src/timer.rs index 2c4997d..196acae 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -206,88 +206,89 @@ impl Timer<'_, Periodic, Running, Clock, Dur #[allow(unsafe_code)] mod test { use crate::{traits::*, units::*, Duration, Error, Instant, Period}; - use std::convert::{Infallible, TryFrom, TryInto}; + use core::convert::{Infallible, TryFrom}; + use crossbeam_utils::thread; + use std::sync::atomic::{AtomicU64, Ordering}; #[derive(Debug, Ord, PartialOrd, Eq, PartialEq)] struct Clock; - static mut START: Option = None; + static TICKS: AtomicU64 = AtomicU64::new(0); impl crate::Clock for Clock { type Rep = u64; - const PERIOD: Period = ::new(1, 64_000_000); + const PERIOD: Period = ::new(1, 1_000); type ImplError = Infallible; fn now(&self) -> Result, crate::clock::Error> { - let since_start = unsafe { START.unwrap() }.elapsed(); - let ticks = Nanoseconds::::try_from(since_start) - .unwrap() - .into_ticks(Self::PERIOD) - .unwrap(); - Ok(Instant::new(ticks)) - } - } - - fn init_start_time() { - unsafe { - START = Some(std::time::Instant::now()); + Ok(Instant::new(TICKS.load(Ordering::Acquire))) } } #[test] fn oneshot_wait() { - init_start_time(); + init_ticks(); let clock = Clock; - let timer = clock - .new_timer() - .set_duration(1_u32.seconds()) - .start() - .wait(); + let timer = clock.new_timer().set_duration(1_u32.seconds()).start(); - unsafe { - assert!(Seconds::::try_from(START.unwrap().elapsed()).unwrap() >= 1_u32.seconds()); - } + thread::scope(|s| { + let timer_handle = s.spawn(|_| timer.wait()); - // WHEN blocking on a timer - timer.start().wait(); + add_to_ticks(1_u32.seconds()); - // THEN the block occurs for _at least_ the given duration - unsafe { - assert!(Seconds::::try_from(START.unwrap().elapsed()).unwrap() >= 2_u32.seconds()); - } + let result = timer_handle.join(); + + assert!(result.is_ok()); + + add_to_ticks(1_u32.seconds()); + + let timer = result.unwrap().start(); + assert!(!timer.is_expired()); + + let timer_handle = s.spawn(|_| timer.wait()); + add_to_ticks(1_u32.seconds()); + + assert!(timer_handle.join().is_ok()); + }) + .unwrap(); } #[test] fn periodic_wait() { - init_start_time(); + init_ticks(); let clock = Clock; let timer = clock .new_timer() .into_periodic() .set_duration(1_u32.seconds()) - .start() - .wait(); + .start(); - unsafe { - assert!(Seconds::::try_from(START.unwrap().elapsed()).unwrap() >= 1_u32.seconds()); - } + thread::scope(|s| { + let timer_handle = s.spawn(|_| timer.wait()); - let timer = timer.wait(); - unsafe { - assert!(Seconds::::try_from(START.unwrap().elapsed()).unwrap() >= 2_u32.seconds()); - } + add_to_ticks(1_u32.seconds()); - timer.wait(); - unsafe { - assert!(Seconds::::try_from(START.unwrap().elapsed()).unwrap() >= 3_u32.seconds()); - } + let result = timer_handle.join(); + + assert!(result.is_ok()); + + let timer = result.unwrap(); + + // WHEN blocking on a timer + let timer_handle = s.spawn(|_| timer.wait()); + + add_to_ticks(1_u32.seconds()); + + assert!(timer_handle.join().is_ok()); + }) + .unwrap(); } #[test] fn periodic_expiration() { - init_start_time(); + init_ticks(); let clock = Clock; let mut timer = clock @@ -296,7 +297,7 @@ mod test { .set_duration(1_u32.seconds()) .start(); - std::thread::sleep(std::time::Duration::from_secs(2)); + add_to_ticks(2_u32.seconds()); assert!(timer.period_complete()); assert!(timer.period_complete()); @@ -304,27 +305,40 @@ mod test { #[test] fn read_timer() { - init_start_time(); + init_ticks(); let clock = Clock; let timer = clock.new_timer().set_duration(2_u32.seconds()).start(); + add_to_ticks(1_u32.milliseconds()); + assert_eq!(timer.elapsed(), 0_u32.seconds()); assert_eq!(timer.remaining(), 1_u32.seconds()); - std::thread::sleep(std::time::Duration::from_secs(1)); + add_to_ticks(1_u32.seconds()); assert_eq!(timer.elapsed(), 1_u32.seconds()); assert_eq!(timer.remaining(), 0_u32.seconds()); - std::thread::sleep(std::time::Duration::from_secs(1)); + add_to_ticks(1_u32.seconds()); assert_eq!(timer.elapsed(), 2_u32.seconds()); assert_eq!(timer.remaining(), 0_u32.seconds()); - std::thread::sleep(std::time::Duration::from_secs(1)); + add_to_ticks(1_u32.seconds()); assert_eq!(timer.elapsed(), 3_u32.seconds()); assert_eq!(timer.remaining(), 0_u32.seconds()); } + + fn init_ticks() {} + + fn add_to_ticks(duration: Dur) { + let ticks = TICKS.load(Ordering::Acquire); + let ticks = ticks + + duration + .into_ticks::<::Rep>(Clock::PERIOD) + .unwrap(); + TICKS.store(ticks, Ordering::Release); + } }