use super::*;
fn assert_decode_rejects(bytes: &[u8], expected: &str) {
match DeleteBitmap::decode(bytes) {
Err(Error::InvalidHeader(msg)) => {
assert_eq!(msg, expected, "decode failed on the wrong validation path");
}
other => panic!("expected InvalidHeader({expected:?}), got {other:?}"),
}
}
#[test]
fn insert_and_contains_across_chunks() {
let mut dv = DeleteBitmap::new();
assert!(dv.is_empty());
assert!(dv.insert(0));
assert!(dv.insert(CHUNK_ROWS - 1)); assert!(dv.insert(CHUNK_ROWS)); assert!(dv.insert(100_000));
assert!(!dv.insert(0));
assert!(dv.contains(0));
assert!(dv.contains(CHUNK_ROWS - 1));
assert!(dv.contains(CHUNK_ROWS));
assert!(dv.contains(100_000));
assert!(!dv.contains(1));
assert!(!dv.contains(CHUNK_ROWS + 1));
assert_eq!(dv.len(), 4);
assert!(!dv.is_empty());
}
#[test]
fn sparse_promotes_to_dense_and_stays_correct() {
let mut dv = DeleteBitmap::new();
let n = (SPARSE_MAX as u32 + 50).min(CHUNK_ROWS / 2);
for i in 0..n {
assert!(dv.insert(i * 2));
}
assert_eq!(dv.len(), u64::from(n));
for i in 0..n {
assert!(dv.contains(i * 2), "even row {} missing", i * 2);
assert!(!dv.contains(i * 2 + 1), "odd row {} present", i * 2 + 1);
}
}
#[test]
fn chunk_has_deletes_enables_block_skip() {
let mut dv = DeleteBitmap::new();
dv.insert(3 * CHUNK_ROWS + 7);
assert!(!dv.chunk_has_deletes(0));
assert!(!dv.chunk_has_deletes(2));
assert!(dv.chunk_has_deletes(3));
assert!(!dv.chunk_has_deletes(4));
}
#[test]
fn full_chunk_bulk_delete_is_dense_and_compact() {
let mut dv = DeleteBitmap::new();
for off in 0..CHUNK_ROWS {
dv.insert(CHUNK_ROWS + off);
}
assert_eq!(
dv.len(),
u64::from(CHUNK_ROWS),
"every row of the chunk marked"
);
assert!(dv.chunk_has_deletes(1), "the full chunk reports deletes");
assert!(
!dv.chunk_has_deletes(0),
"an untouched neighbour skips in O(1)"
);
assert!(!dv.chunk_has_deletes(2));
assert!(dv.contains(CHUNK_ROWS));
assert!(dv.contains(2 * CHUNK_ROWS - 1));
assert!(!dv.contains(CHUNK_ROWS - 1));
assert!(!dv.contains(2 * CHUNK_ROWS));
let encoded = dv.encode();
assert!(
encoded.len() < CHUNK_ROWS as usize,
"a full chunk must store densely (O(1) space), got {} bytes for {CHUNK_ROWS} rows",
encoded.len(),
);
}
#[test]
fn union_is_set_union() {
let mut a = DeleteBitmap::new();
a.insert(1);
a.insert(CHUNK_ROWS + 1);
let mut b = DeleteBitmap::new();
b.insert(1); b.insert(2);
b.insert(5 * CHUNK_ROWS); a.union(&b);
for row in [1, 2, CHUNK_ROWS + 1, 5 * CHUNK_ROWS] {
assert!(a.contains(row), "row {row} missing after union");
}
assert_eq!(a.len(), 4);
}
#[test]
fn iter_yields_ascending_rows() {
let mut dv = DeleteBitmap::new();
let rows = [9_u32, 1, CHUNK_ROWS + 3, 2, CHUNK_ROWS];
for &r in &rows {
dv.insert(r);
}
let got: Vec<u32> = dv.iter().collect();
assert_eq!(got, [1, 2, 9, CHUNK_ROWS, CHUNK_ROWS + 3]);
}
#[test]
fn encode_decode_round_trip_sparse_and_dense() {
let mut dv = DeleteBitmap::new();
dv.insert(5);
dv.insert(900);
for i in 0..=(SPARSE_MAX as u32) {
dv.insert(CHUNK_ROWS + i);
}
let bytes = dv.encode();
let decoded = DeleteBitmap::decode(&bytes).unwrap();
assert_eq!(decoded, dv);
}
#[test]
fn decode_empty_round_trips() {
let dv = DeleteBitmap::new();
let decoded = DeleteBitmap::decode(&dv.encode()).unwrap();
assert!(decoded.is_empty());
assert_eq!(decoded, dv);
}
#[test]
fn decode_truncated_buffer_errors() {
let mut dv = DeleteBitmap::new();
dv.insert(7);
let bytes = dv.encode();
assert!(DeleteBitmap::decode(&bytes[..bytes.len() - 1]).is_err());
assert!(DeleteBitmap::decode(&[]).is_err());
}
#[test]
fn decode_rejects_unknown_kind() {
let bytes = [1, 0, 0, 0, 0, 0, 0, 0, 99, 0, 0];
assert_decode_rejects(&bytes, "delete_bitmap: unknown container kind");
}
#[test]
fn decode_rejects_out_of_range_offset() {
let mut bytes = alloc::vec![1, 0, 0, 0, 0, 0, 0, 0, KIND_SPARSE, 1, 0];
bytes.extend_from_slice(&(CHUNK_ROWS as u16).to_le_bytes());
assert_decode_rejects(&bytes, "delete_bitmap: sparse offset out of range");
}
#[test]
fn decode_rejects_non_ascending_chunks() {
let bytes = [
2,
0,
0,
0, 0,
0,
0,
0,
KIND_SPARSE,
1,
0,
0,
0, 0,
0,
0,
0,
KIND_SPARSE,
1,
0,
0,
0, ];
assert_decode_rejects(
&bytes,
"delete_bitmap: chunk indices not strictly ascending",
);
}
#[test]
fn decode_rejects_chunk_count_exceeding_payload() {
let bytes = u32::MAX.to_le_bytes();
assert_decode_rejects(&bytes, "delete_bitmap: chunk count exceeds encoded payload");
}
#[test]
fn decode_rejects_empty_sparse_container() {
let bytes = [1, 0, 0, 0, 0, 0, 0, 0, KIND_SPARSE, 0, 0];
assert_decode_rejects(&bytes, "delete_bitmap: sparse count out of range");
}
#[test]
fn decode_rejects_chunk_index_out_of_range() {
let mut bytes = alloc::vec![1, 0, 0, 0];
bytes.extend_from_slice(&u32::MAX.to_le_bytes()); bytes.extend_from_slice(&[KIND_SPARSE, 1, 0, 0, 0]);
assert_decode_rejects(
&bytes,
"delete_bitmap: chunk index out of row-position range",
);
}
#[test]
fn decode_rejects_trailing_bytes() {
let mut dv = DeleteBitmap::new();
dv.insert(7);
let mut bytes = dv.encode();
bytes.push(0);
assert_decode_rejects(&bytes, "delete_bitmap: trailing bytes after chunks");
}