From e889b88d04a64bc12f89d2b4f9ecbac7783a47fb Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 15 Mar 2018 14:56:54 -0700 Subject: [PATCH] Split filecheck out into its own repo. --- cranelift/Cargo.toml | 2 +- cranelift/docs/testing.rst | 3 +- cranelift/publish-all.sh | 6 +- lib/filecheck/Cargo.toml | 14 - lib/filecheck/src/checker.rs | 456 -------------------------- lib/filecheck/src/error.rs | 69 ---- lib/filecheck/src/explain.rs | 202 ------------ lib/filecheck/src/lib.rs | 256 --------------- lib/filecheck/src/pattern.rs | 593 ---------------------------------- lib/filecheck/src/variable.rs | 62 ---- lib/filecheck/tests/basic.rs | 402 ----------------------- lib/filetests/Cargo.toml | 2 +- 12 files changed, 4 insertions(+), 2063 deletions(-) delete mode 100644 lib/filecheck/Cargo.toml delete mode 100644 lib/filecheck/src/checker.rs delete mode 100644 lib/filecheck/src/error.rs delete mode 100644 lib/filecheck/src/explain.rs delete mode 100644 lib/filecheck/src/lib.rs delete mode 100644 lib/filecheck/src/pattern.rs delete mode 100644 lib/filecheck/src/variable.rs delete mode 100644 lib/filecheck/tests/basic.rs diff --git a/cranelift/Cargo.toml b/cranelift/Cargo.toml index 064a2ee5ab..b9acc0606c 100644 --- a/cranelift/Cargo.toml +++ b/cranelift/Cargo.toml @@ -19,7 +19,7 @@ cretonne-frontend = { path = "lib/frontend", version = "0.3.4" } cretonne-wasm = { path = "lib/wasm", version = "0.3.4" } cretonne-native = { path = "lib/native", version = "0.3.4" } cretonne-filetests = { path = "lib/filetests", version = "0.3.4" } -filecheck = { path = "lib/filecheck" } +filecheck = "0.2.1" docopt = "0.8.0" serde = "1.0.8" serde_derive = "1.0.8" diff --git a/cranelift/docs/testing.rst b/cranelift/docs/testing.rst index 6ddc53ed84..320cc37389 100644 --- a/cranelift/docs/testing.rst +++ b/cranelift/docs/testing.rst @@ -144,8 +144,7 @@ Filecheck Many of the test commands described below use *filecheck* to verify their output. Filecheck is a Rust implementation of the LLVM tool of the same name. -See the :file:`lib/filecheck` `documentation `_ for -details of its syntax. +See the `documentation `_ for details of its syntax. Comments in :file:`.cton` files are associated with the entity they follow. This typically means an instruction or the whole function. Those tests that diff --git a/cranelift/publish-all.sh b/cranelift/publish-all.sh index 7d8b7f7f3c..c5cd6bfca3 100755 --- a/cranelift/publish-all.sh +++ b/cranelift/publish-all.sh @@ -4,7 +4,6 @@ cd $(dirname "$0") topdir="$(pwd)" # All the cretonne-* crates have the same version number -# The filecheck crate version is managed independently. version="0.3.4" # Update all of the Cargo.toml files. @@ -12,9 +11,6 @@ version="0.3.4" # The main Cargo.toml in the top-level directory is the cretonne-tools crate which we don't publish. echo "Updating crate versions to $version" for crate in . lib/*; do - if [ "$crate" = "lib/filecheck" ]; then - continue - fi # Update the version number of this crate to $version. sed -i.bk -e "s/^version = .*/version = \"$version\"/" "$crate/Cargo.toml" # Update the required version number of any cretonne* dependencies. @@ -31,7 +27,7 @@ cargo update echo git commit -a -m "\"Bump version to $version"\" echo git push -for crate in filecheck cretonne frontend native reader wasm; do +for crate in cretonne frontend native reader wasm; do echo cargo publish --manifest-path "lib/$crate/Cargo.toml" done echo diff --git a/lib/filecheck/Cargo.toml b/lib/filecheck/Cargo.toml deleted file mode 100644 index aec22b0f51..0000000000 --- a/lib/filecheck/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -authors = ["The Cretonne Project Developers"] -name = "filecheck" -version = "0.1.0" -description = "Library for matching test outputs against filecheck directives" -license = "Apache-2.0" -repository = "https://github.com/Cretonne/cretonne" -documentation = "https://docs.rs/filecheck" - -[lib] -name = "filecheck" - -[dependencies] -regex = "0.2.6" diff --git a/lib/filecheck/src/checker.rs b/lib/filecheck/src/checker.rs deleted file mode 100644 index b3a68fba4c..0000000000 --- a/lib/filecheck/src/checker.rs +++ /dev/null @@ -1,456 +0,0 @@ -use error::{Error, Result}; -use variable::{VariableMap, Value, varname_prefix}; -use pattern::Pattern; -use regex::{Regex, Captures}; -use std::borrow::Cow; -use std::collections::HashMap; -use std::cmp::max; -use std::fmt::{self, Display, Formatter}; -use std::mem; -use MatchRange; -use explain::{Recorder, Explainer}; - -// The different kinds of directives we support. -enum Directive { - Check(Pattern), - SameLn(Pattern), - NextLn(Pattern), - Unordered(Pattern), - Not(Pattern), - Regex(String, String), -} - -// Regular expression matching a directive. -// The match groups are: -// -// 1. Keyword. -// 2. Rest of line / pattern. -// -const DIRECTIVE_RX: &str = r"\b(check|sameln|nextln|unordered|not|regex):\s+(.*)"; - -impl Directive { - /// Create a new directive from a `DIRECTIVE_RX` match. - fn new(caps: Captures) -> Result { - let cmd = caps.get(1).map(|m| m.as_str()).expect("group 1 must match"); - let rest = caps.get(2).map(|m| m.as_str()).expect("group 2 must match"); - - if cmd == "regex" { - return Directive::regex(rest); - } - - // All other commands are followed by a pattern. - let pat = rest.parse()?; - - match cmd { - "check" => Ok(Directive::Check(pat)), - "sameln" => Ok(Directive::SameLn(pat)), - "nextln" => Ok(Directive::NextLn(pat)), - "unordered" => Ok(Directive::Unordered(pat)), - "not" => { - if !pat.defs().is_empty() { - let msg = format!( - "can't define variables '$({}=...' in not: {}", - pat.defs()[0], - rest - ); - Err(Error::DuplicateDef(msg)) - } else { - Ok(Directive::Not(pat)) - } - } - _ => panic!("unexpected command {} in regex match", cmd), - } - } - - /// Create a `regex:` directive from a `VAR=...` string. - fn regex(rest: &str) -> Result { - let varlen = varname_prefix(rest); - if varlen == 0 { - return Err(Error::Syntax( - format!("invalid variable name in regex: {}", rest), - )); - } - let var = rest[0..varlen].to_string(); - if !rest[varlen..].starts_with('=') { - return Err(Error::Syntax(format!( - "expected '=' after variable '{}' in regex: {}", - var, - rest - ))); - } - // Ignore trailing white space in the regex, including CR. - Ok(Directive::Regex( - var, - rest[varlen + 1..].trim_right().to_string(), - )) - } -} - - -/// Builder for constructing a `Checker` instance. -pub struct CheckerBuilder { - directives: Vec, - linerx: Regex, -} - -impl CheckerBuilder { - /// Create a new, blank `CheckerBuilder`. - pub fn new() -> Self { - Self { - directives: Vec::new(), - linerx: Regex::new(DIRECTIVE_RX).unwrap(), - } - } - - /// Add a potential directive line. - /// - /// Returns true if this is a directive with one of the known prefixes. - /// Returns false if no known directive was found. - /// Returns an error if there is a problem with the directive. - pub fn directive(&mut self, l: &str) -> Result { - match self.linerx.captures(l) { - Some(caps) => { - self.directives.push(Directive::new(caps)?); - Ok(true) - } - None => Ok(false), - } - } - - /// Add multiple directives. - /// - /// The text is split into lines that are added individually as potential directives. - /// This method can be used to parse a whole test file containing multiple directives. - pub fn text(&mut self, t: &str) -> Result<&mut Self> { - for caps in self.linerx.captures_iter(t) { - self.directives.push(Directive::new(caps)?); - } - Ok(self) - } - - /// Get the finished `Checker`. - pub fn finish(&mut self) -> Checker { - // Move directives into the new checker, leaving `self.directives` empty and ready for - // building a new checker. - let new_directives = mem::replace(&mut self.directives, Vec::new()); - Checker::new(new_directives) - } -} - -/// Verify a list of directives against a test input. -/// -/// Use a `CheckerBuilder` to construct a `Checker`. Then use the `test` method to verify the list -/// of directives against a test input. -pub struct Checker { - directives: Vec, -} - -impl Checker { - fn new(directives: Vec) -> Self { - Self { directives: directives } - } - - /// An empty checker contains no directives, and will match any input string. - pub fn is_empty(&self) -> bool { - self.directives.is_empty() - } - - /// Verify directives against the input text. - /// - /// This returns `true` if the text matches all the directives, `false` if it doesn't. - /// An error is only returned if there is a problem with the directives. - pub fn check(&self, text: &str, vars: &VariableMap) -> Result { - self.run(text, vars, &mut ()) - } - - /// Explain how directives are matched against the input text. - pub fn explain(&self, text: &str, vars: &VariableMap) -> Result<(bool, String)> { - let mut expl = Explainer::new(text); - let success = self.run(text, vars, &mut expl)?; - expl.finish(); - Ok((success, expl.to_string())) - } - - fn run(&self, text: &str, vars: &VariableMap, recorder: &mut Recorder) -> Result { - let mut state = State::new(text, vars, recorder); - - // For each pending `not:` check, store (begin-offset, regex). - let mut nots = Vec::new(); - - for (dct_idx, dct) in self.directives.iter().enumerate() { - let (pat, range) = match *dct { - Directive::Check(ref pat) => (pat, state.check()), - Directive::SameLn(ref pat) => (pat, state.sameln()), - Directive::NextLn(ref pat) => (pat, state.nextln()), - Directive::Unordered(ref pat) => (pat, state.unordered(pat)), - Directive::Not(ref pat) => { - // Resolve `not:` directives immediately to get the right variable values, but - // don't match it until we know the end of the range. - // - // The `not:` directives test the same range as `unordered:` directives. In - // particular, if they refer to defined variables, their range is restricted to - // the text following the match that defined the variable. - nots.push((dct_idx, state.unordered_begin(pat), pat.resolve(&state)?)); - continue; - } - Directive::Regex(ref var, ref rx) => { - state.vars.insert( - var.clone(), - VarDef { - value: Value::Regex(Cow::Borrowed(rx)), - offset: 0, - }, - ); - continue; - } - }; - // Check if `pat` matches in `range`. - state.recorder.directive(dct_idx); - if let Some((match_begin, match_end)) = state.match_positive(pat, range)? { - if let Directive::Unordered(_) = *dct { - // This was an unordered match. - // Keep track of the largest matched position, but leave `last_ordered` alone. - state.max_match = max(state.max_match, match_end); - } else { - // Ordered match. - state.last_ordered = match_end; - state.max_match = match_end; - - // Verify any pending `not:` directives now that we know their range. - for (not_idx, not_begin, rx) in nots.drain(..) { - state.recorder.directive(not_idx); - if let Some(mat) = rx.find(&text[not_begin..match_begin]) { - // Matched `not:` pattern. - state.recorder.matched_not(rx.as_str(), ( - not_begin + mat.start(), - not_begin + mat.end(), - )); - return Ok(false); - } else { - state.recorder.missed_not( - rx.as_str(), - (not_begin, match_begin), - ); - } - } - } - } else { - // No match! - return Ok(false); - } - } - - // Verify any pending `not:` directives after the last ordered directive. - for (not_idx, not_begin, rx) in nots.drain(..) { - state.recorder.directive(not_idx); - if rx.find(&text[not_begin..]).is_some() { - // Matched `not:` pattern. - // TODO: Use matched range for an error message. - return Ok(false); - } - } - - Ok(true) - } -} - -/// A local definition of a variable. -pub struct VarDef<'a> { - /// The value given to the variable. - value: Value<'a>, - /// Offset in input text from where the variable is available. - offset: usize, -} - -struct State<'a> { - text: &'a str, - env_vars: &'a VariableMap, - recorder: &'a mut Recorder, - - vars: HashMap>, - // Offset after the last ordered match. This does not include recent unordered matches. - last_ordered: usize, - // Largest offset following a positive match, including unordered matches. - max_match: usize, -} - -impl<'a> State<'a> { - fn new(text: &'a str, env_vars: &'a VariableMap, recorder: &'a mut Recorder) -> State<'a> { - State { - text, - env_vars, - recorder, - vars: HashMap::new(), - last_ordered: 0, - max_match: 0, - } - } - - // Get the offset following the match that defined `var`, or 0 if var is an environment - // variable or unknown. - fn def_offset(&self, var: &str) -> usize { - self.vars - .get(var) - .map(|&VarDef { offset, .. }| offset) - .unwrap_or(0) - } - - // Get the offset of the beginning of the next line after `pos`. - fn bol(&self, pos: usize) -> usize { - if let Some(offset) = self.text[pos..].find('\n') { - pos + offset + 1 - } else { - self.text.len() - } - } - - // Get the range in text to be matched by a `check:`. - fn check(&self) -> MatchRange { - (self.max_match, self.text.len()) - } - - // Get the range in text to be matched by a `sameln:`. - fn sameln(&self) -> MatchRange { - let b = self.max_match; - let e = self.bol(b); - (b, e) - } - - // Get the range in text to be matched by a `nextln:`. - fn nextln(&self) -> MatchRange { - let b = self.bol(self.max_match); - let e = self.bol(b); - (b, e) - } - - // Get the beginning of the range in text to be matched by a `unordered:` or `not:` directive. - // The unordered directive must match after the directives that define the variables used. - fn unordered_begin(&self, pat: &Pattern) -> usize { - pat.parts() - .iter() - .filter_map(|part| part.ref_var()) - .map(|var| self.def_offset(var)) - .fold(self.last_ordered, max) - } - - // Get the range in text to be matched by a `unordered:` directive. - fn unordered(&self, pat: &Pattern) -> MatchRange { - (self.unordered_begin(pat), self.text.len()) - } - - // Search for `pat` in `range`, return the range matched. - // After a positive match, update variable definitions, if any. - fn match_positive(&mut self, pat: &Pattern, range: MatchRange) -> Result> { - let rx = pat.resolve(self)?; - let txt = &self.text[range.0..range.1]; - let defs = pat.defs(); - let matched_range = if defs.is_empty() { - // Pattern defines no variables. Fastest search is `find`. - rx.find(txt) - } else { - // We need the captures to define variables. - rx.captures(txt).map(|caps| { - let matched_range = caps.get(0).expect("whole expression must match"); - for var in defs { - let txtval = caps.name(var).map(|mat| mat.as_str()).unwrap_or(""); - self.recorder.defined_var(var, txtval); - let vardef = VarDef { - value: Value::Text(Cow::Borrowed(txtval)), - // This offset is the end of the whole matched pattern, not just the text - // defining the variable. - offset: range.0 + matched_range.end(), - }; - self.vars.insert(var.clone(), vardef); - } - matched_range - }) - }; - Ok(if let Some(mat) = matched_range { - let r = (range.0 + mat.start(), range.0 + mat.end()); - self.recorder.matched_check(rx.as_str(), r); - Some(r) - } else { - self.recorder.missed_check(rx.as_str(), range); - None - }) - } -} - -impl<'a> VariableMap for State<'a> { - fn lookup(&self, varname: &str) -> Option { - // First look for a local define. - if let Some(&VarDef { ref value, .. }) = self.vars.get(varname) { - Some(value.clone()) - } else { - // No local, maybe an environment variable? - self.env_vars.lookup(varname) - } - } -} - -impl Display for Directive { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - use self::Directive::*; - match *self { - Check(ref pat) => writeln!(f, "check: {}", pat), - SameLn(ref pat) => writeln!(f, "sameln: {}", pat), - NextLn(ref pat) => writeln!(f, "nextln: {}", pat), - Unordered(ref pat) => writeln!(f, "unordered: {}", pat), - Not(ref pat) => writeln!(f, "not: {}", pat), - Regex(ref var, ref rx) => writeln!(f, "regex: {}={}", var, rx), - } - } -} - -impl Display for Checker { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - for (idx, dir) in self.directives.iter().enumerate() { - write!(f, "#{} {}", idx, dir)?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::CheckerBuilder; - use error::Error; - - fn e2s(e: Error) -> String { - e.to_string() - } - - #[test] - fn directive() { - let mut b = CheckerBuilder::new(); - - assert_eq!(b.directive("not here: more text").map_err(e2s), Ok(false)); - assert_eq!( - b.directive("not here: regex: X=more text").map_err(e2s), - Ok(true) - ); - assert_eq!( - b.directive("regex: X = tommy").map_err(e2s), - Err( - "expected '=' after variable 'X' in regex: X = tommy".to_string(), - ) - ); - assert_eq!( - b.directive("[arm]not: patt $x $(y) here").map_err(e2s), - Ok(true) - ); - assert_eq!( - b.directive("[x86]sameln: $x $(y=[^]]*) there").map_err(e2s), - Ok(true) - ); - // Windows line ending sneaking in. - assert_eq!(b.directive("regex: Y=foo\r").map_err(e2s), Ok(true)); - - let c = b.finish(); - assert_eq!( - c.to_string(), - "#0 regex: X=more text\n#1 not: patt $(x) $(y) here\n#2 sameln: $(x) \ - $(y=[^]]*) there\n#3 regex: Y=foo\n" - ); - } -} diff --git a/lib/filecheck/src/error.rs b/lib/filecheck/src/error.rs deleted file mode 100644 index f15cb17124..0000000000 --- a/lib/filecheck/src/error.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::result; -use std::convert::From; -use std::error::Error as StdError; -use std::fmt; -use regex; - -/// A result from the filecheck library. -pub type Result = result::Result; - -/// A filecheck error. -#[derive(Debug)] -pub enum Error { - /// A syntax error in a check line. - Syntax(String), - /// A check refers to an undefined variable. - /// - /// The pattern contains `$foo` where the `foo` variable has not yet been defined. - /// Use `$$` to match a literal dollar sign. - UndefVariable(String), - /// A pattern contains a back-reference to a variable that was defined in the same pattern. - /// - /// For example, `check: Hello $(world=.*) $world`. Backreferences are not supported. Often the - /// desired effect can be achieved with the `sameln` check: - /// - /// ```text - /// check: Hello $(world=[^ ]*) - /// sameln: $world - /// ``` - Backref(String), - /// A pattern contains multiple definitions of the same variable. - DuplicateDef(String), - /// An error in a regular expression. - /// - /// Use `cause()` to get the underlying `Regex` library error. - Regex(regex::Error), -} - -impl StdError for Error { - fn description(&self) -> &str { - use Error::*; - match *self { - Syntax(ref s) | - UndefVariable(ref s) | - Backref(ref s) | - DuplicateDef(ref s) => s, - Regex(ref err) => err.description(), - } - } - - fn cause(&self) -> Option<&StdError> { - use Error::*; - match *self { - Regex(ref err) => Some(err), - _ => None, - } - } -} - -impl fmt::Display for Error { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!(fmt, "{}", self.description()) - } -} - -impl From for Error { - fn from(e: regex::Error) -> Error { - Error::Regex(e) - } -} diff --git a/lib/filecheck/src/explain.rs b/lib/filecheck/src/explain.rs deleted file mode 100644 index 137d360f89..0000000000 --- a/lib/filecheck/src/explain.rs +++ /dev/null @@ -1,202 +0,0 @@ -//! Explaining how *filecheck* matched or failed to match a file. - -use MatchRange; -use std::fmt::{self, Display, Formatter}; -use std::cmp::min; - -/// Record events during matching. -pub trait Recorder { - /// Set the directive we're talking about now. - fn directive(&mut self, dct: usize); - - /// Matched a positive check directive (check/sameln/nextln/unordered). - fn matched_check(&mut self, regex: &str, matched: MatchRange); - - /// Matched a `not:` directive. This means the match will fail. - fn matched_not(&mut self, regex: &str, matched: MatchRange); - - /// Missed a positive check directive. The range given is the range searched for a match. - fn missed_check(&mut self, regex: &str, searched: MatchRange); - - /// Missed `not:` directive (as intended). - fn missed_not(&mut self, regex: &str, searched: MatchRange); - - /// The directive defined a variable. - fn defined_var(&mut self, varname: &str, value: &str); -} - -/// The null recorder just doesn't listen to anything you say. -impl Recorder for () { - fn directive(&mut self, _: usize) {} - fn matched_check(&mut self, _: &str, _: MatchRange) {} - fn matched_not(&mut self, _: &str, _: MatchRange) {} - fn defined_var(&mut self, _: &str, _: &str) {} - fn missed_check(&mut self, _: &str, _: MatchRange) {} - fn missed_not(&mut self, _: &str, _: MatchRange) {} -} - -struct Match { - directive: usize, - is_match: bool, - is_not: bool, - regex: String, - range: MatchRange, -} - -struct VarDef { - directive: usize, - varname: String, - value: String, -} - -/// Record an explanation for the matching process, success or failure. -pub struct Explainer<'a> { - text: &'a str, - directive: usize, - matches: Vec, - vardefs: Vec, -} - -impl<'a> Explainer<'a> { - pub fn new(text: &'a str) -> Explainer { - Explainer { - text, - directive: 0, - matches: Vec::new(), - vardefs: Vec::new(), - } - } - - /// Finish up after recording all events in a match. - pub fn finish(&mut self) { - self.matches.sort_by_key(|m| (m.range, m.directive)); - self.vardefs.sort_by_key(|v| v.directive); - } -} - -impl<'a> Display for Explainer<'a> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - // Offset of beginning of the last line printed. - let mut curln = 0; - // Offset of beginning of the next line to be printed. - let mut nextln = 0; - - for m in &self.matches { - // Emit lines until m.range.0 is visible. - while nextln <= m.range.0 && nextln < self.text.len() { - let newln = self.text[nextln..] - .find('\n') - .map(|d| nextln + d + 1) - .unwrap_or(self.text.len()); - assert!(newln > nextln); - writeln!(f, "> {}", &self.text[nextln..newln - 1])?; - curln = nextln; - nextln = newln; - } - - // Emit ~~~ under the part of the match in curln. - if m.is_match { - write!(f, " ")?; - let mend = min(m.range.1, nextln - 1); - for pos in curln..mend { - if pos < m.range.0 { - write!(f, " ") - } else if pos == m.range.0 { - write!(f, "^") - } else { - write!(f, "~") - }?; - } - writeln!(f, "")?; - } - - // Emit the match message itself. - writeln!( - f, - "{} #{}{}: {}", - if m.is_match { "Matched" } else { "Missed" }, - m.directive, - if m.is_not { " not" } else { "" }, - m.regex - )?; - - // Emit any variable definitions. - if let Ok(found) = self.vardefs.binary_search_by_key( - &m.directive, - |v| v.directive, - ) - { - let mut first = found; - while first > 0 && self.vardefs[first - 1].directive == m.directive { - first -= 1; - } - for d in &self.vardefs[first..] { - if d.directive != m.directive { - break; - } - writeln!(f, "Define {}={}", d.varname, d.value)?; - } - } - } - - // Emit trailing lines. - for line in self.text[nextln..].lines() { - writeln!(f, "> {}", line)?; - } - Ok(()) - } -} - -impl<'a> Recorder for Explainer<'a> { - fn directive(&mut self, dct: usize) { - self.directive = dct; - } - - fn matched_check(&mut self, regex: &str, matched: MatchRange) { - self.matches.push(Match { - directive: self.directive, - is_match: true, - is_not: false, - regex: regex.to_owned(), - range: matched, - }); - } - - fn matched_not(&mut self, regex: &str, matched: MatchRange) { - self.matches.push(Match { - directive: self.directive, - is_match: true, - is_not: true, - regex: regex.to_owned(), - range: matched, - }); - } - - fn missed_check(&mut self, regex: &str, searched: MatchRange) { - self.matches.push(Match { - directive: self.directive, - is_match: false, - is_not: false, - regex: regex.to_owned(), - range: searched, - }); - } - - fn missed_not(&mut self, regex: &str, searched: MatchRange) { - self.matches.push(Match { - directive: self.directive, - is_match: false, - is_not: true, - regex: regex.to_owned(), - range: searched, - }); - } - - fn defined_var(&mut self, varname: &str, value: &str) { - self.vardefs.push(VarDef { - directive: self.directive, - varname: varname.to_owned(), - value: value.to_owned(), - }); - } -} diff --git a/lib/filecheck/src/lib.rs b/lib/filecheck/src/lib.rs deleted file mode 100644 index 4dec7a9b65..0000000000 --- a/lib/filecheck/src/lib.rs +++ /dev/null @@ -1,256 +0,0 @@ -//! This crate provides a text pattern matching library with functionality similar to the LLVM -//! project's [FileCheck command](https://llvm.org/docs/CommandGuide/FileCheck.html). -//! -//! A list of directives is typically extracted from a file containing a test case. The test case -//! is then run through the program under test, and its output matched against the directives. -//! -//! See the [`CheckerBuilder`](struct.CheckerBuilder.html) and [`Checker`](struct.Checker.html) -//! types for the main library API. -//! -//! # Directives -//! -//! These are the directives recognized by *filecheck*: -//! -//!
-//! check: <pattern>
-//! sameln: <pattern>
-//! nextln: <pattern>
-//! unordered: <pattern>
-//! not: <pattern>
-//! regex: <variable>=<regex>
-//! 
-//! -//! Each directive is described in more detail below. -//! -//! ## Example -//! -//! The Rust program below prints the primes less than 100. It has *filecheck* directives embedded -//! in comments: -//! -//! ```rust -//! fn is_prime(x: u32) -> bool { -//! (2..x).all(|d| x % d != 0) -//! } -//! -//! // Check that we get the primes and nothing else: -//! // regex: NUM=\d+ -//! // not: $NUM -//! // check: 2 -//! // nextln: 3 -//! // check: 89 -//! // nextln: 97 -//! // not: $NUM -//! fn main() { -//! for p in (2..10).filter(|&x| is_prime(x)) { -//! println!("{}", p); -//! } -//! } -//! ``` -//! -//! A test driver compiles and runs the program, then pipes the output through *filecheck*: -//! -//! ```sh -//! $ rustc primes.rs -//! $ ./primes | cton-util filecheck -v -//! #0 regex: NUM=\d+ -//! #1 not: $NUM -//! #2 check: 2 -//! #3 nextln: 3 -//! #4 check: 89 -//! #5 nextln: 97 -//! #6 not: $NUM -//! no match #1: \d+ -//! > 2 -//! ~ -//! match #2: \b2\b -//! > 3 -//! ~ -//! match #3: \b3\b -//! > 5 -//! > 7 -//! ... -//! > 79 -//! > 83 -//! > 89 -//! ~~ -//! match #4: \b89\b -//! > 97 -//! ~~ -//! match #5: \b97\b -//! no match #6: \d+ -//! OK -//! ``` -//! -//! ## The `check:` directive -//! -//! Match patterns non-overlapping and in order: -//! -//! ```sh -//! #0 check: one -//! #1 check: two -//! ``` -//! -//! These directives will match the string `"one two"`, but not `"two one"`. The second directive -//! must match after the first one, and it can't overlap. -//! -//! ## The `sameln:` directive -//! -//! Match a pattern in the same line as the previous match. -//! -//! ```sh -//! #0 check: one -//! #1 sameln: two -//! ``` -//! -//! These directives will match the string `"one two"`, but not `"one\ntwo"`. The second match must -//! be in the same line as the first. Like the `check:` directive, the match must also follow the -//! first match, so `"two one" would not be matched. -//! -//! If there is no previous match, `sameln:` matches on the first line of the input. -//! -//! ## The `nextln:` directive -//! -//! Match a pattern in the next line after the previous match. -//! -//! ```sh -//! #0 check: one -//! #1 nextln: two -//! ``` -//! -//! These directives will match the string `"one\ntwo"`, but not `"one two"` or `"one\n\ntwo"`. -//! -//! If there is no previous match, `nextln:` matches on the second line of the input as if there -//! were a previous match on the first line. -//! -//! ## The `unordered:` directive -//! -//! Match patterns in any order, and possibly overlapping each other. -//! -//! ```sh -//! #0 unordered: one -//! #1 unordered: two -//! ``` -//! -//! These directives will match the string `"one two"` *and* the string `"two one"`. -//! -//! When a normal ordered match is inserted into a sequence of `unordered:` directives, it acts as -//! a barrier: -//! -//! ```sh -//! #0 unordered: one -//! #1 unordered: two -//! #2 check: three -//! #3 unordered: four -//! #4 unordered: five -//! ``` -//! -//! These directives will match `"two one three four five"`, but not `"two three one four five"`. -//! The `unordered:` matches are not allowed to cross the ordered `check:` directive. -//! -//! When `unordered:` matches define and use variables, a topological order is enforced. This means -//! that a match referencing a variable must follow the match where the variable was defined: -//! -//! ```sh -//! #0 regex: V=\bv\d+\b -//! #1 unordered: $(va=$V) = load -//! #2 unordered: $(vb=$V) = iadd $va -//! #3 unordered: $(vc=$V) = load -//! #4 unordered: iadd $va, $vc -//! ``` -//! -//! In the above directives, #2 must match after #1, and #4 must match after both #1 and #3, but -//! otherwise they can match in any order. -//! -//! ## The `not:` directive -//! -//! Check that a pattern *does not* appear between matches. -//! -//! ```sh -//! #0 check: one -//! #1 not: two -//! #2 check: three -//! ``` -//! -//! The directives above will match `"one five three"`, but not `"one two three"`. -//! -//! The pattern in a `not:` directive can't define any variables. Since it never matches anything, -//! the variables would not get a value. -//! -//! ## The `regex:` directive -//! -//! Define a shorthand name for a regular expression. -//! -//! ```sh -//! #0 regex: ID=\b[_a-zA-Z][_0-9a-zA-Z]*\b -//! #1 check: $ID + $ID -//! ``` -//! -//! The `regex:` directive gives a name to a regular expression which can then be used as part of a -//! pattern to match. Patterns are otherwise just plain text strings to match, so this is not -//! simple macro expansion. -//! -//! See [the Rust regex crate](../regex/index.html#syntax) for the regular expression syntax. -//! -//! # Patterns and variables -//! -//! Patterns are plain text strings to be matched in the input file. The dollar sign is used as an -//! escape character to expand variables. The following escape sequences are recognized: -//! -//!
-//! $$                Match single dollar sign.
-//! $()               Match the empty string.
-//! $(=<regex>)       Match regular expression <regex>.
-//! $<var>            Match contents of variable <var>.
-//! $(<var>)          Match contents of variable <var>.
-//! $(<var>=<regex>)  Match <regex>, then
-//!                   define <var> as the matched text.
-//! $(<var>=$<rxvar>) Match regex in <rxvar>, then
-//!                   define <var> as the matched text.
-//! 
-//! -//! Variables can contain either plain text or regular expressions. Plain text variables are -//! defined with the `$(var=...)` syntax in a previous directive. They match the same text again. -//! Backreferences within the same pattern are not allowed. When a variable is defined in a -//! pattern, it can't be referenced again in the same pattern. -//! -//! Regular expression variables are defined with the `regex:` directive. They match the regular -//! expression each time they are used, so the matches don't need to be identical. -//! -//! ## Word boundaries -//! -//! If a pattern begins or ends with a (plain text) letter or number, it will only match on a word -//! boundary. Use the `$()` empty string match to prevent this: -//! -//! ```sh -//! check: one$() -//! ``` -//! -//! This will match `"one"` and `"onetwo"`, but not `"zeroone"`. -//! -//! The empty match syntax can also be used to require leading or trailing whitespace: -//! -//! ```sh -//! check: one, $() -//! ``` -//! -//! This will match `"one, two"` , but not `"one,two"`. Without the `$()`, trailing whitespace -//! would be trimmed from the pattern. - -#![deny(missing_docs, - trivial_numeric_casts, - unused_extern_crates)] - -pub use error::{Error, Result}; -pub use variable::{VariableMap, Value, NO_VARIABLES}; -pub use checker::{Checker, CheckerBuilder}; - -extern crate regex; - -mod error; -mod variable; -mod pattern; -mod checker; -mod explain; - -/// The range of a match in the input text. -pub type MatchRange = (usize, usize); diff --git a/lib/filecheck/src/pattern.rs b/lib/filecheck/src/pattern.rs deleted file mode 100644 index 97977b191c..0000000000 --- a/lib/filecheck/src/pattern.rs +++ /dev/null @@ -1,593 +0,0 @@ -//! Pattern matching for a single directive. - -use error::{Error, Result}; -use variable::{varname_prefix, VariableMap, Value}; -use std::str::FromStr; -use std::fmt::{self, Display, Formatter, Write}; -use regex::{Regex, RegexBuilder, escape}; - -/// A pattern to match as specified in a directive. -/// -/// Each pattern is broken into a sequence of parts that must match in order. The kinds of parts -/// are: -/// -/// 1. Plain text match. -/// 2. Variable match, `$FOO` or `$(FOO)`. The variable `FOO` may expand to plain text or a regex. -/// 3. Variable definition from literal regex, `$(foo=.*)`. Match the regex and assign matching text -/// to variable `foo`. -/// 4. Variable definition from regex variable, `$(foo=$RX)`. Lookup variable `RX` which should -/// expand to a regex, match the regex, and assign matching text to variable `foo`. -/// -pub struct Pattern { - parts: Vec, - // Variables defined by this pattern. - defs: Vec, -} - -/// One atomic part of a pattern. -#[derive(Debug, PartialEq, Eq)] -pub enum Part { - /// Match a plain string. - Text(String), - /// Match a regular expression. The regex has already been wrapped in a non-capturing group if - /// necessary, so it is safe to concatenate. - Regex(String), - /// Match the contents of a variable, which can be plain text or regex. - Var(String), - /// Match literal regex, then assign match to variable. - /// The regex has already been wrapped in a named capture group. - DefLit { def: usize, regex: String }, - /// Lookup variable `var`, match resulting regex, assign matching text to variable `defs[def]`. - DefVar { def: usize, var: String }, -} - -impl Part { - /// Get the variable referenced by this part, if any. - pub fn ref_var(&self) -> Option<&str> { - match *self { - Part::Var(ref var) | - Part::DefVar { ref var, .. } => Some(var), - _ => None, - } - } -} - -impl Pattern { - /// Create a new blank pattern. Use the `FromStr` trait to generate Patterns with content. - fn new() -> Self { - Self { - parts: Vec::new(), - defs: Vec::new(), - } - } - - /// Check if the variable `v` is defined by this pattern. - pub fn defines_var(&self, v: &str) -> bool { - self.defs.iter().any(|d| d == v) - } - - /// Add a definition of a new variable. - /// Return the allocated def number. - fn add_def(&mut self, v: &str) -> Result { - if self.defines_var(v) { - Err(Error::DuplicateDef( - format!("duplicate definition of ${} in same pattern", v), - )) - } else { - let idx = self.defs.len(); - self.defs.push(v.to_string()); - Ok(idx) - } - } - - /// Parse a `Part` from a prefix of `s`. - /// Return the part and the number of bytes consumed from `s`. - /// Adds defined variables to `self.defs`. - fn parse_part(&mut self, s: &str) -> Result<(Part, usize)> { - let dollar = s.find('$'); - if dollar != Some(0) { - // String doesn't begin with a dollar sign, so match plain text up to the dollar sign. - let end = dollar.unwrap_or(s.len()); - return Ok((Part::Text(s[0..end].to_string()), end)); - } - - // String starts with a dollar sign. Look for these possibilities: - // - // 1. `$$`. - // 2. `$var`. - // 3. `$(var)`. - // 4. `$(var=regex)`. Where `regex` is a regular expression possibly containing matching - // braces. - // 5. `$(var=$VAR)`. - - // A doubled dollar sign matches a single dollar sign. - if s.starts_with("$$") { - return Ok((Part::Text("$".to_string()), 2)); - } - - // Look for `$var`. - let varname_end = 1 + varname_prefix(&s[1..]); - if varname_end != 1 { - return Ok((Part::Var(s[1..varname_end].to_string()), varname_end)); - } - - // All remaining possibilities start with `$(`. - if s.len() < 2 || !s.starts_with("$(") { - return Err(Error::Syntax( - "pattern syntax error, use $$ to match a single $" - .to_string(), - )); - } - - // Match the variable name, allowing for an empty varname in `$()`, or `$(=...)`. - let varname_end = 2 + varname_prefix(&s[2..]); - let varname = s[2..varname_end].to_string(); - - match s[varname_end..].chars().next() { - None => { - return Err(Error::Syntax(format!("unterminated $({}...", varname))); - } - Some(')') => { - let part = if varname.is_empty() { - // Match `$()`, turn it into an empty text match. - Part::Text(varname) - } else { - // Match `$(var)`. - Part::Var(varname) - }; - return Ok((part, varname_end + 1)); - } - Some('=') => { - // Variable definition. Fall through. - } - Some(ch) => { - return Err(Error::Syntax( - format!("syntax error in $({}... '{}'", varname, ch), - )); - } - } - - // This is a variable definition of the form `$(var=...`. - - // Allocate a definition index. - let def = if varname.is_empty() { - None - } else { - Some(self.add_def(&varname)?) - }; - - // Match `$(var=$PAT)`. - if s[varname_end + 1..].starts_with('$') { - let refname_begin = varname_end + 2; - let refname_end = refname_begin + varname_prefix(&s[refname_begin..]); - if refname_begin == refname_end { - return Err(Error::Syntax( - format!("expected variable name in $({}=$...", varname), - )); - } - if !s[refname_end..].starts_with(')') { - return Err(Error::Syntax(format!( - "expected ')' after $({}=${}...", - varname, - &s[refname_begin..refname_end] - ))); - } - let refname = s[refname_begin..refname_end].to_string(); - return if let Some(defidx) = def { - Ok(( - Part::DefVar { - def: defidx, - var: refname, - }, - refname_end + 1, - )) - } else { - Err(Error::Syntax( - format!("expected variable name in $(=${})", refname), - )) - }; - } - - // Last case: `$(var=...)` where `...` is a regular expression, possibly containing matched - // parentheses. - let rx_begin = varname_end + 1; - let rx_end = rx_begin + regex_prefix(&s[rx_begin..]); - if s[rx_end..].starts_with(')') { - let part = if let Some(defidx) = def { - // Wrap the regex in a named capture group. - Part::DefLit { - def: defidx, - regex: format!("(?P<{}>{})", varname, &s[rx_begin..rx_end]), - } - } else { - // When the varname is empty just match the regex, don't capture any variables. - // This is `$(=[a-z])`. - // Wrap the regex in a non-capturing group to make it concatenation-safe. - Part::Regex(format!("(?:{})", &s[rx_begin..rx_end])) - }; - Ok((part, rx_end + 1)) - } else { - Err(Error::Syntax(format!( - "missing ')' after regex in $({}={}", - varname, - &s[rx_begin..rx_end] - ))) - } - } -} - -/// Compute the length of a regular expression terminated by `)` or `}`. -/// Handle nested and escaped parentheses in the rx, but don't actually parse it. -/// Return the position of the terminating brace or the length of the string. -fn regex_prefix(s: &str) -> usize { - // The previous char was a backslash. - let mut escape = false; - // State around parsing charsets. - enum State { - Normal, // Outside any charset. - Curly, // Inside curly braces. - CSFirst, // Immediately after opening `[`. - CSNeg, // Immediately after `[^`. - CSBody, // Inside `[...`. - } - let mut state = State::Normal; - - // Current nesting level of parens. - let mut nest = 0usize; - - for (idx, ch) in s.char_indices() { - if escape { - escape = false; - continue; - } else if ch == '\\' { - escape = true; - continue; - } - match state { - State::Normal => { - match ch { - '[' => state = State::CSFirst, - '{' => state = State::Curly, - '(' => nest += 1, - ')' if nest > 0 => nest -= 1, - ')' | '}' => return idx, - _ => {} - } - } - State::Curly => { - if ch == '}' { - state = State::Normal; - } - } - State::CSFirst => { - state = match ch { - '^' => State::CSNeg, - _ => State::CSBody, - } - } - State::CSNeg => state = State::CSBody, - State::CSBody => { - if ch == ']' { - state = State::Normal; - } - } - } - } - s.len() -} - -impl FromStr for Pattern { - type Err = Error; - - fn from_str(s: &str) -> Result { - // Always remove leading and trailing whitespace. - // Use `$()` to actually include that in a match. - let s = s.trim(); - let mut pat = Pattern::new(); - let mut pos = 0; - while pos < s.len() { - let (part, len) = pat.parse_part(&s[pos..])?; - if let Some(v) = part.ref_var() { - if pat.defines_var(v) { - return Err(Error::Backref(format!( - "unsupported back-reference to '${}' \ - defined in same pattern", - v - ))); - } - } - pat.parts.push(part); - pos += len; - } - Ok(pat) - } -} - -impl Pattern { - /// Get a list of parts in this pattern. - pub fn parts(&self) -> &[Part] { - &self.parts - } - - /// Get a list of variable names defined when this pattern matches. - pub fn defs(&self) -> &[String] { - &self.defs - } - - /// Resolve all variable references in this pattern, turning it into a regular expression. - pub fn resolve(&self, vmap: &VariableMap) -> Result { - let mut out = String::new(); - - // Add a word boundary check `\b` to the beginning of the regex, but only if the first part - // is a plain text match that starts with a word character. - // - // This behavior can be disabled by starting the pattern with `$()`. - if let Some(&Part::Text(ref s)) = self.parts.first() { - if s.starts_with(char::is_alphanumeric) { - out.push_str(r"\b"); - } - } - - for part in &self.parts { - match *part { - Part::Text(ref s) => { - out.push_str(&escape(s)); - } - Part::Regex(ref rx) => out.push_str(rx), - Part::Var(ref var) => { - // Resolve the variable. We can handle a plain text expansion. - match vmap.lookup(var) { - None => { - return Err(Error::UndefVariable(format!("undefined variable ${}", var))) - } - Some(Value::Text(s)) => out.push_str(&escape(&s)), - // Wrap regex in non-capturing group for safe concatenation. - Some(Value::Regex(rx)) => write!(out, "(?:{})", rx).unwrap(), - } - } - Part::DefLit { ref regex, .. } => out.push_str(regex), - Part::DefVar { def, ref var } => { - // Wrap regex in a named capture group. - write!(out, "(?P<{}>", self.defs[def]).unwrap(); - match vmap.lookup(var) { - None => { - return Err(Error::UndefVariable(format!("undefined variable ${}", var))) - } - Some(Value::Text(s)) => write!(out, "{})", escape(&s[..])).unwrap(), - Some(Value::Regex(rx)) => write!(out, "{})", rx).unwrap(), - } - } - } - - } - - // Add a word boundary check `\b` to the end of the regex, but only if the final part - // is a plain text match that ends with a word character. - // - // This behavior can be disabled by ending the pattern with `$()`. - if let Some(&Part::Text(ref s)) = self.parts.last() { - if s.ends_with(char::is_alphanumeric) { - out.push_str(r"\b"); - } - } - - Ok(RegexBuilder::new(&out).multi_line(true).build()?) - } -} - -impl Display for Pattern { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - for part in &self.parts { - use self::Part::*; - match *part { - Text(ref txt) if txt == "" => write!(f, "$()"), - Text(ref txt) if txt == "$" => write!(f, "$$"), - Text(ref txt) => write!(f, "{}", txt), - Regex(ref rx) => write!(f, "$(={})", rx), - Var(ref var) => write!(f, "$({})", var), - DefLit { def, ref regex } => { - let defvar = &self.defs[def]; - // (?P...). - let litrx = ®ex[5 + defvar.len()..regex.len() - 1]; - write!(f, "$({}={})", defvar, litrx) - } - DefVar { def, ref var } => write!(f, "$({}=${})", self.defs[def], var), - }?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - #[test] - fn regex() { - use super::regex_prefix; - - assert_eq!(regex_prefix(""), 0); - assert_eq!(regex_prefix(")"), 0); - assert_eq!(regex_prefix(")c"), 0); - assert_eq!(regex_prefix("x"), 1); - assert_eq!(regex_prefix("x)x"), 1); - - assert_eq!(regex_prefix("x(c))x"), 4); - assert_eq!(regex_prefix("()x(c))x"), 6); - assert_eq!(regex_prefix("()x(c)"), 6); - - assert_eq!(regex_prefix("x([)]))x"), 6); - assert_eq!(regex_prefix("x[)])x"), 4); - assert_eq!(regex_prefix("x[^)])x"), 5); - assert_eq!(regex_prefix("x[^])x"), 6); - } - - #[test] - fn part() { - use super::{Pattern, Part}; - let mut pat = Pattern::new(); - - // This is dubious, should we panic instead? - assert_eq!(pat.parse_part("").unwrap(), (Part::Text("".to_string()), 0)); - - assert_eq!( - pat.parse_part("x").unwrap(), - (Part::Text("x".to_string()), 1) - ); - assert_eq!(pat.parse_part("x2").unwrap(), ( - Part::Text("x2".to_string()), - 2, - )); - assert_eq!(pat.parse_part("x$").unwrap(), ( - Part::Text("x".to_string()), - 1, - )); - assert_eq!(pat.parse_part("x$$").unwrap(), ( - Part::Text("x".to_string()), - 1, - )); - - assert_eq!( - pat.parse_part("$").unwrap_err().to_string(), - "pattern syntax error, use $$ to match a single $" - ); - - assert_eq!(pat.parse_part("$$").unwrap(), ( - Part::Text("$".to_string()), - 2, - )); - assert_eq!(pat.parse_part("$$ ").unwrap(), ( - Part::Text("$".to_string()), - 2, - )); - - assert_eq!( - pat.parse_part("$0").unwrap(), - (Part::Var("0".to_string()), 2) - ); - assert_eq!(pat.parse_part("$xx=").unwrap(), ( - Part::Var("xx".to_string()), - 3, - )); - assert_eq!(pat.parse_part("$xx$").unwrap(), ( - Part::Var("xx".to_string()), - 3, - )); - - assert_eq!(pat.parse_part("$(0)").unwrap(), ( - Part::Var("0".to_string()), - 4, - )); - assert_eq!(pat.parse_part("$()").unwrap(), ( - Part::Text("".to_string()), - 3, - )); - - assert_eq!( - pat.parse_part("$(0").unwrap_err().to_string(), - ("unterminated $(0...") - ); - assert_eq!( - pat.parse_part("$(foo:").unwrap_err().to_string(), - ("syntax error in $(foo... ':'") - ); - assert_eq!( - pat.parse_part("$(foo =").unwrap_err().to_string(), - ("syntax error in $(foo... ' '") - ); - assert_eq!( - pat.parse_part("$(eo0=$bar").unwrap_err().to_string(), - ("expected ')' after $(eo0=$bar...") - ); - assert_eq!( - pat.parse_part("$(eo1=$bar}").unwrap_err().to_string(), - ("expected ')' after $(eo1=$bar...") - ); - assert_eq!( - pat.parse_part("$(eo2=$)").unwrap_err().to_string(), - ("expected variable name in $(eo2=$...") - ); - assert_eq!( - pat.parse_part("$(eo3=$-)").unwrap_err().to_string(), - ("expected variable name in $(eo3=$...") - ); - } - - #[test] - fn partdefs() { - use super::{Pattern, Part}; - let mut pat = Pattern::new(); - - assert_eq!(pat.parse_part("$(foo=$bar)").unwrap(), ( - Part::DefVar { - def: 0, - var: "bar".to_string(), - }, - 11, - )); - assert_eq!( - pat.parse_part("$(foo=$bar)").unwrap_err().to_string(), - "duplicate definition of $foo in same pattern" - ); - - assert_eq!(pat.parse_part("$(fxo=$bar)x").unwrap(), ( - Part::DefVar { - def: 1, - var: "bar".to_string(), - }, - 11, - )); - - assert_eq!(pat.parse_part("$(fo2=[a-z])").unwrap(), ( - Part::DefLit { - def: 2, - regex: "(?P[a-z])".to_string(), - }, - 12, - )); - assert_eq!(pat.parse_part("$(fo3=[a-)])").unwrap(), ( - Part::DefLit { - def: 3, - regex: "(?P[a-)])".to_string(), - }, - 12, - )); - assert_eq!(pat.parse_part("$(fo4=)").unwrap(), ( - Part::DefLit { - def: 4, - regex: "(?P)".to_string(), - }, - 7, - )); - - assert_eq!(pat.parse_part("$(=.*)").unwrap(), ( - Part::Regex( - "(?:.*)".to_string(), - ), - 6, - )); - - assert_eq!(pat.parse_part("$(=)").unwrap(), ( - Part::Regex( - "(?:)".to_string(), - ), - 4, - )); - assert_eq!(pat.parse_part("$()").unwrap(), ( - Part::Text("".to_string()), - 3, - )); - } - - #[test] - fn pattern() { - use super::Pattern; - - let p: Pattern = " Hello world! ".parse().unwrap(); - assert_eq!(format!("{:?}", p.parts), "[Text(\"Hello world!\")]"); - - let p: Pattern = " $foo=$(bar) ".parse().unwrap(); - assert_eq!( - format!("{:?}", p.parts), - "[Var(\"foo\"), Text(\"=\"), Var(\"bar\")]" - ); - } -} diff --git a/lib/filecheck/src/variable.rs b/lib/filecheck/src/variable.rs deleted file mode 100644 index 5977f06354..0000000000 --- a/lib/filecheck/src/variable.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::borrow::Cow; - -/// A variable name is one or more ASCII alphanumerical characters, including underscore. -/// Note that numerical variable names like `$45` are allowed too. -/// -/// Try to parse a variable name from the beginning of `s`. -/// Return the index of the character following the varname. -/// This returns 0 if `s` doesn't have a prefix that is a variable name. -pub fn varname_prefix(s: &str) -> usize { - for (idx, ch) in s.char_indices() { - match ch { - 'a'...'z' | 'A'...'Z' | '0'...'9' | '_' => {} - _ => return idx, - } - } - s.len() -} - -/// A variable can contain either a regular expression or plain text. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Value<'a> { - /// Verbatim text. - Text(Cow<'a, str>), - /// Regular expression. - Regex(Cow<'a, str>), -} - -/// Resolve variables by name. -pub trait VariableMap { - /// Get the value of the variable `varname`, or return `None` for an unknown variable name. - fn lookup(&self, varname: &str) -> Option; -} - -impl VariableMap for () { - fn lookup(&self, _: &str) -> Option { - None - } -} - -/// An empty variable map. -pub const NO_VARIABLES: &'static VariableMap = &(); - -#[cfg(test)] -mod tests { - #[test] - fn varname() { - use super::varname_prefix; - - assert_eq!(varname_prefix(""), 0); - assert_eq!(varname_prefix("\0"), 0); - assert_eq!(varname_prefix("_"), 1); - assert_eq!(varname_prefix("0"), 1); - assert_eq!(varname_prefix("01"), 2); - assert_eq!(varname_prefix("b"), 1); - assert_eq!(varname_prefix("C"), 1); - assert_eq!(varname_prefix("."), 0); - assert_eq!(varname_prefix(".s"), 0); - assert_eq!(varname_prefix("0."), 1); - assert_eq!(varname_prefix("01="), 2); - assert_eq!(varname_prefix("0a)"), 2); - } -} diff --git a/lib/filecheck/tests/basic.rs b/lib/filecheck/tests/basic.rs deleted file mode 100644 index 1ccc5991a5..0000000000 --- a/lib/filecheck/tests/basic.rs +++ /dev/null @@ -1,402 +0,0 @@ -extern crate filecheck; - -use filecheck::{CheckerBuilder, NO_VARIABLES, Error as FcError}; - -fn e2s(e: FcError) -> String { - e.to_string() -} - -#[test] -fn empty() { - let c = CheckerBuilder::new().finish(); - assert!(c.is_empty()); - - // An empty checker matches anything. - assert_eq!(c.check("", NO_VARIABLES).map_err(e2s), Ok(true)); - assert_eq!(c.check("hello", NO_VARIABLES).map_err(e2s), Ok(true)); -} - -#[test] -fn no_directives() { - let c = CheckerBuilder::new().text("nothing here").unwrap().finish(); - assert!(c.is_empty()); - - // An empty checker matches anything. - assert_eq!(c.check("", NO_VARIABLES).map_err(e2s), Ok(true)); - assert_eq!(c.check("hello", NO_VARIABLES).map_err(e2s), Ok(true)); -} - -#[test] -fn no_matches() { - let c = CheckerBuilder::new() - .text("regex: FOO=bar") - .unwrap() - .finish(); - assert!(!c.is_empty()); - - // An empty checker matches anything. - assert_eq!(c.check("", NO_VARIABLES).map_err(e2s), Ok(true)); - assert_eq!(c.check("hello", NO_VARIABLES).map_err(e2s), Ok(true)); -} - -#[test] -fn simple() { - let c = CheckerBuilder::new() - .text( - " - check: one - check: two - ", - ) - .unwrap() - .finish(); - - let t = " - zero - one - and a half - two - three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); - - let t = " - zero - and a half - two - one - three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); -} - -#[test] -fn sameln() { - let c = CheckerBuilder::new() - .text( - " - check: one - sameln: two - ", - ) - .unwrap() - .finish(); - - let t = " - zero - one - and a half - two - three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - let t = " - zero - one - two - three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - let t = " - zero - one two - three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); -} - -#[test] -fn nextln() { - let c = CheckerBuilder::new() - .text( - " - check: one - nextln: two - ", - ) - .unwrap() - .finish(); - - let t = " - zero - one - and a half - two - three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - let t = " - zero - one - two - three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); - - let t = " - zero - one two - three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - let t = " - zero - one - two"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); -} - -#[test] -fn leading_nextln() { - // A leading nextln directive should match from line 2. - // This is somewhat arbitrary, but consistent with a preceding 'check: $()' directive. - let c = CheckerBuilder::new() - .text( - " - nextln: one - nextln: two - ", - ) - .unwrap() - .finish(); - - let t = "zero - one - two - three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); - - let t = "one - two - three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); -} - -#[test] -fn leading_sameln() { - // A leading sameln directive should match from line 1. - let c = CheckerBuilder::new() - .text( - " - sameln: one - sameln: two - ", - ) - .unwrap() - .finish(); - - let t = "zero - one two three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - let t = "zero one two three"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); - - let t = "zero one - two three"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); -} - -#[test] -fn not() { - let c = CheckerBuilder::new() - .text( - " - check: one$() - not: $()eat$() - check: $()two - ", - ) - .unwrap() - .finish(); - - let t = "onetwo"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); - - let t = "one eat two"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - let t = "oneeattwo"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - let t = "oneatwo"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); -} - -#[test] -fn notnot() { - let c = CheckerBuilder::new() - .text( - " - check: one$() - not: $()eat$() - not: half - check: $()two - ", - ) - .unwrap() - .finish(); - - let t = "onetwo"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); - - let t = "one eat two"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - let t = "one half two"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - let t = "oneeattwo"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - // The `not: half` pattern only matches whole words, but the bracketing matches are considered - // word boundaries, so it does match in this case. - let t = "onehalftwo"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - let t = "oneatwo"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); -} - -#[test] -fn unordered() { - let c = CheckerBuilder::new() - .text( - " - check: one - unordered: two - unordered: three - check: four - ", - ) - .unwrap() - .finish(); - - assert_eq!( - c.check("one two three four", NO_VARIABLES).map_err(e2s), - Ok(true) - ); - assert_eq!( - c.check("one three two four", NO_VARIABLES).map_err(e2s), - Ok(true) - ); - - assert_eq!( - c.check("one two four three four", NO_VARIABLES).map_err( - e2s, - ), - Ok(true) - ); - assert_eq!( - c.check("one three four two four", NO_VARIABLES).map_err( - e2s, - ), - Ok(true) - ); - - assert_eq!( - c.check("one two four three", NO_VARIABLES).map_err(e2s), - Ok(false) - ); - assert_eq!( - c.check("one three four two", NO_VARIABLES).map_err(e2s), - Ok(false) - ); -} - -#[test] -fn leading_unordered() { - let c = CheckerBuilder::new() - .text( - " - unordered: two - unordered: three - check: four - ", - ) - .unwrap() - .finish(); - - assert_eq!( - c.check("one two three four", NO_VARIABLES).map_err(e2s), - Ok(true) - ); - assert_eq!( - c.check("one three two four", NO_VARIABLES).map_err(e2s), - Ok(true) - ); - - assert_eq!( - c.check("one two four three four", NO_VARIABLES).map_err( - e2s, - ), - Ok(true) - ); - assert_eq!( - c.check("one three four two four", NO_VARIABLES).map_err( - e2s, - ), - Ok(true) - ); - - assert_eq!( - c.check("one two four three", NO_VARIABLES).map_err(e2s), - Ok(false) - ); - assert_eq!( - c.check("one three four two", NO_VARIABLES).map_err(e2s), - Ok(false) - ); -} - -#[test] -fn trailing_unordered() { - let c = CheckerBuilder::new() - .text( - " - check: one - unordered: two - unordered: three - ", - ) - .unwrap() - .finish(); - - assert_eq!( - c.check("one two three four", NO_VARIABLES).map_err(e2s), - Ok(true) - ); - assert_eq!( - c.check("one three two four", NO_VARIABLES).map_err(e2s), - Ok(true) - ); - - assert_eq!( - c.check("one two four three four", NO_VARIABLES).map_err( - e2s, - ), - Ok(true) - ); - assert_eq!( - c.check("one three four two four", NO_VARIABLES).map_err( - e2s, - ), - Ok(true) - ); - - assert_eq!( - c.check("one two four three", NO_VARIABLES).map_err(e2s), - Ok(true) - ); - assert_eq!( - c.check("one three four two", NO_VARIABLES).map_err(e2s), - Ok(true) - ); -} diff --git a/lib/filetests/Cargo.toml b/lib/filetests/Cargo.toml index 215f947e74..4ef875ea2c 100644 --- a/lib/filetests/Cargo.toml +++ b/lib/filetests/Cargo.toml @@ -14,5 +14,5 @@ name = "cton_filetests" [dependencies] cretonne = { path = "../cretonne", version = "0.3.4" } cretonne-reader = { path = "../reader", version = "0.3.4" } -filecheck = { path = "../filecheck", version = "0.1.0" } +filecheck = "0.2.0" num_cpus = "1.5.1"