1 use std::collections::HashMap;
2 use std::usize;
3 
4 use packed::{Config, MatchKind};
5 use Match;
6 
7 /// A description of a single test against a multi-pattern searcher.
8 ///
9 /// A single test may not necessarily pass on every configuration of a
10 /// searcher. The tests are categorized and grouped appropriately below.
11 #[derive(Clone, Debug, Eq, PartialEq)]
12 struct SearchTest {
13     /// The name of this test, for debugging.
14     name: &'static str,
15     /// The patterns to search for.
16     patterns: &'static [&'static str],
17     /// The text to search.
18     haystack: &'static str,
19     /// Each match is a triple of (pattern_index, start, end), where
20     /// pattern_index is an index into `patterns` and `start`/`end` are indices
21     /// into `haystack`.
22     matches: &'static [(usize, usize, usize)],
23 }
24 
25 struct SearchTestOwned {
26     offset: usize,
27     name: String,
28     patterns: Vec<String>,
29     haystack: String,
30     matches: Vec<(usize, usize, usize)>,
31 }
32 
33 impl SearchTest {
variations(&self) -> Vec<SearchTestOwned>34     fn variations(&self) -> Vec<SearchTestOwned> {
35         let mut tests = vec![];
36         for i in 0..=260 {
37             tests.push(self.offset_prefix(i));
38             tests.push(self.offset_suffix(i));
39             tests.push(self.offset_both(i));
40         }
41         tests
42     }
43 
offset_both(&self, off: usize) -> SearchTestOwned44     fn offset_both(&self, off: usize) -> SearchTestOwned {
45         SearchTestOwned {
46             offset: off,
47             name: self.name.to_string(),
48             patterns: self.patterns.iter().map(|s| s.to_string()).collect(),
49             haystack: format!(
50                 "{}{}{}",
51                 "Z".repeat(off),
52                 self.haystack,
53                 "Z".repeat(off)
54             ),
55             matches: self
56                 .matches
57                 .iter()
58                 .map(|&(id, s, e)| (id, s + off, e + off))
59                 .collect(),
60         }
61     }
62 
offset_prefix(&self, off: usize) -> SearchTestOwned63     fn offset_prefix(&self, off: usize) -> SearchTestOwned {
64         SearchTestOwned {
65             offset: off,
66             name: self.name.to_string(),
67             patterns: self.patterns.iter().map(|s| s.to_string()).collect(),
68             haystack: format!("{}{}", "Z".repeat(off), self.haystack),
69             matches: self
70                 .matches
71                 .iter()
72                 .map(|&(id, s, e)| (id, s + off, e + off))
73                 .collect(),
74         }
75     }
76 
offset_suffix(&self, off: usize) -> SearchTestOwned77     fn offset_suffix(&self, off: usize) -> SearchTestOwned {
78         SearchTestOwned {
79             offset: off,
80             name: self.name.to_string(),
81             patterns: self.patterns.iter().map(|s| s.to_string()).collect(),
82             haystack: format!("{}{}", self.haystack, "Z".repeat(off)),
83             matches: self.matches.to_vec(),
84         }
85     }
86 
87     // fn to_owned(&self) -> SearchTestOwned {
88     // SearchTestOwned {
89     // name: self.name.to_string(),
90     // patterns: self.patterns.iter().map(|s| s.to_string()).collect(),
91     // haystack: self.haystack.to_string(),
92     // matches: self.matches.iter().cloned().collect(),
93     // }
94     // }
95 }
96 
97 /// Short-hand constructor for SearchTest. We use it a lot below.
98 macro_rules! t {
99     ($name:ident, $patterns:expr, $haystack:expr, $matches:expr) => {
100         SearchTest {
101             name: stringify!($name),
102             patterns: $patterns,
103             haystack: $haystack,
104             matches: $matches,
105         }
106     };
107 }
108 
109 /// A collection of test groups.
110 type TestCollection = &'static [&'static [SearchTest]];
111 
112 // Define several collections corresponding to the different type of match
113 // semantics supported. These collections have some overlap, but each
114 // collection should have some tests that no other collection has.
115 
116 /// Tests for leftmost-first match semantics.
117 const PACKED_LEFTMOST_FIRST: TestCollection =
118     &[BASICS, LEFTMOST, LEFTMOST_FIRST, REGRESSION, TEDDY];
119 
120 /// Tests for leftmost-longest match semantics.
121 const PACKED_LEFTMOST_LONGEST: TestCollection =
122     &[BASICS, LEFTMOST, LEFTMOST_LONGEST, REGRESSION, TEDDY];
123 
124 // Now define the individual tests that make up the collections above.
125 
126 /// A collection of tests for the that should always be true regardless of
127 /// match semantics. That is, all combinations of leftmost-{first, longest}
128 /// should produce the same answer.
129 const BASICS: &'static [SearchTest] = &[
130     t!(basic001, &["a"], "", &[]),
131     t!(basic010, &["a"], "a", &[(0, 0, 1)]),
132     t!(basic020, &["a"], "aa", &[(0, 0, 1), (0, 1, 2)]),
133     t!(basic030, &["a"], "aaa", &[(0, 0, 1), (0, 1, 2), (0, 2, 3)]),
134     t!(basic040, &["a"], "aba", &[(0, 0, 1), (0, 2, 3)]),
135     t!(basic050, &["a"], "bba", &[(0, 2, 3)]),
136     t!(basic060, &["a"], "bbb", &[]),
137     t!(basic070, &["a"], "bababbbba", &[(0, 1, 2), (0, 3, 4), (0, 8, 9)]),
138     t!(basic100, &["aa"], "", &[]),
139     t!(basic110, &["aa"], "aa", &[(0, 0, 2)]),
140     t!(basic120, &["aa"], "aabbaa", &[(0, 0, 2), (0, 4, 6)]),
141     t!(basic130, &["aa"], "abbab", &[]),
142     t!(basic140, &["aa"], "abbabaa", &[(0, 5, 7)]),
143     t!(basic150, &["aaa"], "aaa", &[(0, 0, 3)]),
144     t!(basic200, &["abc"], "abc", &[(0, 0, 3)]),
145     t!(basic210, &["abc"], "zazabzabcz", &[(0, 6, 9)]),
146     t!(basic220, &["abc"], "zazabczabcz", &[(0, 3, 6), (0, 7, 10)]),
147     t!(basic300, &["a", "b"], "", &[]),
148     t!(basic310, &["a", "b"], "z", &[]),
149     t!(basic320, &["a", "b"], "b", &[(1, 0, 1)]),
150     t!(basic330, &["a", "b"], "a", &[(0, 0, 1)]),
151     t!(
152         basic340,
153         &["a", "b"],
154         "abba",
155         &[(0, 0, 1), (1, 1, 2), (1, 2, 3), (0, 3, 4),]
156     ),
157     t!(
158         basic350,
159         &["b", "a"],
160         "abba",
161         &[(1, 0, 1), (0, 1, 2), (0, 2, 3), (1, 3, 4),]
162     ),
163     t!(basic360, &["abc", "bc"], "xbc", &[(1, 1, 3),]),
164     t!(basic400, &["foo", "bar"], "", &[]),
165     t!(basic410, &["foo", "bar"], "foobar", &[(0, 0, 3), (1, 3, 6),]),
166     t!(basic420, &["foo", "bar"], "barfoo", &[(1, 0, 3), (0, 3, 6),]),
167     t!(basic430, &["foo", "bar"], "foofoo", &[(0, 0, 3), (0, 3, 6),]),
168     t!(basic440, &["foo", "bar"], "barbar", &[(1, 0, 3), (1, 3, 6),]),
169     t!(basic450, &["foo", "bar"], "bafofoo", &[(0, 4, 7),]),
170     t!(basic460, &["bar", "foo"], "bafofoo", &[(1, 4, 7),]),
171     t!(basic470, &["foo", "bar"], "fobabar", &[(1, 4, 7),]),
172     t!(basic480, &["bar", "foo"], "fobabar", &[(0, 4, 7),]),
173     t!(basic700, &["yabcdef", "abcdezghi"], "yabcdefghi", &[(0, 0, 7),]),
174     t!(basic710, &["yabcdef", "abcdezghi"], "yabcdezghi", &[(1, 1, 10),]),
175     t!(
176         basic720,
177         &["yabcdef", "bcdeyabc", "abcdezghi"],
178         "yabcdezghi",
179         &[(2, 1, 10),]
180     ),
181     t!(basic810, &["abcd", "bcd", "cd"], "abcd", &[(0, 0, 4),]),
182     t!(basic820, &["bcd", "cd", "abcd"], "abcd", &[(2, 0, 4),]),
183     t!(basic830, &["abc", "bc"], "zazabcz", &[(0, 3, 6),]),
184     t!(
185         basic840,
186         &["ab", "ba"],
187         "abababa",
188         &[(0, 0, 2), (0, 2, 4), (0, 4, 6),]
189     ),
190     t!(basic850, &["foo", "foo"], "foobarfoo", &[(0, 0, 3), (0, 6, 9),]),
191 ];
192 
193 /// Tests for leftmost match semantics. These should pass for both
194 /// leftmost-first and leftmost-longest match kinds. Stated differently, among
195 /// ambiguous matches, the longest match and the match that appeared first when
196 /// constructing the automaton should always be the same.
197 const LEFTMOST: &'static [SearchTest] = &[
198     t!(leftmost000, &["ab", "ab"], "abcd", &[(0, 0, 2)]),
199     t!(leftmost030, &["a", "ab"], "aa", &[(0, 0, 1), (0, 1, 2)]),
200     t!(leftmost031, &["ab", "a"], "aa", &[(1, 0, 1), (1, 1, 2)]),
201     t!(leftmost032, &["ab", "a"], "xayabbbz", &[(1, 1, 2), (0, 3, 5)]),
202     t!(leftmost300, &["abcd", "bce", "b"], "abce", &[(1, 1, 4)]),
203     t!(leftmost310, &["abcd", "ce", "bc"], "abce", &[(2, 1, 3)]),
204     t!(leftmost320, &["abcd", "bce", "ce", "b"], "abce", &[(1, 1, 4)]),
205     t!(leftmost330, &["abcd", "bce", "cz", "bc"], "abcz", &[(3, 1, 3)]),
206     t!(leftmost340, &["bce", "cz", "bc"], "bcz", &[(2, 0, 2)]),
207     t!(leftmost350, &["abc", "bd", "ab"], "abd", &[(2, 0, 2)]),
208     t!(
209         leftmost360,
210         &["abcdefghi", "hz", "abcdefgh"],
211         "abcdefghz",
212         &[(2, 0, 8),]
213     ),
214     t!(
215         leftmost370,
216         &["abcdefghi", "cde", "hz", "abcdefgh"],
217         "abcdefghz",
218         &[(3, 0, 8),]
219     ),
220     t!(
221         leftmost380,
222         &["abcdefghi", "hz", "abcdefgh", "a"],
223         "abcdefghz",
224         &[(2, 0, 8),]
225     ),
226     t!(
227         leftmost390,
228         &["b", "abcdefghi", "hz", "abcdefgh"],
229         "abcdefghz",
230         &[(3, 0, 8),]
231     ),
232     t!(
233         leftmost400,
234         &["h", "abcdefghi", "hz", "abcdefgh"],
235         "abcdefghz",
236         &[(3, 0, 8),]
237     ),
238     t!(
239         leftmost410,
240         &["z", "abcdefghi", "hz", "abcdefgh"],
241         "abcdefghz",
242         &[(3, 0, 8), (0, 8, 9),]
243     ),
244 ];
245 
246 /// Tests for non-overlapping leftmost-first match semantics. These tests
247 /// should generally be specific to leftmost-first, which means they should
248 /// generally fail under leftmost-longest semantics.
249 const LEFTMOST_FIRST: &'static [SearchTest] = &[
250     t!(leftfirst000, &["ab", "abcd"], "abcd", &[(0, 0, 2)]),
251     t!(leftfirst020, &["abcd", "ab"], "abcd", &[(0, 0, 4)]),
252     t!(leftfirst030, &["ab", "ab"], "abcd", &[(0, 0, 2)]),
253     t!(leftfirst040, &["a", "ab"], "xayabbbz", &[(0, 1, 2), (0, 3, 4)]),
254     t!(leftfirst100, &["abcdefg", "bcde", "bcdef"], "abcdef", &[(1, 1, 5)]),
255     t!(leftfirst110, &["abcdefg", "bcdef", "bcde"], "abcdef", &[(1, 1, 6)]),
256     t!(leftfirst300, &["abcd", "b", "bce"], "abce", &[(1, 1, 2)]),
257     t!(
258         leftfirst310,
259         &["abcd", "b", "bce", "ce"],
260         "abce",
261         &[(1, 1, 2), (3, 2, 4),]
262     ),
263     t!(
264         leftfirst320,
265         &["a", "abcdefghi", "hz", "abcdefgh"],
266         "abcdefghz",
267         &[(0, 0, 1), (2, 7, 9),]
268     ),
269     t!(leftfirst330, &["a", "abab"], "abab", &[(0, 0, 1), (0, 2, 3)]),
270     t!(
271         leftfirst340,
272         &["abcdef", "x", "x", "x", "x", "x", "x", "abcde"],
273         "abcdef",
274         &[(0, 0, 6)]
275     ),
276 ];
277 
278 /// Tests for non-overlapping leftmost-longest match semantics. These tests
279 /// should generally be specific to leftmost-longest, which means they should
280 /// generally fail under leftmost-first semantics.
281 const LEFTMOST_LONGEST: &'static [SearchTest] = &[
282     t!(leftlong000, &["ab", "abcd"], "abcd", &[(1, 0, 4)]),
283     t!(leftlong010, &["abcd", "bcd", "cd", "b"], "abcd", &[(0, 0, 4),]),
284     t!(leftlong040, &["a", "ab"], "a", &[(0, 0, 1)]),
285     t!(leftlong050, &["a", "ab"], "ab", &[(1, 0, 2)]),
286     t!(leftlong060, &["ab", "a"], "a", &[(1, 0, 1)]),
287     t!(leftlong070, &["ab", "a"], "ab", &[(0, 0, 2)]),
288     t!(leftlong100, &["abcdefg", "bcde", "bcdef"], "abcdef", &[(2, 1, 6)]),
289     t!(leftlong110, &["abcdefg", "bcdef", "bcde"], "abcdef", &[(1, 1, 6)]),
290     t!(leftlong300, &["abcd", "b", "bce"], "abce", &[(2, 1, 4)]),
291     t!(
292         leftlong310,
293         &["a", "abcdefghi", "hz", "abcdefgh"],
294         "abcdefghz",
295         &[(3, 0, 8),]
296     ),
297     t!(leftlong320, &["a", "abab"], "abab", &[(1, 0, 4)]),
298     t!(leftlong330, &["abcd", "b", "ce"], "abce", &[(1, 1, 2), (2, 2, 4),]),
299     t!(leftlong340, &["a", "ab"], "xayabbbz", &[(0, 1, 2), (1, 3, 5)]),
300 ];
301 
302 /// Regression tests that are applied to all combinations.
303 ///
304 /// If regression tests are needed for specific match semantics, then add them
305 /// to the appropriate group above.
306 const REGRESSION: &'static [SearchTest] = &[
307     t!(regression010, &["inf", "ind"], "infind", &[(0, 0, 3), (1, 3, 6),]),
308     t!(regression020, &["ind", "inf"], "infind", &[(1, 0, 3), (0, 3, 6),]),
309     t!(
310         regression030,
311         &["libcore/", "libstd/"],
312         "libcore/char/methods.rs",
313         &[(0, 0, 8),]
314     ),
315     t!(
316         regression040,
317         &["libstd/", "libcore/"],
318         "libcore/char/methods.rs",
319         &[(1, 0, 8),]
320     ),
321     t!(
322         regression050,
323         &["\x00\x00\x01", "\x00\x00\x00"],
324         "\x00\x00\x00",
325         &[(1, 0, 3),]
326     ),
327     t!(
328         regression060,
329         &["\x00\x00\x00", "\x00\x00\x01"],
330         "\x00\x00\x00",
331         &[(0, 0, 3),]
332     ),
333 ];
334 
335 const TEDDY: &'static [SearchTest] = &[
336     t!(
337         teddy010,
338         &["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"],
339         "abcdefghijk",
340         &[
341             (0, 0, 1),
342             (1, 1, 2),
343             (2, 2, 3),
344             (3, 3, 4),
345             (4, 4, 5),
346             (5, 5, 6),
347             (6, 6, 7),
348             (7, 7, 8),
349             (8, 8, 9),
350             (9, 9, 10),
351             (10, 10, 11)
352         ]
353     ),
354     t!(
355         teddy020,
356         &["ab", "bc", "cd", "de", "ef", "fg", "gh", "hi", "ij", "jk", "kl"],
357         "abcdefghijk",
358         &[(0, 0, 2), (2, 2, 4), (4, 4, 6), (6, 6, 8), (8, 8, 10),]
359     ),
360     t!(
361         teddy030,
362         &["abc"],
363         "abcdefghijklmnopqrstuvwxyzabcdefghijk",
364         &[(0, 0, 3), (0, 26, 29)]
365     ),
366 ];
367 
368 // Now define a test for each combination of things above that we want to run.
369 // Since there are a few different combinations for each collection of tests,
370 // we define a couple of macros to avoid repetition drudgery. The testconfig
371 // macro constructs the automaton from a given match kind, and runs the search
372 // tests one-by-one over the given collection. The `with` parameter allows one
373 // to configure the config with additional parameters. The testcombo macro
374 // invokes testconfig in precisely this way: it sets up several tests where
375 // each one turns a different knob on Config.
376 
377 macro_rules! testconfig {
378     ($name:ident, $collection:expr, $with:expr) => {
379         #[test]
380         fn $name() {
381             run_search_tests($collection, |test| {
382                 let mut config = Config::new();
383                 $with(&mut config);
384                 config
385                     .builder()
386                     .extend(test.patterns.iter().map(|p| p.as_bytes()))
387                     .build()
388                     .unwrap()
389                     .find_iter(&test.haystack)
390                     .collect()
391             });
392         }
393     };
394 }
395 
396 #[cfg(target_arch = "x86_64")]
397 testconfig!(
398     search_default_leftmost_first,
399     PACKED_LEFTMOST_FIRST,
400     |_: &mut Config| {}
401 );
402 
403 #[cfg(target_arch = "x86_64")]
404 testconfig!(
405     search_default_leftmost_longest,
406     PACKED_LEFTMOST_LONGEST,
407     |c: &mut Config| {
408         c.match_kind(MatchKind::LeftmostLongest);
409     }
410 );
411 
412 #[cfg(target_arch = "x86_64")]
413 testconfig!(
414     search_teddy_leftmost_first,
415     PACKED_LEFTMOST_FIRST,
416     |c: &mut Config| {
417         c.force_teddy(true);
418     }
419 );
420 
421 #[cfg(target_arch = "x86_64")]
422 testconfig!(
423     search_teddy_leftmost_longest,
424     PACKED_LEFTMOST_LONGEST,
425     |c: &mut Config| {
426         c.force_teddy(true).match_kind(MatchKind::LeftmostLongest);
427     }
428 );
429 
430 #[cfg(target_arch = "x86_64")]
431 testconfig!(
432     search_teddy_ssse3_leftmost_first,
433     PACKED_LEFTMOST_FIRST,
434     |c: &mut Config| {
435         c.force_teddy(true);
436         if is_x86_feature_detected!("ssse3") {
437             c.force_avx(Some(false));
438         }
439     }
440 );
441 
442 #[cfg(target_arch = "x86_64")]
443 testconfig!(
444     search_teddy_ssse3_leftmost_longest,
445     PACKED_LEFTMOST_LONGEST,
446     |c: &mut Config| {
447         c.force_teddy(true).match_kind(MatchKind::LeftmostLongest);
448         if is_x86_feature_detected!("ssse3") {
449             c.force_avx(Some(false));
450         }
451     }
452 );
453 
454 #[cfg(target_arch = "x86_64")]
455 testconfig!(
456     search_teddy_avx2_leftmost_first,
457     PACKED_LEFTMOST_FIRST,
458     |c: &mut Config| {
459         c.force_teddy(true);
460         if is_x86_feature_detected!("avx2") {
461             c.force_avx(Some(true));
462         }
463     }
464 );
465 
466 #[cfg(target_arch = "x86_64")]
467 testconfig!(
468     search_teddy_avx2_leftmost_longest,
469     PACKED_LEFTMOST_LONGEST,
470     |c: &mut Config| {
471         c.force_teddy(true).match_kind(MatchKind::LeftmostLongest);
472         if is_x86_feature_detected!("avx2") {
473             c.force_avx(Some(true));
474         }
475     }
476 );
477 
478 #[cfg(target_arch = "x86_64")]
479 testconfig!(
480     search_teddy_fat_leftmost_first,
481     PACKED_LEFTMOST_FIRST,
482     |c: &mut Config| {
483         c.force_teddy(true);
484         if is_x86_feature_detected!("avx2") {
485             c.force_teddy_fat(Some(true));
486         }
487     }
488 );
489 
490 #[cfg(target_arch = "x86_64")]
491 testconfig!(
492     search_teddy_fat_leftmost_longest,
493     PACKED_LEFTMOST_LONGEST,
494     |c: &mut Config| {
495         c.force_teddy(true).match_kind(MatchKind::LeftmostLongest);
496         if is_x86_feature_detected!("avx2") {
497             c.force_teddy_fat(Some(true));
498         }
499     }
500 );
501 
502 testconfig!(
503     search_rabinkarp_leftmost_first,
504     PACKED_LEFTMOST_FIRST,
505     |c: &mut Config| {
506         c.force_rabin_karp(true);
507     }
508 );
509 
510 testconfig!(
511     search_rabinkarp_leftmost_longest,
512     PACKED_LEFTMOST_LONGEST,
513     |c: &mut Config| {
514         c.force_rabin_karp(true).match_kind(MatchKind::LeftmostLongest);
515     }
516 );
517 
518 #[test]
search_tests_have_unique_names()519 fn search_tests_have_unique_names() {
520     let assert = |constname, tests: &[SearchTest]| {
521         let mut seen = HashMap::new(); // map from test name to position
522         for (i, test) in tests.iter().enumerate() {
523             if !seen.contains_key(test.name) {
524                 seen.insert(test.name, i);
525             } else {
526                 let last = seen[test.name];
527                 panic!(
528                     "{} tests have duplicate names at positions {} and {}",
529                     constname, last, i
530                 );
531             }
532         }
533     };
534     assert("BASICS", BASICS);
535     assert("LEFTMOST", LEFTMOST);
536     assert("LEFTMOST_FIRST", LEFTMOST_FIRST);
537     assert("LEFTMOST_LONGEST", LEFTMOST_LONGEST);
538     assert("REGRESSION", REGRESSION);
539     assert("TEDDY", TEDDY);
540 }
541 
run_search_tests<F: FnMut(&SearchTestOwned) -> Vec<Match>>( which: TestCollection, mut f: F, )542 fn run_search_tests<F: FnMut(&SearchTestOwned) -> Vec<Match>>(
543     which: TestCollection,
544     mut f: F,
545 ) {
546     let get_match_triples =
547         |matches: Vec<Match>| -> Vec<(usize, usize, usize)> {
548             matches
549                 .into_iter()
550                 .map(|m| (m.pattern(), m.start(), m.end()))
551                 .collect()
552         };
553     for &tests in which {
554         for spec in tests {
555             for test in spec.variations() {
556                 assert_eq!(
557                     test.matches,
558                     get_match_triples(f(&test)).as_slice(),
559                     "test: {}, patterns: {:?}, haystack: {:?}, offset: {:?}",
560                     test.name,
561                     test.patterns,
562                     test.haystack,
563                     test.offset,
564                 );
565             }
566         }
567     }
568 }
569