use super::*;
#[test]
fn node_size_default() {
assert_eq!(std::mem::size_of::<Node<u32, u16>>(), 76);
}
#[test]
fn node_size_compact() {
assert_eq!(std::mem::size_of::<Node<u16, u16>>(), 40);
}
#[test]
fn insert_empty_and_get() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"hello".to_vec(), 42).unwrap();
assert_eq!(trie.get(b"hello"), Some(&42));
assert_eq!(trie.get(b"world"), None);
}
#[test]
fn insert_duplicate_returns_error() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"hello".to_vec(), 1).unwrap();
let result = trie.insert(b"hello".to_vec(), 2);
assert_eq!(result, Err(()));
assert_eq!(trie.len(), 1);
}
#[test]
fn insert_null_byte_allowed() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"hel\0lo".to_vec(), 1).unwrap();
assert_eq!(trie.get(b"hel\0lo"), Some(&1));
}
#[test]
fn insert_two_keys_split_leaf() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"abd".to_vec(), 2).unwrap();
assert_eq!(trie.get(b"abc"), Some(&1));
assert_eq!(trie.get(b"abd"), Some(&2));
assert_eq!(trie.len(), 2);
}
#[test]
fn insert_prefix_key() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"abcd".to_vec(), 2).unwrap();
assert_eq!(trie.get(b"abc"), Some(&1));
assert_eq!(trie.get(b"abcd"), Some(&2));
}
#[test]
fn insert_reverse_prefix_key() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"abcd".to_vec(), 1).unwrap();
trie.insert(b"abc".to_vec(), 2).unwrap();
assert_eq!(trie.get(b"abcd"), Some(&1));
assert_eq!(trie.get(b"abc"), Some(&2));
assert_eq!(trie.len(), 2);
}
#[test]
fn insert_no_common_prefix() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"xyz".to_vec(), 2).unwrap();
assert_eq!(trie.get(b"abc"), Some(&1));
assert_eq!(trie.get(b"xyz"), Some(&2));
}
#[test]
fn insert_three_keys() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"abd".to_vec(), 2).unwrap();
trie.insert(b"abe".to_vec(), 3).unwrap();
assert_eq!(trie.get(b"abc"), Some(&1));
assert_eq!(trie.get(b"abd"), Some(&2));
assert_eq!(trie.get(b"abe"), Some(&3));
}
#[test]
fn insert_single_char_keys() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
for c in b'a'..=b'f' {
trie.insert(vec![c], c as i32).unwrap();
}
for c in b'a'..=b'f' {
let key = vec![c];
let v = c as i32;
assert_eq!(trie.get(&key), Some(&v));
}
}
#[test]
fn insert_many_keys_same_prefix() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
for i in 0..50 {
let key = format!("prefix_{:02}", i);
trie.insert(key.into_bytes(), i).unwrap();
}
for i in 0..50 {
let key = format!("prefix_{:02}", i);
assert!(trie.get(key.as_bytes()).is_some());
}
}
#[test]
fn insert_deeply_nested() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
let mut key = Vec::new();
for i in 0..100 {
key.push(b'a');
trie.insert(key.clone(), i).unwrap();
assert_eq!(trie.get(&key), Some(&i));
}
}
#[test]
fn len_and_is_empty() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
assert!(trie.is_empty());
assert_eq!(trie.len(), 0);
trie.insert(b"hello".to_vec(), 1).unwrap();
assert!(!trie.is_empty());
assert_eq!(trie.len(), 1);
}
#[test]
fn into_keys_values_roundtrip() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"def".to_vec(), 2).unwrap();
let (keys, values) = trie.into_keys_values();
assert_eq!(keys, vec![b"abc".to_vec(), b"def".to_vec()]);
assert_eq!(values, vec![1, 2]);
}
#[test]
fn iter_empty() {
let trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
let mut iter = trie.iter();
assert!(iter.next().is_none());
}
#[test]
fn iter_single_key() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"hello".to_vec(), 42).unwrap();
let mut iter = trie.iter();
let (k, v) = iter.next().unwrap();
assert_eq!(k, b"hello");
assert_eq!(*v, 42);
assert!(iter.next().is_none());
}
#[test]
fn iter_forward() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"abd".to_vec(), 2).unwrap();
trie.insert(b"abe".to_vec(), 3).unwrap();
let mut results = Vec::new();
let mut iter = trie.iter();
while let Some((k, _)) = iter.next() {
results.push(k.to_vec());
}
assert_eq!(results, vec![b"abc", b"abd", b"abe"]);
}
#[test]
fn iter_backward() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"abd".to_vec(), 2).unwrap();
trie.insert(b"abe".to_vec(), 3).unwrap();
let mut iter = trie.iter_last();
let mut results = Vec::new();
loop {
match iter.current() {
Some((k, _)) => results.push(k.to_vec()),
None => break,
}
if iter.prev().is_none() {
break;
}
}
assert_eq!(results, vec![b"abe", b"abd", b"abc"]);
}
#[test]
fn iter_seek_exact() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"abd".to_vec(), 2).unwrap();
trie.insert(b"abe".to_vec(), 3).unwrap();
let mut iter = trie.iter();
let (k, _) = iter.seek(b"abd").unwrap();
assert_eq!(k, b"abd");
}
#[test]
fn iter_seek_between() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"abd".to_vec(), 2).unwrap();
trie.insert(b"abe".to_vec(), 3).unwrap();
let mut iter = trie.iter();
let (k, _) = iter.seek(b"abd").unwrap();
assert_eq!(k, b"abd");
let mut iter2 = trie.iter();
let result = iter2.seek(b"abc\x7f");
assert!(result.is_some(), "seek to 'abc\\x7f' returned None");
assert_eq!(result.unwrap().0, b"abd");
}
#[test]
fn iter_seek_prefix_key() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"abcd".to_vec(), 2).unwrap();
let mut iter = trie.iter();
let (k, _) = iter.seek(b"abc").unwrap();
assert_eq!(k, b"abc");
}
#[test]
fn get_found_and_missing() {
let mut trie: NibbleTrie<Vec<u8>, String> = NibbleTrie::new();
trie.insert(b"hello".to_vec(), "world".to_string()).unwrap();
assert_eq!(trie.get(b"hello"), Some(&"world".to_string()));
assert_eq!(trie.get(b"world"), None);
}
#[test]
fn iter_backward_large() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
for i in 0..100 {
let key = format!("key_{:03}", i);
trie.insert(key.into_bytes(), i).unwrap();
}
let mut iter = trie.iter_last();
let mut count = 0;
let mut last_key: Vec<u8> = Vec::new();
if let Some((k, _)) = iter.current() {
last_key = k.to_vec();
count += 1;
}
while let Some((k, _)) = iter.prev() {
assert!(k < &last_key[..], "not descending: {:?} >= {:?}",
String::from_utf8_lossy(k), String::from_utf8_lossy(&last_key));
last_key = k.to_vec();
count += 1;
}
assert_eq!(count, 100);
}
#[test]
fn leaf_and_offset_set_on_creation() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
let root = trie.inode(0);
assert!(root.leaf.is_some(), "root leaf field should be set");
let (off, len, _) = trie.index[root.leaf.get().as_usize()].as_ref().unwrap();
let off = off.get();
assert_ne!(off, 0, "root offset should be set");
assert_eq!(&trie.buf[off..off + len.as_usize()], b"abc");
}
#[test]
fn optimize_empty() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.optimize();
assert!(trie.is_empty());
}
#[test]
fn optimize_single_key() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"hello".to_vec(), 42).unwrap();
trie.optimize();
assert_eq!(trie.get(b"hello"), Some(&42));
assert_eq!(trie.len(), 1);
}
#[test]
fn optimize_preserves_lookups() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
for i in 0..100 {
let key = format!("key_{:03}", i);
trie.insert(key.into_bytes(), i).unwrap();
}
trie.optimize();
for i in 0..100 {
let key = format!("key_{:03}", i);
assert_eq!(trie.get(key.as_bytes()), Some(&i),
"lookup failed after optimize for i={}", i);
}
assert_eq!(trie.len(), 100);
}
#[test]
fn optimize_preserves_iteration() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
for i in 0..100 {
let key = format!("key_{:05}", i);
trie.insert(key.into_bytes(), i as i32).unwrap();
}
trie.optimize();
let mut it = trie.iter();
let mut keys: Vec<Vec<u8>> = Vec::new();
while let Some((k, _)) = it.next() {
keys.push(k.to_vec());
}
assert_eq!(keys.len(), 100);
for i in 1..keys.len() {
assert!(keys[i] > keys[i - 1], "not sorted after optimize at index {}", i);
}
let mut it = trie.iter_last();
keys.clear();
loop {
match it.current() {
Some((k, _)) => keys.push(k.to_vec()),
None => break,
}
if it.prev().is_none() { break; }
}
assert_eq!(keys.len(), 100);
for i in 1..keys.len() {
assert!(keys[i] < keys[i - 1], "not reverse-sorted after optimize at index {}", i);
}
}
#[test]
fn optimize_preserves_seek() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
for i in 0..50u32 {
let key = format!("key_{:05}", i);
trie.insert(key.into_bytes(), i as i32).unwrap();
}
trie.optimize();
let mut it = trie.iter();
let (k, v) = it.seek(b"key_00025").unwrap();
assert_eq!(k, b"key_00025");
assert_eq!(*v, 25);
}
#[test]
fn optimize_idempotent() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
for i in 0..100 {
let key = format!("key_{:03}", i);
trie.insert(key.into_bytes(), i).unwrap();
}
trie.optimize();
let arena_len_1 = trie.arena.len();
trie.optimize();
let arena_len_2 = trie.arena.len();
assert_eq!(arena_len_1, arena_len_2, "second optimize changed arena size");
for i in 0..100 {
let key = format!("key_{:03}", i);
assert!(trie.get(key.as_bytes()).is_some());
}
}
#[test]
fn optimize_byte_boundary_keys() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
for b in 1u8..=255 {
trie.insert(vec![b], b as i32).unwrap();
}
trie.optimize();
for b in 1u8..=255 {
let key = vec![b];
assert_eq!(trie.get(&key), Some(&(b as i32)),
"lookup failed after optimize for byte {}", b);
}
}
#[test]
fn optimize_stress_1000() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
for i in 0..1000u32 {
let key = format!("key_{:05}", i);
trie.insert(key.into_bytes(), i as i32).unwrap();
}
trie.optimize();
for i in 0..1000u32 {
let key = format!("key_{:05}", i);
assert_eq!(trie.get(key.as_bytes()), Some(&(i as i32)),
"lookup failed after optimize at i={}", i);
}
}
#[test]
fn optimize_deeply_nested() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
let mut key = Vec::new();
for i in 0..100 {
key.push(b'a');
trie.insert(key.clone(), i).unwrap();
}
trie.optimize();
for i in 0..100 {
let key = vec![b'a'; i + 1];
assert_eq!(trie.get(&key), Some(&(i as i32)));
}
}
#[test]
fn optimize_sorts_buf() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
for i in (0..100u32).rev() {
let key = format!("key_{:05}", i);
trie.insert(key.into_bytes(), i as i32).unwrap();
}
trie.optimize();
let mut it = trie.iter();
let mut prev_key: Option<Vec<u8>> = None;
let mut prev_end: usize = 1; while let Some((k, _)) = it.current() {
if let Some(ref pk) = prev_key {
assert!(pk.as_slice() <= k, "keys not sorted: {:?} > {:?}",
std::str::from_utf8(pk), std::str::from_utf8(k));
}
let ki = it.current_index().unwrap();
let (off, len, _) = trie.index[ki].as_ref().unwrap();
let off = off.get();
assert_eq!(off, prev_end,
"key {:?} not contiguous: expected offset {}, got {}",
std::str::from_utf8(k), prev_end, off);
prev_key = Some(k.to_vec());
prev_end = off + len.as_usize();
it.next();
}
}
#[test]
fn optimize_sorts_index_and_values() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
let n = 100;
for i in (0..n).rev() {
let key = format!("key_{:05}", i);
trie.insert(key.into_bytes(), i as i32).unwrap();
}
trie.optimize();
let mut prev: Option<&[u8]> = None;
for slot in trie.index.iter().skip(1) {
let (off, len, _val) = match slot {
Some(s) => s,
None => continue,
};
let off = off.get();
let key = &trie.buf[off..off + len.as_usize()];
if let Some(p) = prev {
assert!(p <= key,
"index not sorted: {:?} > {:?}",
std::str::from_utf8(p), std::str::from_utf8(key));
}
prev = Some(key);
}
let mut count = 0;
for slot in trie.index.iter().skip(1) {
let (off, len, val) = match slot {
Some(s) => s,
None => continue,
};
let off = off.get();
let key = &trie.buf[off..off + len.as_usize()];
let expected_val = std::str::from_utf8(key).unwrap()
.strip_prefix("key_").unwrap().parse::<i32>().unwrap();
assert_eq!(*val, expected_val,
"value mismatch: got {}, expected {}", val, expected_val);
count += 1;
}
assert_eq!(count, n);
}
#[test]
fn optimize_into_keys_values_sorted() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
for i in (0..50u32).rev() {
let key = format!("key_{:05}", i);
trie.insert(key.into_bytes(), i as i32).unwrap();
}
trie.optimize();
let (keys, values) = trie.into_keys_values();
assert_eq!(keys.len(), 50);
for i in 1..keys.len() {
assert!(keys[i] > keys[i - 1], "keys not sorted at index {}", i);
}
for i in 0..50 {
let expected = i as i32;
assert_eq!(values[i], expected, "value mismatch at index {}", i);
}
}
#[test]
fn iter_forward_prefix_keys() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"ab".to_vec(), 2).unwrap();
trie.insert(b"abd".to_vec(), 3).unwrap();
let mut results = Vec::new();
let mut iter = trie.iter();
if let Some((k, _)) = iter.current() { results.push(k.to_vec()); }
while let Some((k, _)) = iter.next() { results.push(k.to_vec()); }
assert_eq!(results, vec![b"ab".to_vec(), b"abc".to_vec(), b"abd".to_vec()]);
}
#[test]
fn iter_backward_prefix_keys() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"ab".to_vec(), 2).unwrap();
trie.insert(b"abd".to_vec(), 3).unwrap();
let mut iter = trie.iter_last();
let mut results = Vec::new();
loop {
match iter.current() {
Some((k, _)) => results.push(k.to_vec()),
None => break,
}
if iter.prev().is_none() { break; }
}
assert_eq!(results, vec![b"abd".to_vec(), b"abc".to_vec(), b"ab".to_vec()]);
}
#[test]
fn iter_forward_empty_key() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"".to_vec(), 0).unwrap();
trie.insert(b"abc".to_vec(), 1).unwrap();
let mut results = Vec::new();
let mut iter = trie.iter();
if let Some((k, _)) = iter.current() { results.push(k.to_vec()); }
while let Some((k, _)) = iter.next() { results.push(k.to_vec()); }
assert_eq!(results, vec![b"".to_vec(), b"abc".to_vec()]);
}
#[test]
fn optimize_preserves_terminal_flags() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"ab".to_vec(), 1).unwrap();
trie.insert(b"abcd".to_vec(), 2).unwrap();
trie.optimize();
assert_eq!(trie.get(b"ab"), Some(&1), "terminal key 'ab' lost after optimize");
assert_eq!(trie.get(b"abcd"), Some(&2));
assert_eq!(trie.len(), 2);
let mut trie2: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie2.insert(b"abcd".to_vec(), 1).unwrap();
trie2.insert(b"ab".to_vec(), 2).unwrap();
trie2.optimize();
assert_eq!(trie2.get(b"abcd"), Some(&1));
assert_eq!(trie2.get(b"ab"), Some(&2), "terminal key 'ab' lost after optimize (reverse insert)");
}
#[test]
fn null_bytes_in_keys() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"a\0b".to_vec(), 1).unwrap();
trie.insert(b"a\0c".to_vec(), 2).unwrap();
trie.insert(b"\0".to_vec(), 3).unwrap();
trie.insert(b"\0\0".to_vec(), 4).unwrap();
assert_eq!(trie.get(b"a\0b"), Some(&1));
assert_eq!(trie.get(b"a\0c"), Some(&2));
assert_eq!(trie.get(b"\0"), Some(&3));
assert_eq!(trie.get(b"\0\0"), Some(&4));
assert_eq!(trie.len(), 4);
}
#[test]
fn compact_insert_and_get() {
let mut trie: NibbleTrie<Vec<u8>, i32, u16, u16> = NibbleTrie::new();
trie.insert(b"hello".to_vec(), 42).unwrap();
assert_eq!(trie.get(b"hello"), Some(&42));
assert_eq!(trie.get(b"world"), None);
}
#[test]
fn compact_insert_prefix_keys() {
let mut trie: NibbleTrie<Vec<u8>, i32, u16, u16> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"abcd".to_vec(), 2).unwrap();
assert_eq!(trie.get(b"abc"), Some(&1));
assert_eq!(trie.get(b"abcd"), Some(&2));
}
#[test]
fn compact_iter_forward() {
let mut trie: NibbleTrie<Vec<u8>, i32, u16, u16> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"abd".to_vec(), 2).unwrap();
trie.insert(b"abe".to_vec(), 3).unwrap();
let mut results = Vec::new();
let mut iter = trie.iter();
while let Some((k, _)) = iter.next() {
results.push(k.to_vec());
}
assert_eq!(results, vec![b"abc", b"abd", b"abe"]);
}
#[test]
fn compact_iter_backward() {
let mut trie: NibbleTrie<Vec<u8>, i32, u16, u16> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"abd".to_vec(), 2).unwrap();
trie.insert(b"abe".to_vec(), 3).unwrap();
let mut iter = trie.iter_last();
let mut results = Vec::new();
loop {
match iter.current() {
Some((k, _)) => results.push(k.to_vec()),
None => break,
}
if iter.prev().is_none() { break; }
}
assert_eq!(results, vec![b"abe", b"abd", b"abc"]);
}
#[test]
fn iter_mut_forward_updates_values() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"abd".to_vec(), 2).unwrap();
trie.insert(b"abe".to_vec(), 3).unwrap();
{
let mut c = trie.iter_mut();
while let Some((_, v)) = c.next() {
*v += 100;
}
}
assert_eq!(trie.get(b"abc"), Some(&101));
assert_eq!(trie.get(b"abd"), Some(&102));
assert_eq!(trie.get(b"abe"), Some(&103));
}
#[test]
fn iter_mut_backward_updates_values() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"abd".to_vec(), 2).unwrap();
trie.insert(b"abe".to_vec(), 3).unwrap();
{
let mut c = trie.iter_mut_last();
if let Some((_, v)) = c.current() { *v *= 10; }
while let Some((_, v)) = c.prev() { *v *= 10; }
}
assert_eq!(trie.get(b"abc"), Some(&10));
assert_eq!(trie.get(b"abd"), Some(&20));
assert_eq!(trie.get(b"abe"), Some(&30));
}
#[test]
fn iter_mut_seek_then_mutate() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
for i in 0..50u32 {
let key = format!("key_{:05}", i);
trie.insert(key.into_bytes(), i as i32).unwrap();
}
{
let mut c = trie.iter_mut();
let (k, v) = c.seek(b"key_00025").unwrap();
assert_eq!(k, b"key_00025");
assert_eq!(*v, 25);
*v = -1;
}
assert_eq!(trie.get(b"key_00025"), Some(&-1));
assert_eq!(trie.get(b"key_00024"), Some(&24));
assert_eq!(trie.get(b"key_00026"), Some(&26));
}
#[test]
fn iter_mut_empty() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
let mut c = trie.iter_mut();
assert!(c.next().is_none());
assert!(c.current().is_none());
}
#[test]
fn iter_mut_current_revisits_same_slot() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"k".to_vec(), 7).unwrap();
let mut c = trie.iter_mut();
c.first();
{
let (_, v) = c.current().unwrap();
assert_eq!(*v, 7);
} {
let (_, v) = c.current().unwrap();
*v = 9;
}
assert_eq!(trie.get(b"k"), Some(&9));
}
#[test]
fn iter_mut_yields_borrowed_key() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"hello".to_vec(), 1).unwrap();
let mut c = trie.iter_mut();
let (k, _): (&[u8], &mut i32) = c.next().unwrap();
assert_eq!(k, b"hello");
}
#[test]
fn cursor_yields_borrowed_str_for_string_keys() {
let mut trie: NibbleTrie<String, i32> = NibbleTrie::new();
trie.insert("hello".to_string(), 1).unwrap();
trie.insert("world".to_string(), 2).unwrap();
let mut c = trie.iter();
let (k, v): (&str, &i32) = c.next().unwrap();
assert_eq!(k, "hello");
assert_eq!(*v, 1);
let (k, v): (&str, &i32) = c.next().unwrap();
assert_eq!(k, "world");
assert_eq!(*v, 2);
}
#[test]
fn cursor_mut_yields_borrowed_str_for_string_keys() {
let mut trie: NibbleTrie<String, i32> = NibbleTrie::new();
trie.insert("abc".to_string(), 1).unwrap();
trie.insert("abd".to_string(), 2).unwrap();
{
let mut c = trie.iter_mut();
while let Some((k, v)) = c.next() {
let _: &str = k;
*v += 100;
}
}
assert_eq!(trie.get(b"abc"), Some(&101));
assert_eq!(trie.get(b"abd"), Some(&102));
}
#[test]
fn cursor_yields_borrowed_for_vec_keys() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"hello".to_vec(), 1).unwrap();
let mut c = trie.iter();
let (k, _): (&[u8], &i32) = c.next().unwrap();
assert_eq!(k, b"hello");
}
#[test]
fn iter_mut_compact_mode() {
let mut trie: NibbleTrie<Vec<u8>, i32, u16, u16> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"abd".to_vec(), 2).unwrap();
{
let mut c = trie.iter_mut();
while let Some((_, v)) = c.next() { *v += 1; }
}
assert_eq!(trie.get(b"abc"), Some(&2));
assert_eq!(trie.get(b"abd"), Some(&3));
}
#[test]
fn iter_mut_first_last() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"abd".to_vec(), 2).unwrap();
trie.insert(b"abe".to_vec(), 3).unwrap();
let mut c = trie.iter_mut();
let (k, v) = c.first().unwrap();
assert_eq!(k, b"abc");
assert_eq!(*v, 1);
let (k, v) = c.last().unwrap();
assert_eq!(k, b"abe");
assert_eq!(*v, 3);
}
#[test]
fn iter_mut_index_tracking() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"abd".to_vec(), 2).unwrap();
let mut c = trie.iter_mut();
let i0 = c.next_index().unwrap();
let i1 = c.next_index().unwrap();
assert!(c.next_index().is_none());
assert_ne!(i0, i1);
assert!(i0 > 0 && i1 > 0);
}
fn range_trie() -> NibbleTrie<Vec<u8>, i32> {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
for s in ["abc", "abd", "abe", "abf", "acd", "ace"] {
trie.insert(s.as_bytes().to_vec(), s.len() as i32).unwrap();
}
trie
}
#[test]
fn range_inclusive_exclusive_bounds() {
let trie = range_trie();
let got: Vec<&[u8]> = trie.range(b"abd".as_slice()..b"ace".as_slice())
.map(|(k, _)| k).collect();
assert_eq!(got, vec![b"abd", b"abe", b"abf", b"acd"].as_slice());
}
#[test]
fn range_open_lower() {
let trie = range_trie();
let got: Vec<&[u8]> = trie.range(..b"abe".as_slice()).map(|(k, _)| k).collect();
assert_eq!(got, vec![b"abc", b"abd"]);
}
#[test]
fn range_open_upper() {
let trie = range_trie();
let got: Vec<&[u8]> = trie.range(b"abe".as_slice()..).map(|(k, _)| k).collect();
assert_eq!(got, vec![b"abe", b"abf", b"acd", b"ace"]);
}
#[test]
fn range_full() {
let trie = range_trie();
let got: Vec<&[u8]> = trie.range(..).map(|(k, _)| k).collect();
assert_eq!(got, vec![b"abc", b"abd", b"abe", b"abf", b"acd", b"ace"]);
}
#[test]
fn range_bound_included_excluded() {
use std::ops::Bound;
let trie = range_trie();
let got: Vec<&[u8]> = trie
.range_bounds(Bound::Included(b"abe".as_slice()), Bound::Included(b"acd".as_slice()))
.map(|(k, _)| k).collect();
assert_eq!(got, vec![b"abe", b"abf", b"acd"]);
let got: Vec<&[u8]> = trie
.range_bounds(Bound::Excluded(b"abc".as_slice()), Bound::Excluded(b"ace".as_slice()))
.map(|(k, _)| k).collect();
assert_eq!(got, vec![b"abd", b"abe", b"abf", b"acd"]);
}
#[test]
fn range_empty_and_misses() {
let trie = range_trie();
assert_eq!(trie.range(b"az".as_slice()..).count(), 0);
assert_eq!(trie.range(b"ace".as_slice()..b"abc".as_slice()).count(), 0);
let got: Vec<&[u8]> = trie.range(b"abx".as_slice()..b"ace".as_slice()).map(|(k, _)| k).collect();
assert_eq!(got, vec![b"acd"]); }
#[test]
fn range_double_ended() {
let trie = range_trie();
let mut it = trie.range(b"abd".as_slice()..b"ace".as_slice());
let mut got = Vec::new();
got.push(it.next().unwrap().0);
got.push(it.next_back().unwrap().0);
got.push(it.next().unwrap().0);
got.push(it.next_back().unwrap().0);
assert!(it.next().is_none() && it.next_back().is_none());
assert_eq!(got, vec![b"abd", b"acd", b"abe", b"abf"]);
}
#[test]
fn range_values_correct() {
let trie = range_trie();
let pairs: Vec<(&[u8], i32)> = trie.range(b"abd".as_slice()..b"abf".as_slice())
.map(|(k, v)| (k, *v)).collect();
assert_eq!(pairs, vec![(b"abd".as_slice(), 3), (b"abe".as_slice(), 3)]);
}
#[test]
fn range_yields_borrowed_str_for_string_keys() {
let mut trie: NibbleTrie<String, i32> = NibbleTrie::new();
for s in ["apple", "banana", "cherry", "date"] {
trie.insert(s.to_string(), s.len() as i32).unwrap();
}
let got: Vec<&str> = trie
.range((Bound::Included(b"banana".as_slice()), Bound::Included(b"cherry".as_slice())))
.map(|(k, _)| k).collect();
assert_eq!(got, vec!["banana", "cherry"]);
}
#[test]
fn range_after_optimize() {
let mut trie = range_trie();
trie.optimize();
let got: Vec<&[u8]> = trie.range(b"abd".as_slice()..b"acd".as_slice()).map(|(k, _)| k).collect();
assert_eq!(got, vec![b"abd", b"abe", b"abf"]);
}
#[test]
fn range_compact_mode() {
let mut trie: NibbleTrie<Vec<u8>, i32, u16, u16> = NibbleTrie::new();
for s in ["abc", "abd", "abe", "abf"] {
trie.insert(s.as_bytes().to_vec(), s.len() as i32).unwrap();
}
let got: Vec<&[u8]> = trie.range(b"abd".as_slice()..b"abf".as_slice()).map(|(k, _)| k).collect();
assert_eq!(got, vec![b"abd", b"abe"]);
}
#[test]
fn compact_optimize() {
let mut trie: NibbleTrie<Vec<u8>, i32, u16, u16> = NibbleTrie::new();
for i in 0..100 {
let key = format!("key_{:03}", i);
trie.insert(key.into_bytes(), i).unwrap();
}
trie.optimize();
for i in 0..100 {
let key = format!("key_{:03}", i);
assert_eq!(trie.get(key.as_bytes()), Some(&i),
"compact lookup failed after optimize for i={}", i);
}
}
#[test]
fn compact_seek() {
let mut trie: NibbleTrie<Vec<u8>, i32, u16, u16> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"abd".to_vec(), 2).unwrap();
trie.insert(b"abe".to_vec(), 3).unwrap();
let mut iter = trie.iter();
let (k, _) = iter.seek(b"abd").unwrap();
assert_eq!(k, b"abd");
}
#[test]
fn compact_empty_key() {
let mut trie: NibbleTrie<Vec<u8>, i32, u16, u16> = NibbleTrie::new();
trie.insert(b"".to_vec(), 0).unwrap();
trie.insert(b"abc".to_vec(), 1).unwrap();
let mut results = Vec::new();
let mut iter = trie.iter();
if let Some((k, _)) = iter.current() { results.push(k.to_vec()); }
while let Some((k, _)) = iter.next() { results.push(k.to_vec()); }
assert_eq!(results, vec![b"".to_vec(), b"abc".to_vec()]);
}
#[test]
fn trie_index_as_usize() {
assert_eq!(u16::as_usize(42u16), 42);
assert_eq!(u32::as_usize(42u32), 42);
assert_eq!(u64::as_usize(42u64), 42);
assert_eq!(u16::as_usize(u16::MAX), u16::MAX as usize);
assert_eq!(u32::as_usize(u32::MAX), u32::MAX as usize);
}
#[test]
fn trie_index_max_value() {
assert_eq!(<u16 as TrieIndex>::max_value(), u16::MAX as usize);
assert_eq!(<u32 as TrieIndex>::max_value(), u32::MAX as usize);
assert_eq!(<u64 as TrieIndex>::max_value(), u64::MAX as usize);
}
#[test]
fn trie_index_from_usize() {
assert_eq!(u16::from_usize(42), 42u16);
assert_eq!(u32::from_usize(42), 42u32);
assert_eq!(u64::from_usize(42), 42u64);
}
#[test]
fn terminal_flag() {
let mut node: Node<u32, u16> = Node::new();
assert!(!node.is_terminal());
assert_eq!(node.terminal, false);
node.set_terminal(true);
assert!(node.is_terminal());
assert_eq!(node.terminal, true);
node.set_terminal(false);
assert!(!node.is_terminal());
assert_eq!(node.terminal, false);
node.set_leaf_child(3, u32::from_usize(42));
node.set_terminal(true);
assert!(node.is_terminal());
assert!(node.is_occupied(3));
assert!(node.is_leaf(3));
}
#[test]
fn node_size_u8() {
assert_eq!(std::mem::size_of::<Node<u8, u16>>(), 22);
}
#[test]
fn u8_insert_and_get() {
let mut trie: NibbleTrie<Vec<u8>, i32, u8, u16> = NibbleTrie::new();
trie.insert(b"hello".to_vec(), 42).unwrap();
assert_eq!(trie.get(b"hello"), Some(&42));
assert_eq!(trie.get(b"world"), None);
}
#[test]
fn u8_near_capacity() {
let mut trie: NibbleTrie<Vec<u8>, i32, u8, u16> = NibbleTrie::new();
assert!(!trie.near_capacity());
for i in 0..50u32 {
let key = format!("k{:02}", i);
trie.insert(key.into_bytes(), i as i32).unwrap();
}
assert!(!trie.near_capacity());
}
#[test]
fn u8_overflow() {
let mut trie: NibbleTrie<Vec<u8>, i32, u8, u16> = NibbleTrie::new();
let mut count = 0u32;
for i in 0..400u32 {
let key = format!("k{:05}", i);
match trie.insert(key.into_bytes(), i as i32) {
Ok(_) => count += 1,
Err(()) => break,
}
}
assert!(count < 255, "u8 overflow never triggered: inserted {count}");
assert!(count > 0);
for i in 0..count {
let key = format!("k{:05}", i);
assert_eq!(trie.get(key.as_bytes()), Some(&(i as i32)),
"lookup failed for i={i} after u8 overflow");
}
assert_eq!(trie.len(), count as usize);
}
#[test]
fn promote_u8_to_u16() {
let mut trie: NibbleTrie<Vec<u8>, i32, u8, u16> = NibbleTrie::new();
for i in 0..50u32 {
let key = format!("key_{:03}", i);
trie.insert(key.into_bytes(), i as i32).unwrap();
}
let promoted: NibbleTrie<Vec<u8>, i32, u16, u16> = trie.promote::<u16>();
for i in 0..50u32 {
let key = format!("key_{:03}", i);
assert_eq!(promoted.get(key.as_bytes()), Some(&(i as i32)),
"lookup failed after promote for i={}", i);
}
assert_eq!(promoted.len(), 50);
}
#[test]
fn promote_u16_to_u32() {
let mut trie: NibbleTrie<Vec<u8>, i32, u16, u16> = NibbleTrie::new();
for i in 0..100u32 {
let key = format!("key_{:03}", i);
trie.insert(key.into_bytes(), i as i32).unwrap();
}
let promoted: NibbleTrie<Vec<u8>, i32, u32, u16> = trie.promote::<u32>();
for i in 0..100u32 {
let key = format!("key_{:03}", i);
assert_eq!(promoted.get(key.as_bytes()), Some(&(i as i32)));
}
}
#[test]
fn demote_u16_to_u8() {
let mut trie: NibbleTrie<Vec<u8>, i32, u16, u16> = NibbleTrie::new();
for i in 0..10u32 {
let key = format!("key_{:03}", i);
trie.insert(key.into_bytes(), i as i32).unwrap();
}
let demoted: NibbleTrie<Vec<u8>, i32, u8, u16> = match trie.demote::<u8>() {
Ok(d) => d,
Err(_) => panic!("demote should succeed with 10 keys"),
};
for i in 0..10u32 {
let key = format!("key_{:03}", i);
assert_eq!(demoted.get(key.as_bytes()), Some(&(i as i32)));
}
}
#[test]
fn demote_fails_too_large() {
let mut trie: NibbleTrie<Vec<u8>, i32, u16, u16> = NibbleTrie::new();
for i in 0..300u32 {
let key = format!("key_{:05}", i);
trie.insert(key.into_bytes(), i as i32).unwrap();
}
let result = trie.demote::<u8>();
assert!(result.is_err(), "demote should fail when trie is too large");
}
#[test]
fn string_key_insert_and_get() {
let mut trie: NibbleTrie<String, i32> = NibbleTrie::new();
trie.insert("hello".to_string(), 1).unwrap();
trie.insert("world".to_string(), 2).unwrap();
assert_eq!(trie.get(b"hello"), Some(&1));
assert_eq!(trie.get(b"world"), Some(&2));
assert_eq!(trie.get(b"hell"), None);
}
#[test]
fn string_key_into_keys_values() {
let mut trie: NibbleTrie<String, i32> = NibbleTrie::new();
trie.insert("abc".to_string(), 1).unwrap();
trie.insert("def".to_string(), 2).unwrap();
let (keys, values) = trie.into_keys_values();
assert_eq!(keys, vec!["abc".to_string(), "def".to_string()]);
assert_eq!(values, vec![1, 2]);
}
use std::collections::BTreeMap;
fn next_u64(state: &mut u64) -> u64 {
let mut x = *state;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
*state = x;
x
}
fn rand_key(state: &mut u64, max_len: usize) -> Vec<u8> {
let len = 1 + (next_u64(state) as usize % max_len);
(0..len).map(|_| (next_u64(state) & 0xFF) as u8).collect()
}
fn recompute_leftmost<PTR: TrieIndex, LEN: TrieIndex>(
trie: &NibbleTrie<Vec<u8>, i32, PTR, LEN>,
phys: usize,
) -> Result<usize, String> {
if let ArenaNode::Fnode(f) = &trie.arena[phys] {
let base = f.base.as_usize();
if trie.index[base].is_none() {
return Err(format!("Fnode {phys}: base -> gap slot {base}"));
}
for (_plen, offset) in f.slots.as_slice() {
if *offset != FNODE_OFFSET_NULL {
let ki = base + *offset as usize;
if trie.index[ki].is_none() {
return Err(format!("Fnode {phys}: slot offset {offset} -> gap slot {ki}"));
}
}
}
return Ok(base);
}
let node = trie.inode(phys);
let mut min_ki: Option<usize> = None;
if node.is_terminal() {
let ki = node.leaf.get().as_usize();
let (_toff, tlen, _tval) = trie.index[ki].as_ref().ok_or_else(|| format!(
"terminal node {phys}: leaf -> gap slot {ki}"))?;
if tlen.as_usize() * 2 != node.prefix_len.as_usize() {
return Err(format!(
"terminal node {phys}: leaf slot {ki} key len {} != prefix_len {} (not the terminal key)",
tlen.as_usize() * 2, node.prefix_len.as_usize()));
}
min_ki = Some(ki);
}
for nib in 0..16 {
if !node.is_occupied(nib) {
continue;
}
let child_leftmost = if node.is_leaf(nib) {
let ki = node.children[nib].get().as_usize();
if !trie.index[ki].is_some() {
return Err(format!(
"node {phys} leaf child nib {nib}: -> gap slot {ki}"));
}
ki
} else {
recompute_leftmost(trie, node.children[nib].get().as_usize())?
};
min_ki = Some(min_ki.map_or(child_leftmost, |m| m.min(child_leftmost)));
}
let l = min_ki.ok_or_else(|| format!("node {phys} has no keys in subtree"))?;
if l != node.leaf.get().as_usize() {
return Err(format!(
"leftmost-leaf invariant violated at node {phys}: stored {}, recomputed {}",
node.leaf.get().as_usize(), l));
}
Ok(l)
}
fn verify_invariants<PTR: TrieIndex, LEN: TrieIndex>(
trie: &NibbleTrie<Vec<u8>, i32, PTR, LEN>,
) -> Result<(), String> {
if trie.arena.is_empty() {
return if trie.n_keys == 0 { Ok(()) } else { Err("empty trie with nonzero n_keys".into()) };
}
recompute_leftmost(trie, 0)?;
let mut prev: Option<Vec<u8>> = None;
let mut occupied = 0usize;
for (i, slot) in trie.index.iter().enumerate() {
if let Some((off, len, _val)) = slot {
let k = trie.buf[off.get()..off.get() + len.as_usize()].to_vec();
if let Some(p) = &prev {
if k < *p {
return Err(format!(
"index not sorted: slot {i} key {k:?} < prev {p:?}"));
}
}
prev = Some(k);
occupied += 1;
}
}
if occupied != trie.n_keys {
return Err(format!(
"n_keys mismatch: {occupied} occupied slots vs n_keys {}", trie.n_keys));
}
Ok(())
}
fn cross_check_oracle<PTR: TrieIndex, LEN: TrieIndex>(
trie: &NibbleTrie<Vec<u8>, i32, PTR, LEN>,
oracle: &BTreeMap<Vec<u8>, i32>,
) {
assert_eq!(trie.len(), oracle.len());
for (k, v) in oracle {
assert_eq!(trie.get(k), Some(v),
"get mismatch for key {:?}", k);
}
let mut it = trie.iter();
let mut ordered = Vec::new();
if let Some((k, v)) = it.current() {
ordered.push((k.to_vec(), *v));
}
while let Some((k, v)) = it.next() {
ordered.push((k.to_vec(), *v));
}
let expected: Vec<(Vec<u8>, i32)> =
oracle.iter().map(|(k, v)| (k.clone(), *v)).collect();
assert_eq!(ordered, expected, "forward iteration order/value mismatch");
}
fn stress_insert_sequence(keys: &[Vec<u8>]) {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
let mut oracle: BTreeMap<Vec<u8>, i32> = BTreeMap::new();
for (i, key) in keys.iter().enumerate() {
if oracle.contains_key(key) {
assert_eq!(trie.insert(key.clone(), i as i32), Err(()),
"trie accepted duplicate key {:?}", key);
continue;
}
trie.insert(key.clone(), i as i32).unwrap();
oracle.insert(key.clone(), i as i32);
if let Err(msg) = verify_invariants(&trie) {
panic!("after inserting key #{i} {:?}: {msg}", key);
}
}
cross_check_oracle(&trie, &oracle);
}
#[test]
fn invariant_random_keys() {
let mut state = 0x9e3779b97f4a7c15;
let mut keys: Vec<Vec<u8>> = Vec::new();
for _ in 0..500 {
keys.push(rand_key(&mut state, 8));
}
stress_insert_sequence(&keys);
}
#[test]
fn invariant_sorted_keys() {
let keys: Vec<Vec<u8>> = (0..500u32).map(|i| format!("key_{:05}", i).into_bytes()).collect();
stress_insert_sequence(&keys);
}
#[test]
fn invariant_reverse_keys() {
let keys: Vec<Vec<u8>> = (0..500u32)
.rev()
.map(|i| format!("key_{:05}", i).into_bytes())
.collect();
stress_insert_sequence(&keys);
}
#[test]
fn invariant_prefix_heavy() {
let mut keys: Vec<Vec<u8>> = Vec::new();
for i in 0..200u32 {
let base = format!("prefix_{:03}", i);
keys.push(base.as_bytes().to_vec());
keys.push(format!("{}{}", base, "_suffix").into_bytes());
keys.push(format!("{}{}", base, "x").into_bytes());
}
stress_insert_sequence(&keys);
}
#[test]
fn invariant_mixed_lengths() {
let mut state = 0xdeadbeefcafebabe;
let mut keys: Vec<Vec<u8>> = Vec::new();
for _ in 0..400 {
keys.push(rand_key(&mut state, 24));
}
stress_insert_sequence(&keys);
}
#[test]
fn invariant_backwards_iteration_after_shifts() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
let mut oracle: BTreeMap<Vec<u8>, i32> = BTreeMap::new();
for i in (0..300u32).rev() {
let key = format!("k{:05}", i);
let kb = key.into_bytes();
trie.insert(kb.clone(), i as i32).unwrap();
oracle.insert(kb, i as i32);
if let Err(msg) = verify_invariants(&trie) { panic!("after inserting #{i}: {msg}"); }
}
assert_eq!(trie.len(), oracle.len());
let mut it = trie.iter_last();
let mut ordered = Vec::new();
if let Some((k, v)) = it.current() {
ordered.push((k.to_vec(), *v));
}
while let Some((k, v)) = it.prev() {
ordered.push((k.to_vec(), *v));
}
let expected: Vec<(Vec<u8>, i32)> =
oracle.iter().rev().map(|(k, v)| (k.clone(), *v)).collect();
assert_eq!(ordered, expected, "reverse iteration mismatch after shifts");
}
#[test]
fn invariant_seek_after_shifts() {
let mut state = 0x123456789abcdef0;
let mut keys: Vec<Vec<u8>> = Vec::new();
for _ in 0..300 {
keys.push(rand_key(&mut state, 8));
}
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
let mut oracle: BTreeMap<Vec<u8>, i32> = BTreeMap::new();
for (i, key) in keys.iter().enumerate() {
if oracle.contains_key(key) { continue; }
trie.insert(key.clone(), i as i32).unwrap();
oracle.insert(key.clone(), i as i32);
if let Err(msg) = verify_invariants(&trie) {
panic!("after inserting #{i} {:?}: {msg}", key);
}
}
for (k, _v) in &oracle {
let mut it = trie.iter();
it.seek(k);
assert_eq!(it.current().map(|(kk, _)| kk.to_vec()), Some(k.clone()),
"seek mismatch for key {:?}", k);
}
for k in oracle.keys() {
let mut probe = k.clone();
*probe.last_mut().unwrap() = probe.last().copied().unwrap_or(0).wrapping_add(1);
if oracle.contains_key(&probe) {
continue;
}
let expected = oracle.keys().find(|k| k.as_slice() >= probe.as_slice()).cloned();
let mut it = trie.iter();
it.seek(&probe);
assert_eq!(it.current().map(|(kk, _)| kk.to_vec()), expected,
"seek-ceiling mismatch: seek {:?} expected {:?} got {:?}",
probe, expected, it.current().map(|(kk, _)| kk.to_vec()));
}
}
#[test]
fn invariant_past_optimize_repeatedly() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
let mut oracle: BTreeMap<Vec<u8>, i32> = BTreeMap::new();
for i in 0..1000u32 {
let key = format!("{:04}", i); let kb = key.into_bytes();
trie.insert(kb.clone(), i as i32).unwrap();
oracle.insert(kb, i as i32);
if let Err(msg) = verify_invariants(&trie) { panic!("after inserting #{i}: {msg}"); }
}
cross_check_oracle(&trie, &oracle);
}
#[test]
fn cursor_empty_trie() {
let trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
let mut it = trie.iter();
assert!(it.current().is_none());
assert!(it.current_index().is_none());
assert!(it.next().is_none());
assert!(it.prev().is_none());
assert!(it.first().is_none());
assert!(it.last().is_none());
assert!(it.seek(b"anything").is_none());
}
#[test]
fn cursor_forward_before_first() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"abd".to_vec(), 2).unwrap();
trie.insert(b"abe".to_vec(), 3).unwrap();
let mut it = trie.iter();
assert!(it.current().is_none());
assert_eq!(it.next().unwrap().0, b"abc");
assert_eq!(it.current().unwrap().0, b"abc"); assert_eq!(it.next().unwrap().0, b"abd");
assert_eq!(it.next().unwrap().0, b"abe");
assert!(it.next().is_none());
assert!(it.current().is_none());
assert_eq!(it.prev().unwrap().0, b"abe");
}
#[test]
fn cursor_backward_on_last() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"abc".to_vec(), 1).unwrap();
trie.insert(b"abd".to_vec(), 2).unwrap();
trie.insert(b"abe".to_vec(), 3).unwrap();
let mut it = trie.iter_last();
assert_eq!(it.current().unwrap().0, b"abe");
assert!(it.current_index().is_some()); assert_eq!(it.prev().unwrap().0, b"abd");
assert_eq!(it.prev().unwrap().0, b"abc");
assert!(it.prev().is_none());
assert_eq!(it.next().unwrap().0, b"abc");
}
#[test]
fn cursor_first_last_jump() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
trie.insert(b"mid".to_vec(), 5).unwrap();
trie.insert(b"aaa".to_vec(), 1).unwrap();
trie.insert(b"zzz".to_vec(), 9).unwrap();
let mut it = trie.iter();
assert_eq!(it.first().unwrap().0, b"aaa");
assert_eq!(it.current().unwrap().0, b"aaa");
assert_eq!(it.last().unwrap().0, b"zzz");
assert_eq!(it.current().unwrap().0, b"zzz");
assert_eq!(it.prev().unwrap().0, b"mid");
assert_eq!(it.first().unwrap().0, b"aaa");
assert_eq!(it.next().unwrap().0, b"mid");
}
#[test]
fn cursor_seek_then_scan() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
for i in 0..10u32 {
trie.insert(format!("key{i}").into_bytes(), i as i32).unwrap();
}
let mut it = trie.iter();
assert_eq!(it.seek(b"key5").unwrap().0, b"key5");
assert_eq!(it.next().unwrap().0, b"key6");
assert_eq!(it.prev().unwrap().0, b"key5"); assert_eq!(it.prev().unwrap().0, b"key4");
assert!(it.seek(b"zzz").is_none());
assert!(it.current().is_none());
assert_eq!(it.seek(b"").unwrap().0, b"key0");
}
#[test]
fn cursor_scan_order_matches_oracle_after_shifts() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
let mut oracle: BTreeMap<Vec<u8>, i32> = BTreeMap::new();
for i in (0..200u32).rev() {
let k = format!("item{:04}", i).into_bytes();
trie.insert(k.clone(), i as i32).unwrap();
oracle.insert(k, i as i32);
}
trie.optimize();
let mut fwd: Vec<(Vec<u8>, i32)> = Vec::new();
let mut it = trie.iter();
if let Some((k, v)) = it.current() { fwd.push((k.to_vec(), *v)); }
while let Some((k, v)) = it.next() { fwd.push((k.to_vec(), *v)); }
let expected: Vec<(Vec<u8>, i32)> =
oracle.iter().map(|(k, v)| (k.clone(), *v)).collect();
assert_eq!(fwd, expected, "forward scan order mismatch after shifts+optimize");
let mut rev: Vec<(Vec<u8>, i32)> = Vec::new();
let mut it = trie.iter_last();
if let Some((k, v)) = it.current() { rev.push((k.to_vec(), *v)); }
while let Some((k, v)) = it.prev() { rev.push((k.to_vec(), *v)); }
let expected_rev: Vec<(Vec<u8>, i32)> =
oracle.iter().rev().map(|(k, v)| (k.clone(), *v)).collect();
assert_eq!(rev, expected_rev, "backward scan order mismatch after shifts+optimize");
}
#[test]
#[ignore]
fn mem_print() {
use std::mem::{size_of, size_of_val};
type Trie = NibbleTrie<Vec<u8>, i32>;
let mut trie: Trie = NibbleTrie::new();
let mut state: u64 = 0x9e3779b97f4a7c15;
const N: usize = 50_000;
const MAX_LEN: usize = 16;
let mut raw_key_bytes: usize = 0;
let mut inserted = 0usize;
while inserted < N {
let k = rand_key(&mut state, MAX_LEN);
if trie.get(&k).is_some() {
continue;
}
trie.insert(k.clone(), inserted as i32).unwrap();
raw_key_bytes += k.len();
inserted += 1;
}
let val_bytes = trie.len() * size_of::<i32>();
let node_size = size_of_val(&trie.arena[0]);
let slot_size = size_of_val(&trie.index[1]);
let report = |label: &str, t: &Trie| {
let n = t.len();
let idx_len = t.index.len();
let idx_cap = t.index.capacity();
let arena_cap = t.arena.capacity();
let buf_cap = t.buf.capacity();
let arena_bytes = arena_cap * node_size;
let buf_bytes = buf_cap;
let index_bytes = idx_cap * slot_size;
let total = arena_bytes + buf_bytes + index_bytes;
let gaps = idx_len - n; let mut s = String::new();
s.push_str(&format!("\n=== {label} ===\n"));
s.push_str(&format!(" n_keys (distinct) : {n}\n"));
s.push_str(&format!(" raw key bytes : {raw_key_bytes} ({:.1} B/key avg)\n", raw_key_bytes as f64 / n as f64));
s.push_str(&format!(" raw value bytes : {val_bytes} ({} B/val)\n", size_of::<i32>()));
s.push_str(&format!(" raw keys+values : {}\n", raw_key_bytes + val_bytes));
s.push_str(&format!(" sizes: Node={node_size} B, Option<Slot>={slot_size} B\n"));
s.push_str(&format!(" arena : len={} cap={} -> {} B\n", t.arena.len(), arena_cap, arena_bytes));
s.push_str(&format!(" buf : len={} cap={} -> {} B\n", t.buf.len(), buf_cap, buf_bytes));
s.push_str(&format!(" index : len={} cap={} -> {} B (occupied={n}, gaps={gaps})\n", idx_len, idx_cap, index_bytes));
s.push_str(&format!(" TOTAL reserved : {} B ({:.2} MiB)\n", total, total as f64 / (1 << 20) as f64));
s.push_str(&format!(" overhead vs raw : {:.1}x ({} B raw -> {} B reserved)\n",
total as f64 / (raw_key_bytes + val_bytes) as f64,
raw_key_bytes + val_bytes, total));
print!("{s}");
use std::io::Write;
let mut f = std::fs::OpenOptions::new()
.create(true).append(true)
.open("/tmp/nibble_mem.txt").expect("open /tmp/nibble_mem.txt");
f.write_all(s.as_bytes()).expect("write");
};
report("BEFORE optimize (post-insert, with 90%-trigger re-spreads)", &trie);
trie.optimize();
report("AFTER optimize (explicit single optimize)", &trie);
std::hint::black_box(&trie);
}
#[test]
#[ignore]
fn node_stats() {
type Trie = NibbleTrie<Vec<u8>, i32>;
let mut trie: Trie = NibbleTrie::new();
let mut state: u64 = 0x9e3779b97f4a7c15;
const N: usize = 50_000;
const MAX_LEN: usize = 16;
let mut inserted = 0usize;
while inserted < N {
let k = rand_key(&mut state, MAX_LEN);
if trie.get(&k).is_some() {
continue;
}
trie.insert(k, inserted as i32).unwrap();
inserted += 1;
}
trie.optimize();
let mut hist_incl_term: [usize; 18] = [0; 18];
let mut hist_children: [usize; 17] = [0; 17];
let mut terminal_nodes = 0usize;
let mut total_leaf_edges = 0usize; let mut total_internal_edges = 0usize; let mut total_children = 0usize;
for node in trie.arena.iter() {
let node = match node {
ArenaNode::Inode(n) => n,
ArenaNode::Fnode(_) => continue,
};
let mask = node.children_mask();
let occ = mask.count_ones() as usize;
let term = node.is_terminal();
hist_children[occ] += 1;
let occ_incl = occ + term as usize;
hist_incl_term[occ_incl] += 1;
if term { terminal_nodes += 1; }
total_children += occ;
total_leaf_edges += (node.leaf_mask & mask).count_ones() as usize;
total_internal_edges += occ - (node.leaf_mask & mask).count_ones() as usize;
}
let total_nodes = trie.arena.len();
let avg_children = total_children as f64 / total_nodes as f64;
let mut s = String::new();
s.push_str(&format!("\n=== node stats (N={N} keys, max_len={MAX_LEN}) ===\n"));
s.push_str(&format!(" total nodes : {total_nodes}\n"));
s.push_str(&format!(" terminal nodes : {terminal_nodes} ({:.1}%)\n",
100.0 * terminal_nodes as f64 / total_nodes as f64));
s.push_str(&format!(" total child edges : {total_children} (avg {avg_children:.2}/node)\n"));
s.push_str(&format!(" leaf edges (->key) : {total_leaf_edges}\n"));
s.push_str(&format!(" internal edges(->node): {total_internal_edges}\n"));
s.push_str(&format!(" nodes/key : {:.3}\n", total_nodes as f64 / N as f64));
s.push_str("\n occupancy histogram (occupied child slots + terminal flag):\n");
s.push_str(" occ | nodes %\n");
s.push_str(" ----+----------------\n");
for (occ, &cnt) in hist_incl_term.iter().enumerate() {
if cnt == 0 { continue; }
s.push_str(&format!(" {occ:>3} | {cnt:>6} {:>5.1}%\n",
100.0 * cnt as f64 / total_nodes as f64));
}
s.push_str("\n child-count histogram (occupied child slots only):\n");
s.push_str(" kids | nodes %\n");
s.push_str(" -----+----------------\n");
for (kids, &cnt) in hist_children.iter().enumerate() {
if cnt == 0 { continue; }
s.push_str(&format!(" {kids:>4} | {cnt:>6} {:>5.1}%\n",
100.0 * cnt as f64 / total_nodes as f64));
}
print!("{s}");
use std::io::Write;
let mut f = std::fs::OpenOptions::new()
.create(true).append(true)
.open("/tmp/nibble_nodes.txt").expect("open /tmp/nibble_nodes.txt");
f.write_all(s.as_bytes()).expect("write");
std::hint::black_box(&trie);
}
fn flatten_subtree_to_fnode<PTR: TrieIndex, LEN: TrieIndex>(
trie: &NibbleTrie<Vec<u8>, i32, PTR, LEN>,
phys: usize,
) -> Option<FlatNode<PTR, LEN>> {
if phys == 0 {
return None;
}
trie.build_fnode_subtree(phys)
}
fn collapse_to_fnode<PTR: TrieIndex, LEN: TrieIndex>(
trie: &mut NibbleTrie<Vec<u8>, i32, PTR, LEN>,
phys: usize,
) -> bool {
if phys == 0 {
return false; }
let Some(fnode) = flatten_subtree_to_fnode(&*trie, phys) else {
return false;
};
trie.arena[phys] = ArenaNode::Fnode(fnode);
true
}
fn first_fnode_candidate<PTR: TrieIndex, LEN: TrieIndex>(
trie: &NibbleTrie<Vec<u8>, i32, PTR, LEN>,
) -> Option<usize> {
for phys in 1..trie.arena.len() {
if !matches!(trie.arena[phys], ArenaNode::Inode(_)) {
continue;
}
if flatten_subtree_to_fnode(trie, phys).is_some() {
return Some(phys);
}
}
None
}
fn best_fnode_candidate<PTR: TrieIndex, LEN: TrieIndex>(
trie: &NibbleTrie<Vec<u8>, i32, PTR, LEN>,
) -> Option<usize> {
let mut best: Option<(usize, usize)> = None; for phys in 1..trie.arena.len() {
if !matches!(trie.arena[phys], ArenaNode::Inode(_)) {
continue;
}
if let Some(f) = flatten_subtree_to_fnode(trie, phys) {
let n = f.slots.len();
if best.map_or(true, |(_, bn)| n > bn) {
best = Some((phys, n));
}
}
}
best.map(|(p, _)| p)
}
fn cross_check_fnode<PTR: TrieIndex, LEN: TrieIndex>(
trie: &NibbleTrie<Vec<u8>, i32, PTR, LEN>,
oracle: &BTreeMap<Vec<u8>, i32>,
) {
assert_eq!(trie.len(), oracle.len());
for (k, v) in oracle {
assert_eq!(trie.get(k), Some(v), "get mismatch for key {:?}", k);
#[cfg(feature = "unchecked")]
{
let idx = unsafe { trie.get_index_unchecked(k) }.expect("get_index_unchecked missed a present key");
assert_eq!(&trie.index[idx].as_ref().unwrap().2, v, "get_index_unchecked value mismatch for {:?}", k);
}
}
let mut probe = Vec::new();
for (k, _) in oracle {
probe.push(k.clone());
let mut shorter = k.clone();
if shorter.len() > 1 { shorter.truncate(shorter.len() - 1); probe.push(shorter); }
let mut longer = k.clone(); longer.push(k[0]); probe.push(longer);
}
for p in &probe {
let got = trie.get(p);
assert_eq!(got.is_some(), oracle.contains_key(p), "get spurious for {:?}", p);
}
let mut it = trie.iter();
let mut ordered = Vec::new();
if let Some((k, v)) = it.current() { ordered.push((k.to_vec(), *v)); }
while let Some((k, v)) = it.next() { ordered.push((k.to_vec(), *v)); }
let expected: Vec<(Vec<u8>, i32)> = oracle.iter().map(|(k, v)| (k.clone(), *v)).collect();
assert_eq!(ordered, expected, "forward iteration order/value mismatch");
let mut seek_probes: Vec<Vec<u8>> = oracle.keys().cloned().collect();
if oracle.len() >= 2 {
let mid = oracle.keys().nth(oracle.len() / 2).unwrap();
let mut gap = mid.clone(); gap[0] = gap[0].wrapping_add(1); seek_probes.push(gap);
}
if let Some(last) = oracle.keys().next_back() {
let mut past = last.clone(); past[0] = past[0].wrapping_add(1); seek_probes.push(past);
let _ = last;
}
seek_probes.push(Vec::new());
for p in &seek_probes {
let cursor = trie.iter();
let mut c = cursor;
let landed = c.seek(p).map(|(k, _)| k.to_vec());
let want: Option<Vec<u8>> = oracle.range(p.clone()..).next().map(|(k, _)| k.clone());
assert_eq!(landed, want, "seek lower-bound mismatch for {:?}", p);
}
}
#[test]
fn fnode_read_single_level() {
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
let mut oracle = BTreeMap::new();
for (i, k) in [b"a".to_vec(), b"b".to_vec(), b"c".to_vec(), b"d".to_vec()].iter().enumerate() {
trie.insert(k.clone(), i as i32).unwrap();
oracle.insert(k.clone(), i as i32);
}
let phys = first_fnode_candidate(&trie).expect("a flat candidate must exist");
assert!(collapse_to_fnode(&mut trie, phys), "collapse failed");
assert!(matches!(trie.arena[phys], ArenaNode::Fnode(_)), "arena[phys] is now an Fnode");
cross_check_fnode(&trie, &oracle);
}
#[test]
fn fnode_read_multi_level_descent() {
let keys: [Vec<u8>; 4] = [b"aaaa".to_vec(), b"aaab".to_vec(), b"baaa".to_vec(), b"baab".to_vec()];
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
let mut oracle = BTreeMap::new();
for (i, k) in keys.iter().enumerate() {
trie.insert(k.clone(), i as i32).unwrap();
oracle.insert(k.clone(), i as i32);
}
let phys = best_fnode_candidate(&trie).expect("a flat candidate must exist");
assert!(collapse_to_fnode(&mut trie, phys), "collapse failed");
if let ArenaNode::Fnode(f) = &trie.arena[phys] {
let has_branch = f.slots.as_slice().iter().any(|(_, off)| *off == FNODE_OFFSET_NULL);
assert!(has_branch, "multi-level Fnode must contain a branch marker (descent path)");
assert_eq!(f.slots.len(), 6, "multi-level Fnode must be 6 slots (2 branches + 4 leaves)");
} else { panic!("not an Fnode"); }
cross_check_fnode(&trie, &oracle);
}
#[test]
fn fnode_read_prefix_key() {
let keys: [Vec<u8>; 4] = [b"aa".to_vec(), b"aaaa".to_vec(), b"aaab".to_vec(), b"b".to_vec()];
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
let mut oracle = BTreeMap::new();
for (i, k) in keys.iter().enumerate() {
trie.insert(k.clone(), i as i32).unwrap();
oracle.insert(k.clone(), i as i32);
}
let phys = best_fnode_candidate(&trie).expect("a flat candidate must exist");
assert!(collapse_to_fnode(&mut trie, phys), "collapse failed");
if let ArenaNode::Fnode(f) = &trie.arena[phys] {
let s = f.slots.as_slice();
let found_prefix = (0..s.len()).any(|i| {
s[i].1 != FNODE_OFFSET_NULL && i + 1 < s.len() && s[i + 1].0.as_usize() > s[i].0.as_usize()
});
assert!(found_prefix, "expected a terminal+branch (non-NULL offset followed by a deeper) slot");
} else { panic!("not an Fnode"); }
cross_check_fnode(&trie, &oracle);
}
#[test]
fn fnode_read_branch_then_leaves() {
let pairs: &[(Vec<u8>, i32)] = &[
(b"aax".to_vec(), 0), (b"aay".to_vec(), 1),
(b"bbx".to_vec(), 2), (b"bby".to_vec(), 3),
];
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
let mut oracle = BTreeMap::new();
for (k, v) in pairs {
trie.insert(k.clone(), *v).unwrap();
oracle.insert(k.clone(), *v);
}
let phys = first_fnode_candidate(&trie).expect("a flat candidate must exist");
assert!(collapse_to_fnode(&mut trie, phys), "collapse failed");
let has_branch = matches!(&trie.arena[phys], ArenaNode::Fnode(f) if f.slots.as_slice().iter().any(|(_, off)| *off == FNODE_OFFSET_NULL));
assert!(has_branch, "expected the collapsed Fnode to contain a branch marker");
cross_check_fnode(&trie, &oracle);
}
#[test]
fn fnode_read_stress() {
let mut state: u64 = 0x9e3779b97f4a7c15;
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
let mut oracle: BTreeMap<Vec<u8>, i32> = BTreeMap::new();
for _ in 0..200 {
let k = rand_key(&mut state, 6);
if oracle.contains_key(&k) {
continue;
}
trie.insert(k.clone(), oracle.len() as i32).unwrap();
oracle.insert(k, oracle.len() as i32);
}
cross_check_fnode(&trie, &oracle); let mut collapses = 0;
while let Some(phys) = first_fnode_candidate(&trie) {
assert!(collapse_to_fnode(&mut trie, phys), "collapse failed at phys {phys}");
collapses += 1;
cross_check_fnode(&trie, &oracle);
}
assert!(collapses > 0, "stress never collapsed any subtree");
}
#[test]
fn fnode_read_terminal_root() {
let keys: [Vec<u8>; 4] = [b"baaa".to_vec(), b"baab".to_vec(), b"ba".to_vec(), b"c".to_vec()];
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
let mut oracle = BTreeMap::new();
for (i, k) in keys.iter().enumerate() {
trie.insert(k.clone(), i as i32).unwrap();
oracle.insert(k.clone(), i as i32);
}
let phys = (1..trie.arena.len())
.find(|&p| flatten_subtree_to_fnode(&trie, p).map_or(false, |f| f.terminal))
.expect("a terminal-rooted candidate must exist");
assert!(collapse_to_fnode(&mut trie, phys), "collapse failed");
match &trie.arena[phys] {
ArenaNode::Fnode(f) => {
assert!(f.terminal, "expected the root's own key pulled out as terminal base");
assert!(!f.slots.as_slice().is_empty(), "expected descendant array slots");
assert!(f.slots.as_slice().iter().all(|(_, off)| *off != 0),
"terminal-rooted Fnode must not duplicate `base` as an offset-0 slot");
assert!(f.slots.as_slice().iter().any(|(_, off)| *off != FNODE_OFFSET_NULL),
"terminal-rooted Fnode must have at least one descendant terminal");
}
_ => panic!("not an Fnode"),
}
cross_check_fnode(&trie, &oracle);
}
#[test]
fn flatten_basic() {
let keys: [Vec<u8>; 4] = [b"aaaa".to_vec(), b"aaab".to_vec(), b"baaa".to_vec(), b"baab".to_vec()];
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
let mut oracle = BTreeMap::new();
for (i, k) in keys.iter().enumerate() {
trie.insert(k.clone(), i as i32).unwrap();
oracle.insert(k.clone(), i as i32);
}
trie.optimize();
let arena_after_opt = trie.arena.len();
trie.flatten();
assert!(trie.arena.iter().any(|n| matches!(n, ArenaNode::Fnode(_))),
"flatten produced no Fnodes");
assert!(matches!(trie.arena[0], ArenaNode::Inode(_)), "root must stay an Inode");
assert!(trie.arena.len() < arena_after_opt,
"arena did not shrink after flatten: {} vs {}", trie.arena.len(), arena_after_opt);
verify_invariants(&trie).expect("invariants hold after flatten");
cross_check_fnode(&trie, &oracle);
}
#[test]
fn flatten_idempotent() {
let keys: [Vec<u8>; 4] = [b"aaaa".to_vec(), b"aaab".to_vec(), b"baaa".to_vec(), b"baab".to_vec()];
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
let mut oracle = BTreeMap::new();
for (i, k) in keys.iter().enumerate() {
trie.insert(k.clone(), i as i32).unwrap();
oracle.insert(k.clone(), i as i32);
}
trie.optimize();
trie.flatten();
let (fnodes1, len1) = (
trie.arena.iter().filter(|n| matches!(n, ArenaNode::Fnode(_))).count(),
trie.arena.len(),
);
trie.flatten(); let (fnodes2, len2) = (
trie.arena.iter().filter(|n| matches!(n, ArenaNode::Fnode(_))).count(),
trie.arena.len(),
);
assert_eq!((fnodes2, len2), (fnodes1, len1), "re-flatten changed the arena");
verify_invariants(&trie).expect("invariants hold after re-flatten");
cross_check_fnode(&trie, &oracle);
}
#[test]
fn flatten_stress() {
let mut state: u64 = 0x123456789abcdef0;
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
let mut oracle: BTreeMap<Vec<u8>, i32> = BTreeMap::new();
for _ in 0..200 {
let k = rand_key(&mut state, 6);
if oracle.contains_key(&k) { continue; }
trie.insert(k.clone(), oracle.len() as i32).unwrap();
oracle.insert(k, oracle.len() as i32);
}
trie.optimize();
let arena_after_opt = trie.arena.len();
trie.flatten();
assert!(trie.arena.iter().any(|n| matches!(n, ArenaNode::Fnode(_))),
"stress flatten produced no Fnodes");
assert!(trie.arena.len() < arena_after_opt, "stress arena did not shrink");
verify_invariants(&trie).expect("invariants hold after stress flatten");
cross_check_fnode(&trie, &oracle);
trie.optimize();
trie.flatten();
verify_invariants(&trie).expect("invariants hold after re-optimize+flatten");
cross_check_fnode(&trie, &oracle);
}
#[test]
#[ignore = "memory-footprint sanity; run directly (cargo stdout is filtered)"]
fn flatten_memory_footprint() {
let mut state: u64 = 0xfeed1234abcd5678;
let mut trie: NibbleTrie<Vec<u8>, i32> = NibbleTrie::new();
let mut oracle: BTreeMap<Vec<u8>, i32> = BTreeMap::new();
for _ in 0..2000 {
let k = rand_key(&mut state, 6);
if oracle.contains_key(&k) { continue; }
trie.insert(k.clone(), oracle.len() as i32).unwrap();
oracle.insert(k, oracle.len() as i32);
}
trie.optimize();
let before_nodes = trie.arena.len();
let before_bytes = before_nodes * std::mem::size_of::<ArenaNode<u32, u16>>();
let fnodes_before = trie.arena.iter().filter(|n| matches!(n, ArenaNode::Fnode(_))).count();
trie.flatten();
let after_nodes = trie.arena.len();
let after_bytes = after_nodes * std::mem::size_of::<ArenaNode<u32, u16>>();
let fnodes = trie.arena.iter().filter(|n| matches!(n, ArenaNode::Fnode(_))).count();
let leaves = trie.arena.iter().filter(|n| matches!(n, ArenaNode::Inode(n) if n.children_mask() == 0)).count();
eprintln!(
"flatten_memory_footprint: keys={} arena {}->{} nodes ({}B->{}B, {:.1}%), Fnodes {}->{}, leaf-Inodes={}",
oracle.len(), before_nodes, after_nodes, before_bytes, after_bytes,
100.0 * after_bytes as f64 / before_bytes as f64, fnodes_before, fnodes, leaves
);
assert!(fnodes > 0, "flatten produced no Fnodes");
assert!(after_bytes < before_bytes, "arena bytes did not drop: {} -> {}", before_bytes, after_bytes);
verify_invariants(&trie).expect("invariants hold");
cross_check_fnode(&trie, &oracle);
}