use dfajit::{JitDfa, TransitionTable};
use matchkit::Match;
use rusty_fork::rusty_fork_test;
use std::sync::Arc;
use std::thread;
#[test]
fn test_01_empty_input_scan() {
let table = TransitionTable::new(2, 256).unwrap_or_else(|_| panic!("error"));
let jit = JitDfa::compile(&table).unwrap_or_else(|_| panic!("error"));
let mut matches = vec![Match::from_parts(0, 0, 0); 10];
let count = jit.scan(b"", &mut matches);
assert_eq!(count, 0, "Empty input should return 0 matches");
}
#[test]
fn test_02_empty_patterns_list() {
let result = JitDfa::from_patterns(&[]);
assert!(result.is_err(), "Empty pattern list should fail gracefully");
}
#[test]
fn test_03_zero_length_slice_pattern() {
let jit = JitDfa::from_patterns(&[b""]).unwrap_or_else(|_| panic!("error"));
let mut matches = vec![Match::from_parts(0, 0, 0); 10];
let count = jit.scan(b"x", &mut matches);
assert_eq!(count, 0, "Zero length slice pattern should not panic and shouldn't match if it's considered empty string.");
}
#[test]
fn test_04_null_bytes_in_input() {
let mut table = TransitionTable::new(3, 256).unwrap_or_else(|_| panic!("error"));
table.set_transition(0, b'\0', 1);
table.set_transition(1, b'\0', 2);
table.add_accept(2, 0);
table.set_pattern_length(0, 2);
let jit = JitDfa::compile(&table).unwrap_or_else(|_| panic!("error"));
let mut matches = vec![Match::from_parts(0, 0, 0); 10];
let count = jit.scan(b"\0\0x\0\0", &mut matches);
assert_eq!(count, 2, "Should match null byte patterns properly");
}
#[test]
fn test_05_null_bytes_in_regex() {
#[cfg(feature = "regex")]
{
let jit = JitDfa::from_regex_patterns(&["\\x00{2}"]).unwrap_or_else(|_| panic!("error"));
let mut matches = vec![Match::from_parts(0, 0, 0); 10];
let count = jit.scan(b"a\0\0b", &mut matches);
assert_eq!(count, 1, "Should correctly match regex with null bytes");
}
}
rusty_fork_test! {
#[test]
fn test_06_max_u32_state_count() {
let result = TransitionTable::new(u32::MAX as usize, 256);
assert!(result.is_err(), "Max u32 state count should fail memory allocation or sizing gracefully, not panic");
}
#[test]
fn test_07_max_u32_class_count() {
let result = TransitionTable::new(2, u32::MAX as usize);
assert!(result.is_err(), "Max u32 class count should fail sizing gracefully, not panic");
}
}
#[test]
fn test_08_max_u32_pattern_length() {
let mut table = TransitionTable::new(2, 256).unwrap_or_else(|_| panic!("error"));
table.set_transition(0, b'a', 1);
table.add_accept(1, 0);
table.set_pattern_length(0, u32::MAX);
let jit = JitDfa::compile(&table).unwrap_or_else(|_| panic!("error"));
let mut matches = vec![Match::from_parts(0, 0, 0); 1];
let _ = jit.scan(b"a", &mut matches);
}
#[test]
fn test_09_one_megabyte_input() {
let jit = JitDfa::from_patterns(&[b"x"]).unwrap_or_else(|_| panic!("error"));
let mut matches = vec![Match::from_parts(0, 0, 0); 1];
let large_input = vec![b'a'; 1024 * 1024 + 1];
let count = jit.scan(&large_input, &mut matches);
assert_eq!(
count, 0,
"Should handle 1MB+ input gracefully when no match"
);
}
#[test]
fn test_10_one_megabyte_input_many_matches() {
let jit = JitDfa::from_patterns(&[b"a"]).unwrap_or_else(|_| panic!("error"));
let mut matches = vec![Match::from_parts(0, 0, 0); 10];
let large_input = vec![b'a'; 1024 * 1024];
let count = jit.scan(&large_input, &mut matches);
assert!(count > 0, "Should find matches in 1MB+ input");
}
#[test]
fn test_11_concurrent_scanning() {
let mut table = TransitionTable::new(3, 256).unwrap_or_else(|_| panic!("error"));
table.set_transition(0, b'x', 1);
table.set_transition(1, b'y', 2);
table.add_accept(2, 0);
table.set_pattern_length(0, 2);
let jit = Arc::new(JitDfa::compile(&table).unwrap_or_else(|_| panic!("error")));
let mut handles = vec![];
for i in 0..8 {
let jit_clone = jit.clone();
handles.push(thread::spawn(move || {
let mut matches = vec![Match::from_parts(0, 0, 0); 5];
let payload = vec![b'x', b'y', b'z'];
for _ in 0..100 {
let count = jit_clone.scan(&payload, &mut matches);
assert_eq!(count, 1, "Thread {} failed to match correctly", i);
}
}));
}
for handle in handles {
handle.join().unwrap_or_else(|_| panic!("error"));
}
}
#[test]
fn test_12_concurrent_has_match() {
let jit =
Arc::new(JitDfa::from_patterns(&[b"foo", b"bar"]).unwrap_or_else(|_| panic!("error")));
let mut handles = vec![];
for _ in 0..8 {
let jit_clone = jit.clone();
handles.push(thread::spawn(move || {
for _ in 0..100 {
assert!(jit_clone.has_match(b"hello foo world"));
assert!(!jit_clone.has_match(b"no match here"));
}
}));
}
for handle in handles {
handle.join().unwrap_or_else(|_| panic!("error"));
}
}
#[test]
fn test_13_from_bytes_empty() {
let result = TransitionTable::from_bytes(&[]);
assert!(result.is_err());
}
#[test]
fn test_14_from_bytes_truncated_header() {
let result = TransitionTable::from_bytes(&[1, 0, 0, 0, 2, 0, 0]); assert!(result.is_err());
}
#[test]
fn test_15_from_bytes_truncated_transitions() {
let mut data = vec![1, 0, 0, 0, 0, 1, 0, 0]; data.extend_from_slice(&[0; 1000]); let result = TransitionTable::from_bytes(&data);
assert!(result.is_err());
}
#[test]
fn test_16_from_bytes_malformed_accepts() {
let mut table = TransitionTable::new(2, 256).unwrap_or_else(|_| panic!("error"));
table.add_accept(1, 0);
let mut data = table.to_bytes();
data.pop(); let result = TransitionTable::from_bytes(&data);
assert!(result.is_err());
}
#[test]
fn test_17_from_bytes_out_of_bounds_target_state() {
let mut table = TransitionTable::new(2, 256).unwrap_or_else(|_| panic!("error"));
table.set_transition(0, b'a', 9999);
let _ = JitDfa::compile(&table);
}
#[test]
fn test_18_unicode_bom_pattern() {
let jit = JitDfa::from_patterns(&[b"\xef\xbb\xbf"]).unwrap_or_else(|_| panic!("error"));
let mut matches = vec![Match::from_parts(0, 0, 0); 1];
let count = jit.scan(b"start\xef\xbb\xbfend", &mut matches);
assert_eq!(count, 1);
}
rusty_fork_test! {
#[test]
fn test_19_invalid_utf8_sequence_matching() {
let jit = JitDfa::from_patterns(&[b"\xff"]).unwrap_or_else(|_| panic!("error"));
let mut matches = vec![Match::from_parts(0, 0, 0); 2];
let count = jit.scan(b"\xff\xff", &mut matches);
assert_eq!(count, 2);
}
}
#[test]
fn test_20_surrogate_halves_in_regex() {
#[cfg(feature = "regex")]
{
let jit = JitDfa::from_regex_patterns(&["\\x{D800}"]);
let _ = jit;
}
}
#[test]
fn test_21_duplicate_patterns() {
let jit = JitDfa::from_patterns(&[b"dup", b"dup"]).unwrap_or_else(|_| panic!("error"));
let mut matches = vec![Match::from_parts(0, 0, 0); 10];
let count = jit.scan(b"dup", &mut matches);
assert!(count >= 1);
}
rusty_fork_test! {
#[test]
fn test_22_duplicate_accepts_in_table() {
let mut table = TransitionTable::new(2, 256).unwrap_or_else(|_| panic!("error"));
table.set_transition(0, b'a', 1);
table.add_accept(1, 0);
table.add_accept(1, 0); table.set_pattern_length(0, 1);
let jit = JitDfa::compile(&table);
if let Ok(compiled) = jit {
let mut matches = vec![Match::from_parts(0, 0, 0); 10];
let count = compiled.scan(b"a", &mut matches);
assert!(count > 0);
} else {
assert!(jit.is_err());
}
}
}
#[test]
fn test_23_off_by_one_start_boundary() {
let jit = JitDfa::from_patterns(&[b"A"]).unwrap_or_else(|_| panic!("error"));
let mut matches = vec![Match::from_parts(0, 0, 0); 1];
let count = jit.scan(b"A ", &mut matches);
assert_eq!(count, 1);
assert_eq!(matches[0].start, 0);
assert_eq!(matches[0].end, 1);
}
#[test]
fn test_24_off_by_one_end_boundary() {
let jit = JitDfa::from_patterns(&[b"B"]).unwrap_or_else(|_| panic!("error"));
let mut matches = vec![Match::from_parts(0, 0, 0); 1];
let count = jit.scan(b" B", &mut matches);
assert_eq!(count, 1);
assert_eq!(matches[0].start, 1);
assert_eq!(matches[0].end, 2);
}
#[test]
fn test_25_single_byte_input() {
let jit = JitDfa::from_patterns(&[b"C"]).unwrap_or_else(|_| panic!("error"));
let mut matches = vec![Match::from_parts(0, 0, 0); 1];
let count = jit.scan(b"C", &mut matches);
assert_eq!(count, 1);
assert_eq!(matches[0].start, 0);
assert_eq!(matches[0].end, 1);
}
#[test]
fn test_26_many_distinct_patterns() {
let mut patterns = Vec::new();
let mut strings = Vec::new();
for i in 0..10_000 {
strings.push(format!("{:08}", i));
}
for s in &strings {
patterns.push(s.as_bytes());
}
let jit = JitDfa::from_patterns(&patterns);
assert!(
jit.is_ok(),
"Engine should support building large pattern sets"
);
if let Ok(dfa) = jit {
let mut matches = vec![Match::from_parts(0, 0, 0); 1];
let count = dfa.scan(b"00009999", &mut matches);
assert_eq!(count, 1);
}
}
rusty_fork_test! {
#[test]
fn test_27_deeply_nested_structures() {
let pattern = vec![b'x'; 10_000];
let jit = JitDfa::from_patterns(&[&pattern]);
assert!(jit.is_ok(), "Engine should support deep/long patterns gracefully");
if let Ok(dfa) = jit {
let mut matches = vec![Match::from_parts(0, 0, 0); 2];
let input = vec![b'x'; 10_001];
let count = dfa.scan(&input, &mut matches);
assert!(count >= 1);
}
}
}
rusty_fork_test! {
#[test]
fn test_28_memory_exhaustion_massive_table() {
let state_count = 10_000_000;
let table = TransitionTable::new(state_count, 256);
assert!(table.is_err(), "Engine should return error for overly large tables");
}
}
#[test]
fn test_29_jit_allocation_failure() {
let table = TransitionTable::new(5000, 256).unwrap_or_else(|_| panic!("error"));
let jit = JitDfa::compile(&table).unwrap_or_else(|_| panic!("error"));
let mut matches = vec![Match::from_parts(0, 0, 0); 1];
jit.scan(b"xyz", &mut matches);
}
#[test]
fn test_30_minimize_complex_table() {
let mut table = TransitionTable::new(100, 256).unwrap_or_else(|_| panic!("error"));
for i in 0..99 {
table.set_transition(i, b'a', i as u32 + 1);
}
table.set_transition(99, b'a', 0);
table.add_accept(50, 0);
table.set_pattern_length(0, 50);
let minimized = table.minimize();
assert!(minimized.is_none() || minimized.is_some());
}
rusty_fork_test! {
#[test]
fn test_31_from_bytes_integer_overflows() {
let mut data = vec![0; 8];
data[0..4].copy_from_slice(&u32::MAX.to_le_bytes());
data[4..8].copy_from_slice(&u32::MAX.to_le_bytes());
let result = TransitionTable::from_bytes(&data);
assert!(result.is_err(), "Engine should return an error on integer overflow for table size");
}
#[test]
fn test_32_jit_code_size_estimation_overflow() {
let mut table = TransitionTable::new(2, 256).unwrap_or_else(|_| panic!("error"));
table.set_transition(0, b'a', 1);
table.add_accept(1, 0);
let jit = JitDfa::compile(&table);
assert!(jit.is_ok());
}
#[test]
fn test_33_invalid_regex_parsing() {
#[cfg(feature = "regex")]
{
let result = JitDfa::from_regex_patterns(&["(unclosed", "[invalid"]);
assert!(result.is_err(), "Engine should gracefully reject malformed regex");
}
}
#[test]
fn test_34_input_longer_than_u32_max() {
let mut table = TransitionTable::new(2, 256).unwrap();
table.set_transition(0, b'a', 1);
table.add_accept(1, 0);
table.set_pattern_length(0, 1);
let jit = JitDfa::compile(&table).unwrap();
let mut matches = vec![Match::from_parts(0, 0, 0); 1];
let count = jit.scan(b"a", &mut matches);
assert_eq!(count, 1);
}
}