use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Barrier, LazyLock};
use irgx::{Regex, RegexBuilder};
static WORD: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"[a-z]+").unwrap());
static GROUPED: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"(?P<key>\w+)=(?P<value>\d+)").unwrap());
#[test]
fn regex_is_send_and_sync() {
fn require<T: Send + Sync + 'static>() {}
require::<Regex>();
require::<irgx::RegexBuilder>();
require::<irgx::Error>();
fn require_send<T: Send>() {}
require_send::<irgx::Match<'static>>();
require_send::<irgx::Matches<'static>>();
}
#[test]
fn many_threads_one_static() {
const THREADS: usize = 16;
const ROUNDS: usize = 200;
let gate = Arc::new(Barrier::new(THREADS));
let mut crew = Vec::new();
for id in 0..THREADS {
let gate = Arc::clone(&gate);
crew.push(std::thread::spawn(move || {
let text = format!("{}alpha beta{} gamma", " ".repeat(id), "x".repeat(id));
let want: Vec<(usize, usize)> = Regex::new(r"[a-z]+")
.unwrap()
.find_iter(&text)
.map(|m| (m.start(), m.end()))
.collect();
gate.wait();
for _ in 0..ROUNDS {
let got: Vec<(usize, usize)> = WORD
.find_iter(&text)
.map(|m| (m.start(), m.end()))
.collect();
assert_eq!(got, want, "thread {id}");
for found in WORD.find_iter(&text) {
assert_eq!(found.as_str(), &text[found.range()]);
}
}
}));
}
for worker in crew {
worker.join().expect("no thread should panic");
}
}
#[test]
fn many_threads_reading_groups() {
const THREADS: usize = 12;
let counted = Arc::new(AtomicUsize::new(0));
let gate = Arc::new(Barrier::new(THREADS));
let mut crew = Vec::new();
for id in 0..THREADS {
let counted = Arc::clone(&counted);
let gate = Arc::clone(&gate);
crew.push(std::thread::spawn(move || {
let text: String = (0..8).map(|n| format!("k{id}n{n}={n}{id} ")).collect();
gate.wait();
for _ in 0..100 {
for caps in GROUPED.captures_iter(&text) {
let key = caps.name("key").expect("key participates");
let value = caps.name("value").expect("value participates");
assert!(key.as_str().starts_with(&format!("k{id}n")));
assert!(value.as_str().ends_with(&id.to_string()));
let whole = caps.get(0).unwrap();
assert_eq!(whole.start(), key.start());
assert_eq!(whole.end(), value.end());
counted.fetch_add(1, Ordering::Relaxed);
}
}
}));
}
for worker in crew {
worker.join().expect("no thread should panic");
}
assert_eq!(counted.load(Ordering::Relaxed), THREADS * 100 * 8);
}
#[test]
fn handles_outlive_the_threads_that_used_them() {
let re = Arc::new(RegexBuilder::new("café").ignore_case(true).build().unwrap());
for round in 0..40 {
let mut crew = Vec::new();
for _ in 0..8 {
let re = Arc::clone(&re);
crew.push(std::thread::spawn(move || {
let text = format!("{}le CAFÉ noir", "é".repeat(round));
let found = re.find(&text).expect("a match");
assert_eq!(found.as_str(), "CAFÉ");
assert_eq!(&text[found.range()], "CAFÉ");
}));
}
for worker in crew {
worker.join().expect("no thread should panic");
}
}
assert!(re.is_match("un café"));
}
#[test]
fn compile_on_one_thread_drop_on_another() {
let built = std::thread::spawn(|| Regex::new(r"\d+").unwrap())
.join()
.unwrap();
let used = std::thread::spawn(move || {
assert_eq!(built.find_iter("a1 b22").count(), 2);
built
})
.join()
.unwrap();
std::thread::spawn(move || drop(used)).join().unwrap();
}