use proptest::prelude::*;
use rfgrep::search_algorithms::{BoyerMoore, SimdSearch, SimpleSearch};
proptest! {
#[test]
fn test_simd_search_finds_pattern(pattern in "[a-z]{1,10}", text_prefix in "[a-z ]{0,100}", text_suffix in "[a-z ]{0,100}") {
let text = format!("{}{}{}", text_prefix, pattern, text_suffix);
let searcher = SimdSearch::new(&pattern);
let results = searcher.search(&text, &pattern);
prop_assert!(!results.is_empty(), "Pattern '{}' should be found in text", pattern);
for &pos in &results {
let found = &text[pos..pos + pattern.len()];
prop_assert_eq!(found, &pattern, "Found text should match pattern");
}
}
}
proptest! {
#[test]
fn test_boyer_moore_finds_pattern(pattern in "[a-z]{2,10}", text_prefix in "[a-z ]{0,100}", text_suffix in "[a-z ]{0,100}") {
let text = format!("{}{}{}", text_prefix, pattern, text_suffix);
let searcher = BoyerMoore::new(&pattern);
let results = searcher.search(&text, &pattern);
if !results.is_empty() {
for &pos in &results {
if pos + pattern.len() <= text.len() {
let found = &text[pos..pos + pattern.len()];
prop_assert_eq!(found, &pattern, "Found text should match pattern");
}
}
}
}
}
proptest! {
#[test]
fn test_empty_pattern_returns_empty(text in ".*") {
let searcher = SimdSearch::new("");
let results = searcher.search(&text, "");
prop_assert!(results.is_empty(), "Empty pattern should return no results");
}
}
proptest! {
#[test]
fn test_pattern_not_found(pattern in "XXXXX+", text in "[a-z ]{10,100}") {
let searcher = SimdSearch::new(&pattern);
let results = searcher.search(&text, &pattern);
prop_assert!(results.is_empty(), "Pattern '{}' should not be found in text without it", pattern);
}
}
proptest! {
#[test]
fn test_multiple_occurrences(pattern in "[a-z]{3,8}", count in 2usize..10) {
let parts: Vec<String> = (0..count + 1).map(|i| format!("filler{}", i)).collect();
let mut text = String::new();
for (i, part) in parts.iter().enumerate() {
text.push_str(part);
if i < count {
text.push(' ');
text.push_str(&pattern);
text.push(' ');
}
}
let searcher = SimdSearch::new(&pattern);
let results = searcher.search(&text, &pattern);
prop_assert_eq!(results.len(), count, "Should find exactly {} occurrences", count);
}
}
proptest! {
#[test]
fn test_algorithms_consistency(pattern in "[a-z]{2,8}", text in "[a-z ]{20,200}") {
if !text.contains(&pattern) {
return Ok(());
}
let simd = SimdSearch::new(&pattern);
let boyer_moore = BoyerMoore::new(&pattern);
let simple = SimpleSearch::new(&pattern);
let simd_results = simd.search(&text, &pattern);
let bm_results = boyer_moore.search(&text, &pattern);
let simple_results = simple.search(&text, &pattern);
prop_assert_eq!(simd_results.len(), bm_results.len(),
"SIMD and Boyer-Moore should find same number of matches");
prop_assert_eq!(simd_results.len(), simple_results.len(),
"SIMD and Simple should find same number of matches");
for i in 0..simd_results.len() {
prop_assert_eq!(simd_results[i], bm_results[i],
"Match positions should be identical");
prop_assert_eq!(simd_results[i], simple_results[i],
"Match positions should be identical");
}
}
}
proptest! {
#[test]
fn test_case_sensitivity(lower in "[a-z]{3,8}") {
let upper = lower.to_uppercase();
let text = format!(">>> {} <<< {} >>>", lower, upper);
let searcher = SimdSearch::new(&lower);
let results = searcher.search(&text, &lower);
prop_assert_eq!(results.len(), 1, "Should only find lowercase match");
}
}
proptest! {
#[test]
fn test_overlapping_patterns(base in "[a-z]{3,5}") {
let pattern = format!("{}{}", base, base);
let text = format!("{}{}{}", base, base, base);
let searcher = SimdSearch::new(&pattern);
let results = searcher.search(&text, &pattern);
prop_assert!(!results.is_empty(), "Should find pattern in overlapping text");
for &pos in &results {
if pos + pattern.len() <= text.len() {
let found = &text[pos..pos + pattern.len()];
prop_assert_eq!(found, &pattern, "Found text should match pattern");
}
}
}
}
proptest! {
#[test]
fn test_special_characters(pattern in "[a-z0-9]{1,10}") {
let text = format!("start {} middle {} end", pattern, pattern);
let searcher = SimdSearch::new(&pattern);
let results = searcher.search(&text, &pattern);
prop_assert!(results.len() >= 2, "Should find at least both explicit occurrences");
}
}
proptest! {
#[test]
fn test_boundary_search(pattern in "[a-z]{3,8}", middle in "[a-z ]{10,50}") {
let text_start = format!("{}{}", pattern, middle);
let searcher = SimdSearch::new(&pattern);
let results = searcher.search(&text_start, &pattern);
prop_assert!(!results.is_empty(), "Should find pattern at start");
prop_assert_eq!(results[0], 0, "First match should be at position 0");
let text_end = format!("{}{}", middle, pattern);
let results = searcher.search(&text_end, &pattern);
prop_assert!(!results.is_empty(), "Should find pattern at end");
}
}
proptest! {
#[test]
fn test_large_text_handling(pattern in "[a-z]{4,8}") {
let mut text = String::new();
for i in 0..1000 {
text.push_str(&format!("filler{} ", i));
if i % 100 == 0 {
text.push_str(&pattern);
text.push(' ');
}
}
let searcher = SimdSearch::new(&pattern);
let results = searcher.search(&text, &pattern);
prop_assert!(results.len() >= 9 && results.len() <= 11,
"Should find around 10 occurrences in large text");
}
}
proptest! {
#[test]
fn test_unicode_patterns(pattern in "[a-zA-Z0-9]{3,8}") {
let text = format!("Hello {} World {} 你好", pattern, pattern);
let searcher = SimdSearch::new(&pattern);
let results = searcher.search(&text, &pattern);
prop_assert_eq!(results.len(), 2, "Should find both ASCII pattern occurrences");
}
}
proptest! {
#[test]
fn test_repeated_patterns(pattern in "[a-z]{2,4}", repeats in 3usize..20) {
let text = pattern.repeat(repeats);
let searcher = SimdSearch::new(&pattern);
let results = searcher.search(&text, &pattern);
prop_assert!(results.len() >= repeats - 1,
"Should find multiple occurrences of repeated pattern");
}
}
proptest! {
#[test]
fn test_single_char_pattern(text in "[a-z ]{20,100}") {
let pattern = "x";
let text_with_pattern = format!("{}{}{}", text, pattern, text);
let searcher = SimdSearch::new(pattern);
let results = searcher.search(&text_with_pattern, pattern);
prop_assert!(!results.is_empty(), "Should find single character pattern");
}
}
proptest! {
#[test]
fn test_pattern_longer_than_text(short_text in "[a-z]{1,5}") {
let long_pattern = "thisisaverylongpattern";
let searcher = SimdSearch::new(long_pattern);
let results = searcher.search(&short_text, long_pattern);
prop_assert!(results.is_empty(), "Should not find pattern longer than text");
}
}