| 1 | /*! |
| 2 | This module defines tests and test helpers for substring implementations. |
| 3 | */ |
| 4 | |
| 5 | use alloc::{ |
| 6 | boxed::Box, |
| 7 | format, |
| 8 | string::{String, ToString}, |
| 9 | }; |
| 10 | |
| 11 | pub(crate) mod naive; |
| 12 | #[macro_use ] |
| 13 | pub(crate) mod prop; |
| 14 | |
| 15 | const SEEDS: &'static [Seed] = &[ |
| 16 | Seed::new("" , "" , Some(0), Some(0)), |
| 17 | Seed::new("" , "a" , Some(0), Some(1)), |
| 18 | Seed::new("" , "ab" , Some(0), Some(2)), |
| 19 | Seed::new("" , "abc" , Some(0), Some(3)), |
| 20 | Seed::new("a" , "" , None, None), |
| 21 | Seed::new("a" , "a" , Some(0), Some(0)), |
| 22 | Seed::new("a" , "aa" , Some(0), Some(1)), |
| 23 | Seed::new("a" , "ba" , Some(1), Some(1)), |
| 24 | Seed::new("a" , "bba" , Some(2), Some(2)), |
| 25 | Seed::new("a" , "bbba" , Some(3), Some(3)), |
| 26 | Seed::new("a" , "bbbab" , Some(3), Some(3)), |
| 27 | Seed::new("a" , "bbbabb" , Some(3), Some(3)), |
| 28 | Seed::new("a" , "bbbabbb" , Some(3), Some(3)), |
| 29 | Seed::new("a" , "bbbbbb" , None, None), |
| 30 | Seed::new("ab" , "" , None, None), |
| 31 | Seed::new("ab" , "a" , None, None), |
| 32 | Seed::new("ab" , "b" , None, None), |
| 33 | Seed::new("ab" , "ab" , Some(0), Some(0)), |
| 34 | Seed::new("ab" , "aab" , Some(1), Some(1)), |
| 35 | Seed::new("ab" , "aaab" , Some(2), Some(2)), |
| 36 | Seed::new("ab" , "abaab" , Some(0), Some(3)), |
| 37 | Seed::new("ab" , "baaab" , Some(3), Some(3)), |
| 38 | Seed::new("ab" , "acb" , None, None), |
| 39 | Seed::new("ab" , "abba" , Some(0), Some(0)), |
| 40 | Seed::new("abc" , "ab" , None, None), |
| 41 | Seed::new("abc" , "abc" , Some(0), Some(0)), |
| 42 | Seed::new("abc" , "abcz" , Some(0), Some(0)), |
| 43 | Seed::new("abc" , "abczz" , Some(0), Some(0)), |
| 44 | Seed::new("abc" , "zabc" , Some(1), Some(1)), |
| 45 | Seed::new("abc" , "zzabc" , Some(2), Some(2)), |
| 46 | Seed::new("abc" , "azbc" , None, None), |
| 47 | Seed::new("abc" , "abzc" , None, None), |
| 48 | Seed::new("abczdef" , "abczdefzzzzzzzzzzzzzzzzzzzz" , Some(0), Some(0)), |
| 49 | Seed::new("abczdef" , "zzzzzzzzzzzzzzzzzzzzabczdef" , Some(20), Some(20)), |
| 50 | Seed::new( |
| 51 | "xyz" , |
| 52 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaxyz" , |
| 53 | Some(32), |
| 54 | Some(32), |
| 55 | ), |
| 56 | Seed::new(" \u{0}\u{15}" , " \u{0}\u{15}\u{15}\u{0}" , Some(0), Some(0)), |
| 57 | Seed::new(" \u{0}\u{1e}" , " \u{1e}\u{0}" , None, None), |
| 58 | ]; |
| 59 | |
| 60 | /// Runs a host of substring search tests. |
| 61 | /// |
| 62 | /// This has support for "partial" substring search implementations only work |
| 63 | /// for a subset of needles/haystacks. For example, the "packed pair" substring |
| 64 | /// search implementation only works for haystacks of some minimum length based |
| 65 | /// of the pair of bytes selected and the size of the vector used. |
| 66 | pub(crate) struct Runner { |
| 67 | fwd: Option< |
| 68 | Box<dyn FnMut(&[u8], &[u8]) -> Option<Option<usize>> + 'static>, |
| 69 | >, |
| 70 | rev: Option< |
| 71 | Box<dyn FnMut(&[u8], &[u8]) -> Option<Option<usize>> + 'static>, |
| 72 | >, |
| 73 | } |
| 74 | |
| 75 | impl Runner { |
| 76 | /// Create a new test runner for forward and reverse substring search |
| 77 | /// implementations. |
| 78 | pub(crate) fn new() -> Runner { |
| 79 | Runner { fwd: None, rev: None } |
| 80 | } |
| 81 | |
| 82 | /// Run all tests. This panics on the first failure. |
| 83 | /// |
| 84 | /// If the implementation being tested returns `None` for a particular |
| 85 | /// haystack/needle combination, then that test is skipped. |
| 86 | /// |
| 87 | /// This runs tests on both the forward and reverse implementations given. |
| 88 | /// If either (or both) are missing, then tests for that implementation are |
| 89 | /// skipped. |
| 90 | pub(crate) fn run(self) { |
| 91 | if let Some(mut fwd) = self.fwd { |
| 92 | for seed in SEEDS.iter() { |
| 93 | for t in seed.generate() { |
| 94 | match fwd(t.haystack.as_bytes(), t.needle.as_bytes()) { |
| 95 | None => continue, |
| 96 | Some(result) => { |
| 97 | assert_eq!( |
| 98 | t.fwd, result, |
| 99 | "FORWARD, needle: {:?}, haystack: {:?}" , |
| 100 | t.needle, t.haystack, |
| 101 | ); |
| 102 | } |
| 103 | } |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 | if let Some(mut rev) = self.rev { |
| 108 | for seed in SEEDS.iter() { |
| 109 | for t in seed.generate() { |
| 110 | match rev(t.haystack.as_bytes(), t.needle.as_bytes()) { |
| 111 | None => continue, |
| 112 | Some(result) => { |
| 113 | assert_eq!( |
| 114 | t.rev, result, |
| 115 | "REVERSE, needle: {:?}, haystack: {:?}" , |
| 116 | t.needle, t.haystack, |
| 117 | ); |
| 118 | } |
| 119 | } |
| 120 | } |
| 121 | } |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | /// Set the implementation for forward substring search. |
| 126 | /// |
| 127 | /// If the closure returns `None`, then it is assumed that the given |
| 128 | /// test cannot be applied to the particular implementation and it is |
| 129 | /// skipped. For example, if a particular implementation only supports |
| 130 | /// needles or haystacks for some minimum length. |
| 131 | /// |
| 132 | /// If this is not set, then forward substring search is not tested. |
| 133 | pub(crate) fn fwd( |
| 134 | mut self, |
| 135 | search: impl FnMut(&[u8], &[u8]) -> Option<Option<usize>> + 'static, |
| 136 | ) -> Runner { |
| 137 | self.fwd = Some(Box::new(search)); |
| 138 | self |
| 139 | } |
| 140 | |
| 141 | /// Set the implementation for reverse substring search. |
| 142 | /// |
| 143 | /// If the closure returns `None`, then it is assumed that the given |
| 144 | /// test cannot be applied to the particular implementation and it is |
| 145 | /// skipped. For example, if a particular implementation only supports |
| 146 | /// needles or haystacks for some minimum length. |
| 147 | /// |
| 148 | /// If this is not set, then reverse substring search is not tested. |
| 149 | pub(crate) fn rev( |
| 150 | mut self, |
| 151 | search: impl FnMut(&[u8], &[u8]) -> Option<Option<usize>> + 'static, |
| 152 | ) -> Runner { |
| 153 | self.rev = Some(Box::new(search)); |
| 154 | self |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | /// A single substring test for forward and reverse searches. |
| 159 | #[derive(Clone, Debug)] |
| 160 | struct Test { |
| 161 | needle: String, |
| 162 | haystack: String, |
| 163 | fwd: Option<usize>, |
| 164 | rev: Option<usize>, |
| 165 | } |
| 166 | |
| 167 | /// A single substring test for forward and reverse searches. |
| 168 | /// |
| 169 | /// Each seed is valid on its own, but it also serves as a starting point |
| 170 | /// to generate more tests. Namely, we pad out the haystacks with other |
| 171 | /// characters so that we get more complete coverage. This is especially useful |
| 172 | /// for testing vector algorithms that tend to have weird special cases for |
| 173 | /// alignment and loop unrolling. |
| 174 | /// |
| 175 | /// Padding works by assuming certain characters never otherwise appear in a |
| 176 | /// needle or a haystack. Neither should contain a `#` character. |
| 177 | #[derive(Clone, Copy, Debug)] |
| 178 | struct Seed { |
| 179 | needle: &'static str, |
| 180 | haystack: &'static str, |
| 181 | fwd: Option<usize>, |
| 182 | rev: Option<usize>, |
| 183 | } |
| 184 | |
| 185 | impl Seed { |
| 186 | const MAX_PAD: usize = 34; |
| 187 | |
| 188 | const fn new( |
| 189 | needle: &'static str, |
| 190 | haystack: &'static str, |
| 191 | fwd: Option<usize>, |
| 192 | rev: Option<usize>, |
| 193 | ) -> Seed { |
| 194 | Seed { needle, haystack, fwd, rev } |
| 195 | } |
| 196 | |
| 197 | fn generate(self) -> impl Iterator<Item = Test> { |
| 198 | assert!(!self.needle.contains('#' ), "needle must not contain '#'" ); |
| 199 | assert!(!self.haystack.contains('#' ), "haystack must not contain '#'" ); |
| 200 | (0..=Seed::MAX_PAD) |
| 201 | // Generate tests for padding at the beginning of haystack. |
| 202 | .map(move |pad| { |
| 203 | let needle = self.needle.to_string(); |
| 204 | let prefix = "#" .repeat(pad); |
| 205 | let haystack = format!("{}{}" , prefix, self.haystack); |
| 206 | let fwd = if needle.is_empty() { |
| 207 | Some(0) |
| 208 | } else { |
| 209 | self.fwd.map(|i| pad + i) |
| 210 | }; |
| 211 | let rev = if needle.is_empty() { |
| 212 | Some(haystack.len()) |
| 213 | } else { |
| 214 | self.rev.map(|i| pad + i) |
| 215 | }; |
| 216 | Test { needle, haystack, fwd, rev } |
| 217 | }) |
| 218 | // Generate tests for padding at the end of haystack. |
| 219 | .chain((1..=Seed::MAX_PAD).map(move |pad| { |
| 220 | let needle = self.needle.to_string(); |
| 221 | let suffix = "#" .repeat(pad); |
| 222 | let haystack = format!("{}{}" , self.haystack, suffix); |
| 223 | let fwd = if needle.is_empty() { Some(0) } else { self.fwd }; |
| 224 | let rev = if needle.is_empty() { |
| 225 | Some(haystack.len()) |
| 226 | } else { |
| 227 | self.rev |
| 228 | }; |
| 229 | Test { needle, haystack, fwd, rev } |
| 230 | })) |
| 231 | } |
| 232 | } |
| 233 | |