mirror of
https://github.com/BurntSushi/ripgrep.git
synced 2025-04-02 20:45:38 +02:00
The top-level listing was just getting a bit too long for my taste. So put all of the code in one directory and shrink the large top-level mess to a small top-level mess. NOTE: This commit only contains renames. The subsequent commit will actually make ripgrep build again. We do it this way with the naive hope that this will make it easier for git history to track the renames. Sigh.
789 lines
27 KiB
Rust
789 lines
27 KiB
Rust
use std::io::{self, Write};
|
|
use std::str;
|
|
|
|
use bstr::ByteSlice;
|
|
use grep_matcher::{
|
|
LineMatchKind, LineTerminator, Match, Matcher, NoCaptures, NoError,
|
|
};
|
|
use regex::bytes::{Regex, RegexBuilder};
|
|
|
|
use searcher::{BinaryDetection, Searcher, SearcherBuilder};
|
|
use sink::{Sink, SinkContext, SinkFinish, SinkMatch};
|
|
|
|
/// A simple regex matcher.
|
|
///
|
|
/// This supports setting the matcher's line terminator configuration directly,
|
|
/// which we use for testing purposes. That is, the caller explicitly
|
|
/// determines whether the line terminator optimization is enabled. (In reality
|
|
/// this optimization is detected automatically by inspecting and possibly
|
|
/// modifying the regex itself.)
|
|
#[derive(Clone, Debug)]
|
|
pub struct RegexMatcher {
|
|
regex: Regex,
|
|
line_term: Option<LineTerminator>,
|
|
every_line_is_candidate: bool,
|
|
}
|
|
|
|
impl RegexMatcher {
|
|
/// Create a new regex matcher.
|
|
pub fn new(pattern: &str) -> RegexMatcher {
|
|
let regex = RegexBuilder::new(pattern)
|
|
.multi_line(true) // permits ^ and $ to match at \n boundaries
|
|
.build()
|
|
.unwrap();
|
|
RegexMatcher {
|
|
regex: regex,
|
|
line_term: None,
|
|
every_line_is_candidate: false,
|
|
}
|
|
}
|
|
|
|
/// Forcefully set the line terminator of this matcher.
|
|
///
|
|
/// By default, this matcher has no line terminator set.
|
|
pub fn set_line_term(
|
|
&mut self,
|
|
line_term: Option<LineTerminator>,
|
|
) -> &mut RegexMatcher {
|
|
self.line_term = line_term;
|
|
self
|
|
}
|
|
|
|
/// Whether to return every line as a candidate or not.
|
|
///
|
|
/// This forces searchers to handle the case of reporting a false positive.
|
|
pub fn every_line_is_candidate(&mut self, yes: bool) -> &mut RegexMatcher {
|
|
self.every_line_is_candidate = yes;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl Matcher for RegexMatcher {
|
|
type Captures = NoCaptures;
|
|
type Error = NoError;
|
|
|
|
fn find_at(
|
|
&self,
|
|
haystack: &[u8],
|
|
at: usize,
|
|
) -> Result<Option<Match>, NoError> {
|
|
Ok(self
|
|
.regex
|
|
.find_at(haystack, at)
|
|
.map(|m| Match::new(m.start(), m.end())))
|
|
}
|
|
|
|
fn new_captures(&self) -> Result<NoCaptures, NoError> {
|
|
Ok(NoCaptures::new())
|
|
}
|
|
|
|
fn line_terminator(&self) -> Option<LineTerminator> {
|
|
self.line_term
|
|
}
|
|
|
|
fn find_candidate_line(
|
|
&self,
|
|
haystack: &[u8],
|
|
) -> Result<Option<LineMatchKind>, NoError> {
|
|
if self.every_line_is_candidate {
|
|
assert!(self.line_term.is_some());
|
|
if haystack.is_empty() {
|
|
return Ok(None);
|
|
}
|
|
// Make it interesting and return the last byte in the current
|
|
// line.
|
|
let i = haystack
|
|
.find_byte(self.line_term.unwrap().as_byte())
|
|
.map(|i| i)
|
|
.unwrap_or(haystack.len() - 1);
|
|
Ok(Some(LineMatchKind::Candidate(i)))
|
|
} else {
|
|
Ok(self.shortest_match(haystack)?.map(LineMatchKind::Confirmed))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An implementation of Sink that prints all available information.
|
|
///
|
|
/// This is useful for tests because it lets us easily confirm whether data
|
|
/// is being passed to Sink correctly.
|
|
#[derive(Clone, Debug)]
|
|
pub struct KitchenSink(Vec<u8>);
|
|
|
|
impl KitchenSink {
|
|
/// Create a new implementation of Sink that includes everything in the
|
|
/// kitchen.
|
|
pub fn new() -> KitchenSink {
|
|
KitchenSink(vec![])
|
|
}
|
|
|
|
/// Return the data written to this sink.
|
|
pub fn as_bytes(&self) -> &[u8] {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl Sink for KitchenSink {
|
|
type Error = io::Error;
|
|
|
|
fn matched(
|
|
&mut self,
|
|
_searcher: &Searcher,
|
|
mat: &SinkMatch,
|
|
) -> Result<bool, io::Error> {
|
|
assert!(!mat.bytes().is_empty());
|
|
assert!(mat.lines().count() >= 1);
|
|
|
|
let mut line_number = mat.line_number();
|
|
let mut byte_offset = mat.absolute_byte_offset();
|
|
for line in mat.lines() {
|
|
if let Some(ref mut n) = line_number {
|
|
write!(self.0, "{}:", n)?;
|
|
*n += 1;
|
|
}
|
|
|
|
write!(self.0, "{}:", byte_offset)?;
|
|
byte_offset += line.len() as u64;
|
|
self.0.write_all(line)?;
|
|
}
|
|
Ok(true)
|
|
}
|
|
|
|
fn context(
|
|
&mut self,
|
|
_searcher: &Searcher,
|
|
context: &SinkContext,
|
|
) -> Result<bool, io::Error> {
|
|
assert!(!context.bytes().is_empty());
|
|
assert!(context.lines().count() == 1);
|
|
|
|
if let Some(line_number) = context.line_number() {
|
|
write!(self.0, "{}-", line_number)?;
|
|
}
|
|
write!(self.0, "{}-", context.absolute_byte_offset)?;
|
|
self.0.write_all(context.bytes())?;
|
|
Ok(true)
|
|
}
|
|
|
|
fn context_break(
|
|
&mut self,
|
|
_searcher: &Searcher,
|
|
) -> Result<bool, io::Error> {
|
|
self.0.write_all(b"--\n")?;
|
|
Ok(true)
|
|
}
|
|
|
|
fn finish(
|
|
&mut self,
|
|
_searcher: &Searcher,
|
|
sink_finish: &SinkFinish,
|
|
) -> Result<(), io::Error> {
|
|
writeln!(self.0, "")?;
|
|
writeln!(self.0, "byte count:{}", sink_finish.byte_count())?;
|
|
if let Some(offset) = sink_finish.binary_byte_offset() {
|
|
writeln!(self.0, "binary offset:{}", offset)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// A type for expressing tests on a searcher.
|
|
///
|
|
/// The searcher code has a lot of different code paths, mostly for the
|
|
/// purposes of optimizing a bunch of different use cases. The intent of the
|
|
/// searcher is to pick the best code path based on the configuration, which
|
|
/// means there is no obviously direct way to ask that a specific code path
|
|
/// be exercised. Thus, the purpose of this tester is to explicitly check as
|
|
/// many code paths that make sense.
|
|
///
|
|
/// The tester works by assuming you want to test all pertinent code paths.
|
|
/// These can be trimmed down as necessary via the various builder methods.
|
|
#[derive(Debug)]
|
|
pub struct SearcherTester {
|
|
haystack: String,
|
|
pattern: String,
|
|
filter: Option<::regex::Regex>,
|
|
print_labels: bool,
|
|
expected_no_line_number: Option<String>,
|
|
expected_with_line_number: Option<String>,
|
|
expected_slice_no_line_number: Option<String>,
|
|
expected_slice_with_line_number: Option<String>,
|
|
by_line: bool,
|
|
multi_line: bool,
|
|
invert_match: bool,
|
|
line_number: bool,
|
|
binary: BinaryDetection,
|
|
auto_heap_limit: bool,
|
|
after_context: usize,
|
|
before_context: usize,
|
|
passthru: bool,
|
|
}
|
|
|
|
impl SearcherTester {
|
|
/// Create a new tester for testing searchers.
|
|
pub fn new(haystack: &str, pattern: &str) -> SearcherTester {
|
|
SearcherTester {
|
|
haystack: haystack.to_string(),
|
|
pattern: pattern.to_string(),
|
|
filter: None,
|
|
print_labels: false,
|
|
expected_no_line_number: None,
|
|
expected_with_line_number: None,
|
|
expected_slice_no_line_number: None,
|
|
expected_slice_with_line_number: None,
|
|
by_line: true,
|
|
multi_line: true,
|
|
invert_match: false,
|
|
line_number: true,
|
|
binary: BinaryDetection::none(),
|
|
auto_heap_limit: true,
|
|
after_context: 0,
|
|
before_context: 0,
|
|
passthru: false,
|
|
}
|
|
}
|
|
|
|
/// Execute the test. If the test succeeds, then this returns successfully.
|
|
/// If the test fails, then it panics with an informative message.
|
|
pub fn test(&self) {
|
|
// Check for configuration errors.
|
|
if self.expected_no_line_number.is_none() {
|
|
panic!("an 'expected' string with NO line numbers must be given");
|
|
}
|
|
if self.line_number && self.expected_with_line_number.is_none() {
|
|
panic!(
|
|
"an 'expected' string with line numbers must be given, \
|
|
or disable testing with line numbers"
|
|
);
|
|
}
|
|
|
|
let configs = self.configs();
|
|
if configs.is_empty() {
|
|
panic!("test configuration resulted in nothing being tested");
|
|
}
|
|
if self.print_labels {
|
|
for config in &configs {
|
|
let labels = vec![
|
|
format!("reader-{}", config.label),
|
|
format!("slice-{}", config.label),
|
|
];
|
|
for label in &labels {
|
|
if self.include(label) {
|
|
println!("{}", label);
|
|
} else {
|
|
println!("{} (ignored)", label);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for config in &configs {
|
|
let label = format!("reader-{}", config.label);
|
|
if self.include(&label) {
|
|
let got = config.search_reader(&self.haystack);
|
|
assert_eq_printed!(config.expected_reader, got, "{}", label);
|
|
}
|
|
|
|
let label = format!("slice-{}", config.label);
|
|
if self.include(&label) {
|
|
let got = config.search_slice(&self.haystack);
|
|
assert_eq_printed!(config.expected_slice, got, "{}", label);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Set a regex pattern to filter the tests that are run.
|
|
///
|
|
/// By default, no filter is present. When a filter is set, only test
|
|
/// configurations with a label matching the given pattern will be run.
|
|
///
|
|
/// This is often useful when debugging tests, e.g., when you want to do
|
|
/// printf debugging and only want one particular test configuration to
|
|
/// execute.
|
|
#[allow(dead_code)]
|
|
pub fn filter(&mut self, pattern: &str) -> &mut SearcherTester {
|
|
self.filter = Some(::regex::Regex::new(pattern).unwrap());
|
|
self
|
|
}
|
|
|
|
/// When set, the labels for all test configurations are printed before
|
|
/// executing any test.
|
|
///
|
|
/// Note that in order to see these in tests that aren't failing, you'll
|
|
/// want to use `cargo test -- --nocapture`.
|
|
#[allow(dead_code)]
|
|
pub fn print_labels(&mut self, yes: bool) -> &mut SearcherTester {
|
|
self.print_labels = yes;
|
|
self
|
|
}
|
|
|
|
/// Set the expected search results, without line numbers.
|
|
pub fn expected_no_line_number(
|
|
&mut self,
|
|
exp: &str,
|
|
) -> &mut SearcherTester {
|
|
self.expected_no_line_number = Some(exp.to_string());
|
|
self
|
|
}
|
|
|
|
/// Set the expected search results, with line numbers.
|
|
pub fn expected_with_line_number(
|
|
&mut self,
|
|
exp: &str,
|
|
) -> &mut SearcherTester {
|
|
self.expected_with_line_number = Some(exp.to_string());
|
|
self
|
|
}
|
|
|
|
/// Set the expected search results, without line numbers, when performing
|
|
/// a search on a slice. When not present, `expected_no_line_number` is
|
|
/// used instead.
|
|
pub fn expected_slice_no_line_number(
|
|
&mut self,
|
|
exp: &str,
|
|
) -> &mut SearcherTester {
|
|
self.expected_slice_no_line_number = Some(exp.to_string());
|
|
self
|
|
}
|
|
|
|
/// Set the expected search results, with line numbers, when performing a
|
|
/// search on a slice. When not present, `expected_with_line_number` is
|
|
/// used instead.
|
|
#[allow(dead_code)]
|
|
pub fn expected_slice_with_line_number(
|
|
&mut self,
|
|
exp: &str,
|
|
) -> &mut SearcherTester {
|
|
self.expected_slice_with_line_number = Some(exp.to_string());
|
|
self
|
|
}
|
|
|
|
/// Whether to test search with line numbers or not.
|
|
///
|
|
/// This is enabled by default. When enabled, the string that is expected
|
|
/// when line numbers are present must be provided. Otherwise, the expected
|
|
/// string isn't required.
|
|
pub fn line_number(&mut self, yes: bool) -> &mut SearcherTester {
|
|
self.line_number = yes;
|
|
self
|
|
}
|
|
|
|
/// Whether to test search using the line-by-line searcher or not.
|
|
///
|
|
/// By default, this is enabled.
|
|
pub fn by_line(&mut self, yes: bool) -> &mut SearcherTester {
|
|
self.by_line = yes;
|
|
self
|
|
}
|
|
|
|
/// Whether to test search using the multi line searcher or not.
|
|
///
|
|
/// By default, this is enabled.
|
|
#[allow(dead_code)]
|
|
pub fn multi_line(&mut self, yes: bool) -> &mut SearcherTester {
|
|
self.multi_line = yes;
|
|
self
|
|
}
|
|
|
|
/// Whether to perform an inverted search or not.
|
|
///
|
|
/// By default, this is disabled.
|
|
pub fn invert_match(&mut self, yes: bool) -> &mut SearcherTester {
|
|
self.invert_match = yes;
|
|
self
|
|
}
|
|
|
|
/// Whether to enable binary detection on all searches.
|
|
///
|
|
/// By default, this is disabled.
|
|
pub fn binary_detection(
|
|
&mut self,
|
|
detection: BinaryDetection,
|
|
) -> &mut SearcherTester {
|
|
self.binary = detection;
|
|
self
|
|
}
|
|
|
|
/// Whether to automatically attempt to test the heap limit setting or not.
|
|
///
|
|
/// By default, one of the test configurations includes setting the heap
|
|
/// limit to its minimal value for normal operation, which checks that
|
|
/// everything works even at the extremes. However, in some cases, the heap
|
|
/// limit can (expectedly) alter the output slightly. For example, it can
|
|
/// impact the number of bytes searched when performing binary detection.
|
|
/// For convenience, it can be useful to disable the automatic heap limit
|
|
/// test.
|
|
pub fn auto_heap_limit(&mut self, yes: bool) -> &mut SearcherTester {
|
|
self.auto_heap_limit = yes;
|
|
self
|
|
}
|
|
|
|
/// Set the number of lines to include in the "after" context.
|
|
///
|
|
/// The default is `0`, which is equivalent to not printing any context.
|
|
pub fn after_context(&mut self, lines: usize) -> &mut SearcherTester {
|
|
self.after_context = lines;
|
|
self
|
|
}
|
|
|
|
/// Set the number of lines to include in the "before" context.
|
|
///
|
|
/// The default is `0`, which is equivalent to not printing any context.
|
|
pub fn before_context(&mut self, lines: usize) -> &mut SearcherTester {
|
|
self.before_context = lines;
|
|
self
|
|
}
|
|
|
|
/// Whether to enable the "passthru" feature or not.
|
|
///
|
|
/// When passthru is enabled, it effectively treats all non-matching lines
|
|
/// as contextual lines. In other words, enabling this is akin to
|
|
/// requesting an unbounded number of before and after contextual lines.
|
|
///
|
|
/// This is disabled by default.
|
|
pub fn passthru(&mut self, yes: bool) -> &mut SearcherTester {
|
|
self.passthru = yes;
|
|
self
|
|
}
|
|
|
|
/// Return the minimum size of a buffer required for a successful search.
|
|
///
|
|
/// Generally, this corresponds to the maximum length of a line (including
|
|
/// its terminator), but if context settings are enabled, then this must
|
|
/// include the sum of the longest N lines.
|
|
///
|
|
/// Note that this must account for whether the test is using multi line
|
|
/// search or not, since multi line search requires being able to fit the
|
|
/// entire haystack into memory.
|
|
fn minimal_heap_limit(&self, multi_line: bool) -> usize {
|
|
if multi_line {
|
|
1 + self.haystack.len()
|
|
} else if self.before_context == 0 && self.after_context == 0 {
|
|
1 + self.haystack.lines().map(|s| s.len()).max().unwrap_or(0)
|
|
} else {
|
|
let mut lens: Vec<usize> =
|
|
self.haystack.lines().map(|s| s.len()).collect();
|
|
lens.sort();
|
|
lens.reverse();
|
|
|
|
let context_count = if self.passthru {
|
|
self.haystack.lines().count()
|
|
} else {
|
|
// Why do we add 2 here? Well, we need to add 1 in order to
|
|
// have room to search at least one line. We add another
|
|
// because the implementation will occasionally include
|
|
// an additional line when handling the context. There's
|
|
// no particularly good reason, other than keeping the
|
|
// implementation simple.
|
|
2 + self.before_context + self.after_context
|
|
};
|
|
|
|
// We add 1 to each line since `str::lines` doesn't include the
|
|
// line terminator.
|
|
lens.into_iter()
|
|
.take(context_count)
|
|
.map(|len| len + 1)
|
|
.sum::<usize>()
|
|
}
|
|
}
|
|
|
|
/// Returns true if and only if the given label should be included as part
|
|
/// of executing `test`.
|
|
///
|
|
/// Inclusion is determined by the filter specified. If no filter has been
|
|
/// given, then this always returns `true`.
|
|
fn include(&self, label: &str) -> bool {
|
|
let re = match self.filter {
|
|
None => return true,
|
|
Some(ref re) => re,
|
|
};
|
|
re.is_match(label)
|
|
}
|
|
|
|
/// Configs generates a set of all search configurations that should be
|
|
/// tested. The configs generated are based on the configuration in this
|
|
/// builder.
|
|
fn configs(&self) -> Vec<TesterConfig> {
|
|
let mut configs = vec![];
|
|
|
|
let matcher = RegexMatcher::new(&self.pattern);
|
|
let mut builder = SearcherBuilder::new();
|
|
builder
|
|
.line_number(false)
|
|
.invert_match(self.invert_match)
|
|
.binary_detection(self.binary.clone())
|
|
.after_context(self.after_context)
|
|
.before_context(self.before_context)
|
|
.passthru(self.passthru);
|
|
|
|
if self.by_line {
|
|
let mut matcher = matcher.clone();
|
|
let mut builder = builder.clone();
|
|
|
|
let expected_reader =
|
|
self.expected_no_line_number.as_ref().unwrap().to_string();
|
|
let expected_slice = match self.expected_slice_no_line_number {
|
|
None => expected_reader.clone(),
|
|
Some(ref e) => e.to_string(),
|
|
};
|
|
configs.push(TesterConfig {
|
|
label: "byline-noterm-nonumber".to_string(),
|
|
expected_reader: expected_reader.clone(),
|
|
expected_slice: expected_slice.clone(),
|
|
builder: builder.clone(),
|
|
matcher: matcher.clone(),
|
|
});
|
|
|
|
if self.auto_heap_limit {
|
|
builder.heap_limit(Some(self.minimal_heap_limit(false)));
|
|
configs.push(TesterConfig {
|
|
label: "byline-noterm-nonumber-heaplimit".to_string(),
|
|
expected_reader: expected_reader.clone(),
|
|
expected_slice: expected_slice.clone(),
|
|
builder: builder.clone(),
|
|
matcher: matcher.clone(),
|
|
});
|
|
builder.heap_limit(None);
|
|
}
|
|
|
|
matcher.set_line_term(Some(LineTerminator::byte(b'\n')));
|
|
configs.push(TesterConfig {
|
|
label: "byline-term-nonumber".to_string(),
|
|
expected_reader: expected_reader.clone(),
|
|
expected_slice: expected_slice.clone(),
|
|
builder: builder.clone(),
|
|
matcher: matcher.clone(),
|
|
});
|
|
|
|
matcher.every_line_is_candidate(true);
|
|
configs.push(TesterConfig {
|
|
label: "byline-term-nonumber-candidates".to_string(),
|
|
expected_reader: expected_reader.clone(),
|
|
expected_slice: expected_slice.clone(),
|
|
builder: builder.clone(),
|
|
matcher: matcher.clone(),
|
|
});
|
|
}
|
|
if self.by_line && self.line_number {
|
|
let mut matcher = matcher.clone();
|
|
let mut builder = builder.clone();
|
|
|
|
let expected_reader =
|
|
self.expected_with_line_number.as_ref().unwrap().to_string();
|
|
let expected_slice = match self.expected_slice_with_line_number {
|
|
None => expected_reader.clone(),
|
|
Some(ref e) => e.to_string(),
|
|
};
|
|
|
|
builder.line_number(true);
|
|
configs.push(TesterConfig {
|
|
label: "byline-noterm-number".to_string(),
|
|
expected_reader: expected_reader.clone(),
|
|
expected_slice: expected_slice.clone(),
|
|
builder: builder.clone(),
|
|
matcher: matcher.clone(),
|
|
});
|
|
|
|
matcher.set_line_term(Some(LineTerminator::byte(b'\n')));
|
|
configs.push(TesterConfig {
|
|
label: "byline-term-number".to_string(),
|
|
expected_reader: expected_reader.clone(),
|
|
expected_slice: expected_slice.clone(),
|
|
builder: builder.clone(),
|
|
matcher: matcher.clone(),
|
|
});
|
|
|
|
matcher.every_line_is_candidate(true);
|
|
configs.push(TesterConfig {
|
|
label: "byline-term-number-candidates".to_string(),
|
|
expected_reader: expected_reader.clone(),
|
|
expected_slice: expected_slice.clone(),
|
|
builder: builder.clone(),
|
|
matcher: matcher.clone(),
|
|
});
|
|
}
|
|
if self.multi_line {
|
|
let mut builder = builder.clone();
|
|
let expected_slice = match self.expected_slice_no_line_number {
|
|
None => {
|
|
self.expected_no_line_number.as_ref().unwrap().to_string()
|
|
}
|
|
Some(ref e) => e.to_string(),
|
|
};
|
|
|
|
builder.multi_line(true);
|
|
configs.push(TesterConfig {
|
|
label: "multiline-nonumber".to_string(),
|
|
expected_reader: expected_slice.clone(),
|
|
expected_slice: expected_slice.clone(),
|
|
builder: builder.clone(),
|
|
matcher: matcher.clone(),
|
|
});
|
|
|
|
if self.auto_heap_limit {
|
|
builder.heap_limit(Some(self.minimal_heap_limit(true)));
|
|
configs.push(TesterConfig {
|
|
label: "multiline-nonumber-heaplimit".to_string(),
|
|
expected_reader: expected_slice.clone(),
|
|
expected_slice: expected_slice.clone(),
|
|
builder: builder.clone(),
|
|
matcher: matcher.clone(),
|
|
});
|
|
builder.heap_limit(None);
|
|
}
|
|
}
|
|
if self.multi_line && self.line_number {
|
|
let mut builder = builder.clone();
|
|
let expected_slice = match self.expected_slice_with_line_number {
|
|
None => self
|
|
.expected_with_line_number
|
|
.as_ref()
|
|
.unwrap()
|
|
.to_string(),
|
|
Some(ref e) => e.to_string(),
|
|
};
|
|
|
|
builder.multi_line(true);
|
|
builder.line_number(true);
|
|
configs.push(TesterConfig {
|
|
label: "multiline-number".to_string(),
|
|
expected_reader: expected_slice.clone(),
|
|
expected_slice: expected_slice.clone(),
|
|
builder: builder.clone(),
|
|
matcher: matcher.clone(),
|
|
});
|
|
|
|
builder.heap_limit(Some(self.minimal_heap_limit(true)));
|
|
configs.push(TesterConfig {
|
|
label: "multiline-number-heaplimit".to_string(),
|
|
expected_reader: expected_slice.clone(),
|
|
expected_slice: expected_slice.clone(),
|
|
builder: builder.clone(),
|
|
matcher: matcher.clone(),
|
|
});
|
|
builder.heap_limit(None);
|
|
}
|
|
configs
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct TesterConfig {
|
|
label: String,
|
|
expected_reader: String,
|
|
expected_slice: String,
|
|
builder: SearcherBuilder,
|
|
matcher: RegexMatcher,
|
|
}
|
|
|
|
impl TesterConfig {
|
|
/// Execute a search using a reader. This exercises the incremental search
|
|
/// strategy, where the entire contents of the corpus aren't necessarily
|
|
/// in memory at once.
|
|
fn search_reader(&self, haystack: &str) -> String {
|
|
let mut sink = KitchenSink::new();
|
|
let mut searcher = self.builder.build();
|
|
let result = searcher.search_reader(
|
|
&self.matcher,
|
|
haystack.as_bytes(),
|
|
&mut sink,
|
|
);
|
|
if let Err(err) = result {
|
|
let label = format!("reader-{}", self.label);
|
|
panic!("error running '{}': {}", label, err);
|
|
}
|
|
String::from_utf8(sink.as_bytes().to_vec()).unwrap()
|
|
}
|
|
|
|
/// Execute a search using a slice. This exercises the search routines that
|
|
/// have the entire contents of the corpus in memory at one time.
|
|
fn search_slice(&self, haystack: &str) -> String {
|
|
let mut sink = KitchenSink::new();
|
|
let mut searcher = self.builder.build();
|
|
let result = searcher.search_slice(
|
|
&self.matcher,
|
|
haystack.as_bytes(),
|
|
&mut sink,
|
|
);
|
|
if let Err(err) = result {
|
|
let label = format!("slice-{}", self.label);
|
|
panic!("error running '{}': {}", label, err);
|
|
}
|
|
String::from_utf8(sink.as_bytes().to_vec()).unwrap()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use grep_matcher::{Match, Matcher};
|
|
|
|
use super::*;
|
|
|
|
fn m(start: usize, end: usize) -> Match {
|
|
Match::new(start, end)
|
|
}
|
|
|
|
#[test]
|
|
fn empty_line1() {
|
|
let haystack = b"";
|
|
let matcher = RegexMatcher::new(r"^$");
|
|
|
|
assert_eq!(matcher.find_at(haystack, 0), Ok(Some(m(0, 0))));
|
|
}
|
|
|
|
#[test]
|
|
fn empty_line2() {
|
|
let haystack = b"\n";
|
|
let matcher = RegexMatcher::new(r"^$");
|
|
|
|
assert_eq!(matcher.find_at(haystack, 0), Ok(Some(m(0, 0))));
|
|
assert_eq!(matcher.find_at(haystack, 1), Ok(Some(m(1, 1))));
|
|
}
|
|
|
|
#[test]
|
|
fn empty_line3() {
|
|
let haystack = b"\n\n";
|
|
let matcher = RegexMatcher::new(r"^$");
|
|
|
|
assert_eq!(matcher.find_at(haystack, 0), Ok(Some(m(0, 0))));
|
|
assert_eq!(matcher.find_at(haystack, 1), Ok(Some(m(1, 1))));
|
|
assert_eq!(matcher.find_at(haystack, 2), Ok(Some(m(2, 2))));
|
|
}
|
|
|
|
#[test]
|
|
fn empty_line4() {
|
|
let haystack = b"a\n\nb\n";
|
|
let matcher = RegexMatcher::new(r"^$");
|
|
|
|
assert_eq!(matcher.find_at(haystack, 0), Ok(Some(m(2, 2))));
|
|
assert_eq!(matcher.find_at(haystack, 1), Ok(Some(m(2, 2))));
|
|
assert_eq!(matcher.find_at(haystack, 2), Ok(Some(m(2, 2))));
|
|
assert_eq!(matcher.find_at(haystack, 3), Ok(Some(m(5, 5))));
|
|
assert_eq!(matcher.find_at(haystack, 4), Ok(Some(m(5, 5))));
|
|
assert_eq!(matcher.find_at(haystack, 5), Ok(Some(m(5, 5))));
|
|
}
|
|
|
|
#[test]
|
|
fn empty_line5() {
|
|
let haystack = b"a\n\nb\nc";
|
|
let matcher = RegexMatcher::new(r"^$");
|
|
|
|
assert_eq!(matcher.find_at(haystack, 0), Ok(Some(m(2, 2))));
|
|
assert_eq!(matcher.find_at(haystack, 1), Ok(Some(m(2, 2))));
|
|
assert_eq!(matcher.find_at(haystack, 2), Ok(Some(m(2, 2))));
|
|
assert_eq!(matcher.find_at(haystack, 3), Ok(None));
|
|
assert_eq!(matcher.find_at(haystack, 4), Ok(None));
|
|
assert_eq!(matcher.find_at(haystack, 5), Ok(None));
|
|
assert_eq!(matcher.find_at(haystack, 6), Ok(None));
|
|
}
|
|
|
|
#[test]
|
|
fn empty_line6() {
|
|
let haystack = b"a\n";
|
|
let matcher = RegexMatcher::new(r"^$");
|
|
|
|
assert_eq!(matcher.find_at(haystack, 0), Ok(Some(m(2, 2))));
|
|
assert_eq!(matcher.find_at(haystack, 1), Ok(Some(m(2, 2))));
|
|
assert_eq!(matcher.find_at(haystack, 2), Ok(Some(m(2, 2))));
|
|
}
|
|
}
|