use super::*;
use std::cmp::Ordering;
fn roundtrip(s: &[u8], prefix: usize) {
let mut blob = vec![0xEEu8; prefix];
let st = encode_german_string(s, &mut blob);
assert_eq!(
german_string_content(&st, &blob),
s,
"roundtrip failed for len {} at prefix {prefix}",
s.len(),
);
if s.len() > SHORT_STRING_THRESHOLD {
assert_eq!(
read_u64_le(&st, 8) as usize,
prefix,
"a heap payload must sit past the arena's existing bytes"
);
assert_eq!(blob.len(), prefix + s.len(), "the payload is appended whole");
} else {
assert_eq!(blob.len(), prefix, "an inline payload must not touch the arena");
}
assert!(german_string_cell_ok(&st, &blob), "canonical cell rejected: {s:?}");
let short = s.len() <= SHORT_STRING_THRESHOLD;
assert_eq!(canonical_short_cell(&st), short.then_some(st));
assert_eq!(german_string_short_ascii(&st), short && s.is_ascii());
let mut shifted = st;
shift_german_string_heaps(&mut shifted, 7);
let grown = [&[0u8; 7][..], &blob].concat();
assert_eq!(german_string_content(&shifted, &grown), s, "shifted by 7");
}
#[test]
fn roundtrip_across_length_boundaries() {
for prefix in [0usize, 5] {
roundtrip(b"", prefix); roundtrip(b"a", prefix); roundtrip(b"abcd", prefix); roundtrip(b"abcde", prefix); roundtrip(b"\xc3\xa9", prefix); roundtrip(b"abcdefghijkl", prefix); roundtrip(b"abcdefghijklm", prefix); roundtrip(b"abcdefghijklmnopqrstuvwxyz", prefix); roundtrip(&vec![0xABu8; 1000], prefix); }
}
#[test]
fn relocate_overrunning_long_cell_becomes_clean_empty() {
let mut src_cell = [0u8; 16];
write_u32_le(&mut src_cell, 0, 20);
src_cell[4..8].copy_from_slice(&0xDEAD_BEEF_u32.to_le_bytes());
write_u64_le(&mut src_cell, 8, 999);
let mut dst_blob: Vec<u8> = Vec::new();
let result = relocate_german_string(&src_cell, &[0u8; 4], &mut dst_blob);
assert_eq!(result, [0u8; 16]);
assert!(dst_blob.is_empty());
assert!(german_string_cell_ok(&result, &dst_blob));
}
#[test]
fn relocate_canonicalizes_pad_bytes() {
let mut dst_blob: Vec<u8> = Vec::new();
for content in [&b""[..], b"a", b"abc", b"abcd", b"abcdefghijkl"] {
let clean = encode_german_string(content, &mut Vec::new());
let mut dirty = clean;
dirty[4 + content.len()..].fill(0xFF);
assert_eq!(relocate_german_string(&dirty, &[], &mut dst_blob), clean, "{content:?}");
}
assert!(dst_blob.is_empty(), "inline cells must not touch the blob");
}
#[test]
fn relocate_moves_only_a_long_cells_offset() {
let content = b"abcdefghijklmnopqrst";
let mut src_blob = vec![0x11u8; 9];
let src = encode_german_string(content, &mut src_blob);
let mut dst_blob = vec![0x22u8; 5];
let out = relocate_german_string(&src, &src_blob, &mut dst_blob);
assert_eq!(out[..8], src[..8], "length and prefix carry over");
assert_eq!(read_u64_le(&out, 8), 5);
assert_eq!(german_string_content(&out, &dst_blob), content);
assert!(german_string_cell_ok(&out, &dst_blob));
let placed = relocate_german_string_with(&src, &src_blob, |c| {
assert_eq!(c, content);
77
});
assert_eq!((&placed[..8], read_u64_le(&placed, 8)), (&src[..8], 77));
}
#[test]
fn region_ok_refuses_one_bad_cell() {
let mut blob = Vec::new();
let mut region = [
encode_german_string(b"abc", &mut blob),
encode_german_string(b"abcdefghijklmnopqrst", &mut blob),
];
assert!(german_string_region_ok(region.as_flattened(), &blob));
region[0][15] = 1;
assert!(!german_string_region_ok(region.as_flattened(), &blob));
}
#[test]
fn blob_extent_truth_table() {
assert_eq!(blob_extent(8, 2, 3), Some(2..5));
assert_eq!(blob_extent(8, 0, 8), Some(0..8));
assert_eq!(blob_extent(8, 8, 0), Some(8..8));
assert_eq!(blob_extent(8, 6, 10), None);
assert_eq!(blob_extent(8, 99, 1), None);
assert_eq!(blob_extent(8, 4, usize::MAX), None);
assert_eq!(blob_extent(8, u64::MAX - 5, 20), None);
}
#[test]
fn compare_corrupt_long_cell_degrades_to_empty_without_panic() {
let mut a = encode_german_string(&[b'a'; 20], &mut Vec::new());
write_u64_le(&mut a, 8, 500);
let mut b = a;
write_u64_le(&mut b, 8, 900);
let blob = vec![0u8; 4];
assert_eq!(german_string_content(&a, &blob), &[] as &[u8]);
assert_eq!(compare_german_strings(&a, &blob, &b, &blob), Ordering::Equal);
assert!(!german_string_cell_ok(&a, &blob));
assert!(!german_string_cell_ok(&b, &blob));
}
#[test]
fn cell_ok_accepts_canonical_and_rejects_compare_visible_corruption() {
let mut blob = vec![0x7Fu8; 3];
let clean = encode_german_string(b"ab", &mut blob);
let mut dirty = clean;
dirty[6..8].copy_from_slice(&[0xFF, 0xFF]);
assert_eq!(
german_string_content(&clean, &blob),
german_string_content(&dirty, &blob)
);
assert_ne!(
compare_german_strings(&clean, &blob, &dirty, &blob),
Ordering::Equal,
"the pad is compare-visible — that is exactly what cell_ok must reject"
);
assert!(german_string_cell_ok(&clean, &blob));
assert!(!german_string_cell_ok(&dirty, &blob));
let clean_suffix = encode_german_string(b"abcde", &mut blob);
let mut dirty_suffix = clean_suffix;
dirty_suffix[15] = 0x01;
assert!(!german_string_cell_ok(&dirty_suffix, &blob));
assert_eq!(
compare_german_strings(&clean_suffix, &blob, &dirty_suffix, &blob),
Ordering::Equal
);
let mut skewed = encode_german_string(b"abcdefghijklmnopqrstuvwxyz", &mut blob);
assert!(german_string_cell_ok(&skewed, &blob));
skewed[4] = b'z';
assert!(!german_string_cell_ok(&skewed, &blob));
}
#[test]
fn compare_matches_byte_order_across_classes_and_arenas() {
let data: &[&[u8]] = &[
b"",
b"a",
b"ab",
b"ab\0", b"abc",
b"abcd",
b"abcd\0", b"abcde",
b"abce", b"abcdefghijk", b"abcdefghijkl", b"abcdefghijklm", b"abcdefghijklmnopqrst", b"abcdefghijklmnopqrsu", b"abcdefghijklmnopqrst", b"abcd\0fghijklmnopqrst", b"zyxwvutsrqponmlkjihgfedcba", ];
let arena = |prefix: usize| {
let mut blob = vec![0x7Fu8; prefix];
let cells: Vec<[u8; 16]> = data.iter().map(|d| encode_german_string(d, &mut blob)).collect();
(cells, blob)
};
let (a, b) = (arena(3), arena(41));
for (x, y) in [(&a, &a), (&a, &b), (&b, &a)] {
for (i, di) in data.iter().enumerate() {
for (j, dj) in data.iter().enumerate() {
assert_eq!(
compare_german_strings(&x.0[i], &x.1, &y.0[j], &y.1),
di.cmp(dj),
"compare mismatch for {di:?} vs {dj:?}",
);
}
}
}
}
#[test]
fn the_empty_string_is_the_zero_cell() {
let mut blob = Vec::new();
assert_eq!(encode_german_string(&[], &mut blob), [0u8; 16]);
assert!(blob.is_empty());
}