pub const MAX_K: usize = 4096;
#[derive(Clone, Copy)]
pub struct FixedHistory<const K: usize> {
data: [u64; K],
len: usize,
cursor: usize,
}
impl<const K: usize> FixedHistory<K> {
#[allow(dead_code)]
fn _boxed_field_exhaustiveness_tripwire(s: Self) {
let Self {
data: _,
len: _,
cursor: _,
} = s;
}
}
impl<const K: usize> std::fmt::Debug for FixedHistory<K> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
struct MruList<'a, const K: usize>(&'a FixedHistory<K>);
impl<const K: usize> std::fmt::Debug for MruList<'_, K> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_list().entries(self.0.iter()).finish()
}
}
f.debug_struct("FixedHistory")
.field("capacity", &K)
.field("len", &self.len)
.field("mru", &MruList(self))
.finish()
}
}
impl<const K: usize> FixedHistory<K> {
pub fn new() -> Self {
const {
assert!(
K <= MAX_K,
"FixedHistory<K>: K exceeds MAX_K (see cachekit::ds::fixed_history::MAX_K)"
);
}
Self {
data: [0; K],
len: 0,
cursor: 0,
}
}
pub fn boxed() -> Box<Self> {
const {
assert!(
K <= MAX_K,
"FixedHistory<K>: K exceeds MAX_K (see cachekit::ds::fixed_history::MAX_K)"
);
}
let mut uninit: Box<std::mem::MaybeUninit<Self>> = Box::new_uninit();
unsafe {
let p = uninit.as_mut_ptr();
let data_ptr: *mut [u64; K] = std::ptr::addr_of_mut!((*p).data);
std::ptr::write_bytes(data_ptr, 0u8, 1);
std::ptr::addr_of_mut!((*p).len).write(0_usize);
std::ptr::addr_of_mut!((*p).cursor).write(0_usize);
uninit.assume_init()
}
}
pub fn capacity(&self) -> usize {
K
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn record(&mut self, timestamp: u64) {
if K == 0 {
return;
}
debug_assert!(self.cursor < K, "cursor out of range");
debug_assert!(
self.len == K || self.cursor == self.len,
"FixedHistory zero-tail invariant broken before record(): \
cursor = {}, len = {}, K = {}",
self.cursor,
self.len,
K
);
self.data[self.cursor] = timestamp;
self.cursor = (self.cursor + 1) % K;
if self.len < K {
self.len += 1;
}
}
pub fn most_recent(&self) -> Option<u64> {
self.kth_most_recent(1)
}
pub fn kth_most_recent(&self, k: usize) -> Option<u64> {
if K == 0 || k == 0 || k > self.len {
return None;
}
let idx = (self.cursor + (K - k)) % K;
Some(self.data[idx])
}
pub fn to_vec_mru(&self) -> Vec<u64> {
(1..=self.len)
.filter_map(|k| self.kth_most_recent(k))
.collect()
}
pub fn iter(&self) -> Iter<'_, K> {
Iter {
history: self,
pos: 1,
}
}
pub fn clear(&mut self) {
self.data.fill(0);
self.len = 0;
self.cursor = 0;
}
pub fn clear_shrink(&mut self) {
self.clear();
}
pub fn approx_bytes(&self) -> usize {
std::mem::size_of::<Self>()
}
#[cfg(any(test, debug_assertions))]
#[doc(hidden)]
pub fn debug_snapshot_mru(&self) -> Vec<u64> {
self.to_vec_mru()
}
#[cfg(any(test, debug_assertions))]
#[doc(hidden)]
pub fn debug_validate_invariants(&self) {
assert!(self.len <= K);
if K == 0 {
assert_eq!(self.len, 0);
assert_eq!(self.cursor, 0);
} else {
assert!(self.cursor < K);
}
if self.len < K {
for (i, slot) in self.data[self.len..].iter().enumerate() {
assert_eq!(
*slot,
0,
"FixedHistory zero-tail invariant violated at data[{}]",
self.len + i
);
}
}
}
}
impl<const K: usize> Default for FixedHistory<K> {
fn default() -> Self {
Self::new()
}
}
impl<const K: usize> PartialEq for FixedHistory<K> {
fn eq(&self, other: &Self) -> bool {
if self.len != other.len {
return false;
}
for k in 1..=self.len {
if self.kth_most_recent(k) != other.kth_most_recent(k) {
return false;
}
}
true
}
}
impl<const K: usize> Eq for FixedHistory<K> {}
impl<const K: usize> std::hash::Hash for FixedHistory<K> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.len.hash(state);
for k in 1..=self.len {
self.kth_most_recent(k).hash(state);
}
}
}
#[derive(Debug, Clone)]
pub struct Iter<'a, const K: usize> {
history: &'a FixedHistory<K>,
pos: usize, }
impl<'a, const K: usize> Iterator for Iter<'a, K> {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
let val = self.history.kth_most_recent(self.pos)?;
self.pos += 1;
Some(val)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self
.history
.len()
.saturating_sub(self.pos.saturating_sub(1));
(remaining, Some(remaining))
}
}
impl<const K: usize> ExactSizeIterator for Iter<'_, K> {}
#[derive(Debug, Clone)]
pub struct IntoIter<const K: usize> {
history: FixedHistory<K>,
pos: usize,
}
impl<const K: usize> Iterator for IntoIter<K> {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
let val = self.history.kth_most_recent(self.pos)?;
self.pos += 1;
Some(val)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self
.history
.len()
.saturating_sub(self.pos.saturating_sub(1));
(remaining, Some(remaining))
}
}
impl<const K: usize> ExactSizeIterator for IntoIter<K> {}
impl<const K: usize> IntoIterator for FixedHistory<K> {
type Item = u64;
type IntoIter = IntoIter<K>;
fn into_iter(self) -> Self::IntoIter {
IntoIter {
history: self,
pos: 1,
}
}
}
impl<'a, const K: usize> IntoIterator for &'a FixedHistory<K> {
type Item = u64;
type IntoIter = Iter<'a, K>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fixed_history_tracks_last_k() {
let mut history = FixedHistory::<3>::new();
history.record(10);
history.record(20);
history.record(30);
assert_eq!(history.to_vec_mru(), vec![30, 20, 10]);
history.record(40);
assert_eq!(history.to_vec_mru(), vec![40, 30, 20]);
assert_eq!(history.kth_most_recent(3), Some(20));
}
#[test]
fn fixed_history_empty_returns_none() {
let history = FixedHistory::<4>::new();
assert!(history.is_empty());
assert_eq!(history.len(), 0);
assert_eq!(history.most_recent(), None);
assert_eq!(history.kth_most_recent(1), None);
assert_eq!(history.to_vec_mru(), Vec::<u64>::new());
}
#[test]
fn fixed_history_kth_bounds() {
let mut history = FixedHistory::<3>::new();
history.record(10);
history.record(20);
assert_eq!(history.kth_most_recent(0), None);
assert_eq!(history.kth_most_recent(3), None);
assert_eq!(history.kth_most_recent(1), Some(20));
assert_eq!(history.kth_most_recent(2), Some(10));
}
#[test]
fn fixed_history_wraps_and_overwrites_oldest() {
let mut history = FixedHistory::<2>::new();
history.record(1);
history.record(2);
assert_eq!(history.to_vec_mru(), vec![2, 1]);
history.record(3);
assert_eq!(history.to_vec_mru(), vec![3, 2]);
assert_eq!(history.most_recent(), Some(3));
assert_eq!(history.kth_most_recent(2), Some(2));
}
#[test]
fn fixed_history_preserves_order_after_multiple_wraps() {
let mut history = FixedHistory::<3>::new();
for t in 1..=6 {
history.record(t);
}
assert_eq!(history.len(), 3);
assert_eq!(history.to_vec_mru(), vec![6, 5, 4]);
assert_eq!(history.kth_most_recent(1), Some(6));
assert_eq!(history.kth_most_recent(2), Some(5));
assert_eq!(history.kth_most_recent(3), Some(4));
}
#[test]
fn fixed_history_debug_invariants_hold() {
let mut history = FixedHistory::<2>::new();
history.record(1);
history.record(2);
history.record(3);
history.debug_validate_invariants();
}
#[test]
fn fixed_history_debug_snapshot_mru() {
let mut history = FixedHistory::<3>::new();
history.record(10);
history.record(20);
history.record(30);
assert_eq!(history.debug_snapshot_mru(), vec![30, 20, 10]);
}
#[test]
fn iter_yields_mru_order() {
let mut h = FixedHistory::<4>::new();
h.record(10);
h.record(20);
h.record(30);
let v: Vec<_> = h.iter().collect();
assert_eq!(v, vec![30, 20, 10]);
}
#[test]
fn iter_on_empty() {
let h = FixedHistory::<4>::new();
assert_eq!(h.iter().count(), 0);
}
#[test]
fn iter_on_zero_capacity() {
let h = FixedHistory::<0>::new();
assert_eq!(h.iter().count(), 0);
}
#[test]
fn iter_after_wrap() {
let mut h = FixedHistory::<3>::new();
for t in 1..=6 {
h.record(t);
}
let v: Vec<_> = h.iter().collect();
assert_eq!(v, vec![6, 5, 4]);
}
#[test]
fn iter_partially_filled() {
let mut h = FixedHistory::<5>::new();
h.record(100);
h.record(200);
let v: Vec<_> = h.iter().collect();
assert_eq!(v, vec![200, 100]);
}
#[test]
fn iter_count_matches_len() {
let mut h = FixedHistory::<4>::new();
for t in [10, 20, 30, 40, 50] {
h.record(t);
assert_eq!(h.iter().count(), h.len());
}
}
#[test]
fn iter_exact_size() {
let mut h = FixedHistory::<3>::new();
h.record(1);
h.record(2);
let mut it = h.iter();
assert_eq!(it.len(), 2);
it.next();
assert_eq!(it.len(), 1);
it.next();
assert_eq!(it.len(), 0);
assert!(it.next().is_none());
}
#[test]
fn iter_matches_to_vec_mru() {
let mut h = FixedHistory::<5>::new();
for t in [10, 20, 30, 40, 50, 60] {
h.record(t);
}
let from_iter: Vec<_> = h.iter().collect();
assert_eq!(from_iter, h.to_vec_mru());
}
#[test]
fn ref_into_iter_for_loop() {
let mut h = FixedHistory::<3>::new();
h.record(10);
h.record(20);
let mut sum = 0u64;
for t in &h {
sum += t;
}
assert_eq!(sum, 30);
assert_eq!(h.len(), 2); }
#[test]
fn owned_into_iter_for_loop() {
let mut h = FixedHistory::<3>::new();
h.record(10);
h.record(20);
h.record(30);
let mut sum = 0u64;
for t in h {
sum += t;
}
assert_eq!(sum, 60);
}
#[test]
fn into_iter_exact_size() {
let mut h = FixedHistory::<4>::new();
h.record(1);
h.record(2);
h.record(3);
let mut it = h.into_iter();
assert_eq!(it.len(), 3);
it.next();
assert_eq!(it.len(), 2);
}
#[test]
fn into_iter_yields_mru_order() {
let mut h = FixedHistory::<3>::new();
h.record(7);
h.record(8);
h.record(9);
let v: Vec<_> = h.into_iter().collect();
assert_eq!(v, vec![9, 8, 7]);
}
#[test]
fn iter_after_clear() {
let mut h = FixedHistory::<3>::new();
h.record(1);
h.record(2);
h.clear();
assert_eq!(h.iter().count(), 0);
assert_eq!(h.into_iter().count(), 0);
}
#[test]
fn eq_same_entries_same_order() {
let mut a = FixedHistory::<3>::new();
let mut b = FixedHistory::<3>::new();
a.record(10);
a.record(20);
a.record(30);
b.record(10);
b.record(20);
b.record(30);
assert_eq!(a, b);
}
#[test]
fn eq_different_cursor_same_logical_content() {
let mut a = FixedHistory::<3>::new();
a.record(1);
a.record(2);
a.record(3);
let mut b = FixedHistory::<3>::new();
b.record(99);
b.record(1);
b.record(2);
b.record(3);
assert_eq!(a, b);
}
#[test]
fn ne_different_len() {
let mut a = FixedHistory::<3>::new();
a.record(10);
let mut b = FixedHistory::<3>::new();
b.record(10);
b.record(20);
assert_ne!(a, b);
}
#[test]
fn ne_different_values() {
let mut a = FixedHistory::<3>::new();
a.record(10);
a.record(20);
let mut b = FixedHistory::<3>::new();
b.record(10);
b.record(99);
assert_ne!(a, b);
}
#[test]
fn eq_empty_histories() {
let a = FixedHistory::<4>::new();
let b = FixedHistory::<4>::new();
assert_eq!(a, b);
}
#[test]
fn eq_after_clear() {
let mut a = FixedHistory::<3>::new();
a.record(1);
a.record(2);
a.record(3);
a.clear();
let b = FixedHistory::<3>::new();
assert_eq!(a, b);
}
#[test]
fn hash_equal_histories_same_hash() {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut a = FixedHistory::<3>::new();
a.record(10);
a.record(20);
a.record(30);
let mut b = FixedHistory::<3>::new();
b.record(99);
b.record(10);
b.record(20);
b.record(30);
let hash_of = |h: &FixedHistory<3>| {
let mut s = DefaultHasher::new();
h.hash(&mut s);
s.finish()
};
assert_eq!(hash_of(&a), hash_of(&b));
}
#[test]
fn hash_usable_in_hashmap() {
use std::collections::HashMap;
let mut a = FixedHistory::<2>::new();
a.record(1);
a.record(2);
let mut b = FixedHistory::<2>::new();
b.record(1);
b.record(2);
let mut map = HashMap::new();
map.insert(a, "entry");
assert_eq!(map.get(&b), Some(&"entry"));
}
#[test]
fn copy_produces_independent_value() {
let mut original = FixedHistory::<3>::new();
original.record(10);
original.record(20);
let copy = original;
original.record(99);
assert_eq!(copy.most_recent(), Some(20));
assert_eq!(original.most_recent(), Some(99));
}
#[test]
fn copy_can_be_passed_by_value() {
fn sum_history(h: FixedHistory<3>) -> u64 {
h.iter().sum()
}
let mut h = FixedHistory::<3>::new();
h.record(10);
h.record(20);
h.record(30);
assert_eq!(sum_history(h), 60);
assert_eq!(sum_history(h), 60);
}
#[test]
fn max_k_bound_permits_realistic_k() {
let h = FixedHistory::<{ MAX_K }>::new();
assert_eq!(h.capacity(), MAX_K);
assert!(h.is_empty());
}
#[test]
fn kth_most_recent_handles_full_capacity_at_max_k() {
let mut h = FixedHistory::<{ MAX_K }>::new();
for ts in 0..(MAX_K as u64) {
h.record(ts);
}
assert_eq!(h.most_recent(), Some((MAX_K as u64) - 1));
assert_eq!(h.kth_most_recent(MAX_K), Some(0));
assert_eq!(h.kth_most_recent(MAX_K + 1), None);
}
#[test]
fn clear_zeroes_backing_array() {
let mut h = FixedHistory::<4>::new();
h.record(0xDEAD_BEEF);
h.record(0xCAFE_BABE);
h.record(0xFEED_FACE);
h.clear();
for slot in h.data.iter() {
assert_eq!(*slot, 0, "clear() must zero the backing array");
}
}
#[test]
fn debug_output_hides_stale_slots_after_wrap() {
let mut h = FixedHistory::<2>::new();
h.record(0x1111_1111_1111_1111);
h.record(0x2222_2222_2222_2222);
h.record(0x3333_3333_3333_3333);
let rendered = format!("{h:?}");
assert!(
!rendered.contains("1111111111111111"),
"Debug must not expose overwritten timestamp: {rendered}"
);
assert!(rendered.contains("len: 2"), "debug output: {rendered}");
}
#[test]
fn debug_output_hides_stale_slots_after_clear() {
let mut h = FixedHistory::<3>::new();
h.record(0xAAAA_AAAA_AAAA_AAAA);
h.record(0xBBBB_BBBB_BBBB_BBBB);
h.clear();
let rendered = format!("{h:?}");
assert!(
!rendered.contains("AAAA") && !rendered.contains("aaaa"),
"Debug must not expose cleared timestamps: {rendered}"
);
assert!(rendered.contains("len: 0"), "debug output: {rendered}");
}
#[test]
fn zero_tail_invariant_holds_partially_filled() {
let mut h = FixedHistory::<8>::new();
let sentinels = [
0x1111_1111_1111_1111,
0x2222_2222_2222_2222,
0x3333_3333_3333_3333,
];
for ts in sentinels {
h.record(ts);
}
assert_eq!(h.len, sentinels.len());
for (idx, slot) in h.data.iter().enumerate().skip(h.len) {
assert_eq!(
*slot, 0,
"zero-tail invariant broken at data[{idx}] (len = {})",
h.len
);
}
}
#[test]
fn zero_tail_invariant_holds_after_wrap_and_clear() {
let mut h = FixedHistory::<4>::new();
for ts in [
0xAAAA_AAAA_AAAA_AAAA,
0xBBBB_BBBB_BBBB_BBBB,
0xCCCC_CCCC_CCCC_CCCC,
0xDDDD_DDDD_DDDD_DDDD,
0xEEEE_EEEE_EEEE_EEEE,
0xFFFF_FFFF_FFFF_FFFF,
] {
h.record(ts);
}
assert_eq!(h.len, 4, "precondition: history is full");
h.clear();
assert_eq!(h.len, 0);
for (idx, slot) in h.data.iter().enumerate() {
assert_eq!(*slot, 0, "clear() left data[{idx}] = {:#x}", *slot);
}
}
#[test]
fn copy_does_not_leak_stale_slots_via_raw_bytes() {
let mut original = FixedHistory::<6>::new();
original.record(0xDEAD_BEEF_DEAD_BEEF);
original.record(0xCAFE_BABE_CAFE_BABE);
let snap = original;
for (idx, slot) in snap.data.iter().enumerate().skip(snap.len) {
assert_eq!(
*slot, 0,
"Copy leaked non-live slot at data[{idx}] = {:#x}",
*slot
);
}
}
#[test]
fn boxed_returns_empty_history() {
let h: Box<FixedHistory<8>> = FixedHistory::boxed();
assert!(h.is_empty());
assert_eq!(h.len(), 0);
assert_eq!(h.capacity(), 8);
assert_eq!(h.most_recent(), None);
h.debug_validate_invariants();
}
#[test]
fn boxed_is_usable_like_new() {
let mut h: Box<FixedHistory<4>> = FixedHistory::boxed();
h.record(10);
h.record(20);
h.record(30);
assert_eq!(h.most_recent(), Some(30));
assert_eq!(h.to_vec_mru(), vec![30, 20, 10]);
}
#[test]
fn boxed_at_max_k_succeeds() {
let mut h: Box<FixedHistory<{ MAX_K }>> = FixedHistory::boxed();
h.record(42);
assert_eq!(h.most_recent(), Some(42));
assert_eq!(h.len(), 1);
}
#[test]
fn boxed_at_zero_capacity_is_noop() {
let mut h: Box<FixedHistory<0>> = FixedHistory::boxed();
h.record(1);
assert!(h.is_empty());
assert_eq!(h.most_recent(), None);
}
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_len_within_capacity(
timestamps in prop::collection::vec(any::<u64>(), 0..100)
) {
let mut history = FixedHistory::<10>::new();
for ts in timestamps {
history.record(ts);
prop_assert!(history.len() <= history.capacity());
prop_assert!(history.len() <= 10);
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_most_recent_is_last_recorded(
timestamps in prop::collection::vec(any::<u64>(), 1..50)
) {
let mut history = FixedHistory::<8>::new();
for &ts in ×tamps {
history.record(ts);
prop_assert_eq!(history.most_recent(), Some(ts));
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_vec_mru_length_matches_len(
timestamps in prop::collection::vec(any::<u64>(), 0..50)
) {
let mut history = FixedHistory::<7>::new();
for ts in timestamps {
history.record(ts);
let vec = history.to_vec_mru();
prop_assert_eq!(vec.len(), history.len());
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_kth_matches_vec_mru(
timestamps in prop::collection::vec(any::<u64>(), 1..50)
) {
let mut history = FixedHistory::<6>::new();
for ts in timestamps {
history.record(ts);
}
let vec = history.to_vec_mru();
for k in 1..=history.len() {
prop_assert_eq!(history.kth_most_recent(k), Some(vec[k - 1]));
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_invariants_always_hold(
timestamps in prop::collection::vec(any::<u64>(), 0..100)
) {
let mut history = FixedHistory::<5>::new();
for ts in timestamps {
history.record(ts);
history.debug_validate_invariants();
}
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_k_zero_returns_none(
timestamps in prop::collection::vec(any::<u64>(), 0..30)
) {
let mut history = FixedHistory::<5>::new();
for ts in timestamps {
history.record(ts);
prop_assert_eq!(history.kth_most_recent(0), None);
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_k_exceeds_len_returns_none(
timestamps in prop::collection::vec(any::<u64>(), 0..20),
k in 1usize..100
) {
let mut history = FixedHistory::<8>::new();
for ts in timestamps {
history.record(ts);
}
if k > history.len() {
prop_assert_eq!(history.kth_most_recent(k), None);
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_empty_returns_none(k in 0usize..20) {
let history = FixedHistory::<10>::new();
prop_assert!(history.is_empty());
prop_assert_eq!(history.len(), 0);
prop_assert_eq!(history.most_recent(), None);
prop_assert_eq!(history.kth_most_recent(k), None);
prop_assert_eq!(history.to_vec_mru().len(), 0);
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_wrapping_retains_last_k(
timestamps in prop::collection::vec(1u64..1000, 10..50)
) {
const K: usize = 7;
let mut history = FixedHistory::<K>::new();
for ts in ×tamps {
history.record(*ts);
}
prop_assert_eq!(history.len(), K.min(timestamps.len()));
let expected_len = K.min(timestamps.len());
let expected: Vec<u64> = timestamps[timestamps.len() - expected_len..]
.iter()
.rev()
.copied()
.collect();
prop_assert_eq!(history.to_vec_mru(), expected);
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_order_preserved_after_wrapping(
timestamps in prop::collection::vec(any::<u64>(), 5..50)
) {
const K: usize = 5;
let mut history = FixedHistory::<K>::new();
for ts in ×tamps {
history.record(*ts);
}
let vec = history.to_vec_mru();
for (idx, &val) in vec.iter().enumerate() {
prop_assert_eq!(history.kth_most_recent(idx + 1), Some(val));
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_length_grows_then_saturates(
timestamps in prop::collection::vec(any::<u64>(), 1..30)
) {
const K: usize = 8;
let mut history = FixedHistory::<K>::new();
for (idx, ts) in timestamps.iter().enumerate() {
history.record(*ts);
let expected_len = (idx + 1).min(K);
prop_assert_eq!(history.len(), expected_len);
}
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_clear_resets_state(
timestamps in prop::collection::vec(any::<u64>(), 1..30)
) {
let mut history = FixedHistory::<6>::new();
for ts in timestamps {
history.record(ts);
}
history.clear();
prop_assert!(history.is_empty());
prop_assert_eq!(history.len(), 0);
prop_assert_eq!(history.most_recent(), None);
prop_assert_eq!(history.to_vec_mru().len(), 0);
history.debug_validate_invariants();
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_clear_shrink_same_as_clear(
timestamps in prop::collection::vec(any::<u64>(), 1..30)
) {
let mut history1 = FixedHistory::<6>::new();
let mut history2 = FixedHistory::<6>::new();
for ts in ×tamps {
history1.record(*ts);
history2.record(*ts);
}
history1.clear();
history2.clear_shrink();
prop_assert_eq!(history1.len(), history2.len());
prop_assert_eq!(history1.is_empty(), history2.is_empty());
prop_assert_eq!(history1.capacity(), history2.capacity());
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_usable_after_clear(
timestamps1 in prop::collection::vec(any::<u64>(), 1..20),
timestamps2 in prop::collection::vec(any::<u64>(), 1..20)
) {
let mut history = FixedHistory::<5>::new();
for ts in timestamps1 {
history.record(ts);
}
history.clear();
for ts in ×tamps2 {
history.record(*ts);
}
prop_assert_eq!(history.len(), timestamps2.len().min(5));
prop_assert_eq!(history.most_recent(), Some(*timestamps2.last().unwrap()));
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_zero_capacity_always_empty(
timestamps in prop::collection::vec(any::<u64>(), 0..30)
) {
let mut history = FixedHistory::<0>::new();
for ts in timestamps {
history.record(ts);
prop_assert!(history.is_empty());
prop_assert_eq!(history.len(), 0);
prop_assert_eq!(history.capacity(), 0);
prop_assert_eq!(history.most_recent(), None);
}
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_matches_reference_implementation(
timestamps in prop::collection::vec(any::<u64>(), 0..50)
) {
const K: usize = 10;
let mut history = FixedHistory::<K>::new();
let mut reference: Vec<u64> = Vec::new();
for ts in timestamps {
history.record(ts);
reference.push(ts);
if reference.len() > K {
reference.remove(0);
}
prop_assert_eq!(history.len(), reference.len());
if !reference.is_empty() {
prop_assert_eq!(history.most_recent(), Some(*reference.last().unwrap()));
}
let expected: Vec<u64> = reference.iter().rev().copied().collect();
prop_assert_eq!(history.to_vec_mru(), expected);
for k in 1..=reference.len() {
let expected_idx = reference.len() - k;
prop_assert_eq!(history.kth_most_recent(k), Some(reference[expected_idx]));
}
}
}
}
}