-
Notifications
You must be signed in to change notification settings - Fork 21
Regex matching. #85
Regex matching. #85
Changes from all commits
6406d80
e70bdac
e695095
3b2197a
0d589c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
extern crate regex; | ||
|
||
use self::errors::*; | ||
pub use self::errors::{Error, ErrorKind}; | ||
use diff; | ||
|
@@ -63,7 +65,6 @@ impl IsPredicate { | |
bail!(ErrorKind::BytesMatches(got.to_owned())); | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
|
@@ -83,7 +84,6 @@ impl IsPredicate { | |
bail!(ErrorKind::StrMatches(got.to_owned())); | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
@@ -174,11 +174,47 @@ impl fmt::Debug for FnPredicate { | |
} | ||
} | ||
|
||
#[derive(Debug, Clone)] | ||
struct RegexPredicate { | ||
regex: regex::Regex, | ||
times: u32, | ||
} | ||
|
||
impl RegexPredicate { | ||
fn verify(&self, got: &[u8]) -> Result<()> { | ||
let conversion = String::from_utf8_lossy(got); | ||
let got = conversion.as_ref(); | ||
if self.times == 0 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What are your thoughts on using a sentinel value ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a good suggestion, It's just some bad habit's take a while to leave :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I ask not to point out bad habits because sometimes the line is blurry. In another project, I have one behavior when a |
||
let result = self.regex.is_match(got); | ||
if !result { | ||
bail!(ErrorKind::OutputDoesntMatchRegexExactTimes( | ||
String::from(self.regex.as_str()), | ||
got.into(), | ||
1, | ||
1 | ||
)); | ||
} | ||
} else { | ||
let regex_matches = self.regex.captures_iter(got).count(); | ||
if regex_matches != (self.times as usize) { | ||
bail!(ErrorKind::OutputDoesntMatchRegexExactTimes( | ||
String::from(self.regex.as_str()), | ||
got.into(), | ||
self.times, | ||
regex_matches, | ||
)); | ||
} | ||
} | ||
Ok(()) | ||
} | ||
} | ||
|
||
#[derive(Debug, Clone)] | ||
enum ContentPredicate { | ||
Is(IsPredicate), | ||
Contains(ContainsPredicate), | ||
Fn(FnPredicate), | ||
Regex(RegexPredicate), | ||
} | ||
|
||
impl ContentPredicate { | ||
|
@@ -187,6 +223,7 @@ impl ContentPredicate { | |
ContentPredicate::Is(ref pred) => pred.verify(got), | ||
ContentPredicate::Contains(ref pred) => pred.verify(got), | ||
ContentPredicate::Fn(ref pred) => pred.verify(got), | ||
ContentPredicate::Regex(ref pred) => pred.verify(got), | ||
} | ||
} | ||
} | ||
|
@@ -238,6 +275,46 @@ impl Output { | |
Self::new(ContentPredicate::Is(pred)) | ||
} | ||
|
||
/// Expect the command to **match** this `output`. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ```rust | ||
/// extern crate assert_cli; | ||
/// | ||
/// assert_cli::Assert::command(&["echo"]) | ||
/// .with_args(&["42"]) | ||
/// .stdout().matches("[0-9]{2}") | ||
/// .unwrap(); | ||
/// ``` | ||
pub fn matches(output: String) -> Self { | ||
let pred = RegexPredicate { | ||
regex: regex::Regex::new(&output).unwrap(), | ||
times: 0, | ||
}; | ||
Self::new(ContentPredicate::Regex(pred)) | ||
} | ||
|
||
/// Expect the command to **match** this `output` exacly `nmatches` times. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ```rust | ||
/// extern crate assert_cli; | ||
/// | ||
/// assert_cli::Assert::command(&["echo"]) | ||
/// .with_args(&["42"]) | ||
/// .stdout().matches_ntimes("[0-9]{1}", 2) | ||
/// .unwrap(); | ||
/// ``` | ||
pub fn matches_ntimes(output: String, nmatches: u32) -> Self { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (longer term brain storming, feel free to ignore me) Ideally, in the long run I'd like to do this through a more builder-like approach The interesting challenge is deciding how to implement that. The nasty "unsafe" option is for Another option is to move away from having people interact with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is something that can be very useful but out of the scope of this PR, for now, |
||
let pred = RegexPredicate { | ||
regex: regex::Regex::new(&output).unwrap(), | ||
times: nmatches, | ||
}; | ||
Self::new(ContentPredicate::Regex(pred)) | ||
} | ||
|
||
/// Expect the command's output to not **contain** `output`. | ||
/// | ||
/// # Examples | ||
|
@@ -386,6 +463,16 @@ mod errors { | |
description("Output predicate failed") | ||
display("{}\noutput=```{}```", msg, got) | ||
} | ||
/* Adding a single error more makes this break, using the bottom one temporarily | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In what way does this break? Granted, at some point we should probably move beyond error-chain There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Out of stack space, this is a known bug in error-chain. I'm gonna look up some docs for this problem. |
||
OutputDoesntMatchRegex(regex: String, got: String) { | ||
description("Expected to regex to match") | ||
display("expected {}\n to match output=```{}```", regex, got) | ||
} | ||
*/ | ||
OutputDoesntMatchRegexExactTimes(regex: String, got: String, expected_times: u32, got_times: usize) { | ||
description("Expected to regex to match exact number of times") | ||
display("expected {}\n to match output=```{}``` {} times instead of {} times", regex, got, expected_times, got_times) | ||
} | ||
OutputMismatch(kind: super::OutputKind) { | ||
description("Output was not as expected") | ||
display( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we decide on yes, it doesn't mean you have to do them (I don't want to bar for contributions to be perfection) but we need to at least create issues for them and keep them in mind with the design of the API / implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think yes, at least a regex, a byte regex not so much IMHO, however, I see no problem in setting us up for later by implementing an abstraction layer here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, here the current usage code is more like
.matches(String::from("[0-9]")
as such, i will probably use str instead of string (just borrow).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For str vs String, you can always make it accept both