Add integration tests.

This commit is contained in:
Andrew Gallant 2016-09-09 22:58:30 -04:00
parent 9a4527d107
commit f83cd63b11
7 changed files with 819 additions and 52 deletions

View File

@ -18,6 +18,10 @@ bench = false
path = "src/main.rs" path = "src/main.rs"
name = "rg" name = "rg"
[[test]]
name = "integration"
path = "tests/tests.rs"
[dependencies] [dependencies]
crossbeam = "0.2" crossbeam = "0.2"
docopt = "0.6" docopt = "0.6"

View File

@ -109,10 +109,6 @@ Less common options:
-L, --follow -L, --follow
Follow symlinks. Follow symlinks.
--line-terminator ARG
The byte to use for a line terminator. Escape sequences may be used.
[default: \\n]
--mmap --mmap
Search using memory maps when possible. This is enabled by default Search using memory maps when possible. This is enabled by default
when ripgrep thinks it will be faster. (Note that mmap searching when ripgrep thinks it will be faster. (Note that mmap searching
@ -174,7 +170,6 @@ pub struct RawArgs {
flag_ignore_case: bool, flag_ignore_case: bool,
flag_invert_match: bool, flag_invert_match: bool,
flag_line_number: bool, flag_line_number: bool,
flag_line_terminator: String,
flag_literal: bool, flag_literal: bool,
flag_mmap: bool, flag_mmap: bool,
flag_no_heading: bool, flag_no_heading: bool,
@ -248,7 +243,9 @@ impl RawArgs {
}; };
let paths = let paths =
if self.arg_path.is_empty() { if self.arg_path.is_empty() {
if sys::stdin_is_atty() { if sys::stdin_is_atty()
|| self.flag_files
|| self.flag_type_list {
vec![Path::new("./").to_path_buf()] vec![Path::new("./").to_path_buf()]
} else { } else {
vec![Path::new("-").to_path_buf()] vec![Path::new("-").to_path_buf()]
@ -277,15 +274,6 @@ impl RawArgs {
if mmap { if mmap {
debug!("will try to use memory maps"); debug!("will try to use memory maps");
} }
let eol = {
let eol = unescape(&self.flag_line_terminator);
if eol.is_empty() {
errored!("Empty line terminator is not allowed.");
} else if eol.len() > 1 {
errored!("Line terminators are limited to exactly 1 byte.");
}
eol[0]
};
let glob_overrides = let glob_overrides =
if self.flag_glob.is_empty() { if self.flag_glob.is_empty() {
None None
@ -309,6 +297,7 @@ impl RawArgs {
} else { } else {
self.flag_color == "always" self.flag_color == "always"
}; };
let eol = b'\n';
let mut with_filename = self.flag_with_filename; let mut with_filename = self.flag_with_filename;
if !with_filename { if !with_filename {
with_filename = paths.len() > 1 || paths[0].is_dir(); with_filename = paths.len() > 1 || paths[0].is_dir();

View File

@ -695,8 +695,7 @@ mod tests {
use super::{InputBuffer, Searcher, start_of_previous_lines}; use super::{InputBuffer, Searcher, start_of_previous_lines};
lazy_static! { const SHERLOCK: &'static str = "\
static ref SHERLOCK: &'static str = "\
For the Doctor Watsons of this world, as opposed to the Sherlock For the Doctor Watsons of this world, as opposed to the Sherlock
Holmeses, success in the province of detective work must always Holmeses, success in the province of detective work must always
be, to a very large extent, the result of luck. Sherlock Holmes be, to a very large extent, the result of luck. Sherlock Holmes
@ -704,7 +703,8 @@ can extract a clew from a wisp of straw or a flake of cigar ash;
but Doctor Watson has to have it taken out for him and dusted, but Doctor Watson has to have it taken out for him and dusted,
and exhibited clearly, with a label attached.\ and exhibited clearly, with a label attached.\
"; ";
static ref CODE: &'static str = "\
const CODE: &'static str = "\
extern crate snap; extern crate snap;
use std::io; use std::io;
@ -719,7 +719,6 @@ fn main() {
io::copy(&mut rdr, &mut wtr).expect(\"I/O operation failed\"); io::copy(&mut rdr, &mut wtr).expect(\"I/O operation failed\");
} }
"; ";
}
fn hay(s: &str) -> io::Cursor<Vec<u8>> { fn hay(s: &str) -> io::Cursor<Vec<u8>> {
io::Cursor::new(s.to_string().into_bytes()) io::Cursor::new(s.to_string().into_bytes())
@ -874,7 +873,7 @@ fn main() {
#[test] #[test]
fn basic_search1() { fn basic_search1() {
let (count, out) = search_smallcap("Sherlock", &*SHERLOCK, |s|s); let (count, out) = search_smallcap("Sherlock", SHERLOCK, |s|s);
assert_eq!(2, count); assert_eq!(2, count);
assert_eq!(out, "\ assert_eq!(out, "\
/baz.rs:For the Doctor Watsons of this world, as opposed to the Sherlock /baz.rs:For the Doctor Watsons of this world, as opposed to the Sherlock
@ -901,7 +900,7 @@ fn main() {
#[test] #[test]
fn line_numbers() { fn line_numbers() {
let (count, out) = search_smallcap( let (count, out) = search_smallcap(
"Sherlock", &*SHERLOCK, |s| s.line_number(true)); "Sherlock", SHERLOCK, |s| s.line_number(true));
assert_eq!(2, count); assert_eq!(2, count);
assert_eq!(out, "\ assert_eq!(out, "\
/baz.rs:1:For the Doctor Watsons of this world, as opposed to the Sherlock /baz.rs:1:For the Doctor Watsons of this world, as opposed to the Sherlock
@ -912,7 +911,7 @@ fn main() {
#[test] #[test]
fn count() { fn count() {
let (count, out) = search_smallcap( let (count, out) = search_smallcap(
"Sherlock", &*SHERLOCK, |s| s.count(true)); "Sherlock", SHERLOCK, |s| s.count(true));
assert_eq!(2, count); assert_eq!(2, count);
assert_eq!(out, "/baz.rs:2\n"); assert_eq!(out, "/baz.rs:2\n");
} }
@ -920,7 +919,7 @@ fn main() {
#[test] #[test]
fn invert_match() { fn invert_match() {
let (count, out) = search_smallcap( let (count, out) = search_smallcap(
"Sherlock", &*SHERLOCK, |s| s.invert_match(true)); "Sherlock", SHERLOCK, |s| s.invert_match(true));
assert_eq!(4, count); assert_eq!(4, count);
assert_eq!(out, "\ assert_eq!(out, "\
/baz.rs:Holmeses, success in the province of detective work must always /baz.rs:Holmeses, success in the province of detective work must always
@ -932,7 +931,7 @@ fn main() {
#[test] #[test]
fn invert_match_line_numbers() { fn invert_match_line_numbers() {
let (count, out) = search_smallcap("Sherlock", &*SHERLOCK, |s| { let (count, out) = search_smallcap("Sherlock", SHERLOCK, |s| {
s.invert_match(true).line_number(true) s.invert_match(true).line_number(true)
}); });
assert_eq!(4, count); assert_eq!(4, count);
@ -946,7 +945,7 @@ fn main() {
#[test] #[test]
fn invert_match_count() { fn invert_match_count() {
let (count, out) = search_smallcap("Sherlock", &*SHERLOCK, |s| { let (count, out) = search_smallcap("Sherlock", SHERLOCK, |s| {
s.invert_match(true).count(true) s.invert_match(true).count(true)
}); });
assert_eq!(4, count); assert_eq!(4, count);
@ -955,7 +954,7 @@ fn main() {
#[test] #[test]
fn before_context_one1() { fn before_context_one1() {
let (count, out) = search_smallcap("Sherlock", &*SHERLOCK, |s| { let (count, out) = search_smallcap("Sherlock", SHERLOCK, |s| {
s.line_number(true).before_context(1) s.line_number(true).before_context(1)
}); });
assert_eq!(2, count); assert_eq!(2, count);
@ -968,7 +967,7 @@ fn main() {
#[test] #[test]
fn before_context_invert_one1() { fn before_context_invert_one1() {
let (count, out) = search_smallcap("Sherlock", &*SHERLOCK, |s| { let (count, out) = search_smallcap("Sherlock", SHERLOCK, |s| {
s.line_number(true).before_context(1).invert_match(true) s.line_number(true).before_context(1).invert_match(true)
}); });
assert_eq!(4, count); assert_eq!(4, count);
@ -984,7 +983,7 @@ fn main() {
#[test] #[test]
fn before_context_invert_one2() { fn before_context_invert_one2() {
let (count, out) = search_smallcap(" a ", &*SHERLOCK, |s| { let (count, out) = search_smallcap(" a ", SHERLOCK, |s| {
s.line_number(true).before_context(1).invert_match(true) s.line_number(true).before_context(1).invert_match(true)
}); });
assert_eq!(3, count); assert_eq!(3, count);
@ -999,7 +998,7 @@ fn main() {
#[test] #[test]
fn before_context_two1() { fn before_context_two1() {
let (count, out) = search_smallcap("Sherlock", &*SHERLOCK, |s| { let (count, out) = search_smallcap("Sherlock", SHERLOCK, |s| {
s.line_number(true).before_context(2) s.line_number(true).before_context(2)
}); });
assert_eq!(2, count); assert_eq!(2, count);
@ -1012,7 +1011,7 @@ fn main() {
#[test] #[test]
fn before_context_two2() { fn before_context_two2() {
let (count, out) = search_smallcap("dusted", &*SHERLOCK, |s| { let (count, out) = search_smallcap("dusted", SHERLOCK, |s| {
s.line_number(true).before_context(2) s.line_number(true).before_context(2)
}); });
assert_eq!(1, count); assert_eq!(1, count);
@ -1026,7 +1025,7 @@ fn main() {
#[test] #[test]
fn before_context_two3() { fn before_context_two3() {
let (count, out) = search_smallcap( let (count, out) = search_smallcap(
"success|attached", &*SHERLOCK, |s| { "success|attached", SHERLOCK, |s| {
s.line_number(true).before_context(2) s.line_number(true).before_context(2)
}); });
assert_eq!(2, count); assert_eq!(2, count);
@ -1042,7 +1041,7 @@ fn main() {
#[test] #[test]
fn before_context_two4() { fn before_context_two4() {
let (count, out) = search("stdin", &*CODE, |s| { let (count, out) = search("stdin", CODE, |s| {
s.line_number(true).before_context(2) s.line_number(true).before_context(2)
}); });
assert_eq!(3, count); assert_eq!(3, count);
@ -1059,7 +1058,7 @@ fn main() {
#[test] #[test]
fn before_context_two5() { fn before_context_two5() {
let (count, out) = search("stdout", &*CODE, |s| { let (count, out) = search("stdout", CODE, |s| {
s.line_number(true).before_context(2) s.line_number(true).before_context(2)
}); });
assert_eq!(2, count); assert_eq!(2, count);
@ -1076,7 +1075,7 @@ fn main() {
#[test] #[test]
fn before_context_three1() { fn before_context_three1() {
let (count, out) = search_smallcap("Sherlock", &*SHERLOCK, |s| { let (count, out) = search_smallcap("Sherlock", SHERLOCK, |s| {
s.line_number(true).before_context(3) s.line_number(true).before_context(3)
}); });
assert_eq!(2, count); assert_eq!(2, count);
@ -1089,7 +1088,7 @@ fn main() {
#[test] #[test]
fn after_context_one1() { fn after_context_one1() {
let (count, out) = search_smallcap("Sherlock", &*SHERLOCK, |s| { let (count, out) = search_smallcap("Sherlock", SHERLOCK, |s| {
s.line_number(true).after_context(1) s.line_number(true).after_context(1)
}); });
assert_eq!(2, count); assert_eq!(2, count);
@ -1103,7 +1102,7 @@ fn main() {
#[test] #[test]
fn after_context_invert_one1() { fn after_context_invert_one1() {
let (count, out) = search_smallcap("Sherlock", &*SHERLOCK, |s| { let (count, out) = search_smallcap("Sherlock", SHERLOCK, |s| {
s.line_number(true).after_context(1).invert_match(true) s.line_number(true).after_context(1).invert_match(true)
}); });
assert_eq!(4, count); assert_eq!(4, count);
@ -1118,7 +1117,7 @@ fn main() {
#[test] #[test]
fn after_context_invert_one2() { fn after_context_invert_one2() {
let (count, out) = search_smallcap(" a ", &*SHERLOCK, |s| { let (count, out) = search_smallcap(" a ", SHERLOCK, |s| {
s.line_number(true).after_context(1).invert_match(true) s.line_number(true).after_context(1).invert_match(true)
}); });
assert_eq!(3, count); assert_eq!(3, count);
@ -1134,7 +1133,7 @@ fn main() {
#[test] #[test]
fn after_context_two1() { fn after_context_two1() {
let (count, out) = search_smallcap("Sherlock", &*SHERLOCK, |s| { let (count, out) = search_smallcap("Sherlock", SHERLOCK, |s| {
s.line_number(true).after_context(2) s.line_number(true).after_context(2)
}); });
assert_eq!(2, count); assert_eq!(2, count);
@ -1149,7 +1148,7 @@ fn main() {
#[test] #[test]
fn after_context_two2() { fn after_context_two2() {
let (count, out) = search_smallcap("dusted", &*SHERLOCK, |s| { let (count, out) = search_smallcap("dusted", SHERLOCK, |s| {
s.line_number(true).after_context(2) s.line_number(true).after_context(2)
}); });
assert_eq!(1, count); assert_eq!(1, count);
@ -1162,7 +1161,7 @@ fn main() {
#[test] #[test]
fn after_context_two3() { fn after_context_two3() {
let (count, out) = search_smallcap( let (count, out) = search_smallcap(
"success|attached", &*SHERLOCK, |s| { "success|attached", SHERLOCK, |s| {
s.line_number(true).after_context(2) s.line_number(true).after_context(2)
}); });
assert_eq!(2, count); assert_eq!(2, count);
@ -1177,7 +1176,7 @@ fn main() {
#[test] #[test]
fn after_context_three1() { fn after_context_three1() {
let (count, out) = search_smallcap("Sherlock", &*SHERLOCK, |s| { let (count, out) = search_smallcap("Sherlock", SHERLOCK, |s| {
s.line_number(true).after_context(3) s.line_number(true).after_context(3)
}); });
assert_eq!(2, count); assert_eq!(2, count);
@ -1194,7 +1193,7 @@ fn main() {
#[test] #[test]
fn before_after_context_two1() { fn before_after_context_two1() {
let (count, out) = search( let (count, out) = search(
r"fn main|let mut rdr", &*CODE, |s| { r"fn main|let mut rdr", CODE, |s| {
s.line_number(true).after_context(2).before_context(2) s.line_number(true).after_context(2).before_context(2)
}); });
assert_eq!(2, count); assert_eq!(2, count);

View File

@ -151,8 +151,7 @@ mod tests {
use super::BufferSearcher; use super::BufferSearcher;
lazy_static! { const SHERLOCK: &'static str = "\
static ref SHERLOCK: &'static str = "\
For the Doctor Watsons of this world, as opposed to the Sherlock For the Doctor Watsons of this world, as opposed to the Sherlock
Holmeses, success in the province of detective work must always Holmeses, success in the province of detective work must always
be, to a very large extent, the result of luck. Sherlock Holmes be, to a very large extent, the result of luck. Sherlock Holmes
@ -160,7 +159,8 @@ can extract a clew from a wisp of straw or a flake of cigar ash;
but Doctor Watson has to have it taken out for him and dusted, but Doctor Watson has to have it taken out for him and dusted,
and exhibited clearly, with a label attached.\ and exhibited clearly, with a label attached.\
"; ";
static ref CODE: &'static str = "\
const CODE: &'static str = "\
extern crate snap; extern crate snap;
use std::io; use std::io;
@ -175,7 +175,6 @@ fn main() {
io::copy(&mut rdr, &mut wtr).expect(\"I/O operation failed\"); io::copy(&mut rdr, &mut wtr).expect(\"I/O operation failed\");
} }
"; ";
}
fn matcher(pat: &str) -> Grep { fn matcher(pat: &str) -> Grep {
GrepBuilder::new(pat).build().unwrap() GrepBuilder::new(pat).build().unwrap()
@ -205,7 +204,7 @@ fn main() {
#[test] #[test]
fn basic_search() { fn basic_search() {
let (count, out) = search("Sherlock", &*SHERLOCK, |s|s); let (count, out) = search("Sherlock", SHERLOCK, |s|s);
assert_eq!(2, count); assert_eq!(2, count);
assert_eq!(out, "\ assert_eq!(out, "\
/baz.rs:For the Doctor Watsons of this world, as opposed to the Sherlock /baz.rs:For the Doctor Watsons of this world, as opposed to the Sherlock
@ -233,7 +232,7 @@ fn main() {
#[test] #[test]
fn line_numbers() { fn line_numbers() {
let (count, out) = search( let (count, out) = search(
"Sherlock", &*SHERLOCK, |s| s.line_number(true)); "Sherlock", SHERLOCK, |s| s.line_number(true));
assert_eq!(2, count); assert_eq!(2, count);
assert_eq!(out, "\ assert_eq!(out, "\
/baz.rs:1:For the Doctor Watsons of this world, as opposed to the Sherlock /baz.rs:1:For the Doctor Watsons of this world, as opposed to the Sherlock
@ -244,7 +243,7 @@ fn main() {
#[test] #[test]
fn count() { fn count() {
let (count, out) = search( let (count, out) = search(
"Sherlock", &*SHERLOCK, |s| s.count(true)); "Sherlock", SHERLOCK, |s| s.count(true));
assert_eq!(2, count); assert_eq!(2, count);
assert_eq!(out, "/baz.rs:2\n"); assert_eq!(out, "/baz.rs:2\n");
} }
@ -252,7 +251,7 @@ fn main() {
#[test] #[test]
fn invert_match() { fn invert_match() {
let (count, out) = search( let (count, out) = search(
"Sherlock", &*SHERLOCK, |s| s.invert_match(true)); "Sherlock", SHERLOCK, |s| s.invert_match(true));
assert_eq!(4, count); assert_eq!(4, count);
assert_eq!(out, "\ assert_eq!(out, "\
/baz.rs:Holmeses, success in the province of detective work must always /baz.rs:Holmeses, success in the province of detective work must always
@ -264,7 +263,7 @@ fn main() {
#[test] #[test]
fn invert_match_line_numbers() { fn invert_match_line_numbers() {
let (count, out) = search("Sherlock", &*SHERLOCK, |s| { let (count, out) = search("Sherlock", SHERLOCK, |s| {
s.invert_match(true).line_number(true) s.invert_match(true).line_number(true)
}); });
assert_eq!(4, count); assert_eq!(4, count);
@ -278,7 +277,7 @@ fn main() {
#[test] #[test]
fn invert_match_count() { fn invert_match_count() {
let (count, out) = search("Sherlock", &*SHERLOCK, |s| { let (count, out) = search("Sherlock", SHERLOCK, |s| {
s.invert_match(true).count(true) s.invert_match(true).count(true)
}); });
assert_eq!(4, count); assert_eq!(4, count);

24
tests/hay.rs Normal file
View File

@ -0,0 +1,24 @@
pub const SHERLOCK: &'static str = "\
For the Doctor Watsons of this world, as opposed to the Sherlock
Holmeses, success in the province of detective work must always
be, to a very large extent, the result of luck. Sherlock Holmes
can extract a clew from a wisp of straw or a flake of cigar ash;
but Doctor Watson has to have it taken out for him and dusted,
and exhibited clearly, with a label attached.
";
pub const CODE: &'static str = "\
extern crate snap;
use std::io;
fn main() {
let stdin = io::stdin();
let stdout = io::stdout();
// Wrap the stdin reader in a Snappy reader.
let mut rdr = snap::Reader::new(stdin.lock());
let mut wtr = stdout.lock();
io::copy(&mut rdr, &mut wtr).expect(\"I/O operation failed\");
}
";

563
tests/tests.rs Normal file
View File

@ -0,0 +1,563 @@
/*!
This module contains *integration* tests. Their purpose is to test the CLI
interface. Namely, that passing a flag does what it says on the tin.
Tests for more fine grained behavior (like the search or the globber) should be
unit tests in their respective modules.
*/
#![allow(dead_code, unused_imports)]
use std::process::Command;
use workdir::WorkDir;
mod hay;
mod workdir;
macro_rules! sherlock {
($name:ident, $fun:expr) => {
sherlock!($name, "Sherlock", $fun);
};
($name:ident, $query:expr, $fun:expr) => {
sherlock!($name, $query, "sherlock", $fun);
};
($name:ident, $query:expr, $path:expr, $fun:expr) => {
#[test]
fn $name() {
let wd = WorkDir::new(stringify!($name));
wd.create("sherlock", hay::SHERLOCK);
let mut cmd = wd.command();
cmd.arg($query).arg($path);
$fun(wd, cmd);
}
};
}
sherlock!(single_file, |wd: WorkDir, mut cmd| {
let lines: String = wd.stdout(&mut cmd);
let expected = "\
For the Doctor Watsons of this world, as opposed to the Sherlock
be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq!(lines, expected);
});
sherlock!(dir, "Sherlock", ".", |wd: WorkDir, mut cmd| {
let lines: String = wd.stdout(&mut cmd);
let expected = "\
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq!(lines, expected);
});
sherlock!(line_numbers, |wd: WorkDir, mut cmd: Command| {
cmd.arg("-n");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
1:For the Doctor Watsons of this world, as opposed to the Sherlock
3:be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq!(lines, expected);
});
sherlock!(columns, |wd: WorkDir, mut cmd: Command| {
cmd.arg("--column");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
57:For the Doctor Watsons of this world, as opposed to the Sherlock
49:be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq!(lines, expected);
});
sherlock!(with_filename, |wd: WorkDir, mut cmd: Command| {
cmd.arg("-H");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq!(lines, expected);
});
sherlock!(with_heading, |wd: WorkDir, mut cmd: Command| {
// This forces the issue since --with-filename is disabled by default
// when searching one fil.e
cmd.arg("--with-filename").arg("--heading");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
sherlock
For the Doctor Watsons of this world, as opposed to the Sherlock
be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq!(lines, expected);
});
sherlock!(with_heading_default, "Sherlock", ".",
|wd: WorkDir, mut cmd: Command| {
// Search two or more and get --with-filename enabled by default.
// Use -j1 to get deterministic results.
wd.create("foo", "Sherlock Holmes lives on Baker Street.");
cmd.arg("-j1").arg("--heading");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
foo
Sherlock Holmes lives on Baker Street.
sherlock
For the Doctor Watsons of this world, as opposed to the Sherlock
be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq!(lines, expected);
});
sherlock!(inverted, |wd: WorkDir, mut cmd: Command| {
cmd.arg("-v");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
Holmeses, success in the province of detective work must always
can extract a clew from a wisp of straw or a flake of cigar ash;
but Doctor Watson has to have it taken out for him and dusted,
and exhibited clearly, with a label attached.
";
assert_eq!(lines, expected);
});
sherlock!(inverted_line_numbers, |wd: WorkDir, mut cmd: Command| {
cmd.arg("-n").arg("-v");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
2:Holmeses, success in the province of detective work must always
4:can extract a clew from a wisp of straw or a flake of cigar ash;
5:but Doctor Watson has to have it taken out for him and dusted,
6:and exhibited clearly, with a label attached.
";
assert_eq!(lines, expected);
});
sherlock!(case_insensitive, "sherlock", |wd: WorkDir, mut cmd: Command| {
cmd.arg("-i");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
For the Doctor Watsons of this world, as opposed to the Sherlock
be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq!(lines, expected);
});
sherlock!(word, "as", |wd: WorkDir, mut cmd: Command| {
cmd.arg("-w");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
For the Doctor Watsons of this world, as opposed to the Sherlock
";
assert_eq!(lines, expected);
});
sherlock!(literal, "()", "file", |wd: WorkDir, mut cmd: Command| {
wd.create("file", "blib\n()\nblab\n");
cmd.arg("-Q");
let lines: String = wd.stdout(&mut cmd);
assert_eq!(lines, "()\n");
});
sherlock!(quiet, |wd: WorkDir, mut cmd: Command| {
cmd.arg("-q");
let lines: String = wd.stdout(&mut cmd);
assert!(lines.is_empty());
});
sherlock!(replace, |wd: WorkDir, mut cmd: Command| {
cmd.arg("-r").arg("FooBar");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
For the Doctor Watsons of this world, as opposed to the FooBar
be, to a very large extent, the result of luck. FooBar Holmes
";
assert_eq!(lines, expected);
});
sherlock!(replace_groups, "([A-Z][a-z]+) ([A-Z][a-z]+)",
|wd: WorkDir, mut cmd: Command| {
cmd.arg("-r").arg("$2, $1");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
For the Watsons, Doctor of this world, as opposed to the Sherlock
be, to a very large extent, the result of luck. Holmes, Sherlock
but Watson, Doctor has to have it taken out for him and dusted,
";
assert_eq!(lines, expected);
});
sherlock!(replace_named_groups, "(?P<first>[A-Z][a-z]+) (?P<last>[A-Z][a-z]+)",
|wd: WorkDir, mut cmd: Command| {
cmd.arg("-r").arg("$last, $first");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
For the Watsons, Doctor of this world, as opposed to the Sherlock
be, to a very large extent, the result of luck. Holmes, Sherlock
but Watson, Doctor has to have it taken out for him and dusted,
";
assert_eq!(lines, expected);
});
sherlock!(file_types, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
wd.create("file.py", "Sherlock");
wd.create("file.rs", "Sherlock");
cmd.arg("-t").arg("rust");
let lines: String = wd.stdout(&mut cmd);
assert_eq!(lines, "file.rs:Sherlock\n");
});
sherlock!(file_types_negate, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
wd.remove("sherlock");
wd.create("file.py", "Sherlock");
wd.create("file.rs", "Sherlock");
cmd.arg("-T").arg("rust");
let lines: String = wd.stdout(&mut cmd);
assert_eq!(lines, "file.py:Sherlock\n");
});
sherlock!(file_type_clear, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
wd.create("file.py", "Sherlock");
wd.create("file.rs", "Sherlock");
cmd.arg("--type-clear").arg("rust").arg("-t").arg("rust");
wd.assert_err(&mut cmd);
});
sherlock!(file_type_add, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
wd.create("file.py", "Sherlock");
wd.create("file.rs", "Sherlock");
wd.create("file.wat", "Sherlock");
cmd.arg("--type-add").arg("wat:*.wat").arg("-t").arg("wat");
let lines: String = wd.stdout(&mut cmd);
assert_eq!(lines, "file.wat:Sherlock\n");
});
sherlock!(glob, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
wd.create("file.py", "Sherlock");
wd.create("file.rs", "Sherlock");
cmd.arg("-g").arg("*.rs");
let lines: String = wd.stdout(&mut cmd);
assert_eq!(lines, "file.rs:Sherlock\n");
});
sherlock!(glob_negate, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
wd.remove("sherlock");
wd.create("file.py", "Sherlock");
wd.create("file.rs", "Sherlock");
cmd.arg("-g").arg("!*.rs");
let lines: String = wd.stdout(&mut cmd);
assert_eq!(lines, "file.py:Sherlock\n");
});
sherlock!(after_context, |wd: WorkDir, mut cmd: Command| {
cmd.arg("-A").arg("1");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
For the Doctor Watsons of this world, as opposed to the Sherlock
Holmeses, success in the province of detective work must always
be, to a very large extent, the result of luck. Sherlock Holmes
can extract a clew from a wisp of straw or a flake of cigar ash;
";
assert_eq!(lines, expected);
});
sherlock!(after_context_line_numbers, |wd: WorkDir, mut cmd: Command| {
cmd.arg("-A").arg("1").arg("-n");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
1:For the Doctor Watsons of this world, as opposed to the Sherlock
2-Holmeses, success in the province of detective work must always
3:be, to a very large extent, the result of luck. Sherlock Holmes
4-can extract a clew from a wisp of straw or a flake of cigar ash;
";
assert_eq!(lines, expected);
});
sherlock!(before_context, |wd: WorkDir, mut cmd: Command| {
cmd.arg("-B").arg("1");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
For the Doctor Watsons of this world, as opposed to the Sherlock
Holmeses, success in the province of detective work must always
be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq!(lines, expected);
});
sherlock!(before_context_line_numbers, |wd: WorkDir, mut cmd: Command| {
cmd.arg("-B").arg("1").arg("-n");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
1:For the Doctor Watsons of this world, as opposed to the Sherlock
2-Holmeses, success in the province of detective work must always
3:be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq!(lines, expected);
});
sherlock!(context, "world|attached", |wd: WorkDir, mut cmd: Command| {
cmd.arg("-C").arg("1");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
For the Doctor Watsons of this world, as opposed to the Sherlock
Holmeses, success in the province of detective work must always
--
but Doctor Watson has to have it taken out for him and dusted,
and exhibited clearly, with a label attached.
";
assert_eq!(lines, expected);
});
sherlock!(context_line_numbers, "world|attached",
|wd: WorkDir, mut cmd: Command| {
cmd.arg("-C").arg("1").arg("-n");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
1:For the Doctor Watsons of this world, as opposed to the Sherlock
2-Holmeses, success in the province of detective work must always
--
5-but Doctor Watson has to have it taken out for him and dusted,
6:and exhibited clearly, with a label attached.
";
assert_eq!(lines, expected);
});
sherlock!(ignore_hidden, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
wd.remove("sherlock");
wd.create(".sherlock", hay::SHERLOCK);
wd.assert_err(&mut cmd);
});
sherlock!(no_ignore_hidden, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
wd.remove("sherlock");
wd.create(".sherlock", hay::SHERLOCK);
cmd.arg("--hidden");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
.sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
.sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq!(lines, expected);
});
sherlock!(ignore_git, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
wd.create(".gitignore", "sherlock\n");
wd.assert_err(&mut cmd);
});
sherlock!(ignore_ripgrep, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
wd.create(".rgignore", "sherlock\n");
wd.assert_err(&mut cmd);
});
sherlock!(no_ignore, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
wd.create(".gitignore", "sherlock\n");
cmd.arg("--no-ignore");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq!(lines, expected);
});
sherlock!(ignore_git_parent, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
wd.remove("sherlock");
wd.create(".gitignore", "sherlock\n");
wd.create_dir(".git");
wd.create_dir("foo");
wd.create("foo/sherlock", hay::SHERLOCK);
// Even though we search in foo/, which has no .gitignore, ripgrep will
// search parent directories and respect the gitignore files found.
cmd.current_dir(wd.path().join("foo"));
wd.assert_err(&mut cmd);
});
sherlock!(ignore_git_parent_stop, "Sherlock", ".",
|wd: WorkDir, mut cmd: Command| {
// This tests that searching parent directories for .gitignore files stops
// after it sees a .git directory. To test this, we create this directory
// hierarchy:
//
// .gitignore (contains `sherlock`)
// foo/
// .git
// bar/
// sherlock
//
// And we perform the search inside `foo/bar/`. ripgrep will stop looking
// for .gitignore files after it sees `foo/.git/`, and therefore not
// respect the top-level `.gitignore` containing `sherlock`.
wd.remove("sherlock");
wd.create(".gitignore", "sherlock\n");
wd.create_dir("foo");
wd.create_dir("foo/.git");
wd.create_dir("foo/bar");
wd.create("foo/bar/sherlock", hay::SHERLOCK);
cmd.current_dir(wd.path().join("foo").join("bar"));
let lines: String = wd.stdout(&mut cmd);
let expected = "\
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq!(lines, expected);
});
sherlock!(ignore_ripgrep_parent_no_stop, "Sherlock", ".",
|wd: WorkDir, mut cmd: Command| {
// This is like the `ignore_git_parent_stop` test, except it checks that
// ripgrep *doesn't* stop checking for .rgignore files.
wd.remove("sherlock");
wd.create(".rgignore", "sherlock\n");
wd.create_dir("foo");
wd.create_dir("foo/.git");
wd.create_dir("foo/bar");
wd.create("foo/bar/sherlock", hay::SHERLOCK);
cmd.current_dir(wd.path().join("foo").join("bar"));
// The top-level .rgignore applies.
wd.assert_err(&mut cmd);
});
sherlock!(no_parent_ignore_git, "Sherlock", ".",
|wd: WorkDir, mut cmd: Command| {
// Set up a directory hierarchy like this:
//
// .gitignore
// foo/
// .gitignore
// sherlock
// watson
//
// Where `.gitignore` contains `sherlock` and `foo/.gitignore` contains
// `watson`.
//
// Now *do the search* from the foo directory. By default, ripgrep will
// search parent directories for .gitignore files. The --no-ignore-parent
// flag should prevent that. At the same time, the `foo/.gitignore` file
// will still be respected (since the search is happening in `foo/`).
//
// In other words, we should only see results from `sherlock`, not from
// `watson`.
wd.remove("sherlock");
wd.create(".gitignore", "sherlock\n");
wd.create_dir("foo");
wd.create("foo/.gitignore", "watson\n");
wd.create("foo/sherlock", hay::SHERLOCK);
wd.create("foo/watson", hay::SHERLOCK);
cmd.current_dir(wd.path().join("foo"));
cmd.arg("--no-ignore-parent");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq!(lines, expected);
});
sherlock!(symlink_nofollow, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
wd.remove("sherlock");
wd.create_dir("foo");
wd.create_dir("foo/bar");
wd.link("foo/baz", "foo/bar/baz");
wd.create_dir("foo/baz");
wd.create("foo/baz/sherlock", hay::SHERLOCK);
cmd.current_dir(wd.path().join("foo/bar"));
wd.assert_err(&mut cmd);
});
sherlock!(symlink_follow, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
wd.remove("sherlock");
wd.create_dir("foo");
wd.create_dir("foo/bar");
wd.create_dir("foo/baz");
wd.create("foo/baz/sherlock", hay::SHERLOCK);
wd.link("foo/baz", "foo/bar/baz");
cmd.arg("-L");
cmd.current_dir(wd.path().join("foo/bar"));
let lines: String = wd.stdout(&mut cmd);
if cfg!(windows) {
let expected = "\
baz\\sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
baz\\sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq!(lines, expected);
} else {
let expected = "\
baz/sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
baz/sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq!(lines, expected);
}
});
#[test]
fn binary_nosearch() {
let wd = WorkDir::new("binary_nosearch");
wd.create("file", "foo\x00bar\nfoo\x00baz\n");
let mut cmd = wd.command();
cmd.arg("foo").arg("file");
wd.assert_err(&mut cmd);
}
// The following two tests show a discrepancy in search results between
// searching with memory mapped files and stream searching. Stream searching
// uses a heuristic (that GNU grep also uses) where NUL bytes are replaced with
// the EOL terminator, which tends to avoid allocating large amounts of memory
// for really long "lines." The memory map searcher has no need to worry about
// such things, and more than that, it would be pretty hard for it to match
// the semantics of streaming search in this case.
//
// Binary files with lots of NULs aren't really part of the use case of ripgrep
// (or any other grep-like tool for that matter), so we shouldn't feel too bad
// about it.
#[test]
fn binary_search_mmap() {
let wd = WorkDir::new("binary_search_mmap");
wd.create("file", "foo\x00bar\nfoo\x00baz\n");
let mut cmd = wd.command();
cmd.arg("-a").arg("--mmap").arg("foo").arg("file");
let lines: String = wd.stdout(&mut cmd);
assert_eq!(lines, "foo\x00bar\nfoo\x00baz\n");
}
#[test]
fn binary_search_no_mmap() {
let wd = WorkDir::new("binary_search_no_mmap");
wd.create("file", "foo\x00bar\nfoo\x00baz\n");
let mut cmd = wd.command();
cmd.arg("-a").arg("--no-mmap").arg("foo").arg("file");
let lines: String = wd.stdout(&mut cmd);
assert_eq!(lines, "foo\nfoo\n");
}
#[test]
fn files() {
let wd = WorkDir::new("files");
wd.create("file", "");
wd.create_dir("dir");
wd.create("dir/file", "");
let mut cmd = wd.command();
cmd.arg("--files");
let lines: String = wd.stdout(&mut cmd);
assert_eq!(lines, "./file\n./dir/file\n");
}
#[test]
fn type_list() {
let wd = WorkDir::new("type_list");
let mut cmd = wd.command();
cmd.arg("--type-list");
let lines: String = wd.stdout(&mut cmd);
// This can change over time, so just make sure we print something.
assert!(!lines.is_empty());
}

189
tests/workdir.rs Normal file
View File

@ -0,0 +1,189 @@
use std::env;
use std::error;
use std::fmt;
use std::fs::{self, File};
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process;
use std::str::FromStr;
use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering};
use std::thread;
use std::time::Duration;
static TEST_DIR: &'static str = "ripgrep-tests";
static NEXT_ID: AtomicUsize = ATOMIC_USIZE_INIT;
/// WorkDir represents a directory in which tests are run.
///
/// Directories are created from a global atomic counter to avoid duplicates.
#[derive(Debug)]
pub struct WorkDir {
/// The directory in which this test executable is running.
root: PathBuf,
/// The directory in which the test should run. If a test needs to create
/// files, they should go in here.
dir: PathBuf,
}
impl WorkDir {
/// Create a new test working directory with the given name. The name
/// does not need to be distinct for each invocation, but should correspond
/// to a logical grouping of tests.
pub fn new(name: &str) -> WorkDir {
let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
let root = env::current_exe().unwrap()
.parent().expect("executable's directory").to_path_buf();
let dir = root.join(TEST_DIR).join(name).join(&format!("{}", id));
nice_err(&dir, repeat(|| fs::create_dir_all(&dir)));
WorkDir {
root: root,
dir: dir,
}
}
/// Create a new file with the given name and contents in this directory.
pub fn create<P: AsRef<Path>>(&self, name: P, contents: &str) {
let path = self.dir.join(name);
let mut file = nice_err(&path, File::create(&path));
nice_err(&path, file.write_all(contents.as_bytes()));
nice_err(&path, file.flush());
}
/// Remove a file with the given name from this directory.
pub fn remove<P: AsRef<Path>>(&self, name: P) {
let path = self.dir.join(name);
nice_err(&path, fs::remove_file(&path));
}
/// Create a new directory with the given path (and any directories above
/// it) inside this directory.
pub fn create_dir<P: AsRef<Path>>(&self, path: P) {
let path = self.dir.join(path);
nice_err(&path, repeat(|| fs::create_dir_all(&path)));
}
/// Creates a new command that is set to use the ripgrep executable in
/// this working directory.
pub fn command(&self) -> process::Command {
let mut cmd = process::Command::new(&self.bin());
cmd.current_dir(&self.dir);
cmd
}
/// Returns the path to the ripgrep executable.
pub fn bin(&self) -> PathBuf {
self.root.join("rg")
}
/// Returns the path to this directory.
pub fn path(&self) -> &Path {
&self.dir
}
/// Creates a directory symlink to the src with the given target name
/// in this directory.
#[cfg(not(windows))]
pub fn link<S: AsRef<Path>, T: AsRef<Path>>(&self, src: S, target: T) {
use std::os::unix::fs::symlink;
let src = self.dir.join(src);
let target = self.dir.join(target);
let _ = fs::remove_file(&target);
nice_err(&target, symlink(&src, &target));
}
#[cfg(windows)]
pub fn link<S: AsRef<Path>, T: AsRef<Path>>(&self, src: S, target: T) {
use std::os::windows::fs::symlink_dir;
let src = self.dir.join(src);
let target = self.dir.join(target);
let _ = fs::remove_dir(&target);
nice_err(&target, symlink_dir(&src, &target));
}
/// Runs and captures the stdout of the given command.
///
/// If the return type could not be created from a string, then this
/// panics.
pub fn stdout<E: fmt::Debug, T: FromStr<Err=E>>(
&self,
cmd: &mut process::Command,
) -> T {
let o = self.output(cmd);
let stdout = String::from_utf8_lossy(&o.stdout);
match stdout.parse() {
Ok(t) => t,
Err(err) => {
panic!("could not convert from string: {:?}\n\n{}", err, stdout);
}
}
}
/// Gets the output of a command. If the command failed, then this panics.
pub fn output(&self, cmd: &mut process::Command) -> process::Output {
let o = cmd.output().unwrap();
if !o.status.success() {
let suggest =
if o.stderr.is_empty() {
"\n\nDid your search end up with no results?".to_string()
} else {
"".to_string()
};
panic!("\n\n==========\n\
command failed but expected success!\
{}\
\n\ncommand: {:?}\
\ncwd: {}\
\n\nstatus: {}\
\n\nstdout: {}\
\n\nstderr: {}\
\n\n==========\n",
suggest, cmd, self.dir.display(), o.status,
String::from_utf8_lossy(&o.stdout),
String::from_utf8_lossy(&o.stderr));
}
o
}
/// Runs the given command and asserts that it resulted in an error exit
/// code.
pub fn assert_err(&self, cmd: &mut process::Command) {
let o = cmd.output().unwrap();
if o.status.success() {
panic!("\n\n===== {:?} =====\n\
command succeeded but expected failure!\
\n\ncwd: {}\
\n\nstatus: {}\
\n\nstdout: {}\n\nstderr: {}\
\n\n=====\n",
cmd, self.dir.display(), o.status,
String::from_utf8_lossy(&o.stdout),
String::from_utf8_lossy(&o.stderr));
}
}
}
fn nice_err<P: AsRef<Path>, T, E: error::Error>(
path: P,
res: Result<T, E>,
) -> T {
match res {
Ok(t) => t,
Err(err) => {
panic!("{}: {:?}", path.as_ref().display(), err);
}
}
}
fn repeat<F: FnMut() -> io::Result<()>>(mut f: F) -> io::Result<()> {
let mut last_err = None;
for _ in 0..10 {
if let Err(err) = f() {
last_err = Some(err);
thread::sleep(Duration::from_millis(500));
} else {
return Ok(());
}
}
Err(last_err.unwrap())
}