use regexr::{Regex, RegexBuilder};
use std::sync::Arc;
const THREADS: usize = 16;
const PASSES: usize = 5;
const HAYSTACK_BYTES: usize = 200 * 1024;
fn haystack() -> String {
const WORDS: &[&str] = &[
"the", "quick", "brown", "fox", "jumps", "over", "lazy", "dog", "and", "then", "runs",
"through", "forest", "near", "river", "where", "birds", "sing", "their", "morning",
];
let mut text = String::with_capacity(HAYSTACK_BYTES);
let mut index = 0usize;
while text.len() < HAYSTACK_BYTES {
text.push_str(WORDS[index % WORDS.len()]);
text.push(if index % 7 == 0 { '\n' } else { ' ' });
index += 1;
}
text
}
fn spans(re: &Regex, text: &str) -> Vec<(usize, usize)> {
re.find_iter(text).map(|m| (m.start(), m.end())).collect()
}
fn capture_spans(re: &Regex, text: &str) -> Vec<(usize, usize)> {
re.captures_iter(text)
.map(|c| {
let m = c.get(0).expect("group 0 is always set on a match");
(m.start(), m.end())
})
.collect()
}
fn lazy_dfa_regex(pattern: &str) -> Regex {
let re = RegexBuilder::new(pattern)
.jit(false)
.build()
.expect("pattern should compile");
assert_eq!(
re.engine_name(),
"LazyDfa",
"{pattern}: this test is only meaningful on the lazy DFA"
);
re
}
fn assert_agrees_across_threads(pattern: &'static str) {
let re = lazy_dfa_regex(pattern);
let text = Arc::new(haystack());
let expected = Arc::new(spans(&re, &text));
let expected_captures = Arc::new(capture_spans(&re, &text));
assert!(
!expected.is_empty(),
"{pattern}: expected the pattern to match the haystack"
);
let re = Arc::new(re);
let handles: Vec<_> = (0..THREADS)
.map(|thread| {
let re = Arc::clone(&re);
let text = Arc::clone(&text);
let expected = Arc::clone(&expected);
let expected_captures = Arc::clone(&expected_captures);
std::thread::spawn(move || {
for pass in 0..PASSES {
assert_eq!(
spans(&re, &text),
*expected,
"{pattern}: thread {thread} pass {pass} disagreed with the \
single-threaded run"
);
assert_eq!(
capture_spans(&re, &text),
*expected_captures,
"{pattern}: thread {thread} pass {pass} disagreed with the \
single-threaded captures_iter run"
);
}
})
})
.collect();
for handle in handles {
handle.join().expect("thread panicked");
}
}
#[test]
fn a_shared_lazy_dfa_agrees_across_threads() {
assert_agrees_across_threads(r"(?m)^\w+");
}
#[test]
fn a_shared_lazy_dfa_agrees_across_threads_on_a_longer_pattern() {
assert_agrees_across_threads(r"(?m)^\w+\s\w+");
}
#[test]
fn an_abandoned_iterator_returns_its_instance() {
const ABANDONED: usize = 32;
let re = lazy_dfa_regex(r"(?m)^\w+");
let text = haystack();
let expected = spans(&re, &text);
let expected_captures = capture_spans(&re, &text);
assert!(
expected.len() > 3 && expected_captures.len() > 3,
"expected the pattern to match the haystack more than three times"
);
for _ in 0..ABANDONED {
let partial: Vec<_> = re
.find_iter(&text)
.take(3)
.map(|m| (m.start(), m.end()))
.collect();
assert_eq!(partial, expected[..3].to_vec(), "a partial run diverged");
drop(re.find_iter(&text));
let partial: Vec<_> = re
.captures_iter(&text)
.take(3)
.map(|c| {
let m = c.get(0).expect("group 0 is always set on a match");
(m.start(), m.end())
})
.collect();
assert_eq!(
partial,
expected_captures[..3].to_vec(),
"a partial capture run diverged"
);
assert_eq!(spans(&re, &text), expected, "a full run diverged");
}
assert_eq!(capture_spans(&re, &text), expected_captures);
}
#[test]
fn abandoned_iterators_do_not_disturb_other_threads() {
let re = Arc::new(lazy_dfa_regex(r"(?m)^\w+\s\w+"));
let text = Arc::new(haystack());
let expected = Arc::new(spans(&re, &text));
assert!(
expected.len() >= 4,
"expected the pattern to match the haystack at least four times"
);
let handles: Vec<_> = (0..THREADS)
.map(|thread| {
let re = Arc::clone(&re);
let text = Arc::clone(&text);
let expected = Arc::clone(&expected);
std::thread::spawn(move || {
for pass in 0..PASSES {
for take in 0..4 {
let partial: Vec<_> = re
.find_iter(&text)
.take(take)
.map(|m| (m.start(), m.end()))
.collect();
assert_eq!(
partial,
expected[..take].to_vec(),
"thread {thread} pass {pass} take {take} diverged"
);
}
assert_eq!(
spans(&re, &text),
*expected,
"thread {thread} pass {pass} diverged after abandoning iterators"
);
}
})
})
.collect();
for handle in handles {
handle.join().expect("thread panicked");
}
}