use regexr::{Regex, RegexBuilder};
const PATTERNS: &[&str] = &[
r"\w+(?=ing\b)",
r"\w*(?=ing)",
r"[a-z]+(?=xy)",
r"\w+(?=(ing)\b)",
];
const HAYSTACKS: &[&str] = &[
"",
"singing",
"singing ringing",
"sing ing",
"ings",
"inging",
"ing",
" ing",
"xy",
" xyxy",
"abxyxy",
"naïveing",
"中ing 中",
"中inging",
];
fn compiled(pattern: &str, jit: bool) -> Regex {
RegexBuilder::new(pattern)
.jit(jit)
.build()
.expect("pattern compiles")
}
#[test]
fn is_match_agrees_with_find() {
for pattern in PATTERNS {
for jit in [false, true] {
let re = compiled(pattern, jit);
for haystack in HAYSTACKS {
let expected = re.find(haystack).is_some();
assert_eq!(
re.is_match(haystack),
expected,
"is_match disagrees: pattern={pattern:?} jit={jit} haystack={haystack:?}"
);
assert_eq!(
re.try_is_match(haystack).expect("no backtracking budget"),
expected,
"try_is_match disagrees: pattern={pattern:?} jit={jit} haystack={haystack:?}"
);
}
}
}
}
#[test]
fn captures_span_agrees_with_find() {
for pattern in PATTERNS {
for jit in [false, true] {
let re = compiled(pattern, jit);
for haystack in HAYSTACKS {
let expected = re.find(haystack).map(|m| (m.start(), m.end()));
let context = format!("pattern={pattern:?} jit={jit} haystack={haystack:?}");
let whole = re
.captures(haystack)
.map(|caps| caps.get(0).map(|m| (m.start(), m.end())));
assert_eq!(whole, expected.map(Some), "captures disagrees: {context}");
let whole = re
.try_captures(haystack)
.expect("no backtracking budget")
.map(|caps| caps.get(0).map(|m| (m.start(), m.end())));
assert_eq!(
whole,
expected.map(Some),
"try_captures disagrees: {context}"
);
}
}
}
}
#[test]
fn every_search_entry_point_reports_the_same_first_match() {
for pattern in PATTERNS {
for jit in [false, true] {
let re = compiled(pattern, jit);
for haystack in HAYSTACKS {
let expected = re.find(haystack).map(|m| (m.start(), m.end()));
let context = format!("pattern={pattern:?} jit={jit} haystack={haystack:?}");
assert_eq!(
re.try_find(haystack)
.expect("no backtracking budget")
.map(|m| (m.start(), m.end())),
expected,
"try_find disagrees: {context}"
);
assert_eq!(
re.find_iter(haystack).next().map(|m| (m.start(), m.end())),
expected,
"find_iter disagrees: {context}"
);
assert_eq!(
re.captures_iter(haystack)
.next()
.and_then(|caps| caps.get(0).map(|m| (m.start(), m.end()))),
expected,
"captures_iter disagrees: {context}"
);
}
}
}
}
#[test]
fn a_literal_with_no_match_around_it_matches_nothing() {
for jit in [false, true] {
let re = compiled(r"\w+(?=ing\b)", jit);
for haystack in ["ing", "ings"] {
assert!(
re.find(haystack).is_none(),
"jit={jit} haystack={haystack:?}"
);
assert!(!re.is_match(haystack), "jit={jit} haystack={haystack:?}");
assert!(
re.captures(haystack).is_none(),
"jit={jit} haystack={haystack:?}"
);
}
let found = re.find("singing").expect("singing matches");
assert_eq!((found.start(), found.end()), (0, 4));
assert!(re.is_match("singing"));
let caps = re.captures("singing").expect("singing matches");
let whole = caps.get(0).expect("slot 0 is always set");
assert_eq!((whole.start(), whole.end()), (0, 4));
}
}