summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDaniel Silverstone <dsilvers@digital-scurf.org>2023-08-12 10:36:10 +0100
committerDaniel Silverstone <dsilvers@digital-scurf.org>2023-08-12 10:36:10 +0100
commit1646f8cbc8191d7e7a68abb0a99090b55b069097 (patch)
treeca4aa2e555a7cb6604d637d6717efdf20b298af3 /src
parentf6093a207f7f46eb547d90f2bb20113a9b009028 (diff)
downloadsubplot-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.rs27
-rw-r--r--src/html.rs4
-rw-r--r--src/md.rs22
-rw-r--r--src/scenarios.rs15
-rw-r--r--src/steps.rs35
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 {
diff --git a/src/md.rs b/src/md.rs
index b895528..554cc00 100644
--- a/src/md.rs
+++ b/src/md.rs
@@ -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!(),