summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2020-05-16 08:33:26 +0300
committerLars Wirzenius <liw@liw.fi>2020-05-17 12:11:02 +0300
commit8e8a6ee6249b365b3d50c64c442141d704e59f62 (patch)
tree797bc0a5a55661db432eb1b0b4dd56307830bf35
parent5db9eef3e02f7e3b344becd08c74a99fb274f2ed (diff)
downloadsubplot-8e8a6ee6249b365b3d50c64c442141d704e59f62.tar.gz
feat: add optional "cleanup" function to bindings
This adds an optional "cleanup" field to bindings, as the name of a function to be called when the scenario ends. As a result, the code generation templates will be able to generate code to call the cleanup functions for successful steps. The diff for this commit is a little extra long because the extra argument to various ::new functions makes lines so long, rustfmt breaks them into several lines.
-rw-r--r--src/bindings.rs95
-rw-r--r--src/matches.rs4
2 files changed, 73 insertions, 26 deletions
diff --git a/src/bindings.rs b/src/bindings.rs
index 946e8fb..67c45d6 100644
--- a/src/bindings.rs
+++ b/src/bindings.rs
@@ -17,22 +17,29 @@ use regex::{escape, Regex};
/// Contains the pattern used to match against scenario steps,
/// combined with the step kind. The pattern is a regular expression
/// as understood by the regex crate.
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct Binding {
kind: StepKind,
pattern: String,
regex: Regex,
function: String,
+ cleanup: Option<String>,
}
impl Binding {
/// Create a new Binding, from a step kind and a pattern.
- pub fn new(kind: StepKind, pattern: &str, function: &str) -> Result<Binding> {
+ pub fn new(
+ kind: StepKind,
+ pattern: &str,
+ function: &str,
+ cleanup: Option<&str>,
+ ) -> Result<Binding> {
Ok(Binding {
kind,
pattern: pattern.to_owned(),
regex: Regex::new(&format!("^{}$", pattern))?,
function: function.to_string(),
+ cleanup: cleanup.map(String::from),
})
}
@@ -51,6 +58,14 @@ impl Binding {
&self.function
}
+ /// Return name of function that implements cleanup.
+ pub fn cleanup(&self) -> Option<&str> {
+ match self.cleanup {
+ None => None,
+ Some(ref s) => Some(&s),
+ }
+ }
+
/// Return the compiled regular expression for the pattern of the
/// binding.
///
@@ -69,7 +84,7 @@ impl Binding {
let caps = self.regex.captures(step_text)?;
// If there is only one capture, it's the whole string.
- let mut m = MatchedStep::new(self.kind(), &self.function);
+ let mut m = MatchedStep::new(self.kind(), &self.function, self.cleanup());
if caps.len() == 1 {
m.append_part(PartialStep::uncaptured(step_text));
return Some(m);
@@ -125,12 +140,6 @@ impl PartialEq for Binding {
impl Eq for Binding {}
-impl Clone for Binding {
- fn clone(&self) -> Binding {
- Binding::new(self.kind, self.pattern.as_str(), &self.function).unwrap()
- }
-}
-
#[cfg(test)]
mod test_binding {
use super::Binding;
@@ -139,47 +148,65 @@ mod test_binding {
use crate::StepKind;
#[test]
- fn creates_new() {
- let b = Binding::new(StepKind::Given, "I am Tomjon", "set_name").unwrap();
+ fn creates_new_without_cleanup() {
+ let b = Binding::new(StepKind::Given, "I am Tomjon", "set_name", None).unwrap();
+ assert_eq!(b.kind(), StepKind::Given);
+ assert!(b.regex().is_match("I am Tomjon"));
+ assert!(!b.regex().is_match("I am Tomjon of Lancre"));
+ assert!(!b.regex().is_match("Hello, I am Tomjon"));
+ assert_eq!(b.function(), "set_name");
+ assert_eq!(b.cleanup(), None);
+ }
+
+ #[test]
+ fn creates_new_with_cleanup() {
+ let b = Binding::new(
+ StepKind::Given,
+ "I am Tomjon",
+ "set_name",
+ Some("unset_name"),
+ )
+ .unwrap();
assert_eq!(b.kind(), StepKind::Given);
assert!(b.regex().is_match("I am Tomjon"));
assert!(!b.regex().is_match("I am Tomjon of Lancre"));
assert!(!b.regex().is_match("Hello, I am Tomjon"));
assert_eq!(b.function(), "set_name");
+ assert_eq!(b.cleanup(), Some("unset_name"));
}
#[test]
fn equal() {
- let a = Binding::new(StepKind::Given, "I am Tomjon", "set_name").unwrap();
- let b = Binding::new(StepKind::Given, "I am Tomjon", "set_name").unwrap();
+ let a = Binding::new(StepKind::Given, "I am Tomjon", "set_name", Some("unset")).unwrap();
+ let b = Binding::new(StepKind::Given, "I am Tomjon", "set_name", Some("unset")).unwrap();
assert_eq!(a, b);
}
#[test]
fn not_equal() {
- let a = Binding::new(StepKind::Given, "I am Tomjon", "set_name").unwrap();
- let b = Binding::new(StepKind::Given, "I am Tomjon of Lancre", "set_name").unwrap();
+ let a = Binding::new(StepKind::Given, "I am Tomjon", "set_name", None).unwrap();
+ let b = Binding::new(StepKind::Given, "I am Tomjon of Lancre", "set_name", None).unwrap();
assert_ne!(a, b);
}
#[test]
fn does_not_match_with_wrong_kind() {
let step = ScenarioStep::new(StepKind::Given, "given", "yo");
- let b = Binding::new(StepKind::When, "yo", "do_yo").unwrap();
+ let b = Binding::new(StepKind::When, "yo", "do_yo", None).unwrap();
assert!(b.match_with_step(&step).is_none());
}
#[test]
fn does_not_match_with_wrong_text() {
let step = ScenarioStep::new(StepKind::Given, "given", "foo");
- let b = Binding::new(StepKind::Given, "bar", "yo").unwrap();
+ let b = Binding::new(StepKind::Given, "bar", "yo", None).unwrap();
assert!(b.match_with_step(&step).is_none());
}
#[test]
fn match_with_fixed_pattern() {
let step = ScenarioStep::new(StepKind::Given, "given", "foo");
- let b = Binding::new(StepKind::Given, "foo", "do_foo").unwrap();
+ let b = Binding::new(StepKind::Given, "foo", "do_foo", None).unwrap();
let m = b.match_with_step(&step).unwrap();
assert_eq!(m.kind(), StepKind::Given);
let mut parts = m.parts();
@@ -191,7 +218,13 @@ mod test_binding {
#[test]
fn match_with_regex() {
let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon, I am");
- let b = Binding::new(StepKind::Given, r"I am (?P<who>\S+), I am", "set_name").unwrap();
+ let b = Binding::new(
+ StepKind::Given,
+ r"I am (?P<who>\S+), I am",
+ "set_name",
+ None,
+ )
+ .unwrap();
let m = b.match_with_step(&step).unwrap();
assert_eq!(m.kind(), StepKind::Given);
let mut parts = m.parts();
@@ -220,6 +253,7 @@ struct ParsedBinding {
when: Option<String>,
then: Option<String>,
function: String,
+ cleanup: Option<String>,
regex: Option<bool>,
}
@@ -331,7 +365,15 @@ fn from_hashmap(parsed: &ParsedBinding) -> Result<Binding> {
regex_from_simple_pattern(pattern)?
};
- Ok(Binding::new(kind, &pattern, &parsed.function)?)
+ Ok(Binding::new(
+ kind,
+ &pattern,
+ &parsed.function,
+ match parsed.cleanup {
+ None => None,
+ Some(ref s) => Some(s),
+ },
+ )?)
}
#[cfg(test)]
@@ -351,7 +393,8 @@ mod test_bindings {
#[test]
fn adds_binding() {
- let binding = Binding::new(StepKind::Given, r"I am (?P<name>\S+)", "set_name").unwrap();
+ let binding =
+ Binding::new(StepKind::Given, r"I am (?P<name>\S+)", "set_name", None).unwrap();
let mut bindings = Bindings::new();
bindings.add(&binding);
assert_eq!(bindings.bindings(), &[binding]);
@@ -393,7 +436,7 @@ mod test_bindings {
#[test]
fn does_not_find_match_for_unmatching_kind() {
let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon");
- let binding = Binding::new(StepKind::When, r"I am Tomjon", "set_foo").unwrap();
+ let binding = Binding::new(StepKind::When, r"I am Tomjon", "set_foo", None).unwrap();
let mut bindings = Bindings::new();
bindings.add(&binding);
assert!(bindings.find(&step).is_none());
@@ -402,7 +445,8 @@ mod test_bindings {
#[test]
fn does_not_find_match_for_unmatching_pattern() {
let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon");
- let binding = Binding::new(StepKind::Given, r"I am Tomjon of Lancre", "set_foo").unwrap();
+ let binding =
+ Binding::new(StepKind::Given, r"I am Tomjon of Lancre", "set_foo", None).unwrap();
let mut bindings = Bindings::new();
bindings.add(&binding);
assert!(bindings.find(&step).is_none());
@@ -411,7 +455,7 @@ mod test_bindings {
#[test]
fn finds_match_for_fixed_string_pattern() {
let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon");
- let binding = Binding::new(StepKind::Given, r"I am Tomjon", "set_name").unwrap();
+ let binding = Binding::new(StepKind::Given, r"I am Tomjon", "set_name", None).unwrap();
let mut bindings = Bindings::new();
bindings.add(&binding);
let m = bindings.find(&step).unwrap();
@@ -428,7 +472,8 @@ mod test_bindings {
#[test]
fn finds_match_for_regexp_pattern() {
let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon");
- let binding = Binding::new(StepKind::Given, r"I am (?P<name>\S+)", "set_name").unwrap();
+ let binding =
+ Binding::new(StepKind::Given, r"I am (?P<name>\S+)", "set_name", None).unwrap();
let mut bindings = Bindings::new();
bindings.add(&binding);
let m = bindings.find(&step).unwrap();
diff --git a/src/matches.rs b/src/matches.rs
index 89cf791..27a71e1 100644
--- a/src/matches.rs
+++ b/src/matches.rs
@@ -46,16 +46,18 @@ pub struct MatchedStep {
text: String,
parts: Vec<PartialStep>,
function: String,
+ cleanup: Option<String>,
}
impl MatchedStep {
/// Return a new empty match. Empty means it has no step parts.
- pub fn new(kind: StepKind, function: &str) -> MatchedStep {
+ pub fn new(kind: StepKind, function: &str, cleanup: Option<&str>) -> MatchedStep {
MatchedStep {
kind,
text: "".to_string(),
parts: vec![],
function: function.to_string(),
+ cleanup: cleanup.map(String::from),
}
}