Skip to content

Commit

Permalink
Add fixture tests and ast parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
mladedav committed Mar 14, 2022
1 parent 73e1c32 commit bbc2b20
Show file tree
Hide file tree
Showing 5 changed files with 281 additions and 5 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ syn = "1.0"
async-trait = "0.1.40"
cucumber = "0.11"
futures = "0.3.5"
serde_json = "1.0.78"

[[test]]
name = "cucumber"
Expand Down
145 changes: 145 additions & 0 deletions src/ast_checker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
use std::collections::HashMap;

use crate::Feature;

pub(crate) fn check_ast(parsed: Feature, ast_parsed: &str) {
let d: HashMap<String, serde_json::Value> = serde_json::from_str(&ast_parsed).unwrap();

let document = d
.get("gherkinDocument")
.expect("There is no document in the file");
let feature = document
.get("feature")
.expect("There is no feature in the document");
let children = feature.get("children");

if children.is_none() {
assert!(parsed.background.is_none());
assert_eq!(parsed.scenarios.len(), 0);
assert_eq!(parsed.rules.len(), 0);
return;
}

let children = children.unwrap().as_array().unwrap();

let mut backgrounds = 0;
let mut scenarios = 0;
let mut rules = 0;

for child in children {
if !child.is_object() {
panic!("Child must be object: {:#?}", child);
}
if child.as_object().unwrap().len() != 1 {
panic!(
"Child must have exactly one inner object, it had {}: {:#?}",
child.as_object().unwrap().len(),
child
);
}

if let Some(background) = child.get("background") {
backgrounds += 1;
let parsed_background = parsed.background.as_ref();
assert!(parsed_background.is_some());
let parsed_background = parsed_background.unwrap();

let name = background.get("name");
if name.is_none() {
assert!(parsed_background.name.is_none());
} else {
let name = name.unwrap().as_str();
assert_eq!(parsed_background.name.as_ref().map(|x| &(*x)[..]), name);
}

let steps = background.get("steps");

if steps.is_none() {
assert_eq!(parsed_background.steps.len(), 0);
continue;
}

let steps = steps.unwrap().as_array().expect("Steps must be an array");
if !check_steps(&parsed_background.steps, steps) {
panic!("Background steps are different from fixture");
}
} else if let Some(json_scenario) = child.get("scenario") {
scenarios += 1;

let parsed_scenarios = &parsed.scenarios;
let mut parsed_scenario_candidates = Vec::<&crate::Scenario>::new();

let json_scenario_name = json_scenario
.get("name")
.map(|x| x.as_str().unwrap())
.unwrap_or_default();

for scenario in parsed_scenarios.iter() {
if scenario.name == json_scenario_name {
parsed_scenario_candidates.push(&scenario);
}
}

let json_steps = json_scenario.get("steps");
if json_steps.is_none() {
// We should check that there is a scenario candidate with 0 steps but hey
} else {
let mut success = false;
for candidate in &parsed_scenario_candidates {
if check_steps(&candidate.steps, json_steps.unwrap().as_array().unwrap()) {
success = true;
break;
}
}
if !success {
panic!("Scenario steps are different from fixture");
}
}
} else if let Some(json_rule) = child.get("rule") {
rules += 1;
let parsed_rules = &parsed.rules;

let json_rule_name = json_rule
.get("name")
.map(|x| x.as_str().unwrap())
.unwrap_or_default();

for rule in parsed_rules.iter() {
// This is not perfect but shoul work for the provided data
if rule.name == json_rule_name {
continue;
}
}
} else {
panic!("Unknown child type: {:#?}", child);
}
}

if parsed.background.is_some() {
assert_eq!(1, backgrounds);
} else {
assert_eq!(0, backgrounds);
}

assert_eq!(parsed.scenarios.len(), scenarios);
assert_eq!(parsed.rules.len(), rules);
}

fn check_steps(parsed: &Vec<crate::Step>, json: &Vec<serde_json::Value>) -> bool {
if parsed.len() != json.len() {
return false;
}

for i in 0..parsed.len() {
if parsed[i].keyword != json[i].get("keyword").unwrap().as_str().unwrap() {
// println!("`{}` != `{}`", parsed[i].keyword, json[i].get("keyword").unwrap().as_str().unwrap());
return false;
}
if parsed[i].value != json[i].get("text").unwrap().as_str().unwrap() {
// println!("`{}` != `{}`", parsed[i].value, json[i].get("text").unwrap().as_str().unwrap());
return false;
}
}

true
}
44 changes: 40 additions & 4 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,55 @@ impl<'a> Keywords<'a> {
}

