use super::*;
const ROW_WIDTH: usize = 1 + 8 + 8;
const ROWS: usize = 2;
fn fixture() -> Vec<Vec<u8>> {
vec![vec![7; ROWS], vec![1; ROWS * 8], vec![0; ROWS * 8], b"a heap".to_vec()]
}
fn slices(regions: &[Vec<u8>]) -> Vec<&[u8]> {
regions.iter().map(Vec::as_slice).collect()
}
fn encode_dead(regions: &[&[u8]], heap_dead: usize) -> Vec<u8> {
let mut buf = vec![0u8; block_size(regions)];
assert_eq!(write_block(regions, heap_dead, &mut buf), buf.len());
buf
}
#[test]
fn a_block_round_trips_through_both_framers() {
let f = fixture();
let regions = slices(&f);
let (heap, fixed_regions) = regions.split_last().unwrap();
for dead in [0, 1, heap.len()] {
let buf = encode_dead(®ions, dead);
let mut appended = vec![0xAB];
append_block(®ions, dead, &mut appended);
assert_eq!(appended[1..], buf, "dead={dead}");
assert_eq!(
parse_block(&buf, ROW_WIDTH),
Ok((ROWS, &fixed_regions.concat()[..], *heap, dead)),
"dead={dead}"
);
}
let mut no_heap = regions.clone();
*no_heap.last_mut().unwrap() = &[];
let empty: [&[u8]; 4] = [&[], &[], &[], &[]];
for list in [&no_heap[..], &empty[..]] {
let mut appended = Vec::new();
append_block(list, 0, &mut appended);
assert_eq!(appended, encode_dead(list, 0), "{} regions", list.len());
}
let mut two = encode_dead(®ions, 0);
let end1 = two.len();
two.extend_from_slice(&encode_dead(®ions, 0));
for off in [0, end1] {
let block = block_slice(&two[off..]).expect("a framed block");
assert_eq!(parse_block(block, ROW_WIDTH).unwrap().0, ROWS);
}
}
#[test]
fn a_zero_row_block_is_refused() {
for heap in [&b""[..], b"orphan heap"] {
let buf = encode_dead(&[&[], &[], &[], heap], 0);
assert_eq!(parse_block(&buf, ROW_WIDTH), Err("block holds no rows"));
}
}
#[test]
fn each_guard_rejects_its_forgery() {
let clean = encode_dead(&slices(&fixture()), 0);
let forged = |off: usize, f: &dyn Fn(u32) -> u32| {
let mut buf = clean.clone();
let v = f(read_u32_le(&buf, off));
write_u32_le(&mut buf, off, v);
buf
};
let mismatch = "block size does not match its rows and heap";
let heap_len = b"a heap".len();
let mut cases: Vec<(String, Vec<u8>, Option<&str>)> = vec![
(
"short of a header".into(),
clean[..WAL_HEADER_SIZE - 1].to_vec(),
Some("block shorter than header"),
),
(
"size past the buffer".into(),
clean[..clean.len() - 1].to_vec(),
Some("declared size past buffer"),
),
("one row fewer".into(), forged(WAL_OFF_ROWS, &|r| r - 1), Some(mismatch)),
];
for bit in 0..32 {
let flip = |off| forged(off, &|v| v ^ (1 << bit));
let block = flip(WAL_OFF_SIZE);
let size_err = if read_u32_le(&block, WAL_OFF_SIZE) as usize > clean.len() {
"declared size past buffer"
} else {
mismatch
};
cases.push((format!("SIZE bit {bit}"), block, Some(size_err)));
cases.push((
format!("VERSION bit {bit}"),
flip(WAL_OFF_VERSION),
Some("unknown block version"),
));
let rows_err = if ROWS ^ (1 << bit) == 0 {
"block holds no rows"
} else {
mismatch
};
cases.push((format!("ROWS bit {bit}"), flip(WAL_OFF_ROWS), Some(rows_err)));
cases.push((format!("HEAP_LEN bit {bit}"), flip(WAL_OFF_HEAP_LEN), Some(mismatch)));
let dead_err = ((1usize << bit) > heap_len).then_some("block declares more dead heap than heap");
cases.push((format!("HEAP_DEAD bit {bit}"), flip(WAL_OFF_HEAP_DEAD), dead_err));
}
for (what, block, want) in &cases {
assert_eq!(parse_block(block, ROW_WIDTH).err(), *want, "{what}");
}
}