Refactor tests to put match tests in lib.rs
Also replaces Lipsum with something a bit more readablemaster
parent
121d7f215f
commit
d98ab5877d
|
@ -192,6 +192,7 @@ name = "hl"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"grep",
|
||||
"stringreader",
|
||||
"termion",
|
||||
"test-case",
|
||||
"thiserror",
|
||||
|
@ -356,6 +357,12 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stringreader"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "913e7b03d63752f6cdd2df77da36749d82669904798fe8944b9ec3d23f159905"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.81"
|
||||
|
|
|
@ -12,3 +12,4 @@ termion = "1"
|
|||
|
||||
[dev-dependencies]
|
||||
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)?;
|
||||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::testutil::mock_print::MockPrinter;
|
||||
use grep::regex::RegexMatcher;
|
||||
use grep::searcher::SearcherBuilder;
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use test_case::test_case;
|
||||
|
||||
#[derive(Default)]
|
||||
struct MockPrinter {
|
||||
messages: RefCell<Vec<String>>,
|
||||
colored_messages: RefCell<Vec<String>>,
|
||||
next_error: RefCell<Option<print::Error>>,
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
const SEARCH_TEXT: &str = "The quick \n\
|
||||
brown fox \n\
|
||||
jumped over \n\
|
||||
the lazy \n\
|
||||
dog.";
|
||||
|
||||
// TODO: This is a bit overkill for a single setting, and could probably be simplified
|
||||
enum RequiredSearcherSettings {
|
||||
|
@ -274,7 +139,7 @@ mod tests {
|
|||
) {
|
||||
// This must be wrapped so we can safely use `panic::catch_unwind`
|
||||
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 sink = ContextPrintingSink {
|
||||
|
@ -289,7 +154,7 @@ mod tests {
|
|||
}
|
||||
|
||||
let mut searcher = builder.build();
|
||||
searcher.search_slice(matcher, LIPSUM.as_bytes(), sink)
|
||||
searcher.search_slice(matcher, SEARCH_TEXT.as_bytes(), sink)
|
||||
};
|
||||
|
||||
if valid {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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 New Issue