use super::*;
#[test]
fn insert_contains_delete_round_trip() {
let mut cf = CuckooFilter::with_capacity(1000);
for i in 0..500u32 {
assert!(cf.insert(&format!("k{i}")));
}
for i in 0..500u32 {
assert!(cf.contains(&format!("k{i}")), "k{i} should be present");
}
for i in 0..500u32 {
assert!(cf.delete(&format!("k{i}")));
}
for i in 0..500u32 {
assert!(!cf.contains(&format!("k{i}")), "k{i} should be gone");
}
assert_eq!(cf.len(), 0);
}
#[test]
fn delete_nonexistent_returns_false() {
let mut cf = CuckooFilter::with_capacity(100);
assert!(!cf.delete("never-inserted"));
}
#[test]
fn empty_contains_returns_false() {
let cf = CuckooFilter::with_capacity(100);
assert!(!cf.contains("anything"));
}
#[test]
fn false_positive_rate_under_three_percent() {
let n = 10_000;
let mut cf = CuckooFilter::with_capacity(n);
for i in 0..n {
assert!(cf.insert(&format!("present{i}")));
}
let probes = 10_000;
let mut fp = 0usize;
for i in 0..probes {
if cf.contains(&format!("absent{i}")) {
fp += 1;
}
}
let fpr = fp as f64 / probes as f64;
assert!(fpr < 0.03, "fpr {fpr:.4} too high");
}
#[test]
fn bucket_count_is_power_of_two() {
let cf = CuckooFilter::with_capacity(1000);
let n = cf.bucket_count();
assert!(n.is_power_of_two(), "{n} should be power of 2");
}
#[test]
fn len_tracks_inserts_and_deletes() {
let mut cf = CuckooFilter::with_capacity(1000);
assert_eq!(cf.len(), 0);
cf.insert("a");
cf.insert("b");
assert_eq!(cf.len(), 2);
cf.delete("a");
assert_eq!(cf.len(), 1);
cf.delete("absent");
assert_eq!(cf.len(), 1);
}
#[test]
fn is_empty_initially() {
let cf = CuckooFilter::with_capacity(100);
assert!(cf.is_empty());
}
#[test]
fn duplicate_insert_increases_count() {
let mut cf = CuckooFilter::with_capacity(100);
cf.insert("dup");
cf.insert("dup");
cf.insert("dup");
assert_eq!(cf.len(), 3);
assert!(cf.contains("dup"));
cf.delete("dup");
cf.delete("dup");
cf.delete("dup");
assert!(!cf.contains("dup"));
assert_eq!(cf.len(), 0);
}
#[test]
fn default_constructor_via_with_capacity_zero() {
let cf = CuckooFilter::with_capacity(0);
assert!(cf.bucket_count() >= 2);
}
#[test]
fn saturation_never_produces_a_false_negative() {
let mut cf = CuckooFilter::with_capacity(1);
let mut accepted = Vec::new();
for i in 0..4096u32 {
let key = format!("k{i}");
if cf.insert(&key) {
accepted.push(key);
}
}
assert!(
accepted.len() < 4096,
"a 2-bucket filter must refuse somewhere"
);
for key in &accepted {
assert!(cf.contains(key), "{key} was accepted then lost");
}
assert_eq!(cf.len(), accepted.len());
}
#[test]
fn insert_if_absent_suppresses_a_repeat() {
let mut cf = CuckooFilter::with_capacity(1000);
assert!(cf.insert_if_absent("SEQ-1"));
assert!(
!cf.insert_if_absent("SEQ-1"),
"a repeat is not stored twice"
);
assert_eq!(cf.len(), 1);
assert!(cf.insert_if_absent("SEQ-2"));
assert_eq!(cf.len(), 2);
assert!(cf.delete("SEQ-1"));
assert!(cf.insert_if_absent("SEQ-1"), "absent again after delete");
}
#[test]
fn try_insert_reports_not_enough_space() {
let mut cf = CuckooFilter::with_capacity(1);
let mut err = None;
for i in 0..4096u32 {
if let Err(e) = cf.try_insert(&format!("k{i}")) {
err = Some(e);
break;
}
}
assert_eq!(err, Some(CuckooError::NotEnoughSpace));
}
#[test]
fn victim_is_rehomed_once_a_delete_frees_a_slot() {
let mut cf = CuckooFilter::with_capacity(1);
let mut accepted = Vec::new();
for i in 0..4096u32 {
let key = format!("k{i}");
if cf.insert(&key) {
accepted.push(key);
} else {
break;
}
}
assert!(!cf.insert("blocked"));
assert!(cf.delete(&accepted[0]));
assert!(cf.insert("blocked"));
assert!(cf.contains("blocked"));
}
#[test]
fn clear_resets_to_empty_and_keeps_geometry() {
let mut cf = CuckooFilter::with_capacity(1000);
let buckets = cf.bucket_count();
for i in 0..500u32 {
cf.insert(&format!("k{i}"));
}
cf.clear();
assert!(cf.is_empty());
assert_eq!(cf.bucket_count(), buckets);
assert_eq!(cf.load_factor(), 0.0);
assert!(!cf.contains("k1"));
assert!(cf.insert("after-clear"));
}
#[test]
fn byte_and_str_apis_agree() {
let mut cf = CuckooFilter::with_capacity(100);
assert!(cf.insert_bytes(b"ORD-7"));
assert!(cf.contains("ORD-7"));
assert!(cf.contains_bytes(b"ORD-7"));
assert!(cf.delete_bytes(b"ORD-7"));
assert!(!cf.contains("ORD-7"));
assert!(cf.insert_bytes(&[0xff, 0x00, 0xfe]));
assert!(cf.contains_bytes(&[0xff, 0x00, 0xfe]));
}
#[test]
fn capacity_load_factor_and_size_track_occupancy() {
let mut cf = CuckooFilter::with_capacity(1000);
assert_eq!(cf.capacity(), cf.bucket_count() * BUCKET_SIZE);
assert_eq!(cf.size_in_bytes(), cf.bucket_count() * BUCKET_SIZE);
assert_eq!(cf.load_factor(), 0.0);
for i in 0..256u32 {
cf.insert(&format!("k{i}"));
}
let expected = 256.0 / cf.capacity() as f64;
assert!((cf.load_factor() - expected).abs() < 1e-12);
}
#[test]
fn estimated_fpp_rises_with_load_and_matches_the_closed_form() {
let mut cf = CuckooFilter::with_capacity(1000);
assert_eq!(cf.estimated_fpp(), 0.0);
for i in 0..400u32 {
cf.insert(&format!("k{i}"));
}
let low = cf.estimated_fpp();
for i in 400..900u32 {
cf.insert(&format!("k{i}"));
}
let high = cf.estimated_fpp();
assert!(high > low, "fpp should rise with occupancy");
let alpha = cf.load_factor();
let expected = 1.0 - (1.0 - 1.0 / 256.0f64).powf(2.0 * BUCKET_SIZE as f64 * alpha);
assert!((high - expected).abs() < 1e-12);
assert!(high < 0.04, "fpp {high} above the 8-bit ceiling");
}
#[test]
fn union_merges_a_second_filter() {
let mut a = CuckooFilter::with_capacity(1000);
let mut b = CuckooFilter::with_capacity(1000);
for i in 0..200u32 {
a.insert(&format!("a{i}"));
b.insert(&format!("b{i}"));
}
a.union(&b).expect("same geometry, plenty of room");
for i in 0..200u32 {
assert!(a.contains(&format!("a{i}")));
assert!(a.contains(&format!("b{i}")), "b{i} lost in the merge");
}
assert_eq!(a.len(), 400);
}
#[test]
fn union_refuses_a_different_geometry() {
let mut a = CuckooFilter::with_capacity(1000);
let b = CuckooFilter::with_capacity(100_000);
let err = a.union(&b).unwrap_err();
assert_eq!(
err,
CuckooError::GeometryMismatch {
lhs: a.bucket_count(),
rhs: b.bucket_count()
}
);
assert!(err.to_string().contains("incompatible cuckoo geometry"));
}
#[test]
fn union_refuses_when_the_target_is_full() {
let mut a = CuckooFilter::with_capacity(1);
let mut b = CuckooFilter::with_capacity(1);
for i in 0..64u32 {
a.insert(&format!("a{i}"));
b.insert(&format!("b{i}"));
}
assert_eq!(a.union(&b), Err(CuckooError::NotEnoughSpace));
}
#[test]
fn serialise_round_trip_preserves_membership() {
let mut cf = CuckooFilter::with_capacity(1000);
for i in 0..500u32 {
cf.insert(&format!("k{i}"));
}
let mut buf = Vec::new();
cf.write_to(&mut buf).unwrap();
assert_eq!(buf.len(), 17 + cf.bucket_count() * BUCKET_SIZE);
let reloaded = CuckooFilter::parse(&buf).unwrap();
assert_eq!(reloaded.len(), cf.len());
assert_eq!(reloaded.bucket_count(), cf.bucket_count());
for i in 0..500u32 {
assert!(reloaded.contains(&format!("k{i}")));
}
}
#[test]
fn serialise_round_trip_carries_the_victim() {
let mut cf = CuckooFilter::with_capacity(1);
let mut accepted = Vec::new();
for i in 0..4096u32 {
let key = format!("k{i}");
if cf.insert(&key) {
accepted.push(key);
} else {
break;
}
}
let mut buf = Vec::new();
cf.write_to(&mut buf).unwrap();
let reloaded = CuckooFilter::parse(&buf).unwrap();
for key in &accepted {
assert!(reloaded.contains(key), "{key} lost across serialisation");
}
}
#[test]
fn parse_rejects_malformed_input() {
assert!(CuckooFilter::parse(&[0u8; 4]).is_err());
let mut cf = CuckooFilter::with_capacity(100);
cf.insert("k");
let mut buf = Vec::new();
cf.write_to(&mut buf).unwrap();
let truncated = &buf[..buf.len() - 1];
assert!(CuckooFilter::parse(truncated).is_err());
let mut bad_geometry = buf.clone();
bad_geometry[0..4].copy_from_slice(&3u32.to_be_bytes());
assert!(CuckooFilter::parse(&bad_geometry).is_err());
let mut bad_victim = buf.clone();
bad_victim[13..17].copy_from_slice(&u32::MAX.to_be_bytes());
assert!(CuckooFilter::parse(&bad_victim).is_err());
}
#[test]
fn stress_insert_contains_delete_cycle() {
let mut cf = CuckooFilter::with_capacity(2000);
for cycle in 0..3 {
for i in 0..1000 {
cf.insert(&format!("cycle{cycle}-k{i}"));
}
for i in 0..1000 {
assert!(cf.contains(&format!("cycle{cycle}-k{i}")));
}
for i in 0..1000 {
cf.delete(&format!("cycle{cycle}-k{i}"));
}
}
assert_eq!(cf.len(), 0);
}
#[test]
fn wire_format_fixture() {
let mut cf = CuckooFilter::with_capacity(4);
for sym in ["AAPL", "MSFT", "GOOG"] {
assert!(cf.insert(sym));
}
let mut buf = Vec::new();
cf.write_to(&mut buf).unwrap();
let hex: String = buf.iter().map(|b| format!("{b:02x}")).collect();
assert_eq!(hex, "000000020000000000000003000000000098000000a81a0000");
}