#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::unreadable_literal,
clippy::panic,
clippy::manual_let_else
)]
use proptest::prelude::*;
use simdsieve::SimdSieve;
proptest! {
#[test]
fn no_false_positives(
haystack in proptest::collection::vec(0u8..=255, 0..=1024),
pattern in proptest::collection::vec(0u8..=255, 1..=8),
) {
prop_assume!(!pattern.is_empty());
let pat_slice: &[u8] = &pattern;
let sieve = SimdSieve::new(&haystack, &[pat_slice]).unwrap();
for pos in sieve {
prop_assert!(
pos + pattern.len() <= haystack.len(),
"Position {} exceeds haystack bounds (len={})",
pos, haystack.len()
);
prop_assert_eq!(
&haystack[pos..pos + pattern.len()],
&pattern[..],
"False positive at position {}",
pos
);
}
}
}
fn brute_force_find(haystack: &[u8], pattern: &[u8]) -> Vec<usize> {
let mut results = Vec::new();
for i in 0..haystack.len().saturating_sub(pattern.len() - 1) {
if &haystack[i..i + pattern.len()] == pattern {
results.push(i);
}
}
results
}
proptest! {
#[test]
fn no_false_negatives_short(
haystack in proptest::collection::vec(0u8..=255, 0..=256),
pattern in proptest::collection::vec(0u8..=255, 1..=8),
) {
prop_assume!(!pattern.is_empty());
let pat_slice: &[u8] = &pattern;
let sieve = SimdSieve::new(&haystack, &[pat_slice]).unwrap();
let sieve_results: Vec<_> = sieve.collect();
let brute_results = brute_force_find(&haystack, &pattern);
prop_assert!(
sieve_results == brute_results,
"Mismatch: sieve found {:?}, brute found {:?}",
sieve_results, brute_results
);
}
}
fn brute_force_find_ci(haystack: &[u8], pattern: &[u8]) -> Vec<usize> {
let mut results = Vec::new();
for i in 0..haystack.len().saturating_sub(pattern.len() - 1) {
let candidate = &haystack[i..i + pattern.len()];
let matches = candidate
.iter()
.zip(pattern.iter())
.all(|(&c, &p)| c.eq_ignore_ascii_case(&p));
if matches {
results.push(i);
}
}
results
}
proptest! {
#[test]
fn case_insensitive_parity(
haystack_bytes in proptest::collection::vec(0u8..=127u8, 0..=256),
pattern_bytes in proptest::collection::vec(b'a'..=b'z', 1..=8),
) {
let haystack: Vec<u8> = haystack_bytes
.iter()
.map(|&b| b.to_ascii_lowercase())
.collect();
let pattern: Vec<u8> = pattern_bytes.iter().map(|&b| b.to_ascii_lowercase()).collect();
let pat_slice: &[u8] = &pattern;
let ci_sieve = SimdSieve::new_case_insensitive(&haystack, &[pat_slice]).unwrap();
let ci_results: Vec<_> = ci_sieve.collect();
let exact_sieve = SimdSieve::new(&haystack, &[pat_slice]).unwrap();
let exact_results: Vec<_> = exact_sieve.collect();
prop_assert_eq!(
ci_results, exact_results,
"CI and exact should match when haystack is already lowercase"
);
}
}
proptest! {
#[test]
fn case_insensitive_vs_brute(
haystack in proptest::collection::vec(0u8..=127u8, 0..=256),
pattern in proptest::collection::vec(0u8..=127u8, 1..=8),
) {
prop_assume!(!pattern.is_empty());
let pat_slice: &[u8] = &pattern;
let sieve = SimdSieve::new_case_insensitive(&haystack, &[pat_slice]).unwrap();
let sieve_results: Vec<_> = sieve.collect();
let brute_results = brute_force_find_ci(&haystack, &pattern);
prop_assert!(
sieve_results == brute_results,
"CI mismatch: sieve found {:?}, brute found {:?}",
sieve_results, brute_results
);
}
}
proptest! {
#[test]
fn fused_iterator(
haystack in proptest::collection::vec(0u8..=255, 0..=256),
pattern in proptest::collection::vec(0u8..=255, 1..=8),
) {
prop_assume!(!pattern.is_empty());
let pat_slice: &[u8] = &pattern;
let mut sieve = SimdSieve::new(&haystack, &[pat_slice]).unwrap();
while sieve.next().is_some() {}
prop_assert!(sieve.next().is_none());
prop_assert!(sieve.next().is_none());
prop_assert!(sieve.next().is_none());
}
}
proptest! {
#[test]
fn multi_pattern_consistency(
haystack in proptest::collection::vec(b'a'..=b'z', 0..=256),
pat1 in proptest::collection::vec(b'a'..=b'z', 1..=4),
pat2 in proptest::collection::vec(b'a'..=b'z', 1..=4),
) {
prop_assume!(!pat1.is_empty() && !pat2.is_empty());
let pat1_slice: &[u8] = &pat1;
let pat2_slice: &[u8] = &pat2;
let sieve1 = SimdSieve::new(&haystack, &[pat1_slice]).unwrap();
let results1: std::collections::HashSet<_> = sieve1.collect();
let sieve2 = SimdSieve::new(&haystack, &[pat2_slice]).unwrap();
let results2: std::collections::HashSet<_> = sieve2.collect();
let multi_sieve = SimdSieve::new(&haystack, &[pat1_slice, pat2_slice]).unwrap();
let multi_results: std::collections::HashSet<_> = multi_sieve.collect();
let expected: std::collections::HashSet<_> =
results1.union(&results2).copied().collect();
prop_assert_eq!(
multi_results, expected,
"Multi-pattern results don't match union of singles"
);
}
}
proptest! {
#[test]
fn results_in_ascending_order(
haystack in proptest::collection::vec(b'a'..=b'z', 0..=512),
patterns in proptest::collection::vec(proptest::collection::vec(b'a'..=b'z', 1..=4), 1..=8),
) {
prop_assume!(!patterns.is_empty());
let pattern_refs: Vec<&[u8]> = patterns.iter().map(std::vec::Vec::as_slice).collect();
let sieve = SimdSieve::new(&haystack, &pattern_refs).unwrap();
let results: Vec<_> = sieve.collect();
for i in 1..results.len() {
prop_assert!(
results[i] > results[i - 1],
"Results not in ascending order: {:?}",
results
);
}
}
}
proptest! {
#[test]
fn deterministic_results(
haystack in proptest::collection::vec(0u8..=255, 0..=256),
patterns in proptest::collection::vec(proptest::collection::vec(0u8..=255, 1..=4), 1..=4),
) {
prop_assume!(!patterns.is_empty());
let pattern_refs: Vec<&[u8]> = patterns.iter().map(std::vec::Vec::as_slice).collect();
let sieve1 = SimdSieve::new(&haystack, &pattern_refs).unwrap();
let results1: Vec<_> = sieve1.collect();
let sieve2 = SimdSieve::new(&haystack, &pattern_refs).unwrap();
let results2: Vec<_> = sieve2.collect();
prop_assert_eq!(results1, results2, "Non-deterministic results");
}
}
proptest! {
#[test]
fn empty_pattern_set_errors(haystack in proptest::collection::vec(0u8..=255, 0..=256)) {
let result = SimdSieve::new(&haystack, &[]);
prop_assert!(result.is_err());
}
}
proptest! {
#[test]
fn too_many_patterns_errors(
haystack in proptest::collection::vec(0u8..=255, 0..=256),
patterns in proptest::collection::vec(proptest::collection::vec(0u8..=255, 1..=4), 17..=24),
) {
let pattern_refs: Vec<&[u8]> = patterns.iter().map(std::vec::Vec::as_slice).collect();
let result = SimdSieve::new(&haystack, &pattern_refs);
prop_assert!(result.is_err());
}
}