Expand description
§metrique-timesource
A flexible time source abstraction for Rust applications that allows for easy testing and mocking of time-dependent code.
This was originally written to support Metrique, but can be used for any application that wants to use deterministic time in tests.
§Features
- Zero-cost abstraction when not compiled with the
custom-timesourceenabled - Built in support for
tokio’s timepausewithtokiofeature - Provide a time source manually or via a thread-local
- Compatible with
std::time::Instantandstd::time::SystemTime
§Usage
Add the crate to your Cargo.toml:
[dependencies]
metrique-timesource = "0.1.0"
# If you plan on testing:
[dev-dependencies]
metrique-timesource = { version = "0.1.0", features = ["custom-timesource", "tokio"] }
tokio = { version = "1", features = ["test-util", "full"] }§In production code
In production code, you can choose from two patterns to access a time source. Generally, loading a timesource automatically is the recommended pattern, but some teams prefer the benefits of threading it explicitly (or, e.g. are testing across many threads that makes the mocks difficult to use).
- Manually thread the
TimeSourcewhere needed.
use metrique_timesource::TimeSource;
struct MyThingThatUsesTime {
time_source: TimeSource
}
impl MyThingThatUsesTime {
pub fn new(time_source: TimeSource) -> Self {
Self {
time_source
}
}
}- Use
get_time_source/time_sourceto source it automatically:
use metrique_timesource::{TimeSource, time_source};
struct MyThingThatUsesTime {
time_source: TimeSource
}
impl MyThingThatUsesTime {
pub fn new() -> Self {
Self {
time_source: time_source()
}
}
}get_time_source allows passing an Option<TimeSource> that is used as the highest priority option. This is equivalent to maybe_ts.unwrap_or_else(||time_source()).
use metrique_timesource::{TimeSource, get_time_source};
struct Thing { timesource: TimeSource }
struct Builder {
timesource: Option<TimeSource>,
}
impl Builder {
pub fn build(self) -> Thing {
let timesource = get_time_source(self.timesource);
Thing { timesource }
}
}In both cases, you can then use the timesource to source SystemTimes and Instants that can be externally controlled. Even in the time_source case, when the custom-timesource feature is not enabled, the function is inlined to return a zero-sized-type.
§Working with TimeSource
TimeSource returns wrapped versions of Instant and SystemTime. This allows elapsed to function as expected, even when a time source is overridden.
They provide many of the same methods as the std variants. If you need a method that is not available, you can use .as_std().
§In tests
If you use time_source, you can override the timesource for the current thread with set_time_source which returns a guard:
use metrique_timesource::{TimeSource, time_source, set_time_source, Instant};
use std::time::{Duration, UNIX_EPOCH};
struct MyThingThatUsesTime {
create_time: Instant
}
impl MyThingThatUsesTime {
pub fn new() -> Self {
Self {
create_time: time_source().instant()
}
}
}
// Note: when using the tokio time source, you can't use multiple threads—tokio::time::pause only works
// on the current-thread runtime. See https://docs.rs/tokio/latest/tokio/time/fn.pause.html
tokio::time::pause();
let _guard = set_time_source(TimeSource::tokio(UNIX_EPOCH));
let my_thing = MyThingThatUsesTime::new();
assert_eq!(my_thing.create_time.elapsed(), Duration::from_secs(0));
tokio::time::advance(Duration::from_secs(5)).await;
assert_eq!(my_thing.create_time.elapsed(), Duration::from_secs(5));
with_time_source is also provided which allows running a given closure with a time_source installed.
use metrique_timesource::{TimeSource, fakes::StaticTimeSource, time_source, with_time_source};
use std::time::UNIX_EPOCH;
let ts = StaticTimeSource::at_time(UNIX_EPOCH);
let custom = TimeSource::custom(ts);
// Run code with the custom time source
with_time_source(custom, || {
// Code here will use the custom time source
let now = time_source().system_time();
assert_eq!(now, UNIX_EPOCH);
});§Writing your own mock time
2 mock time sources are provided:
fake::StaticTimeSourcewhich always returns the same time and instantTokioTimewhich usestokio::time::Instant::now
It is also possible to write your own by implementing the Time trait. See the fakes module for an example.
Modules§
- fakes
test-util - Module containing fake time sources for testing
- tokio
tokio - Tokio-specific time source implementations
Structs§
- Instant
Instantwrapper- System
Time SystemTimewrapper- Thread
Local Time Source Guard - Guard for thread-local time source override
Enums§
- Time
Source - Enum representing different time source options
Traits§
- Time
- Trait for providing custom time sources
Functions§
- get_
time_ source - Get the current time source, following the priority order:
- set_
time_ source custom-timesource - Set a thread-local time source override and return a guard When the guard is dropped, the thread-local override will be cleared
- time_
source - Get the current time source
- with_
time_ source custom-timesource - Run a closure with a thread-local time source override