diff options
author | Daniel Silverstone <dsilvers@digital-scurf.org> | 2023-08-12 10:36:10 +0100 |
---|---|---|
committer | Daniel Silverstone <dsilvers@digital-scurf.org> | 2023-08-12 10:36:10 +0100 |
commit | 1646f8cbc8191d7e7a68abb0a99090b55b069097 (patch) | |
tree | ca4aa2e555a7cb6604d637d6717efdf20b298af3 /src | |
parent | f6093a207f7f46eb547d90f2bb20113a9b009028 (diff) | |
download | subplot-1646f8cbc8191d7e7a68abb0a99090b55b069097.tar.gz |
steps: Pass location information into scenarios and scenario steps for error messages
Signed-off-by: Daniel Silverstone <dsilvers@digital-scurf.org>
Diffstat (limited to 'src')
-rw-r--r-- | src/bindings.rs | 27 | ||||
-rw-r--r-- | src/html.rs | 4 | ||||
-rw-r--r-- | src/md.rs | 22 | ||||
-rw-r--r-- | src/scenarios.rs | 15 | ||||
-rw-r--r-- | src/steps.rs | 35 |
5 files changed, 71 insertions, 32 deletions
diff --git a/src/bindings.rs b/src/bindings.rs index 2738aa1..e94b64e 100644 --- a/src/bindings.rs +++ b/src/bindings.rs @@ -311,6 +311,7 @@ impl Eq for Binding {} #[cfg(test)] mod test_binding { use super::Binding; + use crate::html::Location; use crate::PartialStep; use crate::ScenarioStep; use crate::StepKind; @@ -347,21 +348,21 @@ mod test_binding { #[test] fn does_not_match_with_wrong_kind() { - let step = ScenarioStep::new(StepKind::Given, "given", "yo"); + let step = ScenarioStep::new(StepKind::Given, "given", "yo", Location::Unknown); let b = Binding::new(StepKind::When, "yo", false, HashMap::new()).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 step = ScenarioStep::new(StepKind::Given, "given", "foo", Location::Unknown); let b = Binding::new(StepKind::Given, "bar", false, HashMap::new()).unwrap(); assert!(b.match_with_step("", &step).is_none()); } #[test] fn match_with_fixed_pattern() { - let step = ScenarioStep::new(StepKind::Given, "given", "foo"); + let step = ScenarioStep::new(StepKind::Given, "given", "foo", Location::Unknown); let b = Binding::new(StepKind::Given, "foo", false, HashMap::new()).unwrap(); let m = b.match_with_step("", &step).unwrap(); assert_eq!(m.kind(), StepKind::Given); @@ -373,7 +374,12 @@ mod test_binding { #[test] fn match_with_regex() { - let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon, I am"); + let step = ScenarioStep::new( + StepKind::Given, + "given", + "I am Tomjon, I am", + Location::Unknown, + ); let b = Binding::new( StepKind::Given, r"I am (?P<who>\S+), I am", @@ -392,7 +398,7 @@ mod test_binding { #[test] fn case_sensitive_mismatch() { - let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon"); + let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon", Location::Unknown); let b = Binding::new(StepKind::Given, r"i am tomjon", false, HashMap::new()).unwrap(); assert!(b.match_with_step("", &step).is_some()); let b = Binding::new(StepKind::Given, r"i am tomjon", true, HashMap::new()).unwrap(); @@ -583,6 +589,7 @@ fn from_hashmap(parsed: &ParsedBinding) -> Result<Binding, SubplotError> { #[cfg(test)] mod test_bindings { + use crate::html::Location; use crate::Binding; use crate::Bindings; use crate::PartialStep; @@ -686,7 +693,7 @@ mod test_bindings { #[test] fn does_not_find_match_for_unmatching_kind() { - let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon"); + let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon", Location::Unknown); let binding = Binding::new(StepKind::When, r"I am Tomjon", false, HashMap::new()).unwrap(); let mut bindings = Bindings::new(); bindings.add(binding); @@ -698,7 +705,7 @@ mod test_bindings { #[test] fn does_not_find_match_for_unmatching_pattern() { - let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon"); + let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon", Location::Unknown); let binding = Binding::new( StepKind::Given, r"I am Tomjon of Lancre", @@ -716,7 +723,7 @@ mod test_bindings { #[test] fn two_matching_bindings() { - let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon"); + let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon", Location::Unknown); let mut bindings = Bindings::default(); bindings.add(Binding::new(StepKind::Given, r"I am Tomjon", false, HashMap::new()).unwrap()); bindings.add( @@ -737,7 +744,7 @@ mod test_bindings { #[test] fn finds_match_for_fixed_string_pattern() { - let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon"); + let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon", Location::Unknown); let binding = Binding::new(StepKind::Given, r"I am Tomjon", false, HashMap::new()).unwrap(); let mut bindings = Bindings::new(); bindings.add(binding); @@ -754,7 +761,7 @@ mod test_bindings { #[test] fn finds_match_for_regexp_pattern() { - let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon"); + let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon", Location::Unknown); let binding = Binding::new( StepKind::Given, r"I am (?P<name>\S+)", diff --git a/src/html.rs b/src/html.rs index d9788d9..c5edc5b 100644 --- a/src/html.rs +++ b/src/html.rs @@ -6,6 +6,7 @@ use html_escape::{encode_double_quoted_attribute, encode_text}; use line_col::LineColLookup; use log::{debug, trace}; use pulldown_cmark::{CodeBlockKind, Event, HeadingLevel, Options, Parser, Tag}; +use serde::{Deserialize, Serialize}; use std::fmt::Write as _; use std::io::Write; use std::path::{Path, PathBuf}; @@ -547,7 +548,8 @@ impl Content { } /// Location of element in source file. -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] pub enum Location { /// A known location. Known { @@ -275,8 +275,8 @@ fn extract_scenario(e: &[StructureElement]) -> Result<(Option<Scenario>, usize), match &e[0] { StructureElement::Snippet(_, loc) => Err(SubplotError::ScenarioBeforeHeading(loc.clone())), - StructureElement::Heading(title, level, _loc) => { - let mut scen = Scenario::new(title); + StructureElement::Heading(title, level, loc) => { + let mut scen = Scenario::new(title, loc.clone()); let mut prevkind = None; for (i, item) in e.iter().enumerate().skip(1) { match item { @@ -293,9 +293,21 @@ fn extract_scenario(e: &[StructureElement]) -> Result<(Option<Scenario>, usize), return Ok((None, i)); } } - StructureElement::Snippet(text, _) => { - for line in parse_scenario_snippet(text) { - let step = ScenarioStep::new_from_str(line, prevkind)?; + StructureElement::Snippet(text, loc) => { + for (idx, line) in parse_scenario_snippet(text).enumerate() { + let line_loc = match loc.clone() { + Location::Known { + filename, + line, + col, + } => Location::Known { + filename, + line: line + idx, + col, + }, + Location::Unknown => Location::Unknown, + }; + let step = ScenarioStep::new_from_str(line, prevkind, line_loc)?; scen.add(&step); prevkind = Some(step.kind()); } diff --git a/src/scenarios.rs b/src/scenarios.rs index 31a321f..7039d98 100644 --- a/src/scenarios.rs +++ b/src/scenarios.rs @@ -1,4 +1,4 @@ -use crate::ScenarioStep; +use crate::{html::Location, ScenarioStep}; use serde::{Deserialize, Serialize}; /// An acceptance test scenario. @@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] pub struct Scenario { title: String, + origin: Location, steps: Vec<ScenarioStep>, } @@ -17,9 +18,10 @@ impl Scenario { /// Construct a new scenario. /// /// The new scenario will have a title, but no steps. - pub fn new(title: &str) -> Scenario { + pub fn new(title: &str, origin: Location) -> Scenario { Scenario { title: title.to_string(), + origin, steps: vec![], } } @@ -48,25 +50,26 @@ impl Scenario { #[cfg(test)] mod test { use super::Scenario; + use crate::html::Location; use crate::ScenarioStep; use crate::StepKind; #[test] fn has_title() { - let scen = Scenario::new("title"); + let scen = Scenario::new("title", Location::Unknown); assert_eq!(scen.title(), "title"); } #[test] fn has_no_steps_initially() { - let scen = Scenario::new("title"); + let scen = Scenario::new("title", Location::Unknown); assert_eq!(scen.steps().len(), 0); } #[test] fn adds_step() { - let mut scen = Scenario::new("title"); - let step = ScenarioStep::new(StepKind::Given, "and", "foo"); + let mut scen = Scenario::new("title", Location::Unknown); + let step = ScenarioStep::new(StepKind::Given, "and", "foo", Location::Unknown); scen.add(&step); assert_eq!(scen.steps(), &[step]); } diff --git a/src/steps.rs b/src/steps.rs index 1005ad8..43e66e2 100644 --- a/src/steps.rs +++ b/src/steps.rs @@ -1,4 +1,4 @@ -use crate::SubplotError; +use crate::{html::Location, SubplotError}; use serde::{Deserialize, Serialize}; use std::fmt; @@ -16,15 +16,17 @@ pub struct ScenarioStep { kind: StepKind, keyword: String, text: String, + origin: Location, } impl ScenarioStep { /// Construct a new step. - pub fn new(kind: StepKind, keyword: &str, text: &str) -> ScenarioStep { + pub fn new(kind: StepKind, keyword: &str, text: &str, origin: Location) -> ScenarioStep { ScenarioStep { kind, keyword: keyword.to_owned(), text: text.to_owned(), + origin, } } @@ -50,6 +52,7 @@ impl ScenarioStep { pub fn new_from_str( text: &str, default: Option<StepKind>, + origin: Location, ) -> Result<ScenarioStep, SubplotError> { if text.trim_start() != text { return Err(SubplotError::NotAtBoln(text.into())); @@ -79,7 +82,7 @@ impl ScenarioStep { if joined.len() > 1 { joined.pop(); } - Ok(ScenarioStep::new(kind, keyword, &joined)) + Ok(ScenarioStep::new(kind, keyword, &joined, origin)) } } @@ -119,47 +122,59 @@ impl fmt::Display for StepKind { #[cfg(test)] mod test { + use crate::html::Location; + use super::{ScenarioStep, StepKind, SubplotError}; #[test] fn parses_given() { - let step = ScenarioStep::new_from_str("GIVEN I am Tomjon", None).unwrap(); + let step = + ScenarioStep::new_from_str("GIVEN I am Tomjon", None, Location::Unknown).unwrap(); assert_eq!(step.kind(), StepKind::Given); assert_eq!(step.text(), "I am Tomjon"); } #[test] fn parses_given_with_extra_spaces() { - let step = ScenarioStep::new_from_str("given I am Tomjon ", None).unwrap(); + let step = + ScenarioStep::new_from_str("given I am Tomjon ", None, Location::Unknown) + .unwrap(); assert_eq!(step.kind(), StepKind::Given); assert_eq!(step.text(), "I am Tomjon"); } #[test] fn parses_when() { - let step = ScenarioStep::new_from_str("when I declare myself king", None).unwrap(); + let step = + ScenarioStep::new_from_str("when I declare myself king", None, Location::Unknown) + .unwrap(); assert_eq!(step.kind(), StepKind::When); assert_eq!(step.text(), "I declare myself king"); } #[test] fn parses_then() { - let step = ScenarioStep::new_from_str("thEN everyone accepts it", None).unwrap(); + let step = ScenarioStep::new_from_str("thEN everyone accepts it", None, Location::Unknown) + .unwrap(); assert_eq!(step.kind(), StepKind::Then); assert_eq!(step.text(), "everyone accepts it"); } #[test] fn parses_and() { - let step = - ScenarioStep::new_from_str("and everyone accepts it", Some(StepKind::Then)).unwrap(); + let step = ScenarioStep::new_from_str( + "and everyone accepts it", + Some(StepKind::Then), + Location::Unknown, + ) + .unwrap(); assert_eq!(step.kind(), StepKind::Then); assert_eq!(step.text(), "everyone accepts it"); } #[test] fn fails_to_parse_and() { - let step = ScenarioStep::new_from_str("and everyone accepts it", None); + let step = ScenarioStep::new_from_str("and everyone accepts it", None, Location::Unknown); assert!(step.is_err()); match step.err() { None => unreachable!(), |