use super::*;
use std::cell;
impl Uuid {
fn timestamp(&self) -> u64 {
assert_eq!(self.version(), Some(7));
u64::from_be_bytes(self.as_bytes()[..8].try_into().unwrap()) >> 16
}
}
fn new_rand_source() -> impl RandSource {
struct MockRandSource;
impl RandSource for MockRandSource {
fn next_u32(&mut self) -> u32 {
rand::random()
}
fn next_u64(&mut self) -> u64 {
rand::random()
}
}
MockRandSource
}
fn new_time_source() -> impl TimeSource {
#[cfg(feature = "std")]
return StdSystemTime;
#[cfg(not(feature = "std"))]
{
struct MockTimeSource(u64);
impl TimeSource for MockTimeSource {
fn unix_ts_ms(&mut self) -> u64 {
self.0 += 8;
self.0
}
}
MockTimeSource(0x0123_4567_89abu64)
}
}
#[test]
fn reads_timestamp_from_time_source() {
struct PeekableTimeSource<'a, T>(&'a cell::Cell<u64>, T);
impl<T: TimeSource> TimeSource for PeekableTimeSource<'_, T> {
fn unix_ts_ms(&mut self) -> u64 {
self.0.set(self.1.unix_ts_ms());
self.0.get()
}
}
let ts = cell::Cell::default();
let time_source = PeekableTimeSource(&ts, new_time_source());
let mut g = V7Generator::with_rand_and_time_sources(new_rand_source(), time_source);
assert_eq!(g.generate().timestamp(), ts.get());
assert_eq!(g.generate().timestamp(), ts.get());
assert_eq!(g.generate_or_abort().unwrap().timestamp(), ts.get());
assert_eq!(g.generate_or_abort().unwrap().timestamp(), ts.get());
assert_eq!(g.generate().timestamp(), ts.get());
assert_eq!(g.generate_or_abort().unwrap().timestamp(), ts.get());
assert_eq!(g.generate().timestamp(), ts.get());
assert_eq!(g.generate_or_abort().unwrap().timestamp(), ts.get());
}
#[test]
fn handle_clock_rollback() {
const DEFAULT_ROLLBACK_ALLOWANCE: u64 = 10_000;
struct CellTimeSource<'a>(&'a cell::Cell<u64>);
impl TimeSource for CellTimeSource<'_> {
fn unix_ts_ms(&mut self) -> u64 {
self.0.get()
}
}
for rollback_allowance in [DEFAULT_ROLLBACK_ALLOWANCE, 5_000, 20_000] {
let ts = cell::Cell::new(0);
let [mut g0, mut g1, mut g2, mut g3, mut g4, mut g5] = [
V7Generator::with_rand_and_time_sources(new_rand_source(), CellTimeSource(&ts)),
V7Generator::with_rand_and_time_sources(new_rand_source(), CellTimeSource(&ts)),
V7Generator::with_rand_and_time_sources(new_rand_source(), CellTimeSource(&ts)),
V7Generator::with_rand_and_time_sources(new_rand_source(), CellTimeSource(&ts)),
V7Generator::with_rand_and_time_sources(new_rand_source(), CellTimeSource(&ts)),
V7Generator::with_rand_and_time_sources(new_rand_source(), CellTimeSource(&ts)),
];
if rollback_allowance != DEFAULT_ROLLBACK_ALLOWANCE {
g0.set_rollback_allowance(rollback_allowance);
g1.set_rollback_allowance(rollback_allowance);
g2.set_rollback_allowance(rollback_allowance);
g3.set_rollback_allowance(rollback_allowance);
}
#[allow(deprecated)]
let methods: [(&mut dyn FnMut() -> Option<Uuid>, bool); 6] = [
(&mut || Some(g0.generate()), true),
(&mut || g1.generate_or_abort(), false),
(&mut || Some(g2.generate_or_reset_with_ts(ts.get())), true),
(&mut || g3.generate_or_abort_with_ts(ts.get()), false),
(
&mut || Some(g4.generate_or_reset_core(ts.get(), rollback_allowance)),
true,
),
(
&mut || g5.generate_or_abort_core(ts.get(), rollback_allowance),
false,
),
];
for (generate, is_reset) in methods {
let mut ts_base = new_time_source().unix_ts_ms();
ts.set(ts_base);
let mut prev = generate().unwrap();
assert_eq!(prev.timestamp(), ts_base);
for _ in 0..50 {
let curr = generate().unwrap();
assert!(prev < curr);
assert!(curr.timestamp() >= ts_base);
prev = curr;
}
for i in 0..50_000u64 {
ts.set(ts_base - i.min(rollback_allowance - 1));
let curr = generate().unwrap();
assert!(prev < curr);
assert!(curr.timestamp() >= ts_base);
prev = curr;
}
ts_base += rollback_allowance * 4;
ts.set(ts_base);
prev = generate().unwrap();
assert_eq!(prev.timestamp(), ts_base);
ts.set(ts_base - rollback_allowance);
let mut curr = generate();
assert!(prev < curr.unwrap());
assert!(curr.unwrap().timestamp() >= ts_base);
if is_reset {
prev = curr.unwrap();
ts.set(ts_base - rollback_allowance - 1);
curr = generate();
assert!(prev > curr.unwrap());
assert_eq!(curr.unwrap().timestamp(), ts_base - rollback_allowance - 1);
prev = curr.unwrap();
ts.set(ts_base - rollback_allowance - 2);
curr = generate();
assert!(prev < curr.unwrap());
assert!(curr.unwrap().timestamp() >= ts_base - rollback_allowance - 1);
} else {
ts.set(ts_base - rollback_allowance - 1);
curr = generate();
assert!(curr.is_none());
ts.set(ts_base - rollback_allowance - 2);
curr = generate();
assert!(curr.is_none());
}
}
}
}
impl V7Generator<()> {
fn for_testing() -> V7Generator<impl RandSource, impl TimeSource> {
V7Generator::with_rand_and_time_sources(new_rand_source(), new_time_source())
}
}
#[test]
#[allow(deprecated)]
fn core_fns_do_not_change_rollback_allowance() {
let ts = new_time_source().unix_ts_ms();
let mut g = V7Generator::for_testing();
g.set_rollback_allowance(100);
assert_eq!(g.rollback_allowance, 100);
g.generate_or_reset_core(ts, 1_000);
assert_eq!(g.rollback_allowance, 100);
g.generate_or_abort_core(ts, 1_000);
assert_eq!(g.rollback_allowance, 100);
}
#[test]
fn is_iterable_with_for_in_loop() {
let mut i = 0;
for e in V7Generator::for_testing() {
assert!(e.timestamp() > 0);
i += 1;
if i > 100 {
break;
}
}
assert_eq!(i, 101);
}