Refactor tests to put match tests in lib.rs
Also replaces Lipsum with something a bit more readable
This commit is contained in:
parent
121d7f215f
commit
d98ab5877d
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -192,6 +192,7 @@ name = "hl"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"grep",
|
"grep",
|
||||||
|
"stringreader",
|
||||||
"termion",
|
"termion",
|
||||||
"test-case",
|
"test-case",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
@ -356,6 +357,12 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stringreader"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "913e7b03d63752f6cdd2df77da36749d82669904798fe8944b9ec3d23f159905"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.81"
|
version = "1.0.81"
|
||||||
|
|
|
@ -12,3 +12,4 @@ termion = "1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
test-case = "1.2.1"
|
test-case = "1.2.1"
|
||||||
|
stringreader = "0.1"
|
||||||
|
|
94
src/lib.rs
94
src/lib.rs
|
@ -74,3 +74,97 @@ pub fn scan_pattern_to_printer<R: Read, P: Printer>(
|
||||||
searcher.search_reader(matcher, reader, context_sink)?;
|
searcher.search_reader(matcher, reader, context_sink)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::io;
|
||||||
|
use stringreader::StringReader;
|
||||||
|
use test_case::test_case;
|
||||||
|
use testutil::mock_print::MockPrinter;
|
||||||
|
|
||||||
|
const SEARCH_TEXT: &str = "The \"computable\" numbers may be described briefly as the real \n\
|
||||||
|
numbers whose expressions as a decimal are calculable by finite means. \n\
|
||||||
|
Although the subject of this paper is ostensibly the computable numbers. \n\
|
||||||
|
it is almost equally easy to define and investigate computable functions \n\
|
||||||
|
of an integral variable or a real or computable variable, computable \n\
|
||||||
|
predicates, and so forth. The fundamental problems involved are, \n\
|
||||||
|
however, the same in each case, and I have chosen the computable numbers \n\
|
||||||
|
for explicit treatment as involving the least cumbrous technique. I hope \n\
|
||||||
|
shortly to give an account of the relations of the computable numbers, \n\
|
||||||
|
functions, and so forth to one another. This will include a development \n\
|
||||||
|
of the theory of functions of a real variable expressed in terms of \n\
|
||||||
|
computable numbers. According to my definition, a number is computable \n\
|
||||||
|
if its decimal can be written down by a machine.\n";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_highlights_matches() {
|
||||||
|
let mock_printer = MockPrinter::default();
|
||||||
|
let mut lipsum_reader = StringReader::new(SEARCH_TEXT);
|
||||||
|
let res = scan_pattern_to_printer(
|
||||||
|
&mut lipsum_reader,
|
||||||
|
r#""?computable"?\snumbers"#,
|
||||||
|
&mock_printer,
|
||||||
|
);
|
||||||
|
if let Err(err) = res {
|
||||||
|
panic!("failed to search: {}", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
let colored_messages = mock_printer.colored_messages.borrow();
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let expected_colored_messages = [
|
||||||
|
"The \"computable\" numbers may be described briefly as the real \n".to_string(),
|
||||||
|
"Although the subject of this paper is ostensibly the computable numbers. \n".to_string(),
|
||||||
|
"however, the same in each case, and I have chosen the computable numbers \n".to_string(),
|
||||||
|
"shortly to give an account of the relations of the computable numbers, \n".to_string(),
|
||||||
|
"computable numbers. According to my definition, a number is computable \n".to_string(),
|
||||||
|
];
|
||||||
|
assert!(
|
||||||
|
testutil::are_slices_eq(&colored_messages, &expected_colored_messages),
|
||||||
|
"(expected) {:?} != (actual) {:?}",
|
||||||
|
expected_colored_messages,
|
||||||
|
colored_messages,
|
||||||
|
);
|
||||||
|
|
||||||
|
let uncolored_messages = mock_printer.messages.borrow();
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let expected_uncolored_messages = [
|
||||||
|
"numbers whose expressions as a decimal are calculable by finite means. \n".to_string(),
|
||||||
|
"it is almost equally easy to define and investigate computable functions \n".to_string(),
|
||||||
|
"of an integral variable or a real or computable variable, computable \n".to_string(),
|
||||||
|
"predicates, and so forth. The fundamental problems involved are, \n".to_string(),
|
||||||
|
"for explicit treatment as involving the least cumbrous technique. I hope \n".to_string(),
|
||||||
|
"functions, and so forth to one another. This will include a development \n".to_string(),
|
||||||
|
"of the theory of functions of a real variable expressed in terms of \n".to_string(),
|
||||||
|
"if its decimal can be written down by a machine.\n".to_string(),
|
||||||
|
];
|
||||||
|
assert!(
|
||||||
|
testutil::are_slices_eq(&uncolored_messages, &expected_uncolored_messages),
|
||||||
|
"(expected) {:?} != (actual) {:?}",
|
||||||
|
expected_uncolored_messages,
|
||||||
|
colored_messages,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test_case(".", 0, 1; "failure on first match will only attempt to print that match")]
|
||||||
|
#[test_case("hello I am alan turing", 1, 0; "never matching will only attempt to print the first line")]
|
||||||
|
fn test_does_not_attempt_to_print_after_broken_pipe_error(
|
||||||
|
pattern: &str,
|
||||||
|
num_uncolored_messages: usize,
|
||||||
|
num_colored_messages: usize,
|
||||||
|
) {
|
||||||
|
let mut mock_printer = MockPrinter::default();
|
||||||
|
let broken_pipe_err =
|
||||||
|
print::Error::from(io::Error::new(io::ErrorKind::BrokenPipe, "broken pipe"));
|
||||||
|
mock_printer.fail_next(broken_pipe_err);
|
||||||
|
let mut lipsum_reader = StringReader::new(SEARCH_TEXT);
|
||||||
|
let res = scan_pattern_to_printer(&mut lipsum_reader, pattern, &mock_printer);
|
||||||
|
|
||||||
|
assert!(!res.is_err(), "failed to search: {:?}", res.unwrap_err());
|
||||||
|
assert_eq!(
|
||||||
|
num_colored_messages,
|
||||||
|
mock_printer.colored_messages.borrow().len()
|
||||||
|
);
|
||||||
|
assert_eq!(num_uncolored_messages, mock_printer.messages.borrow().len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
151
src/sink.rs
151
src/sink.rs
|
@ -115,151 +115,16 @@ impl<P: Printer> Sink for ContextPrintingSink<P> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::testutil::mock_print::MockPrinter;
|
||||||
use grep::regex::RegexMatcher;
|
use grep::regex::RegexMatcher;
|
||||||
use grep::searcher::SearcherBuilder;
|
use grep::searcher::SearcherBuilder;
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::fmt;
|
|
||||||
use std::io;
|
|
||||||
use test_case::test_case;
|
use test_case::test_case;
|
||||||
|
|
||||||
#[derive(Default)]
|
const SEARCH_TEXT: &str = "The quick \n\
|
||||||
struct MockPrinter {
|
brown fox \n\
|
||||||
messages: RefCell<Vec<String>>,
|
jumped over \n\
|
||||||
colored_messages: RefCell<Vec<String>>,
|
the lazy \n\
|
||||||
next_error: RefCell<Option<print::Error>>,
|
dog.";
|
||||||
}
|
|
||||||
|
|
||||||
impl MockPrinter {
|
|
||||||
fn fail_next(&mut self, error: print::Error) {
|
|
||||||
self.next_error.replace(Some(error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Printer for &MockPrinter {
|
|
||||||
fn print<S: fmt::Display>(&self, msg: S) -> print::Result {
|
|
||||||
self.messages.borrow_mut().push(msg.to_string());
|
|
||||||
|
|
||||||
if self.next_error.borrow().is_some() {
|
|
||||||
Err(self.next_error.replace(None).unwrap())
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn colored_print<S: fmt::Display, C: color::Color>(
|
|
||||||
&self,
|
|
||||||
_color: color::Fg<C>,
|
|
||||||
msg: S,
|
|
||||||
) -> print::Result {
|
|
||||||
// Unfortunately, termion colors don't implement PartialEq, so checking for the exact color is not
|
|
||||||
// feasible unless we wanted to write a wrapper, which I don't care enough to just for unit testing
|
|
||||||
self.colored_messages.borrow_mut().push(msg.to_string());
|
|
||||||
|
|
||||||
if self.next_error.borrow().is_some() {
|
|
||||||
Err(self.next_error.replace(None).unwrap())
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const LIPSUM: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vel magna a magna porta \n\
|
|
||||||
viverra eu ac metus. Integer auctor enim id turpis mollis, quis sagittis nulla accumsan. Nam sagittis lorem ut \n\
|
|
||||||
elit convallis ultricies. Suspendisse sed lobortis enim. Nulla lobortis tristique maximus. Mauris fermentum urna \n\
|
|
||||||
id ex finibus commodo. Aliquam erat volutpat. Maecenas tristique erat vel consectetur varius. Fusce a \n\
|
|
||||||
condimentum orci. Praesent at rhoncus felis, et tempus nulla. Morbi consectetur, elit quis interdum tincidunt, \n\
|
|
||||||
felis risus malesuada elit, non feugiat tortor velit vel risus. Nullam a odio sodales, iaculis quam sit amet, \n\
|
|
||||||
molestie dolor. Praesent et nibh id nisl convallis hendrerit ac sed sapien. Fusce tempus venenatis odio, \n\
|
|
||||||
ut maximus nisi egestas vel.";
|
|
||||||
|
|
||||||
fn are_slices_eq<T: PartialEq>(v1: &[T], v2: &[T]) -> bool {
|
|
||||||
if v1.len() != v2.len() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://stackoverflow.com/a/29504547
|
|
||||||
let len = v1.len();
|
|
||||||
v1.iter().zip(v2).filter(|&(a, b)| a == b).count() == len
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_highlights_matches() {
|
|
||||||
let matcher = RegexMatcher::new("Integer").expect("regexp doesn't compile");
|
|
||||||
|
|
||||||
let mock_printer = MockPrinter::default();
|
|
||||||
let sink = ContextPrintingSink {
|
|
||||||
printer: &mock_printer,
|
|
||||||
};
|
|
||||||
|
|
||||||
let res = SearcherBuilder::new().passthru(true).build().search_slice(
|
|
||||||
matcher,
|
|
||||||
LIPSUM.as_bytes(),
|
|
||||||
sink,
|
|
||||||
);
|
|
||||||
if let Err(err) = res {
|
|
||||||
panic!("failed to search: {}", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
let colored_messages = mock_printer.colored_messages.borrow();
|
|
||||||
let expected_colored_messages = ["viverra eu ac metus. Integer auctor enim id turpis mollis, quis sagittis nulla accumsan. Nam sagittis lorem ut \n".to_string()];
|
|
||||||
assert!(
|
|
||||||
are_slices_eq(&colored_messages, &expected_colored_messages),
|
|
||||||
"(expected) {:?} != (actual) {:?}",
|
|
||||||
expected_colored_messages,
|
|
||||||
colored_messages,
|
|
||||||
);
|
|
||||||
|
|
||||||
let uncolored_messages = mock_printer.messages.borrow();
|
|
||||||
let expected_uncolored_messages = [
|
|
||||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vel magna a magna porta \n".to_string(),
|
|
||||||
// only missing the line that's in expected
|
|
||||||
"elit convallis ultricies. Suspendisse sed lobortis enim. Nulla lobortis tristique maximus. Mauris fermentum urna \n".to_string(),
|
|
||||||
"id ex finibus commodo. Aliquam erat volutpat. Maecenas tristique erat vel consectetur varius. Fusce a \n".to_string(),
|
|
||||||
"condimentum orci. Praesent at rhoncus felis, et tempus nulla. Morbi consectetur, elit quis interdum tincidunt, \n".to_string(),
|
|
||||||
"felis risus malesuada elit, non feugiat tortor velit vel risus. Nullam a odio sodales, iaculis quam sit amet, \n".to_string(),
|
|
||||||
"molestie dolor. Praesent et nibh id nisl convallis hendrerit ac sed sapien. Fusce tempus venenatis odio, \n".to_string(),
|
|
||||||
"ut maximus nisi egestas vel.".to_string()
|
|
||||||
];
|
|
||||||
assert!(
|
|
||||||
are_slices_eq(&uncolored_messages, &expected_uncolored_messages),
|
|
||||||
"(expected) {:?} != (actual) {:?}",
|
|
||||||
expected_uncolored_messages,
|
|
||||||
colored_messages,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test_case(".", 0, 1; "failure on first match will only attempt to print that match")]
|
|
||||||
#[test_case("this doesn't appear in lorem ipsum", 1, 0; "never matching will only attempt to print the first line")]
|
|
||||||
fn test_does_not_attempt_to_print_after_broken_pipe_error(
|
|
||||||
pattern: &str,
|
|
||||||
num_uncolored_messages: usize,
|
|
||||||
num_colored_messages: usize,
|
|
||||||
) {
|
|
||||||
let matcher = RegexMatcher::new(pattern).expect("regexp doesn't compile");
|
|
||||||
|
|
||||||
let mut mock_printer = MockPrinter::default();
|
|
||||||
|
|
||||||
let broken_pipe_err =
|
|
||||||
print::Error::from(io::Error::new(io::ErrorKind::BrokenPipe, "broken pipe"));
|
|
||||||
mock_printer.fail_next(broken_pipe_err);
|
|
||||||
|
|
||||||
let sink = ContextPrintingSink {
|
|
||||||
printer: &mock_printer,
|
|
||||||
};
|
|
||||||
|
|
||||||
let res = SearcherBuilder::new().passthru(true).build().search_slice(
|
|
||||||
matcher,
|
|
||||||
LIPSUM.as_bytes(),
|
|
||||||
sink,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(!res.is_err(), "failed to search: {:?}", res.unwrap_err());
|
|
||||||
assert_eq!(
|
|
||||||
num_colored_messages,
|
|
||||||
mock_printer.colored_messages.borrow().len()
|
|
||||||
);
|
|
||||||
assert_eq!(num_uncolored_messages, mock_printer.messages.borrow().len());
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: This is a bit overkill for a single setting, and could probably be simplified
|
// TODO: This is a bit overkill for a single setting, and could probably be simplified
|
||||||
enum RequiredSearcherSettings {
|
enum RequiredSearcherSettings {
|
||||||
|
@ -274,7 +139,7 @@ mod tests {
|
||||||
) {
|
) {
|
||||||
// This must be wrapped so we can safely use `panic::catch_unwind`
|
// This must be wrapped so we can safely use `panic::catch_unwind`
|
||||||
let perform_search = || {
|
let perform_search = || {
|
||||||
let matcher = RegexMatcher::new("Integer").expect("regexp doesn't compile");
|
let matcher = RegexMatcher::new("fox").expect("regexp doesn't compile");
|
||||||
|
|
||||||
let mock_printer = MockPrinter::default();
|
let mock_printer = MockPrinter::default();
|
||||||
let sink = ContextPrintingSink {
|
let sink = ContextPrintingSink {
|
||||||
|
@ -289,7 +154,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut searcher = builder.build();
|
let mut searcher = builder.build();
|
||||||
searcher.search_slice(matcher, LIPSUM.as_bytes(), sink)
|
searcher.search_slice(matcher, SEARCH_TEXT.as_bytes(), sink)
|
||||||
};
|
};
|
||||||
|
|
||||||
if valid {
|
if valid {
|
||||||
|
|
13
src/testutil.rs
Normal file
13
src/testutil.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#![cfg(test)]
|
||||||
|
#![allow(dead_code)]
|
||||||
|
pub(crate) mod mock_print;
|
||||||
|
|
||||||
|
pub(crate) fn are_slices_eq<T: PartialEq>(v1: &[T], v2: &[T]) -> bool {
|
||||||
|
if v1.len() != v2.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/29504547
|
||||||
|
let len = v1.len();
|
||||||
|
v1.iter().zip(v2).filter(|&(a, b)| a == b).count() == len
|
||||||
|
}
|
47
src/testutil/mock_print.rs
Normal file
47
src/testutil/mock_print.rs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
#![cfg(test)]
|
||||||
|
use crate::print;
|
||||||
|
use crate::print::Printer;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::fmt;
|
||||||
|
use termion::color;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(crate) struct MockPrinter {
|
||||||
|
pub(crate) messages: RefCell<Vec<String>>,
|
||||||
|
pub(crate) colored_messages: RefCell<Vec<String>>,
|
||||||
|
next_error: RefCell<Option<print::Error>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MockPrinter {
|
||||||
|
pub(crate) fn fail_next(&mut self, error: print::Error) {
|
||||||
|
self.next_error.replace(Some(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Printer for &MockPrinter {
|
||||||
|
fn print<S: fmt::Display>(&self, msg: S) -> print::Result {
|
||||||
|
self.messages.borrow_mut().push(msg.to_string());
|
||||||
|
|
||||||
|
if self.next_error.borrow().is_some() {
|
||||||
|
Err(self.next_error.replace(None).unwrap())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn colored_print<S: fmt::Display, C: color::Color>(
|
||||||
|
&self,
|
||||||
|
_color: color::Fg<C>,
|
||||||
|
msg: S,
|
||||||
|
) -> print::Result {
|
||||||
|
// Unfortunately, termion colors don't implement PartialEq, so checking for the exact color is not
|
||||||
|
// feasible unless we wanted to write a wrapper, which I don't care enough to just for unit testing
|
||||||
|
self.colored_messages.borrow_mut().push(msg.to_string());
|
||||||
|
|
||||||
|
if self.next_error.borrow().is_some() {
|
||||||
|
Err(self.next_error.replace(None).unwrap())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue