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