use super::*;
fn kv(vals: &[f32]) -> Array {
Array::from_slice::<f32>(vals, &(1usize, 1, vals.len(), 1)).unwrap()
}
fn rows(a: &Array) -> Vec<f32> {
ops::shape::contiguous(a, false)
.unwrap()
.to_vec::<f32>()
.unwrap()
}
#[test]
fn maybe_trim_front_empty_cache_is_noop() {
let mut c = ChunkedKvCache::new(Some(4));
assert!(c.is_empty());
c.maybe_trim_front().unwrap();
assert!(c.is_empty(), "no-op must not populate the buffer");
assert_eq!(c.offset(), 0);
assert_eq!(c.meta_state(), vec!["4", "0"], "start_position untouched");
}
#[test]
fn maybe_trim_front_start_position_overflow_is_rejected() {
let buf = kv(&[10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0]); let vbuf = kv(&[100.0, 101.0, 102.0, 103.0, 104.0, 105.0, 106.0, 107.0]);
let mut c = ChunkedKvCache {
keys: Some(buf.try_clone().unwrap()),
values: Some(vbuf.try_clone().unwrap()),
offset: 8,
chunk_size: Some(4),
start_position: usize::MAX,
};
let err = c.maybe_trim_front().unwrap_err();
match err {
Error::ArithmeticOverflow(p) => {
assert!(
p.context().contains("maybe_trim_front") && p.context().contains("start_position"),
"context must name the maybe_trim_front start_position add, got: {}",
p.context()
);
assert!(
p.operands().iter().any(|(n, v)| *n == "added" && *v == 4),
"operands must carry added=4, got: {:?}",
p.operands()
);
assert!(
p.operands()
.iter()
.any(|(n, v)| *n == "start_position" && *v == usize::MAX as u64),
"operands must carry start_position=usize::MAX, got: {:?}",
p.operands()
);
}
other => panic!("expected ArithmeticOverflow, got {other:?}"),
}
assert_eq!(
c.meta_state(),
vec!["4".to_string(), usize::MAX.to_string()],
"start_position must be unchanged on the Err path"
);
let st = c.state().unwrap();
assert_eq!(st[0].shape(), vec![1, 1, 8, 1], "keys buffer untrimmed");
}
#[test]
fn set_seq_write_start_plus_s_overflow_keys_and_values() {
let buf = kv(&[10.0]); let new = kv(&[20.0]);
for (name, want_ctx) in [
("keys", "keys write start"),
("values", "values write start"),
] {
let err = ChunkedKvCache::set_seq(name, &buf, usize::MAX, 1, &new).unwrap_err();
match err {
Error::ArithmeticOverflow(p) => {
assert!(
p.context().contains(want_ctx),
"{name}: context must be the per-target write-start arm, got: {}",
p.context()
);
assert!(
p.operands()
.iter()
.any(|(n, v)| *n == "start" && *v == usize::MAX as u64),
"{name}: operands must carry start=usize::MAX, got: {:?}",
p.operands()
);
assert!(
p.operands().iter().any(|(n, v)| *n == "S" && *v == 1),
"{name}: operands must carry S=1, got: {:?}",
p.operands()
);
}
other => panic!("{name}: expected ArithmeticOverflow, got {other:?}"),
}
}
}
#[test]
fn set_seq_window_past_buffer_is_out_of_range() {
let buf = kv(&[10.0]); let new = kv(&[20.0, 21.0, 22.0, 23.0, 24.0]); for name in ["keys", "values", "other"] {
let err = ChunkedKvCache::set_seq(name, &buf, 0, 5, &new).unwrap_err();
match err {
Error::OutOfRange(p) => {
assert!(
p.context().contains("write window end"),
"{name}: context must name the out-of-bounds write window, got: {}",
p.context()
);
if name == "keys" || name == "values" {
assert!(
p.context().contains(name),
"{name}: named arm must include the target buffer name, got: {}",
p.context()
);
}
}
other => panic!("{name}: expected OutOfRange, got {other:?}"),
}
}
}
#[test]
fn set_seq_partial_window_splices_in_place() {
let buf = kv(&[10.0, 11.0, 12.0, 13.0]);
let new = kv(&[99.0]);
let spliced = ChunkedKvCache::set_seq("keys", &buf, 1, 1, &new).unwrap();
assert_eq!(spliced.shape(), vec![1, 1, 4, 1], "buffer length preserved");
assert_eq!(
rows(&spliced),
vec![10.0, 99.0, 12.0, 13.0],
"only row 1 overwritten; rows 0/2/3 retained"
);
}
#[test]
fn update_prev_underflow_when_start_exceeds_offset() {
let mut c = ChunkedKvCache {
keys: None,
values: None,
offset: 3,
chunk_size: Some(4),
start_position: 5, };
let t = kv(&[10.0]);
let err = c.update(&t, &t).unwrap_err();
match err {
Error::ArithmeticOverflow(p) => {
assert!(
p.context().contains("offset - start_position"),
"context must name the prev underflow, got: {}",
p.context()
);
assert!(
p.operands().iter().any(|(n, v)| *n == "offset" && *v == 3)
&& p
.operands()
.iter()
.any(|(n, v)| *n == "start_position" && *v == 5),
"operands must carry offset=3, start_position=5, got: {:?}",
p.operands()
);
}
other => panic!("expected ArithmeticOverflow, got {other:?}"),
}
assert_eq!(c.offset(), 3, "offset unchanged on the Err path");
assert!(c.is_empty(), "buffer unchanged (still None)");
}
#[test]
fn update_prev_plus_s_overflow_is_rejected() {
let mut c = ChunkedKvCache {
keys: None,
values: None,
offset: usize::MAX,
chunk_size: None,
start_position: 0,
};
let t = kv(&[10.0]);
let err = c.update(&t, &t).unwrap_err();
match err {
Error::ArithmeticOverflow(p) => {
assert!(
p.context().contains("prev + S"),
"context must name prev + S, got: {}",
p.context()
);
assert!(
p.operands()
.iter()
.any(|(n, v)| *n == "prev" && *v == usize::MAX as u64),
"operands must carry prev=usize::MAX, got: {:?}",
p.operands()
);
}
other => panic!("expected ArithmeticOverflow, got {other:?}"),
}
assert_eq!(c.offset(), usize::MAX, "offset unchanged on the Err path");
}
#[test]
fn update_offset_plus_s_overflow_after_realloc() {
let mut c = ChunkedKvCache {
keys: None,
values: None,
offset: usize::MAX,
chunk_size: None,
start_position: usize::MAX, };
let t = kv(&[10.0]); let err = c.update(&t, &t).unwrap_err();
match err {
Error::ArithmeticOverflow(p) => {
assert!(
p.context().contains("offset + S"),
"context must name offset + S, got: {}",
p.context()
);
assert!(
p.operands()
.iter()
.any(|(n, v)| *n == "offset" && *v == usize::MAX as u64),
"operands must carry offset=usize::MAX, got: {:?}",
p.operands()
);
}
other => panic!("expected ArithmeticOverflow, got {other:?}"),
}
assert_eq!(c.offset(), usize::MAX, "offset unchanged on the Err path");
assert!(c.is_empty(), "buffer not committed on the Err path");
}
#[test]
fn update_realloc_prev_multiple_of_step_keeps_existing_buffer() {
let mut c = ChunkedKvCache::new(None);
let kpre: Vec<f32> = (0..256).map(|i| 1000.0 + i as f32).collect();
let vpre: Vec<f32> = (0..256).map(|i| 2000.0 + i as f32).collect();
let (_pk, _pv) = c.update(&kv(&kpre), &kv(&vpre)).unwrap();
assert_eq!(c.offset(), 256);
let (rk, rv) = c.update(&kv(&[314.0]), &kv(&[628.0])).unwrap();
assert_eq!(c.offset(), 257);
assert_eq!(
rk.shape(),
vec![1, 1, 257, 1],
"logical length 257 returned"
);
let rk_rows = rows(&rk);
let rv_rows = rows(&rv);
assert_eq!(rk_rows.len(), 257);
assert_eq!(
&rk_rows[..256],
kpre.as_slice(),
"prefill keys retained whole by the prev%step==0 realloc arm"
);
assert_eq!(rk_rows[256], 314.0, "new key row appended at index 256");
assert_eq!(
&rv_rows[..256],
vpre.as_slice(),
"prefill values retained whole (own stream)"
);
assert_eq!(rv_rows[256], 628.0, "new value row appended at index 256");
}
#[test]
fn state_empty_cache_is_empty_vec() {
let c = ChunkedKvCache::new(Some(4));
assert!(c.state().unwrap().is_empty());
let c2 = ChunkedKvCache::new(None);
assert!(c2.state().unwrap().is_empty());
}
#[test]
fn materialize_evals_buffers_and_empty_is_noop() {
let mut c = ChunkedKvCache::new(Some(4));
let (_k, _v) = c.update(&kv(&[10.0, 20.0]), &kv(&[100.0, 200.0])).unwrap();
c.materialize().unwrap();
assert_eq!(c.offset(), 2);
let st = c.state().unwrap();
assert_eq!(
rows(&st[0]),
vec![10.0, 20.0],
"keys unchanged by materialize"
);
assert_eq!(
rows(&st[1]),
vec![100.0, 200.0],
"values unchanged by materialize (own stream)"
);
let mut empty = ChunkedKvCache::new(None);
empty.materialize().unwrap();
assert!(empty.is_empty());
}
#[test]
fn set_meta_state_chunk_size_parse_error_leaves_cache_unmutated() {
let mut c = ChunkedKvCache::new(Some(7));
let err = c
.set_meta_state(&["not_a_number".to_string(), "0".to_string()])
.unwrap_err();
match err {
Error::Parse(p) => {
assert!(
p.context().contains("chunk_size"),
"context must name chunk_size, got: {}",
p.context()
);
assert_eq!(p.input_kind(), "usize");
}
other => panic!("expected Parse, got {other:?}"),
}
assert_eq!(c.meta_state(), vec!["7", "0"]);
}
#[test]
fn trim_start_exceeds_offset_underflow_is_rejected() {
let mut c = ChunkedKvCache {
keys: None,
values: None,
offset: 2,
chunk_size: Some(4),
start_position: 5, };
let err = c.trim(1).unwrap_err();
match err {
Error::ArithmeticOverflow(p) => {
assert!(
p.context().contains("trim") && p.context().contains("offset - start_position"),
"context must name the trim span underflow, got: {}",
p.context()
);
assert!(
p.operands().iter().any(|(n, v)| *n == "offset" && *v == 2)
&& p
.operands()
.iter()
.any(|(n, v)| *n == "start_position" && *v == 5),
"operands must carry offset=2, start_position=5, got: {:?}",
p.operands()
);
}
other => panic!("expected ArithmeticOverflow, got {other:?}"),
}
assert_eq!(c.offset(), 2, "offset unchanged on the Err path");
}
#[test]
fn copy_clones_both_buffers_and_empty_takes_none_arms() {
let mut c = ChunkedKvCache::new(Some(4));
c.update(&kv(&[10.0, 20.0, 30.0]), &kv(&[100.0, 200.0, 300.0]))
.unwrap();
let cp = c.copy().unwrap();
assert_eq!(cp.offset(), 3, "copied scalar offset matches");
assert_eq!(cp.reference_class_name(), "ChunkedKVCache");
let st = cp.state().unwrap();
assert_eq!(st.len(), 2);
assert_eq!(
rows(&st[0]),
vec![10.0, 20.0, 30.0],
"copied keys are an exact independent duplicate"
);
assert_eq!(
rows(&st[1]),
vec![100.0, 200.0, 300.0],
"copied values track their own stream"
);
c.update(&kv(&[40.0]), &kv(&[400.0])).unwrap();
assert_eq!(c.offset(), 4, "original advanced");
assert_eq!(cp.offset(), 3, "copy untouched by the original's update");
let empty = ChunkedKvCache::new(None);
let ecp = empty.copy().unwrap();
assert!(ecp.is_empty());
assert_eq!(ecp.offset(), 0);
assert!(ecp.state().unwrap().is_empty());
}