pub fn excluded_background(&'a self) -> Vec<&'a str> {
[self.scenario, self.scenario_outline, self.given, self.when, self.then, self.and, self.but].concat()
[
self.scenario,
self.scenario_outline,
self.given,
self.when,
self.then,
self.and,
self.but,
]
.concat()
}

pub fn excluded_scenario(&'a self) -> Vec<&'a str> {
[self.scenario, self.scenario_outline, self.given, self.when, self.then, self.and, self.but].concat()
[
self.scenario,
self.scenario_outline,
self.given,
self.when,
self.then,
self.and,
self.but,
]
.concat()
}

pub fn excluded_scenario_outline(&'a self) -> Vec<&'a str> {
[self.scenario, self.scenario_outline, self.given, self.when, self.then, self.and, self.but].concat()
[
self.scenario,
self.scenario_outline,
self.given,
self.when,
self.then,
self.and,
self.but,
]
.concat()
}

pub fn excluded_examples(&'a self) -> Vec<&'a str> {
let mut r = [self.scenario, self.scenario_outline, self.given, self.when, self.then, self.and, self.but].concat();
let mut r = [
self.scenario,
self.scenario_outline,
self.given,
self.when,
self.then,
self.and,
self.but,
]
.concat();
r.push("|");
r
}
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ mod parser;
#[cfg(feature = "parser")]
pub mod tagexpr;

#[cfg(test)]
pub(crate) mod ast_checker;

#[cfg(feature = "parser")]
use std::path::Path;
use std::{
Expand Down
93 changes: 92 additions & 1 deletion src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ impl GherkinEnv {
let line = line_offsets
.iter()
.position(|x| x > &offset)
.unwrap_or_else(|| line_offsets.len());
.unwrap_or(line_offsets.len());

let col = offset - line_offsets[line - 1] + 1;

Expand Down Expand Up @@ -558,6 +558,8 @@ pub(crate) rule tag_operation() -> TagOperation = precedence!{

#[cfg(test)]
mod test {
use crate::ast_checker;

use super::*;

const FOO: &str = "# language: formal\r\n
Expand Down Expand Up @@ -689,4 +691,93 @@ Scenario: Hello
assert_eq!(feature.scenarios.len(), 0);
assert!(feature.description.is_none());
}

#[test]
fn empty_feature() {
let env = GherkinEnv::default();
let input = " \n\t \t\n\n ";
let feature = gherkin_parser::feature(input, &env);
assert!(feature.is_err());
}

#[test]
fn one_feature() {
let env = GherkinEnv::default();
let input = r#"Feature: Basic functionality
"#;
let features = gherkin_parser::features(input, &env).unwrap();
assert_eq!(features.len(), 1);
}

#[test]
fn no_feature() {
let env = GherkinEnv::default();
let input = " \n\t \t\n\n ";
let features = gherkin_parser::features(input, &env).unwrap();
assert_eq!(features.len(), 0);
}

#[test]
fn multiple_features() {
let env = GherkinEnv::default();
let input = r#"Feature: Basic functionality
here's some text
really
Scenario: Hello
Given a step
Feature: Another"#;

// let features = gherkin_parser::features(input, &env);
// println!("{:?}", features.unwrap_err());
// println!("{:?}", env.last_error);
// panic!();

let features = gherkin_parser::features(input, &env).unwrap();
assert_eq!(features.len(), 2);
}

#[test]
fn fixture() {
// We cannot handle missing features very well yet
let skip = ["empty.feature", "incomplete_feature_3.feature"];
let mut failed = 0;

let d = env!("CARGO_MANIFEST_DIR");
let files = std::fs::read_dir(format!("{}/tests/fixtures/data/good/", d)).unwrap();
for file in files {
let file = file.unwrap();
let filename = file.file_name();
let filename = filename.to_str().unwrap();
if filename.ends_with(".feature") {
if skip.contains(&filename) {
continue;
}
let res = std::panic::catch_unwind(|| {
let env = GherkinEnv::default();
let input = std::fs::read_to_string(format!(
"{}/tests/fixtures/data/good/{}",
d, filename
))
.unwrap();
let feature = gherkin_parser::feature(&input, &env).unwrap();
let fixture = std::fs::read_to_string(format!(
"{}/tests/fixtures/data/good/{}.ast.ndjson",
d, filename
))
.unwrap();
println!("{:#?}", feature);
ast_checker::check_ast(feature, &fixture);
});
if res.is_err() {
failed += 1;
println!("{}", filename);
}
}
}

if failed != 0 {
panic!("{} fixtures have failed", failed);
}
}
}

0 comments on commit bbc2b20

Please sign in to comment.