1 use std::io::{self, Write};
2 use std::str;
3 
4 use bstr::ByteSlice;
5 use grep_matcher::{
6     LineMatchKind, LineTerminator, Match, Matcher, NoCaptures, NoError,
7 };
8 use regex::bytes::{Regex, RegexBuilder};
9 
10 use crate::searcher::{BinaryDetection, Searcher, SearcherBuilder};
11 use crate::sink::{Sink, SinkContext, SinkFinish, SinkMatch};
12 
13 /// A simple regex matcher.
14 ///
15 /// This supports setting the matcher's line terminator configuration directly,
16 /// which we use for testing purposes. That is, the caller explicitly
17 /// determines whether the line terminator optimization is enabled. (In reality
18 /// this optimization is detected automatically by inspecting and possibly
19 /// modifying the regex itself.)
20 #[derive(Clone, Debug)]
21 pub struct RegexMatcher {
22     regex: Regex,
23     line_term: Option<LineTerminator>,
24     every_line_is_candidate: bool,
25 }
26 
27 impl RegexMatcher {
28     /// Create a new regex matcher.
new(pattern: &str) -> RegexMatcher29     pub fn new(pattern: &str) -> RegexMatcher {
30         let regex = RegexBuilder::new(pattern)
31             .multi_line(true) // permits ^ and $ to match at \n boundaries
32             .build()
33             .unwrap();
34         RegexMatcher {
35             regex: regex,
36             line_term: None,
37             every_line_is_candidate: false,
38         }
39     }
40 
41     /// Forcefully set the line terminator of this matcher.
42     ///
43     /// By default, this matcher has no line terminator set.
set_line_term( &mut self, line_term: Option<LineTerminator>, ) -> &mut RegexMatcher44     pub fn set_line_term(
45         &mut self,
46         line_term: Option<LineTerminator>,
47     ) -> &mut RegexMatcher {
48         self.line_term = line_term;
49         self
50     }
51 
52     /// Whether to return every line as a candidate or not.
53     ///
54     /// This forces searchers to handle the case of reporting a false positive.
every_line_is_candidate(&mut self, yes: bool) -> &mut RegexMatcher55     pub fn every_line_is_candidate(&mut self, yes: bool) -> &mut RegexMatcher {
56         self.every_line_is_candidate = yes;
57         self
58     }
59 }
60 
61 impl Matcher for RegexMatcher {
62     type Captures = NoCaptures;
63     type Error = NoError;
64 
find_at( &self, haystack: &[u8], at: usize, ) -> Result<Option<Match>, NoError>65     fn find_at(
66         &self,
67         haystack: &[u8],
68         at: usize,
69     ) -> Result<Option<Match>, NoError> {
70         Ok(self
71             .regex
72             .find_at(haystack, at)
73             .map(|m| Match::new(m.start(), m.end())))
74     }
75 
new_captures(&self) -> Result<NoCaptures, NoError>76     fn new_captures(&self) -> Result<NoCaptures, NoError> {
77         Ok(NoCaptures::new())
78     }
79 
line_terminator(&self) -> Option<LineTerminator>80     fn line_terminator(&self) -> Option<LineTerminator> {
81         self.line_term
82     }
83 
find_candidate_line( &self, haystack: &[u8], ) -> Result<Option<LineMatchKind>, NoError>84     fn find_candidate_line(
85         &self,
86         haystack: &[u8],
87     ) -> Result<Option<LineMatchKind>, NoError> {
88         if self.every_line_is_candidate {
89             assert!(self.line_term.is_some());
90             if haystack.is_empty() {
91                 return Ok(None);
92             }
93             // Make it interesting and return the last byte in the current
94             // line.
95             let i = haystack
96                 .find_byte(self.line_term.unwrap().as_byte())
97                 .map(|i| i)
98                 .unwrap_or(haystack.len() - 1);
99             Ok(Some(LineMatchKind::Candidate(i)))
100         } else {
101             Ok(self.shortest_match(haystack)?.map(LineMatchKind::Confirmed))
102         }
103     }
104 }
105 
106 /// An implementation of Sink that prints all available information.
107 ///
108 /// This is useful for tests because it lets us easily confirm whether data
109 /// is being passed to Sink correctly.
110 #[derive(Clone, Debug)]
111 pub struct KitchenSink(Vec<u8>);
112 
113 impl KitchenSink {
114     /// Create a new implementation of Sink that includes everything in the
115     /// kitchen.
new() -> KitchenSink116     pub fn new() -> KitchenSink {
117         KitchenSink(vec![])
118     }
119 
120     /// Return the data written to this sink.
as_bytes(&self) -> &[u8]121     pub fn as_bytes(&self) -> &[u8] {
122         &self.0
123     }
124 }
125 
126 impl Sink for KitchenSink {
127     type Error = io::Error;
128 
matched( &mut self, _searcher: &Searcher, mat: &SinkMatch<'_>, ) -> Result<bool, io::Error>129     fn matched(
130         &mut self,
131         _searcher: &Searcher,
132         mat: &SinkMatch<'_>,
133     ) -> Result<bool, io::Error> {
134         assert!(!mat.bytes().is_empty());
135         assert!(mat.lines().count() >= 1);
136 
137         let mut line_number = mat.line_number();
138         let mut byte_offset = mat.absolute_byte_offset();
139         for line in mat.lines() {
140             if let Some(ref mut n) = line_number {
141                 write!(self.0, "{}:", n)?;
142                 *n += 1;
143             }
144 
145             write!(self.0, "{}:", byte_offset)?;
146             byte_offset += line.len() as u64;
147             self.0.write_all(line)?;
148         }
149         Ok(true)
150     }
151 
context( &mut self, _searcher: &Searcher, context: &SinkContext<'_>, ) -> Result<bool, io::Error>152     fn context(
153         &mut self,
154         _searcher: &Searcher,
155         context: &SinkContext<'_>,
156     ) -> Result<bool, io::Error> {
157         assert!(!context.bytes().is_empty());
158         assert!(context.lines().count() == 1);
159 
160         if let Some(line_number) = context.line_number() {
161             write!(self.0, "{}-", line_number)?;
162         }
163         write!(self.0, "{}-", context.absolute_byte_offset)?;
164         self.0.write_all(context.bytes())?;
165         Ok(true)
166     }
167 
context_break( &mut self, _searcher: &Searcher, ) -> Result<bool, io::Error>168     fn context_break(
169         &mut self,
170         _searcher: &Searcher,
171     ) -> Result<bool, io::Error> {
172         self.0.write_all(b"--\n")?;
173         Ok(true)
174     }
175 
finish( &mut self, _searcher: &Searcher, sink_finish: &SinkFinish, ) -> Result<(), io::Error>176     fn finish(
177         &mut self,
178         _searcher: &Searcher,
179         sink_finish: &SinkFinish,
180     ) -> Result<(), io::Error> {
181         writeln!(self.0, "")?;
182         writeln!(self.0, "byte count:{}", sink_finish.byte_count())?;
183         if let Some(offset) = sink_finish.binary_byte_offset() {
184             writeln!(self.0, "binary offset:{}", offset)?;
185         }
186         Ok(())
187     }
188 }
189 
190 /// A type for expressing tests on a searcher.
191 ///
192 /// The searcher code has a lot of different code paths, mostly for the
193 /// purposes of optimizing a bunch of different use cases. The intent of the
194 /// searcher is to pick the best code path based on the configuration, which
195 /// means there is no obviously direct way to ask that a specific code path
196 /// be exercised. Thus, the purpose of this tester is to explicitly check as
197 /// many code paths that make sense.
198 ///
199 /// The tester works by assuming you want to test all pertinent code paths.
200 /// These can be trimmed down as necessary via the various builder methods.
201 #[derive(Debug)]
202 pub struct SearcherTester {
203     haystack: String,
204     pattern: String,
205     filter: Option<::regex::Regex>,
206     print_labels: bool,
207     expected_no_line_number: Option<String>,
208     expected_with_line_number: Option<String>,
209     expected_slice_no_line_number: Option<String>,
210     expected_slice_with_line_number: Option<String>,
211     by_line: bool,
212     multi_line: bool,
213     invert_match: bool,
214     line_number: bool,
215     binary: BinaryDetection,
216     auto_heap_limit: bool,
217     after_context: usize,
218     before_context: usize,
219     passthru: bool,
220 }
221 
222 impl SearcherTester {
223     /// Create a new tester for testing searchers.
new(haystack: &str, pattern: &str) -> SearcherTester224     pub fn new(haystack: &str, pattern: &str) -> SearcherTester {
225         SearcherTester {
226             haystack: haystack.to_string(),
227             pattern: pattern.to_string(),
228             filter: None,
229             print_labels: false,
230             expected_no_line_number: None,
231             expected_with_line_number: None,
232             expected_slice_no_line_number: None,
233             expected_slice_with_line_number: None,
234             by_line: true,
235             multi_line: true,
236             invert_match: false,
237             line_number: true,
238             binary: BinaryDetection::none(),
239             auto_heap_limit: true,
240             after_context: 0,
241             before_context: 0,
242             passthru: false,
243         }
244     }
245 
246     /// Execute the test. If the test succeeds, then this returns successfully.
247     /// If the test fails, then it panics with an informative message.
test(&self)248     pub fn test(&self) {
249         // Check for configuration errors.
250         if self.expected_no_line_number.is_none() {
251             panic!("an 'expected' string with NO line numbers must be given");
252         }
253         if self.line_number && self.expected_with_line_number.is_none() {
254             panic!(
255                 "an 'expected' string with line numbers must be given, \
256                     or disable testing with line numbers"
257             );
258         }
259 
260         let configs = self.configs();
261         if configs.is_empty() {
262             panic!("test configuration resulted in nothing being tested");
263         }
264         if self.print_labels {
265             for config in &configs {
266                 let labels = vec![
267                     format!("reader-{}", config.label),
268                     format!("slice-{}", config.label),
269                 ];
270                 for label in &labels {
271                     if self.include(label) {
272                         println!("{}", label);
273                     } else {
274                         println!("{} (ignored)", label);
275                     }
276                 }
277             }
278         }
279         for config in &configs {
280             let label = format!("reader-{}", config.label);
281             if self.include(&label) {
282                 let got = config.search_reader(&self.haystack);
283                 assert_eq_printed!(config.expected_reader, got, "{}", label);
284             }
285 
286             let label = format!("slice-{}", config.label);
287             if self.include(&label) {
288                 let got = config.search_slice(&self.haystack);
289                 assert_eq_printed!(config.expected_slice, got, "{}", label);
290             }
291         }
292     }
293 
294     /// Set a regex pattern to filter the tests that are run.
295     ///
296     /// By default, no filter is present. When a filter is set, only test
297     /// configurations with a label matching the given pattern will be run.
298     ///
299     /// This is often useful when debugging tests, e.g., when you want to do
300     /// printf debugging and only want one particular test configuration to
301     /// execute.
302     #[allow(dead_code)]
filter(&mut self, pattern: &str) -> &mut SearcherTester303     pub fn filter(&mut self, pattern: &str) -> &mut SearcherTester {
304         self.filter = Some(::regex::Regex::new(pattern).unwrap());
305         self
306     }
307 
308     /// When set, the labels for all test configurations are printed before
309     /// executing any test.
310     ///
311     /// Note that in order to see these in tests that aren't failing, you'll
312     /// want to use `cargo test -- --nocapture`.
313     #[allow(dead_code)]
print_labels(&mut self, yes: bool) -> &mut SearcherTester314     pub fn print_labels(&mut self, yes: bool) -> &mut SearcherTester {
315         self.print_labels = yes;
316         self
317     }
318 
319     /// Set the expected search results, without line numbers.
expected_no_line_number( &mut self, exp: &str, ) -> &mut SearcherTester320     pub fn expected_no_line_number(
321         &mut self,
322         exp: &str,
323     ) -> &mut SearcherTester {
324         self.expected_no_line_number = Some(exp.to_string());
325         self
326     }
327 
328     /// Set the expected search results, with line numbers.
expected_with_line_number( &mut self, exp: &str, ) -> &mut SearcherTester329     pub fn expected_with_line_number(
330         &mut self,
331         exp: &str,
332     ) -> &mut SearcherTester {
333         self.expected_with_line_number = Some(exp.to_string());
334         self
335     }
336 
337     /// Set the expected search results, without line numbers, when performing
338     /// a search on a slice. When not present, `expected_no_line_number` is
339     /// used instead.
expected_slice_no_line_number( &mut self, exp: &str, ) -> &mut SearcherTester340     pub fn expected_slice_no_line_number(
341         &mut self,
342         exp: &str,
343     ) -> &mut SearcherTester {
344         self.expected_slice_no_line_number = Some(exp.to_string());
345         self
346     }
347 
348     /// Set the expected search results, with line numbers, when performing a
349     /// search on a slice. When not present, `expected_with_line_number` is
350     /// used instead.
351     #[allow(dead_code)]
expected_slice_with_line_number( &mut self, exp: &str, ) -> &mut SearcherTester352     pub fn expected_slice_with_line_number(
353         &mut self,
354         exp: &str,
355     ) -> &mut SearcherTester {
356         self.expected_slice_with_line_number = Some(exp.to_string());
357         self
358     }
359 
360     /// Whether to test search with line numbers or not.
361     ///
362     /// This is enabled by default. When enabled, the string that is expected
363     /// when line numbers are present must be provided. Otherwise, the expected
364     /// string isn't required.
line_number(&mut self, yes: bool) -> &mut SearcherTester365     pub fn line_number(&mut self, yes: bool) -> &mut SearcherTester {
366         self.line_number = yes;
367         self
368     }
369 
370     /// Whether to test search using the line-by-line searcher or not.
371     ///
372     /// By default, this is enabled.
by_line(&mut self, yes: bool) -> &mut SearcherTester373     pub fn by_line(&mut self, yes: bool) -> &mut SearcherTester {
374         self.by_line = yes;
375         self
376     }
377 
378     /// Whether to test search using the multi line searcher or not.
379     ///
380     /// By default, this is enabled.
381     #[allow(dead_code)]
multi_line(&mut self, yes: bool) -> &mut SearcherTester382     pub fn multi_line(&mut self, yes: bool) -> &mut SearcherTester {
383         self.multi_line = yes;
384         self
385     }
386 
387     /// Whether to perform an inverted search or not.
388     ///
389     /// By default, this is disabled.
invert_match(&mut self, yes: bool) -> &mut SearcherTester390     pub fn invert_match(&mut self, yes: bool) -> &mut SearcherTester {
391         self.invert_match = yes;
392         self
393     }
394 
395     /// Whether to enable binary detection on all searches.
396     ///
397     /// By default, this is disabled.
binary_detection( &mut self, detection: BinaryDetection, ) -> &mut SearcherTester398     pub fn binary_detection(
399         &mut self,
400         detection: BinaryDetection,
401     ) -> &mut SearcherTester {
402         self.binary = detection;
403         self
404     }
405 
406     /// Whether to automatically attempt to test the heap limit setting or not.
407     ///
408     /// By default, one of the test configurations includes setting the heap
409     /// limit to its minimal value for normal operation, which checks that
410     /// everything works even at the extremes. However, in some cases, the heap
411     /// limit can (expectedly) alter the output slightly. For example, it can
412     /// impact the number of bytes searched when performing binary detection.
413     /// For convenience, it can be useful to disable the automatic heap limit
414     /// test.
auto_heap_limit(&mut self, yes: bool) -> &mut SearcherTester415     pub fn auto_heap_limit(&mut self, yes: bool) -> &mut SearcherTester {
416         self.auto_heap_limit = yes;
417         self
418     }
419 
420     /// Set the number of lines to include in the "after" context.
421     ///
422     /// The default is `0`, which is equivalent to not printing any context.
after_context(&mut self, lines: usize) -> &mut SearcherTester423     pub fn after_context(&mut self, lines: usize) -> &mut SearcherTester {
424         self.after_context = lines;
425         self
426     }
427 
428     /// Set the number of lines to include in the "before" context.
429     ///
430     /// The default is `0`, which is equivalent to not printing any context.
before_context(&mut self, lines: usize) -> &mut SearcherTester431     pub fn before_context(&mut self, lines: usize) -> &mut SearcherTester {
432         self.before_context = lines;
433         self
434     }
435 
436     /// Whether to enable the "passthru" feature or not.
437     ///
438     /// When passthru is enabled, it effectively treats all non-matching lines
439     /// as contextual lines. In other words, enabling this is akin to
440     /// requesting an unbounded number of before and after contextual lines.
441     ///
442     /// This is disabled by default.
passthru(&mut self, yes: bool) -> &mut SearcherTester443     pub fn passthru(&mut self, yes: bool) -> &mut SearcherTester {
444         self.passthru = yes;
445         self
446     }
447 
448     /// Return the minimum size of a buffer required for a successful search.
449     ///
450     /// Generally, this corresponds to the maximum length of a line (including
451     /// its terminator), but if context settings are enabled, then this must
452     /// include the sum of the longest N lines.
453     ///
454     /// Note that this must account for whether the test is using multi line
455     /// search or not, since multi line search requires being able to fit the
456     /// entire haystack into memory.
minimal_heap_limit(&self, multi_line: bool) -> usize457     fn minimal_heap_limit(&self, multi_line: bool) -> usize {
458         if multi_line {
459             1 + self.haystack.len()
460         } else if self.before_context == 0 && self.after_context == 0 {
461             1 + self.haystack.lines().map(|s| s.len()).max().unwrap_or(0)
462         } else {
463             let mut lens: Vec<usize> =
464                 self.haystack.lines().map(|s| s.len()).collect();
465             lens.sort();
466             lens.reverse();
467 
468             let context_count = if self.passthru {
469                 self.haystack.lines().count()
470             } else {
471                 // Why do we add 2 here? Well, we need to add 1 in order to
472                 // have room to search at least one line. We add another
473                 // because the implementation will occasionally include
474                 // an additional line when handling the context. There's
475                 // no particularly good reason, other than keeping the
476                 // implementation simple.
477                 2 + self.before_context + self.after_context
478             };
479 
480             // We add 1 to each line since `str::lines` doesn't include the
481             // line terminator.
482             lens.into_iter()
483                 .take(context_count)
484                 .map(|len| len + 1)
485                 .sum::<usize>()
486         }
487     }
488 
489     /// Returns true if and only if the given label should be included as part
490     /// of executing `test`.
491     ///
492     /// Inclusion is determined by the filter specified. If no filter has been
493     /// given, then this always returns `true`.
include(&self, label: &str) -> bool494     fn include(&self, label: &str) -> bool {
495         let re = match self.filter {
496             None => return true,
497             Some(ref re) => re,
498         };
499         re.is_match(label)
500     }
501 
502     /// Configs generates a set of all search configurations that should be
503     /// tested. The configs generated are based on the configuration in this
504     /// builder.
configs(&self) -> Vec<TesterConfig>505     fn configs(&self) -> Vec<TesterConfig> {
506         let mut configs = vec![];
507 
508         let matcher = RegexMatcher::new(&self.pattern);
509         let mut builder = SearcherBuilder::new();
510         builder
511             .line_number(false)
512             .invert_match(self.invert_match)
513             .binary_detection(self.binary.clone())
514             .after_context(self.after_context)
515             .before_context(self.before_context)
516             .passthru(self.passthru);
517 
518         if self.by_line {
519             let mut matcher = matcher.clone();
520             let mut builder = builder.clone();
521 
522             let expected_reader =
523                 self.expected_no_line_number.as_ref().unwrap().to_string();
524             let expected_slice = match self.expected_slice_no_line_number {
525                 None => expected_reader.clone(),
526                 Some(ref e) => e.to_string(),
527             };
528             configs.push(TesterConfig {
529                 label: "byline-noterm-nonumber".to_string(),
530                 expected_reader: expected_reader.clone(),
531                 expected_slice: expected_slice.clone(),
532                 builder: builder.clone(),
533                 matcher: matcher.clone(),
534             });
535 
536             if self.auto_heap_limit {
537                 builder.heap_limit(Some(self.minimal_heap_limit(false)));
538                 configs.push(TesterConfig {
539                     label: "byline-noterm-nonumber-heaplimit".to_string(),
540                     expected_reader: expected_reader.clone(),
541                     expected_slice: expected_slice.clone(),
542                     builder: builder.clone(),
543                     matcher: matcher.clone(),
544                 });
545                 builder.heap_limit(None);
546             }
547 
548             matcher.set_line_term(Some(LineTerminator::byte(b'\n')));
549             configs.push(TesterConfig {
550                 label: "byline-term-nonumber".to_string(),
551                 expected_reader: expected_reader.clone(),
552                 expected_slice: expected_slice.clone(),
553                 builder: builder.clone(),
554                 matcher: matcher.clone(),
555             });
556 
557             matcher.every_line_is_candidate(true);
558             configs.push(TesterConfig {
559                 label: "byline-term-nonumber-candidates".to_string(),
560                 expected_reader: expected_reader.clone(),
561                 expected_slice: expected_slice.clone(),
562                 builder: builder.clone(),
563                 matcher: matcher.clone(),
564             });
565         }
566         if self.by_line && self.line_number {
567             let mut matcher = matcher.clone();
568             let mut builder = builder.clone();
569 
570             let expected_reader =
571                 self.expected_with_line_number.as_ref().unwrap().to_string();
572             let expected_slice = match self.expected_slice_with_line_number {
573                 None => expected_reader.clone(),
574                 Some(ref e) => e.to_string(),
575             };
576 
577             builder.line_number(true);
578             configs.push(TesterConfig {
579                 label: "byline-noterm-number".to_string(),
580                 expected_reader: expected_reader.clone(),
581                 expected_slice: expected_slice.clone(),
582                 builder: builder.clone(),
583                 matcher: matcher.clone(),
584             });
585 
586             matcher.set_line_term(Some(LineTerminator::byte(b'\n')));
587             configs.push(TesterConfig {
588                 label: "byline-term-number".to_string(),
589                 expected_reader: expected_reader.clone(),
590                 expected_slice: expected_slice.clone(),
591                 builder: builder.clone(),
592                 matcher: matcher.clone(),
593             });
594 
595             matcher.every_line_is_candidate(true);
596             configs.push(TesterConfig {
597                 label: "byline-term-number-candidates".to_string(),
598                 expected_reader: expected_reader.clone(),
599                 expected_slice: expected_slice.clone(),
600                 builder: builder.clone(),
601                 matcher: matcher.clone(),
602             });
603         }
604         if self.multi_line {
605             let mut builder = builder.clone();
606             let expected_slice = match self.expected_slice_no_line_number {
607                 None => {
608                     self.expected_no_line_number.as_ref().unwrap().to_string()
609                 }
610                 Some(ref e) => e.to_string(),
611             };
612 
613             builder.multi_line(true);
614             configs.push(TesterConfig {
615                 label: "multiline-nonumber".to_string(),
616                 expected_reader: expected_slice.clone(),
617                 expected_slice: expected_slice.clone(),
618                 builder: builder.clone(),
619                 matcher: matcher.clone(),
620             });
621 
622             if self.auto_heap_limit {
623                 builder.heap_limit(Some(self.minimal_heap_limit(true)));
624                 configs.push(TesterConfig {
625                     label: "multiline-nonumber-heaplimit".to_string(),
626                     expected_reader: expected_slice.clone(),
627                     expected_slice: expected_slice.clone(),
628                     builder: builder.clone(),
629                     matcher: matcher.clone(),
630                 });
631                 builder.heap_limit(None);
632             }
633         }
634         if self.multi_line && self.line_number {
635             let mut builder = builder.clone();
636             let expected_slice = match self.expected_slice_with_line_number {
637                 None => self
638                     .expected_with_line_number
639                     .as_ref()
640                     .unwrap()
641                     .to_string(),
642                 Some(ref e) => e.to_string(),
643             };
644 
645             builder.multi_line(true);
646             builder.line_number(true);
647             configs.push(TesterConfig {
648                 label: "multiline-number".to_string(),
649                 expected_reader: expected_slice.clone(),
650                 expected_slice: expected_slice.clone(),
651                 builder: builder.clone(),
652                 matcher: matcher.clone(),
653             });
654 
655             builder.heap_limit(Some(self.minimal_heap_limit(true)));
656             configs.push(TesterConfig {
657                 label: "multiline-number-heaplimit".to_string(),
658                 expected_reader: expected_slice.clone(),
659                 expected_slice: expected_slice.clone(),
660                 builder: builder.clone(),
661                 matcher: matcher.clone(),
662             });
663             builder.heap_limit(None);
664         }
665         configs
666     }
667 }
668 
669 #[derive(Debug)]
670 struct TesterConfig {
671     label: String,
672     expected_reader: String,
673     expected_slice: String,
674     builder: SearcherBuilder,
675     matcher: RegexMatcher,
676 }
677 
678 impl TesterConfig {
679     /// Execute a search using a reader. This exercises the incremental search
680     /// strategy, where the entire contents of the corpus aren't necessarily
681     /// in memory at once.
search_reader(&self, haystack: &str) -> String682     fn search_reader(&self, haystack: &str) -> String {
683         let mut sink = KitchenSink::new();
684         let mut searcher = self.builder.build();
685         let result = searcher.search_reader(
686             &self.matcher,
687             haystack.as_bytes(),
688             &mut sink,
689         );
690         if let Err(err) = result {
691             let label = format!("reader-{}", self.label);
692             panic!("error running '{}': {}", label, err);
693         }
694         String::from_utf8(sink.as_bytes().to_vec()).unwrap()
695     }
696 
697     /// Execute a search using a slice. This exercises the search routines that
698     /// have the entire contents of the corpus in memory at one time.
search_slice(&self, haystack: &str) -> String699     fn search_slice(&self, haystack: &str) -> String {
700         let mut sink = KitchenSink::new();
701         let mut searcher = self.builder.build();
702         let result = searcher.search_slice(
703             &self.matcher,
704             haystack.as_bytes(),
705             &mut sink,
706         );
707         if let Err(err) = result {
708             let label = format!("slice-{}", self.label);
709             panic!("error running '{}': {}", label, err);
710         }
711         String::from_utf8(sink.as_bytes().to_vec()).unwrap()
712     }
713 }
714 
715 #[cfg(test)]
716 mod tests {
717     use grep_matcher::{Match, Matcher};
718 
719     use super::*;
720 
m(start: usize, end: usize) -> Match721     fn m(start: usize, end: usize) -> Match {
722         Match::new(start, end)
723     }
724 
725     #[test]
empty_line1()726     fn empty_line1() {
727         let haystack = b"";
728         let matcher = RegexMatcher::new(r"^$");
729 
730         assert_eq!(matcher.find_at(haystack, 0), Ok(Some(m(0, 0))));
731     }
732 
733     #[test]
empty_line2()734     fn empty_line2() {
735         let haystack = b"\n";
736         let matcher = RegexMatcher::new(r"^$");
737 
738         assert_eq!(matcher.find_at(haystack, 0), Ok(Some(m(0, 0))));
739         assert_eq!(matcher.find_at(haystack, 1), Ok(Some(m(1, 1))));
740     }
741 
742     #[test]
empty_line3()743     fn empty_line3() {
744         let haystack = b"\n\n";
745         let matcher = RegexMatcher::new(r"^$");
746 
747         assert_eq!(matcher.find_at(haystack, 0), Ok(Some(m(0, 0))));
748         assert_eq!(matcher.find_at(haystack, 1), Ok(Some(m(1, 1))));
749         assert_eq!(matcher.find_at(haystack, 2), Ok(Some(m(2, 2))));
750     }
751 
752     #[test]
empty_line4()753     fn empty_line4() {
754         let haystack = b"a\n\nb\n";
755         let matcher = RegexMatcher::new(r"^$");
756 
757         assert_eq!(matcher.find_at(haystack, 0), Ok(Some(m(2, 2))));
758         assert_eq!(matcher.find_at(haystack, 1), Ok(Some(m(2, 2))));
759         assert_eq!(matcher.find_at(haystack, 2), Ok(Some(m(2, 2))));
760         assert_eq!(matcher.find_at(haystack, 3), Ok(Some(m(5, 5))));
761         assert_eq!(matcher.find_at(haystack, 4), Ok(Some(m(5, 5))));
762         assert_eq!(matcher.find_at(haystack, 5), Ok(Some(m(5, 5))));
763     }
764 
765     #[test]
empty_line5()766     fn empty_line5() {
767         let haystack = b"a\n\nb\nc";
768         let matcher = RegexMatcher::new(r"^$");
769 
770         assert_eq!(matcher.find_at(haystack, 0), Ok(Some(m(2, 2))));
771         assert_eq!(matcher.find_at(haystack, 1), Ok(Some(m(2, 2))));
772         assert_eq!(matcher.find_at(haystack, 2), Ok(Some(m(2, 2))));
773         assert_eq!(matcher.find_at(haystack, 3), Ok(None));
774         assert_eq!(matcher.find_at(haystack, 4), Ok(None));
775         assert_eq!(matcher.find_at(haystack, 5), Ok(None));
776         assert_eq!(matcher.find_at(haystack, 6), Ok(None));
777     }
778 
779     #[test]
empty_line6()780     fn empty_line6() {
781         let haystack = b"a\n";
782         let matcher = RegexMatcher::new(r"^$");
783 
784         assert_eq!(matcher.find_at(haystack, 0), Ok(Some(m(2, 2))));
785         assert_eq!(matcher.find_at(haystack, 1), Ok(Some(m(2, 2))));
786         assert_eq!(matcher.find_at(haystack, 2), Ok(Some(m(2, 2))));
787     }
788 }
789