mock_instant-0.3.2/.cargo_vcs_info.json0000644000000001360000000000100135270ustar { "git": { "sha1": "e9e24b7d92249457678c1b9ab9f7b1daad6073d7" }, "path_in_vcs": "" }mock_instant-0.3.2/.gitignore000064400000000000000000000000250072674642500143340ustar 00000000000000/target Cargo.lock mock_instant-0.3.2/Cargo.toml0000644000000016610000000000100115310ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "mock_instant" version = "0.3.2" authors = ["museun "] description = "a simple way to mock an std::time::Instant" documentation = "https://docs.rs/mock_instant" readme = "README.md" keywords = [ "mock", "test", "time", "instant", ] license = "0BSD" repository = "https://github.com/museun/mock_instant" [dependencies.once_cell] version = "1.17" optional = true [features] default = [] sync = ["once_cell"] mock_instant-0.3.2/Cargo.toml.orig000064400000000000000000000007540072674642500152440ustar 00000000000000[package] name = "mock_instant" version = "0.3.2" authors = [ "museun " ] edition = "2018" license = "0BSD" readme = "README.md" keywords = [ "mock", "test", "time", "instant" ] description = "a simple way to mock an std::time::Instant" documentation = "https://docs.rs/mock_instant" repository = "https://github.com/museun/mock_instant" [features] default = [ ] sync = [ "once_cell" ] [dependencies] once_cell = { version = "1.17", optional = true } mock_instant-0.3.2/LICENSE.txt000064400000000000000000000012360072674642500141740ustar 00000000000000Copyright (C) 2020 by museun Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. mock_instant-0.3.2/README.md000064400000000000000000000030340072674642500136260ustar 00000000000000# mock_instant ## mock_instant This crate allows you to test Instant/Duration code, deterministically **_per thread_**. If cross-thread determinism is required, enable the `sync` feature: ```toml mock_instant = { version = "0.3", features = ["sync"] } ``` It provides a replacement `std::time::Instant` and `std::time::SystemTime` that uses a deterministic thread-local 'clock' You can swap out the `std::time::Instant` with this one by doing something similar to: ```rust #[cfg(test)] use mock_instant::Instant; #[cfg(not(test))] use std::time::Instant; ``` or for a `std::time::SystemTime` ``` #[cfg(test)] use mock_instant::{SystemTime, SystemTimeError}; #[cfg(not(test))] use std::time::{SystemTime, SystemTimeError}; ``` ## Example ```rust use std::time::Duration; let now = Instant::now(); MockClock::advance(Duration::from_secs(15)); MockClock::advance(Duration::from_secs(2)); // its been '17' seconds assert_eq!(now.elapsed(), Duration::from_secs(17)); ``` # Mocking a SystemTime ``` # use mock_instant::{MockClock, SystemTime}; use std::time::Duration; let now = SystemTime::now(); MockClock::advance_system_time(Duration::from_secs(15)); MockClock::advance_system_time(Duration::from_secs(2)); // its been '17' seconds assert_eq!(now.elapsed().unwrap(), Duration::from_secs(17)); ``` # Caveats If the `sync` feature is enabled then all tests using this crate will use a global singleton clock. see License: 0BSD mock_instant-0.3.2/src/lib.rs000064400000000000000000000361620072674642500142620ustar 00000000000000/*! # mock_instant This crate allows you to test Instant/Duration code, deterministically ***per thread***. If cross-thread determinism is required, enable the `sync` feature: ```toml mock_instant = { version = "0.2", features = ["sync"] } ``` It provides a replacement `std::time::Instant` and `std::time::SystemTime` that uses a deterministic thread-local 'clock' **NOTE:** if this is enabled then all tests will use the same singleton `MockClock` source --- You can swap out the `std::time::Instant` with this one by doing something similar to: ```rust #[cfg(test)] use mock_instant::Instant; #[cfg(not(test))] use std::time::Instant; ``` or for a `std::time::SystemTime` ```rust #[cfg(test)] use mock_instant::{SystemTime, SystemTimeError}; #[cfg(not(test))] use std::time::{SystemTime, SystemTimeError}; ``` # Example ```rust # use mock_instant::{MockClock, Instant}; use std::time::Duration; let now = Instant::now(); MockClock::advance(Duration::from_secs(15)); MockClock::advance(Duration::from_secs(2)); // its been '17' seconds assert_eq!(now.elapsed(), Duration::from_secs(17)); ``` # Mocking a SystemTime ```rust # use mock_instant::{MockClock, SystemTime}; use std::time::Duration; let now = SystemTime::now(); MockClock::advance_system_time(Duration::from_secs(15)); MockClock::advance_system_time(Duration::from_secs(2)); // its been '17' seconds assert_eq!(now.elapsed().unwrap(), Duration::from_secs(17)); ``` */ use std::time::Duration; #[cfg(feature = "sync")] mod reference { use once_cell::sync::OnceCell; use std::{sync::Mutex, time::Duration}; pub static TIME: OnceCell> = OnceCell::new(); pub static SYSTEM_TIME: OnceCell> = OnceCell::new(); pub fn with_time(d: impl Fn(&mut Duration)) { let t = TIME.get_or_init(Mutex::default); let mut t = t.lock().unwrap(); d(&mut t); } pub fn get_time() -> Duration { *TIME.get_or_init(Mutex::default).lock().unwrap() } pub fn with_system_time(d: impl Fn(&mut Duration)) { let t = SYSTEM_TIME.get_or_init(Mutex::default); let mut t = t.lock().unwrap(); d(&mut t); } pub fn get_system_time() -> Duration { *SYSTEM_TIME.get_or_init(Mutex::default).lock().unwrap() } } #[cfg(not(feature = "sync"))] mod reference { use std::cell::RefCell; use std::time::Duration; thread_local! { pub static TIME: RefCell = RefCell::new(Duration::default()); pub static SYSTEM_TIME: RefCell = RefCell::new(Duration::default()); } pub fn with_time(d: impl Fn(&mut Duration)) { TIME.with(|t| d(&mut t.borrow_mut())) } pub fn with_system_time(d: impl Fn(&mut Duration)) { SYSTEM_TIME.with(|t| d(&mut t.borrow_mut())) } pub fn get_time() -> Duration { TIME.with(|t| *t.borrow()) } pub fn get_system_time() -> Duration { SYSTEM_TIME.with(|t| *t.borrow()) } } /// A Mock clock /// /// This uses thread local state to have a deterministic clock. #[derive(Copy, Clone)] pub struct MockClock; impl std::fmt::Debug for MockClock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("MockClock") .field("time", &Self::time()) .field("system_time", &Self::system_time()) .finish() } } impl MockClock { /// Set the internal Instant clock to this 'Duration' pub fn set_time(time: Duration) { reference::with_time(|t| *t = time); } /// Advance the internal Instant clock by this 'Duration' pub fn advance(time: Duration) { reference::with_time(|t| *t += time); } /// Get the current Instant duration pub fn time() -> Duration { reference::get_time() } /// Set the internal SystemTime clock to this 'Duration' pub fn set_system_time(time: Duration) { reference::with_system_time(|t| *t = time); } /// Advance the internal SystemTime clock by this 'Duration' pub fn advance_system_time(time: Duration) { reference::with_system_time(|t| *t += time); } /// Get the current SystemTime duration pub fn system_time() -> Duration { reference::get_system_time() } } /// An error returned from the duration_since and elapsed methods on SystemTime, used to learn how far in the opposite direction a system time lies. #[derive(Clone, Debug)] pub struct SystemTimeError(Duration); impl SystemTimeError { pub fn duration(&self) -> Duration { self.0 } } impl std::fmt::Display for SystemTimeError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "second time provided was later than self") } } impl std::error::Error for SystemTimeError { #[allow(deprecated)] fn description(&self) -> &str { "other time was not earlier than self" } } /// A simple deterministic SystemTime wrapped around a modifiable Duration /// /// This used a thread-local state as the 'wall clock' that is configurable via /// the `MockClock` #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] pub struct SystemTime(Duration); pub const UNIX_EPOCH: SystemTime = SystemTime(Duration::from_secs(0)); impl SystemTime { pub const UNIX_EPOCH: SystemTime = UNIX_EPOCH; pub fn now() -> Self { Self(MockClock::system_time()) } pub fn duration_since(&self, earlier: SystemTime) -> Result { self.0 .checked_sub(earlier.0) .ok_or_else(|| SystemTimeError(earlier.0 - self.0)) } pub fn elapsed(&self) -> Result { Self::now().duration_since(*self) } pub fn checked_add(&self, duration: Duration) -> Option { duration .as_millis() .checked_add(self.0.as_millis()) .map(|c| Duration::from_millis(c as _)) .map(Self) } pub fn checked_sub(&self, duration: Duration) -> Option { self.0 .as_millis() .checked_sub(duration.as_millis()) .map(|c| Duration::from_millis(c as _)) .map(Self) } } impl std::ops::Add for SystemTime { type Output = SystemTime; fn add(self, rhs: Duration) -> Self::Output { self.checked_add(rhs) .expect("overflow when adding duration to instant") } } impl std::ops::AddAssign for SystemTime { fn add_assign(&mut self, rhs: Duration) { *self = *self + rhs } } impl std::ops::Sub for SystemTime { type Output = SystemTime; fn sub(self, rhs: Duration) -> Self::Output { self.checked_sub(rhs) .expect("overflow when subtracting duration from instant") } } impl std::ops::SubAssign for SystemTime { fn sub_assign(&mut self, rhs: Duration) { *self = *self - rhs } } impl From for SystemTime { fn from(value: std::time::SystemTime) -> Self { Self( value .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("std::time::SystemTime is before UNIX_EPOCH"), ) } } impl From for std::time::SystemTime { fn from(value: SystemTime) -> Self { Self::UNIX_EPOCH + value.0 } } /// A simple deterministic Instant wrapped around a modifiable Duration /// /// This used a thread-local state as the 'wall clock' that is configurable via /// the `MockClock` #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] pub struct Instant(Duration); impl Instant { pub fn now() -> Self { Self(MockClock::time()) } pub fn duration_since(&self, earlier: Self) -> Duration { self.0 - earlier.0 } pub fn checked_duration_since(&self, earlier: Self) -> Option { self.0.checked_sub(earlier.0) } pub fn saturating_duration_since(&self, earlier: Self) -> Duration { self.checked_duration_since(earlier).unwrap_or_default() } pub fn elapsed(&self) -> Duration { MockClock::time() - self.0 } pub fn checked_add(&self, duration: Duration) -> Option { duration .as_millis() .checked_add(self.0.as_millis()) .map(|c| Duration::from_millis(c as _)) .map(Self) } pub fn checked_sub(&self, duration: Duration) -> Option { self.0 .as_millis() .checked_sub(duration.as_millis()) .map(|c| Duration::from_millis(c as _)) .map(Self) } } impl std::ops::Add for Instant { type Output = Self; fn add(self, rhs: Duration) -> Self::Output { Self(self.0 + rhs) } } impl std::ops::AddAssign for Instant { fn add_assign(&mut self, rhs: Duration) { self.0 += rhs } } impl std::ops::Sub for Instant { type Output = Duration; fn sub(self, rhs: Self) -> Self::Output { self.duration_since(rhs) } } impl std::ops::Sub for Instant { type Output = Instant; fn sub(self, rhs: Duration) -> Self::Output { self.checked_sub(rhs) .expect("overflow when substracting duration from instant") } } impl std::ops::SubAssign for Instant { fn sub_assign(&mut self, rhs: Duration) { self.0 -= rhs } } #[cfg(test)] mod tests { use super::*; fn reset_time() { MockClock::set_time(Duration::default()) } fn reset_system_time() { MockClock::set_system_time(Duration::default()) } #[test] fn set_system_time() { MockClock::set_system_time(Duration::from_secs(42)); assert_eq!(MockClock::system_time(), Duration::from_secs(42)); reset_system_time(); assert_eq!(MockClock::system_time(), Duration::default()); } #[test] fn advance_system_time() { for i in 0..3 { MockClock::advance_system_time(Duration::from_millis(100)); let time = Duration::from_millis(100 * (i + 1)); assert_eq!(MockClock::system_time(), time); } } #[test] fn system_time() { let now = SystemTime::now(); for i in 0..3 { MockClock::advance_system_time(Duration::from_millis(100)); assert_eq!(now.elapsed().unwrap(), Duration::from_millis(100 * (i + 1))); } MockClock::advance_system_time(Duration::from_millis(100)); let next = SystemTime::now(); assert_eq!( next.duration_since(now).unwrap(), Duration::from_millis(400) ); } #[test] fn system_time_methods() { let system_time = SystemTime::now(); let interval = Duration::from_millis(42); MockClock::advance_system_time(interval); // zero + 1 = 1 assert_eq!( system_time.checked_add(Duration::from_millis(1)).unwrap(), SystemTime(Duration::from_millis(1)) ); // now + 1 = diff + 1 assert_eq!( SystemTime::now() .checked_add(Duration::from_millis(1)) .unwrap(), SystemTime(Duration::from_millis(43)) ); // zero - 1 = None assert!(system_time.checked_sub(Duration::from_millis(1)).is_none()); // now - 1 = diff -1 assert_eq!( SystemTime::now() .checked_sub(Duration::from_millis(1)) .unwrap(), SystemTime(Duration::from_millis(41)) ); // now - 1 = diff - 1 assert_eq!( SystemTime::now() - Duration::from_millis(1), SystemTime(Duration::from_millis(41)) ); // now - diff + 1 = none assert!(SystemTime::now() .checked_sub(Duration::from_millis(43)) .is_none()); } #[test] fn system_time_from_std_roundtrip() { let std_now = std::time::SystemTime::now(); let mock_now: SystemTime = std_now.into(); assert!(mock_now.0 > Duration::from_secs(1708041600)); // Friday 16 February 2024 00:00:00 GMT let roundtrip_now: std::time::SystemTime = mock_now.into(); assert_eq!(std_now, roundtrip_now) } #[test] fn set_time() { MockClock::set_time(Duration::from_secs(42)); assert_eq!(MockClock::time(), Duration::from_secs(42)); reset_time(); assert_eq!(MockClock::time(), Duration::default()); } #[test] fn advance() { for i in 0..3 { MockClock::advance(Duration::from_millis(100)); let time = Duration::from_millis(100 * (i + 1)); assert_eq!(MockClock::time(), time); } } #[test] fn instant() { let now = Instant::now(); for i in 0..3 { MockClock::advance(Duration::from_millis(100)); assert_eq!(now.elapsed(), Duration::from_millis(100 * (i + 1))); } MockClock::advance(Duration::from_millis(100)); let next = Instant::now(); assert_eq!(next.duration_since(now), Duration::from_millis(400)); } #[test] fn methods() { let instant = Instant::now(); let interval = Duration::from_millis(42); MockClock::advance(interval); // 0 - now = None assert!(instant.checked_duration_since(Instant::now()).is_none()); // now - 0 = diff assert_eq!( Instant::now().checked_duration_since(instant).unwrap(), interval ); // 0 since now = none assert!(instant.checked_duration_since(Instant::now()).is_none()); // now since 0 = diff assert_eq!( Instant::now().checked_duration_since(instant).unwrap(), interval ); // zero + 1 = 1 assert_eq!( instant.checked_add(Duration::from_millis(1)).unwrap(), Instant(Duration::from_millis(1)) ); // now + 1 = diff + 1 assert_eq!( Instant::now() .checked_add(Duration::from_millis(1)) .unwrap(), Instant(Duration::from_millis(43)) ); // zero - 1 = None assert!(instant.checked_sub(Duration::from_millis(1)).is_none()); // now - 1 = diff -1 assert_eq!( Instant::now() .checked_sub(Duration::from_millis(1)) .unwrap(), Instant(Duration::from_millis(41)) ); // now - 1 = diff - 1 assert_eq!( Instant::now() - Duration::from_millis(1), Instant(Duration::from_millis(41)) ); // now - diff + 1 = none assert!(Instant::now() .checked_sub(Duration::from_millis(43)) .is_none()); } }