mock_instant-0.6.0/.cargo_vcs_info.json0000644000000001360000000000100135300ustar { "git": { "sha1": "97e35a8fc477baff0d3619766ce8c45e551144bb" }, "path_in_vcs": "" }mock_instant-0.6.0/.github/workflows/ci.yml000064400000000000000000000021241046102023000170320ustar 00000000000000name: CI on: push: branches: [main] pull_request: jobs: ci: name: CI needs: [test, clippy] runs-on: ubuntu-latest steps: - name: Done run: exit 0 test: name: Tests strategy: fail-fast: false matrix: os: [ubuntu-latest] rust: [stable] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - name: Install rust uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} components: rustfmt - uses: taiki-e/install-action@cargo-deny - uses: Swatinem/rust-cache@v2 - run: cargo test - run: cargo test --all-targets --all-features --workspace - run: cargo doc --no-deps --all-features clippy: name: Clippy runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install rust uses: dtolnay/rust-toolchain@stable with: components: clippy - uses: Swatinem/rust-cache@v2 - run: cargo clippy - run: cargo clippy --all-targets --all-features --workspace mock_instant-0.6.0/.gitignore000064400000000000000000000000231046102023000143030ustar 00000000000000/target Cargo.lock mock_instant-0.6.0/Cargo.lock0000644000000002340000000000100115020ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "mock_instant" version = "0.6.0" mock_instant-0.6.0/Cargo.toml0000644000000017430000000000100115330ustar # 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 = "2021" name = "mock_instant" version = "0.6.0" authors = ["museun "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false 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" [lib] name = "mock_instant" path = "src/lib.rs" mock_instant-0.6.0/Cargo.toml.orig000064400000000000000000000005461046102023000152140ustar 00000000000000[package] name = "mock_instant" version = "0.6.0" authors = [ "museun " ] edition = "2021" 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" mock_instant-0.6.0/LICENSE.txt000064400000000000000000000012221046102023000141400ustar 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.6.0/README.md000064400000000000000000000103271046102023000136020ustar 00000000000000# mock_instant ## Modules **_NOTE_** As of verison 0.6, the timer resolution is nanosecond-based, which may be much more accurate than the real thing **_NOTE_** As of version 0.5. MockClock/Instant/SystemTime have been moved to specific modules **_NOTE_** The modules, `global` and `thread_local` change the behavior across threads. If `global` is used, the clock keeps its state across threads, otherwise if `thread_local` is used, a new _source_ is made for each thread. In most cases, especially when unit testing, `thread_local` will deliver the most expected behavior. To ensure unsurprising behavior, **reset** the clock _before_ each test (if that behavior is applicable.) ## Basics This crate allows you to test `Instant`/`Duration`/`SystemTime` code, deterministically. It provides a replacement `std::time::Instant` that uses a deterministic 'clock' You can swap out the `std::time::Instant` with this one by doing something similar to: ```rust #[cfg(test)] use mock_instant::thread_local::Instant; #[cfg(not(test))] use std::time::Instant; ``` or for a `std::time::SystemTime` ```rust #[cfg(test)] use mock_instant::thread_local::{SystemTime, SystemTimeError}; #[cfg(not(test))] use std::time::{SystemTime, SystemTimeError}; ``` ```rust use mock_instant::thread_local::{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)); ``` ## API ```rust,compile_fail // Overrides the current time to this `Duration` MockClock::set_time(time: Duration) // Advance the current time by this `Duration` MockClock::advance(time: Duration) // Get the current time MockClock::time() -> Duration // Overrides the current `SystemTime` to this duration MockClock::set_system_time(time: Duration) // Advance the current `SystemTime` by this duration MockClock::advance_system_time(time: Duration) // Get the current `SystemTime` MockClock::system_time() -> Duration // Determine if this MockClock was thread-local: (useful for assertions to ensure the right mode is being used) MockClock::is_thread_local() -> bool Instant::now().is_thread_local() -> bool SystemTime::now().is_thread_local() -> bool ``` ## Usage **_NOTE_** The clock starts at `Duration::ZERO` In your tests, you can use `MockClock::set_time(Duration::ZERO)` to reset the clock back to 0. Or, you can set it to some sentinel time value. Then, before you check your time-based logic, you can advance the clock by some `Duration` (it'll freeze the time to that duration) You can also get the current frozen time with `MockClock::time` `SystemTime` is also mockable with a similar API. ## Thread-safety Two modes are provided via modules. The APIs are identical but the `MockClock` source has different behavior in different threads. ### `mock_instant::thread_local` - `MockClock` will have an independent state per thread - `Instant`will have an independent state per thread - `SystemTime` will have an independent state per thread Using `thread_local` creates a shared clock in each thread of operation within the test application. It is recommended for unit tests and integration tests that test single-threaded behavior. `thread_local` will be the module used in most cases and will create the least surprise. ### `mock_instant::global` - `MockClock` will have shared state per thread - `Instant`will have shared state per thread - `SystemTime` will have shared state per thread Using `global` creates a shared clock across all thread of operation within the test application. It is recommended for integration tests that are specifically testing multi-threaded behavior. Using `global` when not testing multi-threaded behavior is likely to cause inconsistent and confusing errors. Rust's unit test test-runner executes unit tests in multiple threads (unless directed to run single-threaded). If multiple unit tests are running simultaneouly within the test execution, changes to the `global` clock (resetting or advancing the clock) in one test can created unexpected results in other tests that are executing simultaneously, by changing the clock when the tests assume the clock is not being changed. ## License License: 0BSD mock_instant-0.6.0/src/global.rs000064400000000000000000000040211046102023000147120ustar 00000000000000use std::{sync::Mutex, time::Duration}; static TIME: Mutex = Mutex::new(Duration::ZERO); static SYSTEM_TIME: Mutex = Mutex::new(Duration::ZERO); fn with_time(d: impl Fn(&mut Duration)) { let mut t = TIME.lock().unwrap(); d(&mut t); } fn get_time() -> Duration { *TIME.lock().unwrap() } fn with_system_time(d: impl Fn(&mut Duration)) { let mut t = SYSTEM_TIME.lock().unwrap(); d(&mut t); } fn get_system_time() -> Duration { *SYSTEM_TIME.lock().unwrap() } crate::macros::define_mock_clock! { false; /// This uses a global mutex state for the deterministic clock } crate::macros::define_instant! { MockClock::time; false; /// This uses a global mutex for its time source } crate::macros::define_system_time! { MockClock::system_time; false; /// This uses a global mutex for its time source } crate::macros::define_instant_tests!(); #[cfg(test)] mod tests { use super::*; #[test] fn is_thread_local() { assert!(!MockClock::is_thread_local()); assert!(!Instant::now().is_thread_local()); assert!(!SystemTime::now().is_thread_local()); } #[test] fn thread_sharing() { MockClock::set_time(Duration::ZERO); let start = Instant::now(); std::thread::spawn(move || { let start = Instant::now(); MockClock::advance(Duration::from_secs(3)); assert_eq!(start.elapsed(), Duration::from_secs(3)); }) .join() .unwrap(); std::thread::spawn(move || { let next = Instant::now(); MockClock::advance(Duration::from_secs(30)); assert_eq!(next.elapsed(), Duration::from_secs(30)); }) .join() .unwrap(); MockClock::advance(Duration::from_secs(10)); // using seconds because a mutex can be slow on some operating systems // the creation/locking time may be in microseconds so we'll have something like 43.002 != 43 assert_eq!(start.elapsed().as_secs(), 43); } } mock_instant-0.6.0/src/lib.rs000064400000000000000000000076641046102023000142400ustar 00000000000000/*! # mock_instant **_NOTE_** As of verison 0.6, the timer resolution is nanosecond-based, which may be much more accurate than the real thing **_NOTE_** As of version 0.5. MockClock/Instant/SystemTime have been moved to specific modules **_NOTE_** The modules, `global` and `thread_local` change the behavior across threads. If `global` is used, the clock keeps its state across threads, otherwise if `thread_local` is used, a new *source* is made for each thread To ensure unsurprising behavior, **reset** the clock _before_ each test (if that behavior is applicable.) --- This crate allows you to test `Instant`/`Duration`/`SystemTime` code, deterministically. It provides a replacement `std::time::Instant` that uses a deterministic 'clock' You can swap out the `std::time::Instant` with this one by doing something similar to: ```rust #[cfg(test)] use mock_instant::global::Instant; #[cfg(not(test))] use std::time::Instant; ``` or for a `std::time::SystemTime` ```rust #[cfg(test)] use mock_instant::global::{SystemTime, SystemTimeError}; #[cfg(not(test))] use std::time::{SystemTime, SystemTimeError}; ``` ```rust use mock_instant::global::{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)); ``` ## API: ```rust,compile_fail // Overrides the current time to this `Duration` MockClock::set_time(time: Duration) // Advance the current time by this `Duration` MockClock::advance(time: Duration) // Get the current time MockClock::time() -> Duration // Overrides the current `SystemTime` to this duration MockClock::set_system_time(time: Duration) // Advance the current `SystemTime` by this duration MockClock::advance_system_time(time: Duration) // Get the current `SystemTime` MockClock::system_time() -> Duration // Determine if this MockClock was thread-local: (useful for assertions to ensure the right mode is being used) MockClock::is_thread_local() -> bool Instant::now().is_thread_local() -> bool SystemTime::now().is_thread_local() -> bool ``` ## Usage: **_NOTE_** The clock starts at `Duration::ZERO` In your tests, you can use `MockClock::set_time(Duration::ZERO)` to reset the clock back to 0. Or, you can set it to some sentinel time value. Then, before you check your time-based logic, you can advance the clock by some `Duration` (it'll freeze the time to that duration) You can also get the current frozen time with `MockClock::time` `SystemTime` is also mockable with a similar API. ## Thread-safety: Two modes are provided via modules. The APIs are identical but the `MockClock` source has different behavior in different threads. - `mock_instant::global` - `MockClock` will have a new state per thread - `Instant`will have a new state per thread - `SystemTime` will have a new state per thread - `mock_instant::thread_local` - `MockClock` will have a new state per thread - `Instant`will have a new state per thread - `SystemTime` will have a new state per thread */ use std::time::Duration; mod macros; /// 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" } } /// Thread-local state. /// /// This creates a new state when accessed from a new thread pub mod thread_local; /// Global state. /// /// This shares its 'clock' across threads pub mod global; mock_instant-0.6.0/src/macros.rs000064400000000000000000000373051046102023000147510ustar 00000000000000macro_rules! define_mock_clock { ($thread_local:expr; $(#[$outer:meta])*) => { /// A Mock clock /// $(#[$outer])* #[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) { self::with_time(|t| *t = time); } /// Advance the internal [`Instant`] clock by this [`Duration`] pub fn advance(time: Duration) { self::with_time(|t| *t += time); } /// Get the current [`Instant`] duration pub fn time() -> Duration { self::get_time() } /// Set the internal [`SystemTime`] clock to this [`Duration`] pub fn set_system_time(time: Duration) { self::with_system_time(|t| *t = time); } /// Advance the internal [`SystemTime`] clock by this [`Duration`] pub fn advance_system_time(time: Duration) { self::with_system_time(|t| *t += time); } /// Get the current [`SystemTime`] duration pub fn system_time() -> Duration { self::get_system_time() } /// Is this MockClock thread-local? pub const fn is_thread_local() -> bool { $thread_local } } }; } macro_rules! define_instant { ($now:expr ; $thread_local:expr ; $(#[$outer:meta])* ) => { /// A simple deterministic [`std::time::Instant`] wrapped around a modifiable [`std::time::Duration`] /// $(#[$outer])* #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] pub struct Instant(Duration); impl Instant { pub fn now() -> Self { Self($now()) } pub fn duration_since(&self, earlier: Self) -> Duration { self.checked_duration_since(earlier).unwrap_or_default() } 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 { $now() - self.0 } pub fn checked_add(&self, duration: Duration) -> Option { self.0.checked_add(duration).map(Self) } pub fn checked_sub(&self, duration: Duration) -> Option { self.0.checked_sub(duration).map(Self) } /// Is this Instant thread-local? pub const fn is_thread_local(&self) -> bool { $thread_local } } 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 } } }; } macro_rules! define_system_time { ($now:expr; $thread_local:expr; $(#[$outer:meta])*) => { /// A simple deterministic [`std::time::SystemTime`] wrapped around a modifiable [`std::time::Duration`] /// /// The source is the [`MockClock`] /// $(#[$outer])* #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] pub struct SystemTime(Duration); /// A mocked UNIX_EPOCH. This starts at `0` rather than the traditional unix epoch date. pub const UNIX_EPOCH: SystemTime = SystemTime(Duration::ZERO); impl SystemTime { pub const UNIX_EPOCH: SystemTime = UNIX_EPOCH; pub fn now() -> Self { Self($now()) } pub fn duration_since( &self, earlier: SystemTime, ) -> Result { self.0 .checked_sub(earlier.0) .ok_or_else(|| $crate::SystemTimeError(earlier.0 - self.0)) } pub fn elapsed(&self) -> Result { Self::now().duration_since(*self) } pub fn checked_add(&self, duration: Duration) -> Option { self.0.checked_add(duration).map(Self) } pub fn checked_sub(&self, duration: Duration) -> Option { self.0.checked_sub(duration).map(Self) } /// Is this SystemTime thread-local? pub const fn is_thread_local(&self) -> bool { $thread_local } } 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 } } }; } macro_rules! define_instant_tests { () => { #[cfg(test)] mod common { use super::*; fn reset_time() { MockClock::set_time(Duration::ZERO) } fn reset_system_time() { MockClock::set_system_time(Duration::ZERO) } #[test] fn set_system_time() { reset_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::ZERO); } #[test] fn advance_system_time() { reset_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() { reset_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() { reset_system_time(); 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_sub_millisecond_precision() { reset_system_time(); let t1 = SystemTime::UNIX_EPOCH; let t2 = SystemTime::UNIX_EPOCH .checked_add(Duration::from_micros(100)) .unwrap(); assert!(t2 > t1, "{t2:?} > {t1:?}"); let t3 = t2.checked_sub(Duration::from_nanos(1)).unwrap(); assert!(t2 > t3, "{t2:?} > {t3:?}"); } #[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() { reset_time(); MockClock::set_time(Duration::from_secs(42)); assert_eq!(MockClock::time(), Duration::from_secs(42)); reset_time(); assert_eq!(MockClock::time(), Duration::ZERO); } #[test] fn advance() { reset_time(); 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() { reset_time(); 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() { reset_time(); 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 = 0 assert_eq!( instant.saturating_duration_since(Instant::now()), Duration::ZERO ); // now since 0 = diff assert_eq!(Instant::now().saturating_duration_since(instant), interval); // 0 since now = 0 - same behavior as saturating_duration_since assert_eq!(instant.duration_since(Instant::now()), Duration::ZERO); // now since 0 = diff assert_eq!(Instant::now().duration_since(instant), 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()); } #[test] fn instant_sub_millisecond_precision() { reset_time(); let t1 = Instant::now(); let t2 = t1.checked_add(Duration::from_micros(100)).unwrap(); assert!(t2 > t1, "{t2:?} > {t1:?}"); let t3 = t2.checked_add(Duration::from_nanos(1)).unwrap(); assert!(t3 > t2, "{t3:?} > {t2:?}"); let t4 = t3.checked_sub(Duration::from_nanos(1)).unwrap(); assert_eq!(t4, t2); let t5 = t4.checked_sub(Duration::from_micros(100)).unwrap(); assert_eq!(t5, t1); } } }; } pub(super) use define_instant; pub(super) use define_instant_tests; pub(super) use define_mock_clock; pub(super) use define_system_time; mock_instant-0.6.0/src/thread_local.rs000064400000000000000000000040561046102023000161030ustar 00000000000000use std::{cell::RefCell, time::Duration}; std::thread_local! { static TIME: RefCell = const { RefCell::new(Duration::ZERO) }; static SYSTEM_TIME: RefCell = const { RefCell::new(Duration::ZERO) }; } fn with_time(d: impl Fn(&mut Duration)) { TIME.with(|t| d(&mut t.borrow_mut())); } fn get_time() -> Duration { TIME.with(|t| *t.borrow()) } fn with_system_time(d: impl Fn(&mut Duration)) { SYSTEM_TIME.with(|t| d(&mut t.borrow_mut())); } fn get_system_time() -> Duration { SYSTEM_TIME.with(|t| *t.borrow()) } crate::macros::define_mock_clock! { true; /// This uses thread-local state for the deterministic clock } crate::macros::define_instant! { MockClock::time; true; /// This uses a thread-local cell for its time source } crate::macros::define_system_time! { MockClock::system_time; true; /// This uses a thread-local cell for its time source } crate::macros::define_instant_tests!(); #[cfg(test)] mod tests { use super::*; #[test] fn is_thread_local() { assert!(MockClock::is_thread_local()); assert!(Instant::now().is_thread_local()); assert!(SystemTime::now().is_thread_local()); } // this checks that threads get their own time source #[test] fn thread_locality() { MockClock::set_time(Duration::ZERO); let start = Instant::now(); let handles = [ std::thread::spawn(move || { let start = Instant::now(); MockClock::advance(Duration::from_secs(3)); assert_eq!(start.elapsed(), Duration::from_secs(3)); }), std::thread::spawn(move || { let start = Instant::now(); MockClock::advance(Duration::from_secs(30)); assert_eq!(start.elapsed(), Duration::from_secs(30)); }), ]; MockClock::advance(Duration::from_secs(10)); assert_eq!(start.elapsed(), Duration::from_secs(10)); for handle in handles { handle.join().unwrap(); } } }