From 0a1618ed1b6ad6a4d1fdf22e52ce461c2377c0df Mon Sep 17 00:00:00 2001 From: Mikhail Baykov Date: Mon, 10 Aug 2026 06:20:59 -0400 Subject: [PATCH] coretests: Add more pattern tests. Right now things are undertested and underspecified. Some of the library code would get in a loop if searcher starts returning empty rejects. And there's no tests for backwards multi byte char matchers. Pull request I'm reviving had a problem implementing that, so making sure it's tested before the actual code lands. Right now it is possible to break both tests (and user code) without breaking anything else in the test suite I think. --- library/coretests/tests/pattern.rs | 100 +++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) 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." + ); +}