use std::cell::Cell;
use crate::core::hlc::HybridTimestamp;
use crate::core::temporal::Timestamp;
thread_local! {
static SIMULATED_NOW_MICROS: Cell<Option<i64>> = const { Cell::new(None) };
}
#[inline]
pub(crate) fn thread_local_now() -> Option<i64> {
SIMULATED_NOW_MICROS.with(|c| c.get())
}
fn set_thread_local(micros: Option<i64>) {
SIMULATED_NOW_MICROS.with(|c| c.set(micros));
}
#[derive(Debug)]
pub struct SimulatedClock {
current_micros: i64,
}
impl SimulatedClock {
pub fn new(initial_micros: i64) -> Self {
Self {
current_micros: initial_micros,
}
}
pub fn now_micros(&self) -> i64 {
self.current_micros
}
pub fn now_as_timestamp(&self) -> Timestamp {
HybridTimestamp::new_unchecked(self.current_micros, 0)
}
pub fn advance_by(&mut self, delta_micros: i64) {
assert!(
delta_micros >= 0,
"Use jump_to() to move the clock backwards"
);
self.current_micros = self.current_micros.saturating_add(delta_micros);
if SIMULATED_NOW_MICROS.with(|c| c.get()).is_some() {
set_thread_local(Some(self.current_micros));
}
}
pub fn jump_to(&mut self, absolute_micros: i64) {
self.current_micros = absolute_micros;
if SIMULATED_NOW_MICROS.with(|c| c.get()).is_some() {
set_thread_local(Some(self.current_micros));
}
}
pub fn jump_by(&mut self, delta_micros: i64) {
self.current_micros = self.current_micros.saturating_add(delta_micros);
if SIMULATED_NOW_MICROS.with(|c| c.get()).is_some() {
set_thread_local(Some(self.current_micros));
}
}
pub fn inject(&self) -> ClockInjectionGuard {
let previous = thread_local_now();
set_thread_local(Some(self.current_micros));
ClockInjectionGuard { previous }
}
}
#[must_use = "dropping this guard immediately would restore the clock override at once"]
#[derive(Debug)]
pub struct ClockInjectionGuard {
previous: Option<i64>,
}
impl Drop for ClockInjectionGuard {
fn drop(&mut self) {
set_thread_local(self.previous);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::temporal::time;
#[test]
fn clock_new_starts_at_given_time() {
let c = SimulatedClock::new(999);
assert_eq!(c.now_micros(), 999);
}
#[test]
fn clock_advance_by_accumulates() {
let mut c = SimulatedClock::new(0);
c.advance_by(100);
c.advance_by(50);
assert_eq!(c.now_micros(), 150);
}
#[test]
fn clock_jump_to_allows_backwards() {
let mut c = SimulatedClock::new(1000);
c.jump_to(1);
assert_eq!(c.now_micros(), 1);
}
#[test]
fn inject_overrides_time_now() {
let c = SimulatedClock::new(7_777_777);
let _g = c.inject();
assert_eq!(time::now().wallclock(), 7_777_777);
}
#[test]
fn guard_drop_restores_real_clock() {
{
let c = SimulatedClock::new(1);
let _g = c.inject();
}
assert!(thread_local_now().is_none());
}
#[test]
fn nested_injections_restore_outer_on_inner_drop() {
let outer = SimulatedClock::new(1_000);
let inner = SimulatedClock::new(2_000);
let _outer_guard = outer.inject();
assert_eq!(time::now().wallclock(), 1_000);
{
let _inner_guard = inner.inject();
assert_eq!(time::now().wallclock(), 2_000);
}
assert_eq!(time::now().wallclock(), 1_000);
}
}