use eregex::Regex;
fn re(p: &str) -> Regex {
Regex::new(p).unwrap_or_else(|_| panic!("failed to compile {p:?}"))
}
#[test]
fn tc01_partial_literal_prefix() {
let r = re(r"ABCD-[0-9]{4}");
let m = r.find_partial("noise noise START: ABCD").expect("partial");
assert!(m.is_partial());
assert_eq!(m.matched, "ABCD");
assert_eq!(m.start, 19);
}
#[test]
fn tc02_group_complete_next_missing() {
let r = re(r"user:([a-z]+)@([a-z]+)\.com");
let m = r.find_partial("prefix user:john@").expect("partial");
assert!(m.is_partial());
assert_eq!(m.matched, "user:john@");
assert_eq!(m.group(1), Some("john"));
assert!(m.group_matched(1));
assert!(m.group_partial(2));
assert_eq!(m.group(2), Some(""));
}
#[test]
fn tc03_partial_inside_group() {
let r = re(r"order:(ORD-[0-9]{4})");
let m = r.find_partial("prefix order:ORD-12").expect("partial");
assert!(m.is_partial());
assert_eq!(m.matched, "order:ORD-12");
assert!(m.group_partial(1));
assert_eq!(m.group(1), Some("ORD-12"));
}
#[test]
fn tc04_mixed_complete_and_partial_groups() {
let r = re(r"id=([0-9]+) user=([a-z]{5})");
let m = r.find_partial("log id=42 user=ali").expect("partial");
assert!(m.is_partial());
assert_eq!(m.matched, "id=42 user=ali");
assert!(m.group_matched(1));
assert_eq!(m.group(1), Some("42"));
assert!(m.group_partial(2));
assert_eq!(m.group(2), Some("ali"));
}
#[test]
fn tc05_partial_empty_second_group() {
let r = re(r"card=([0-9]{4})-([0-9]{4})");
let m = r.find_partial("payment card=4242-").expect("partial");
assert!(m.is_partial());
assert_eq!(m.matched, "card=4242-");
assert!(m.group_matched(1));
assert_eq!(m.group(1), Some("4242"));
assert!(m.group_partial(2));
assert_eq!(m.group(2), Some(""));
}
#[test]
fn tc08_wrong_char_means_no_match() {
let r = re(r"token=([a-z]+)([0-9]+)");
assert!(r.find_partial("xxx token=abc!").is_none());
}
#[test]
fn tc09_partial_input_ended_before_required() {
let r = re(r"token=([a-z]+)([0-9]+)");
let m = r.find_partial("xxx token=abc").expect("partial");
assert!(m.is_partial());
assert_eq!(m.matched, "token=abc");
assert!(m.group_matched(1));
assert_eq!(m.group(1), Some("abc"));
assert!(m.group_partial(2));
assert_eq!(m.group(2), Some(""));
}
#[test]
fn tc10_alternation_all_branches_partial() {
let r = re(r"cmd=(start|stop|status)");
let m = r.find_partial("cmd=sta").expect("partial");
assert!(m.is_partial());
assert_eq!(m.matched, "cmd=sta");
assert!(m.group_partial(1));
assert_eq!(m.group(1), Some("sta"));
}
#[test]
fn tc12_repetition_last_element_partial() {
let r = re(r"ids: ([0-9]{3})(,[0-9]{3})*");
let m = r.find_partial("ids: 123,456,7").expect("partial");
assert!(m.is_partial());
assert_eq!(m.matched, "ids: 123,456,7");
assert!(m.group_matched(1));
assert_eq!(m.group(1), Some("123"));
assert!(m.group_partial(2));
assert_eq!(m.group(2), Some(",7"));
}
#[test]
fn tc13_repetition_separator_only() {
let r = re(r"ids: ([0-9]{3})(,[0-9]{3})*");
let m = r.find_partial("ids: 123,").expect("partial");
assert!(m.is_partial());
assert_eq!(m.matched, "ids: 123,");
assert!(m.group_matched(1));
assert_eq!(m.group(1), Some("123"));
assert!(m.group_partial(2));
assert_eq!(m.group(2), Some(","));
}
#[test]
fn tc14_search_finds_partial_later() {
let r = re(r"order:([A-Z]{4})");
let m = r.find_partial("junk foo junk order:AB").expect("partial");
assert!(m.is_partial());
assert_eq!(m.matched, "order:AB");
assert_eq!(m.start, 14);
assert_eq!(m.end, 22);
assert!(m.group_partial(1));
assert_eq!(m.group(1), Some("AB"));
}
#[test]
fn tc16_invalid_then_partial() {
let r = re(r"id=([0-9]{3})");
let m = r.find_partial("id=12x other id=45").expect("partial");
assert!(m.is_partial());
assert_eq!(m.matched, "id=45");
assert!(m.group_partial(1));
assert_eq!(m.group(1), Some("45"));
}
#[test]
fn tc17_boundary_word_started() {
let r = re(r"\bhello\b");
let m = r.find_partial("say hel").expect("partial");
assert!(m.is_partial());
assert_eq!(m.matched, "hel");
}
#[test]
fn tc19_email_tld_partial() {
let r = re(r"([a-z]+(?:\.[a-z]+)*)@([a-z]+)\.([a-z]{2,})");
let m = r
.find_partial("contact: john.doe@example")
.expect("partial");
assert!(m.is_partial());
assert_eq!(m.matched, "john.doe@example");
assert!(m.group_matched(1));
assert_eq!(m.group(1), Some("john.doe"));
assert!(m.group_matched(2));
assert_eq!(m.group(2), Some("example"));
assert!(m.group_none(3));
}
#[test]
fn tc21_date_partial_day_missing() {
let r = re(r"created_at=([0-9]{4})-([0-9]{2})-([0-9]{2})");
let m = r.find_partial("created_at=2026-06").expect("partial");
assert!(m.is_partial());
assert_eq!(m.matched, "created_at=2026-06");
assert!(m.group_matched(1));
assert_eq!(m.group(1), Some("2026"));
assert!(m.group_matched(2));
assert_eq!(m.group(2), Some("06"));
assert!(m.group_none(3));
}
#[test]
fn tc22_wrong_separator_no_match() {
let r = re(r"created_at=([0-9]{4})-([0-9]{2})-([0-9]{2})");
assert!(r.find_partial("created_at=2026/06").is_none());
}
#[test]
fn tc23_named_groups_second_partial() {
let r = re(r"user=(?P<user>[a-z]+) action=(?P<action>login|logout)");
let m = r
.find_partial("event user=alice action=lo")
.expect("partial");
assert!(m.is_partial());
assert_eq!(m.name("user"), Some("alice"));
assert_eq!(m.group(1), Some("alice"));
assert_eq!(m.name("action"), Some("lo"));
assert_eq!(m.group(2), Some("lo"));
assert!(m.group_partial(2));
}
#[test]
fn tc24_nested_groups_last_partial() {
let r = re(r"rgb\(([0-9]{1,3}),([0-9]{1,3}),([0-9]{1,3})\)");
let m = r.find_partial("color: rgb(255,12").expect("partial");
assert!(m.is_partial());
assert_eq!(m.matched, "rgb(255,12");
assert_eq!(m.group(1), Some("255"));
assert!(m.group_matched(1));
assert_eq!(m.group(2), Some("12"));
assert!(m.group_matched(2));
assert!(m.group_none(3));
}
#[test]
fn tc26_optional_group_started_partial() {
let r = re(r"user:([a-z]+)(?: role=(admin|editor|viewer))?");
let m = r.find_partial("user:alice role=ad").expect("partial");
assert!(m.is_partial());
assert!(m.group_matched(1));
assert_eq!(m.group(1), Some("alice"));
assert!(m.group_partial(2));
assert_eq!(m.group(2), Some("ad"));
}
#[test]
fn tc27_greedy_before_required_suffix() {
let r = re(r"title: (.+) END");
let m = r.find_partial("title: hello wor").expect("partial");
assert!(m.is_partial());
assert_eq!(m.matched, "title: hello wor");
assert_eq!(m.group(1), Some("hello wor"));
}
#[test]
fn tc28_greedy_suffix_like_continuation() {
let r = re(r"title: (.+) END");
let m = r.find_partial("title: hello wor STOP").expect("partial");
assert!(m.is_partial());
assert_eq!(m.matched, "title: hello wor STOP");
}
#[test]
fn tc30_anchored_required_chars_missing() {
let r = re(r"code=([A-Z]{3})$");
let m = r.find_partial("code=AB").expect("partial");
assert!(m.is_partial());
assert_eq!(m.matched, "code=AB");
assert!(m.group_partial(1));
assert_eq!(m.group(1), Some("AB"));
}
#[test]
fn tc30_full_counterpart() {
let r = re(r"code=([A-Z]{3})$");
let m = r.find_partial("code=ABC").expect("full");
assert!(m.is_full());
assert_eq!(m.matched, "code=ABC");
assert!(m.group_matched(1));
assert_eq!(m.group(1), Some("ABC"));
}
#[test]
fn find_partial_contrasts_with_find() {
let r = re(r"id=([0-9]{3})");
assert_eq!(r.find("id=123 other id=45").unwrap().as_str(), "id=123");
let m = r.find_partial("id=123 other id=45").expect("partial");
assert!(m.is_partial());
assert_eq!(m.matched, "id=45");
assert_eq!(m.start, 13);
assert!(m.group_partial(1));
assert_eq!(m.group(1), Some("45"));
}