-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
clock: new clock implementation, with some tests
- Loading branch information
Showing
5 changed files
with
246 additions
and
2 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
use std::cell::RefCell; | ||
pub use std::time::Duration; | ||
use std::time::SystemTime; | ||
|
||
use thiserror::Error; | ||
|
||
use crate::static_time; | ||
|
||
pub type SinceUnixEpoch = Duration; | ||
|
||
#[derive(Error, Debug)] | ||
pub enum ClockError { | ||
#[error("overflow error")] | ||
OverflowError, | ||
} | ||
|
||
pub trait Time: Sized { | ||
fn now() -> SinceUnixEpoch; | ||
|
||
fn now_add(duration: &Duration) -> Option<SinceUnixEpoch> { | ||
Self::now().checked_add(*duration) | ||
} | ||
fn now_sub(duration: &Duration) -> Option<SinceUnixEpoch> { | ||
Self::now().checked_sub(*duration) | ||
} | ||
fn now_periods(period: &Duration) -> Option<u128> { | ||
Self::now().as_nanos().checked_div(period.as_nanos()) | ||
} | ||
fn now_add_periods(duration: &Duration, period: &Duration) -> Option<u128> { | ||
match Self::now_add(duration) { | ||
None => None, | ||
Some(time) => time.as_nanos().checked_div(period.as_nanos()), | ||
} | ||
} | ||
fn now_sub_periods(duration: &Duration, period: &Duration) -> Option<u128> { | ||
match Self::now_sub(duration) { | ||
None => None, | ||
Some(time) => time.as_nanos().checked_div(period.as_nanos()), | ||
} | ||
} | ||
} | ||
|
||
pub trait StoppedTime: Time { | ||
fn local_set(unix_time: &SinceUnixEpoch); | ||
fn local_set_to_unix_epoch() { | ||
Self::local_set(&SinceUnixEpoch::ZERO) | ||
} | ||
|
||
fn local_set_to_app_start_time(); | ||
fn local_set_to_system_time_now(); | ||
fn local_add(duration: &Duration) -> Result<(), ClockError>; | ||
fn local_sub(duration: &Duration) -> Result<(), ClockError>; | ||
fn local_reset(); | ||
} | ||
|
||
pub struct Clock<const T: usize>; | ||
|
||
pub enum ClockType { | ||
WorkingClock, | ||
StoppedClock, | ||
} | ||
|
||
pub type WorkingClock = Clock<{ ClockType::WorkingClock as usize }>; | ||
pub type StoppedClock = Clock<{ ClockType::StoppedClock as usize }>; | ||
|
||
#[cfg(not(test))] | ||
pub type DefaultClock = WorkingClock; | ||
|
||
#[cfg(test)] | ||
pub type DefaultClock = StoppedClock; | ||
|
||
mod detail { | ||
use self::super::*; | ||
|
||
pub fn get_app_start_time() -> SinceUnixEpoch { | ||
(*static_time::TIME_AT_APP_START) | ||
.duration_since(SystemTime::UNIX_EPOCH) | ||
.unwrap() | ||
} | ||
|
||
#[cfg(not(test))] | ||
pub fn get_default_fixed_time() -> SinceUnixEpoch { | ||
get_app_start_time() | ||
} | ||
|
||
#[cfg(test)] | ||
pub fn get_default_fixed_time() -> SinceUnixEpoch { | ||
Duration::ZERO | ||
} | ||
|
||
thread_local!(pub static FIXED_TIME: RefCell<SinceUnixEpoch> = RefCell::new(get_default_fixed_time())); | ||
} | ||
|
||
impl Time for WorkingClock { | ||
fn now() -> SinceUnixEpoch { | ||
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap() | ||
} | ||
} | ||
|
||
impl Time for StoppedClock { | ||
fn now() -> SinceUnixEpoch { | ||
detail::FIXED_TIME.with(|time| { | ||
return *time.borrow(); | ||
}) | ||
} | ||
} | ||
|
||
impl StoppedTime for StoppedClock { | ||
fn local_set(unix_time: &SinceUnixEpoch) { | ||
detail::FIXED_TIME.with(|time| { | ||
*time.borrow_mut() = *unix_time; | ||
}) | ||
} | ||
|
||
fn local_set_to_app_start_time() { | ||
Self::local_set(&detail::get_app_start_time()) | ||
} | ||
|
||
fn local_set_to_system_time_now() { | ||
Self::local_set(&detail::get_app_start_time()) | ||
} | ||
|
||
fn local_add(duration: &Duration) -> Result<(), ClockError> { | ||
detail::FIXED_TIME.with(|time| { | ||
let time_borrowed = *time.borrow(); | ||
*time.borrow_mut() = match time_borrowed.checked_add(*duration) { | ||
Some(time) => time, | ||
None => { | ||
return Err(ClockError::OverflowError); | ||
} | ||
}; | ||
Ok(()) | ||
}) | ||
} | ||
|
||
fn local_sub(duration: &Duration) -> Result<(), ClockError> { | ||
detail::FIXED_TIME.with(|time| { | ||
let time_borrowed = *time.borrow(); | ||
*time.borrow_mut() = match time_borrowed.checked_sub(*duration) { | ||
Some(time) => time, | ||
None => { | ||
return Err(ClockError::OverflowError); | ||
} | ||
}; | ||
Ok(()) | ||
}) | ||
} | ||
|
||
fn local_reset() { | ||
Self::local_set(&detail::get_default_fixed_time()) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use std::thread; | ||
|
||
use super::*; | ||
|
||
#[test] | ||
fn the_stopped_time_and_default_time_should_be_the_same_when_testing() { | ||
// We are testing, so we should default to the fixed time. | ||
assert_eq!(StoppedClock::now(), DefaultClock::now()) | ||
} | ||
|
||
#[test] | ||
fn the_stopped_time_and_working_time_should_be_different() { | ||
assert_ne!(StoppedClock::now(), WorkingClock::now()) | ||
} | ||
|
||
#[test] | ||
fn the_stopped_time_should_default_to_unix_epoch_when_testing() { | ||
assert_eq!(StoppedClock::now(), SinceUnixEpoch::ZERO) | ||
} | ||
|
||
#[test] | ||
fn the_stopped_time_should_be_modifiable() { | ||
// Check we start with ZERO. | ||
assert_eq!(StoppedClock::now(), Duration::ZERO); | ||
|
||
// Set to Current Time and Check | ||
let timestamp = WorkingClock::now(); | ||
StoppedClock::local_set(×tamp); | ||
assert_eq!(StoppedClock::now(), timestamp); | ||
|
||
// Elapse the Current Time and Check | ||
StoppedClock::local_add(×tamp).unwrap(); | ||
assert_eq!(StoppedClock::now(), timestamp + timestamp); | ||
|
||
// Reset to ZERO and Check | ||
StoppedClock::local_reset(); | ||
assert_eq!(StoppedClock::now(), Duration::ZERO); | ||
} | ||
|
||
#[test] | ||
fn fixed_time_should_default_to_zero_on_new_and_thread_exit() { | ||
assert_eq!(StoppedClock::now(), Duration::ZERO); | ||
let after5 = WorkingClock::now_add(&Duration::from_secs(5)).unwrap(); | ||
StoppedClock::local_set(&after5); | ||
assert_eq!(StoppedClock::now(), after5); | ||
|
||
let t = thread::spawn(move || { | ||
// each thread starts out with the initial value of ZERO | ||
assert_eq!(StoppedClock::now(), Duration::ZERO); | ||
|
||
// and gets set to the current time. | ||
let timestamp = WorkingClock::now(); | ||
StoppedClock::local_set(×tamp); | ||
assert_eq!(StoppedClock::now(), timestamp); | ||
}); | ||
|
||
// wait for the thread to complete and bail out on panic | ||
t.join().unwrap(); | ||
|
||
// we retain our original value of current time + 5sec despite the child thread | ||
assert_eq!(StoppedClock::now(), after5); | ||
|
||
// Reset to ZERO and Check | ||
StoppedClock::local_reset(); | ||
assert_eq!(StoppedClock::now(), Duration::ZERO); | ||
} | ||
} |