diff --git a/library/coretests/tests/pattern.rs b/library/coretests/tests/pattern.rs index b5cd019c59e76..2e3475be57ceb 100644 --- a/library/coretests/tests/pattern.rs +++ b/library/coretests/tests/pattern.rs @@ -572,3 +572,103 @@ fn double_ended_regression_test() { next_match => Done ); } + +#[test] +fn two_way_next_reject_skips_matches() { + // `next_reject` must not report the matched regions as rejects + + // plen - pattern len + // ulen - unmatch len + #[track_caller] + fn check_fw_bw<'a, T>(haystack: &'a str, pat: T, plen: usize, ulen: usize) + where + T: Pattern + Copy, + T::Searcher<'a>: ReverseSearcher<'a>, + { + // haystack is a concatenation of 3 fragments: [Match, Reject, Match] + // prepare ranges that point to each fragment + let f1 = (0, plen); + let f2 = (f1.1, f1.1 + ulen); + let f3 = (f2.1, f2.1 + plen); + + // search forward, should match fragments f2 (possibly partially) as a rejection + // and f3 as a match + + let mut searcher = pat.into_searcher(haystack); + // find first forward rejection + let (start, end) = searcher.next_reject().expect( + "inputs are constructed such that the haystack contains a reject in the middle", + ); + assert_eq!(start, f2.0, "forward reject should start at the second fragment (f2)"); + assert!(start < end && end <= f2.1, "fw reject must be a non-empty part of f2"); + assert_eq!( + searcher.next_match(), + Some(f3), + "first fw match after the rejection should point at the entirety of the third fragment (f3)" + ); + assert_eq!(searcher.next_match(), None, "there should be no matches after the f3"); + + // search backwards , should match fragments f2 (possibly partially) as a rejection + // and f1 as a match + + let mut searcher = pat.into_searcher(haystack); + let (start, end) = searcher.next_reject_back().expect( + "inputs are constructed such that the haystack contains a reject in the middle", + ); + assert_eq!(end, f2.1, "forward reject should end at the second fragment (f2)"); + assert!(f2.0 <= start && start < end, "bw reject must be a non-empty part of f2"); + assert_eq!( + searcher.next_match_back(), + Some(f1), + "first bw match after the rejection should point at the entirety of the first fragment (f1)" + ); + assert_eq!(searcher.next_match_back(), None, "there should be no matches after the f1"); + } + + // haystack is always a concatenation of [Match, Reject, Match] + // look for a string slice with several ASCII chars + check_fw_bw("XYZabcXYZ", "XYZ", 3, 3); + + // look for a string slice made of a single utf8 character, 1 to 4 bytes + check_fw_bw("XabcX", "X", 1, 3); + check_fw_bw("\u{00e9}abc\u{00e9}", "\u{00e9}", 2, 3); + check_fw_bw("\u{20ac}abc\u{20ac}", "\u{20ac}", 3, 3); + check_fw_bw("\u{1f60a}abc\u{1f60a}", "\u{1f60a}", 4, 3); + + // look for a single utf8 character, 1 to 4 bytes + check_fw_bw("XabcX", 'X', 1, 3); + check_fw_bw("\u{00e9}abc\u{00e9}", '\u{00e9}', 2, 3); + check_fw_bw("\u{20ac}abc\u{20ac}", '\u{20ac}', 3, 3); + check_fw_bw("\u{1f60a}abc\u{1f60a}", '\u{1f60a}', 4, 3); + + // same, but a singleton set of characters, matches any + check_fw_bw("XabcX", ['X'], 1, 3); + check_fw_bw("\u{00e9}abc\u{00e9}", ['\u{00e9}'], 2, 3); + check_fw_bw("\u{20ac}abc\u{20ac}", ['\u{20ac}'], 3, 3); + check_fw_bw("\u{1f60a}abc\u{1f60a}", ['\u{1f60a}'], 4, 3); + + // same, but match by a closure + check_fw_bw("XabcX", |c| c == 'X', 1, 3); + check_fw_bw("\u{00e9}abc\u{00e9}", |c| c == '\u{00e9}', 2, 3); + check_fw_bw("\u{20ac}abc\u{20ac}", |c| c == '\u{20ac}', 3, 3); + check_fw_bw("\u{1f60a}abc\u{1f60a}", |c| c == '\u{1f60a}', 4, 3); +} + +#[test] +fn two_way_next_reject_never_reports_empty_reject() { + // A reject must never be an empty range + + let mut searcher = "XYZ".into_searcher("XYZ"); + assert_eq!( + searcher.next_reject(), + None, + "Haystack fully covered by the pattern: there are no rejects at all." + ); + + let mut searcher = "XYZ".into_searcher("XYZ"); + assert_eq!( + searcher.next_reject_back(), + None, + "Haystack fully covered by the pattern: there are no rejects at all." + ); +}