#![allow(clippy::pedantic)]
#![allow(clippy::cast_precision_loss, clippy::doc_markdown, clippy::explicit_iter_loop, clippy::uninlined_format_args, clippy::unreadable_literal)]
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use flashsieve::{
BlockIndexBuilder, ByteFilter, ByteHistogram, FileBloomIndex, IncrementalBuilder,
NgramBloom, NgramFilter,
};
#[test]
fn empty_data_yields_zero_blocks() {
let index = BlockIndexBuilder::new().block_size(256).build(b"").unwrap();
assert_eq!(index.block_count(), 0);
assert_eq!(index.total_data_length(), 0);
}
#[test]
fn single_byte_block() {
let index = BlockIndexBuilder::new().block_size(256).build(b"x").unwrap();
assert_eq!(index.block_count(), 1);
let bf = ByteFilter::from_patterns(&[b"x".as_slice()]);
let nf = NgramFilter::from_patterns(&[b"x".as_slice()]);
let candidates = index.candidate_blocks(&bf, &nf);
assert_eq!(candidates.len(), 1);
}
#[test]
fn all_identical_bytes() {
let data = vec![b'a'; 1024];
let index = BlockIndexBuilder::new().block_size(256).build(&data).unwrap();
assert_eq!(index.block_count(), 4);
let bf = ByteFilter::from_patterns(&[b"aa".as_slice()]);
let nf = NgramFilter::from_patterns(&[b"aa".as_slice()]);
let candidates = index.candidate_blocks(&bf, &nf);
assert_eq!(candidates.len(), 1);
assert_eq!(candidates[0].length, 1024);
}
#[test]
fn pattern_longer_than_block_size() {
let block_size = 256;
let pattern: Vec<u8> = (0_usize..300).map(|i| b'a' + (i % 26) as u8).collect();
assert!(pattern.len() > block_size);
let mut data = vec![b'x'; block_size * 4];
data[block_size + 10..block_size + 10 + pattern.len()].copy_from_slice(&pattern);
let index = BlockIndexBuilder::new()
.block_size(block_size)
.bloom_bits(1024)
.build(&data)
.unwrap();
let bf = ByteFilter::from_patterns(&[pattern.as_slice()]);
let nf = NgramFilter::from_patterns(&[pattern.as_slice()]);
let candidates = index.candidate_blocks(&bf, &nf);
assert!(!candidates.is_empty(), "long pattern should produce candidates");
}
#[test]
fn empty_patterns_never_match() {
let index = BlockIndexBuilder::new()
.block_size(256)
.build(b"hello world")
.unwrap();
let bf = ByteFilter::from_patterns(&[]);
let nf = NgramFilter::from_patterns(&[]);
let candidates = index.candidate_blocks(&bf, &nf);
assert!(candidates.is_empty());
}
#[test]
fn minimum_block_size() {
let data = vec![0u8; 256];
let index = BlockIndexBuilder::new().block_size(256).build(&data).unwrap();
assert_eq!(index.block_count(), 1);
}
#[test]
fn unaligned_data_length() {
let data = vec![0u8; 513]; let index = BlockIndexBuilder::new().block_size(256).build(&data).unwrap();
assert_eq!(index.block_count(), 3);
assert_eq!(index.total_data_length(), 513);
}
#[test]
fn histogram_saturates_u32_max() {
let data = vec![b'a'; 1024];
let hist = ByteHistogram::from_block(&data);
assert_eq!(hist.count(b'a'), 1024);
}
#[test]
fn file_bloom_requires_at_least_one_block() {
let index = BlockIndexBuilder::new().block_size(256).build(b"").unwrap();
let result = FileBloomIndex::try_new(index);
assert!(matches!(
result,
Err(flashsieve::Error::EmptyBlockIndex)
));
}
#[test]
fn incremental_append_to_unaligned_index_fails() {
let index = BlockIndexBuilder::new()
.block_size(256)
.bloom_bits(1024)
.build(b"hello") .unwrap();
let serialized = index.to_bytes();
let result = IncrementalBuilder::append_blocks(&serialized, &[vec![b'x'; 256].as_slice()]);
assert!(result.is_err(), "append to unaligned index should fail");
}
#[test]
fn remove_non_suffix_block_errors() {
let mut index = BlockIndexBuilder::new()
.block_size(256)
.bloom_bits(1024)
.build(&[0u8; 512])
.unwrap();
let result = index.remove_blocks(&[0]);
assert!(matches!(
result,
Err(flashsieve::Error::NonSuffixBlockRemoval)
));
}
#[test]
fn remove_suffix_block_succeeds() {
let mut index = BlockIndexBuilder::new()
.block_size(256)
.bloom_bits(1024)
.build(&[0u8; 512])
.unwrap();
index.remove_blocks(&[1]).unwrap();
assert_eq!(index.block_count(), 1);
assert_eq!(index.total_data_length(), 256);
}
#[test]
fn invalid_fpr_values_are_rejected() {
for bad in [0.0_f64, 1.0, f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
let result = NgramBloom::with_target_fpr(bad, 100);
assert!(
result.is_err(),
"FPR {bad} is invalid and should be rejected"
);
}
}