diff --git a/tests/features/manifest.feature b/tests/features/manifest.feature index a4d6ec22..dcab19c3 100644 --- a/tests/features/manifest.feature +++ b/tests/features/manifest.feature @@ -1,36 +1,47 @@ -Feature: Manifest parsing - - Scenario: Parse minimal manifest - When the manifest file "tests/data/minimal.yml" is parsed +Feature: Manifest Parsing + As a user, + I want to define my build in a YAML manifest, + So that Netsuke can understand and execute it. + + Scenario: Parsing a minimal valid manifest + Given the manifest file "tests/data/minimal.yml" is parsed + When the version is checked Then the manifest version is "1.0.0" And the first target name is "hello" - Scenario: Parse phony and always flags - When the manifest file "tests/data/phony.yml" is parsed + Scenario: Parsing a manifest with phony and always flags + Given the manifest file "tests/data/phony.yml" is parsed + When the flags are checked Then the first target is phony And the first target is always rebuilt - Scenario: Actions are always treated as phony - When the manifest file "tests/data/actions.yml" is parsed + Scenario: A target in the 'actions' block is implicitly phony + Given the manifest file "tests/data/actions.yml" is parsed + When the flags are checked Then the first action is phony - Scenario: Invalid action fails to parse - When the manifest file "tests/data/action_invalid.yml" is parsed - Then parsing the manifest fails - - Scenario: Manifest with rules parses correctly - When the manifest file "tests/data/rules.yml" is parsed + Scenario: Parsing a manifest with rules + Given the manifest file "tests/data/rules.yml" is parsed + When the rules are checked Then the first rule name is "compile" And the first target name is "hello.o" - Scenario: Unknown field fails to parse - When the manifest file "tests/data/unknown_field.yml" is parsed + Scenario: Parsing fails for a manifest with an unknown top-level field + Given the manifest file "tests/data/unknown_field.yml" is parsed + When the parsing result is checked + Then parsing the manifest fails + + Scenario: Parsing fails for a manifest with an invalid version string + Given the manifest file "tests/data/invalid_version.yml" is parsed + When the parsing result is checked Then parsing the manifest fails - Scenario: Invalid version fails to parse - When the manifest file "tests/data/invalid_version.yml" is parsed + Scenario: Parsing fails for a target that is missing a recipe + Given the manifest file "tests/data/missing_recipe.yml" is parsed + When the parsing result is checked Then parsing the manifest fails - Scenario: Missing recipe fails to parse - When the manifest file "tests/data/missing_recipe.yml" is parsed + Scenario: Parsing fails for an action that is missing a recipe + Given the manifest file "tests/data/action_invalid.yml" is parsed + When the parsing result is checked Then parsing the manifest fails diff --git a/tests/steps/manifest_steps.rs b/tests/steps/manifest_steps.rs index 5f2ae08a..686b090f 100644 --- a/tests/steps/manifest_steps.rs +++ b/tests/steps/manifest_steps.rs @@ -1,16 +1,15 @@ //! Step definitions for manifest parsing scenarios. +#![expect( + clippy::needless_pass_by_value, + reason = "Cucumber requires owned String arguments" +)] use crate::CliWorld; -use cucumber::{then, when}; +use cucumber::{given, then, when}; use netsuke::{ast::StringOrList, manifest}; -#[expect( - clippy::needless_pass_by_value, - reason = "Cucumber requires owned String arguments" -)] -#[when(expr = "the manifest file {string} is parsed")] -fn parse_manifest(world: &mut CliWorld, path: String) { - match manifest::from_path(&path) { +fn parse_manifest_inner(world: &mut CliWorld, path: &str) { + match manifest::from_path(path) { Ok(manifest) => { world.manifest = Some(manifest); world.manifest_error = None; @@ -22,20 +21,44 @@ fn parse_manifest(world: &mut CliWorld, path: String) { } } -#[expect( - clippy::needless_pass_by_value, - reason = "Cucumber requires owned String arguments" -)] +fn assert_manifest(world: &CliWorld) { + assert!( + world.manifest.is_some(), + "manifest should have been parsed successfully" + ); +} + +fn assert_parsed(world: &CliWorld) { + assert!( + world.manifest.is_some() || world.manifest_error.is_some(), + "manifest should have been parsed" + ); +} +#[given(expr = "the manifest file {string} is parsed")] +fn given_parse_manifest(world: &mut CliWorld, path: String) { + parse_manifest_inner(world, &path); +} + +#[when(expr = "the manifest file {string} is parsed")] +fn parse_manifest(world: &mut CliWorld, path: String) { + parse_manifest_inner(world, &path); +} + +#[when(regex = r"^the (?P[a-z ]+) (?:is|are) checked$")] +fn when_item_checked(world: &mut CliWorld, item: String) { + match item.as_str() { + "parsing result" => assert_parsed(world), + "manifest" | "version" | "flags" | "rules" => assert_manifest(world), + unexpected => panic!("Unexpected item checked: '{unexpected}'"), + } +} + #[then(expr = "the manifest version is {string}")] fn manifest_version(world: &mut CliWorld, version: String) { let manifest = world.manifest.as_ref().expect("manifest"); assert_eq!(manifest.netsuke_version.to_string(), version); } -#[expect( - clippy::needless_pass_by_value, - reason = "Cucumber requires owned String arguments" -)] #[then(expr = "the first target name is {string}")] fn first_target_name(world: &mut CliWorld, name: String) { let manifest = world.manifest.as_ref().expect("manifest"); @@ -72,10 +95,6 @@ fn manifest_parse_error(world: &mut CliWorld) { assert!(world.manifest_error.is_some(), "expected parse error"); } -#[expect( - clippy::needless_pass_by_value, - reason = "Cucumber requires owned String arguments" -)] #[then(expr = "the first rule name is {string}")] fn first_rule_name(world: &mut CliWorld, name: String) { let manifest = world.manifest.as_ref().expect("manifest");