diff --git a/cef-test-core/src/harness/feedback.rs b/cef-test-core/src/harness/feedback.rs index d5f2e9c..2bfbe56 100644 --- a/cef-test-core/src/harness/feedback.rs +++ b/cef-test-core/src/harness/feedback.rs @@ -1,4 +1,5 @@ /// Harness information for a test runner +#[derive(Clone)] pub enum Feedback { /// Start of run (no feedback to provide) Start, diff --git a/cef-test-core/src/harness/json_runner/runner.rs b/cef-test-core/src/harness/json_runner/runner.rs index 659f68f..c055824 100644 --- a/cef-test-core/src/harness/json_runner/runner.rs +++ b/cef-test-core/src/harness/json_runner/runner.rs @@ -1,5 +1,7 @@ +use std::path::{Path, PathBuf}; + use super::super::{Instruction, Feedback, TestRunner, TestMetadata}; -use super::{Test, FailureMode}; +use super::{Test, FailureMode, TestStepType}; /// Test runner for specific JSON data structures. pub struct JsonRunner { @@ -7,11 +9,13 @@ pub struct JsonRunner { step_i: usize, op_i: usize, success: bool, + filepath: Option, + current_macro: Option>, } impl JsonRunner { /// Load test information from file - pub fn from_file>(path: P) -> std::io::Result { + pub fn from_file>(path: P) -> std::io::Result { let file = std::io::BufReader::new(std::fs::File::open(path.as_ref())?); let test = serde_json::from_reader(file)?; Ok(Self { @@ -19,6 +23,8 @@ impl JsonRunner { step_i: 0, op_i: 0, success: true, + filepath: Some(path.as_ref().into()), + current_macro: None, }) } @@ -29,6 +35,8 @@ impl JsonRunner { step_i: 0, op_i: 0, success: true, + filepath: None, + current_macro: None, } } } @@ -40,18 +48,50 @@ impl TestRunner for JsonRunner { if matches!(fail_mode, FailureMode::FastFail) && !feedback.is_ok() { return None; } + // handle in-progress macro execution + if let Some(current_macro) = &mut self.current_macro { + if let Some(instr) = current_macro.next(feedback.clone()) { + return Some(instr); + } else { + self.current_macro = None; + } + } #[allow(clippy::never_loop)] 'step_loop: while self.step_i < self.test_data.test.len() { let step = &self.test_data.test[self.step_i]; - 'op_loop: while self.op_i < step.operations.len() { - if matches!(fail_mode, FailureMode::SkipInstructions) && !feedback.is_ok() { - log::info!("{:?} Failing instruction, going to next step", fail_mode); - break 'op_loop; + match step { + TestStepType::Regular(step) => { + 'op_loop: while self.op_i < step.operations.len() { + if matches!(fail_mode, FailureMode::SkipInstructions) && !feedback.is_ok() { + log::info!("{:?} Failing instruction, going to next step", fail_mode); + break 'op_loop; + } + let instruction = &step.operations[self.op_i]; + log::debug!("Performing step {}, operation {}", self.step_i, self.op_i); + self.op_i += 1; + return Some(instruction.clone().into_instruction(step.tab.clone())); + } + }, + TestStepType::Macro { name, file } => { + let path = if let Some(filepath) = &self.filepath { + filepath.join(file) + } else { + file.to_owned() + }; + log::debug!("Macro {} expanded to {}", name, path.display()); + self.step_i += 1; + match Self::from_file(&path) { + Ok(mut new_macro) => { + if let Some(instr) = new_macro.next(feedback.clone()) { + self.current_macro = Some(Box::new(new_macro)); + return Some(instr); + } else { + log::warn!("Macro {} ({}) is empty!", name, file.display()); + } + }, + Err(e) => log::error!("Macro {} ({}) failed to load: {}", name, file.display(), e), + } } - let instruction = &step.operations[self.op_i]; - log::debug!("Performing step {}, operation {}", self.step_i, self.op_i); - self.op_i += 1; - return Some(instruction.clone().into_instruction(step.tab.clone())); } if matches!(fail_mode, FailureMode::SkipSteps) && !self.success { log::info!("{:?} Failing step complete, ending test", fail_mode); diff --git a/cef-test-core/src/harness/json_runner/structure.rs b/cef-test-core/src/harness/json_runner/structure.rs index 426e94c..7a99b89 100644 --- a/cef-test-core/src/harness/json_runner/structure.rs +++ b/cef-test-core/src/harness/json_runner/structure.rs @@ -8,7 +8,7 @@ use super::super::{TabSelector, ElementSelector, ElementOpType, ElementOp, TabOp #[derive(Serialize, Deserialize, Debug)] pub struct Test { pub(super) info: TestInfo, - pub(super) test: Vec, + pub(super) test: Vec, } /// Test metadata @@ -41,7 +41,18 @@ impl std::convert::From for TestMetadata { } } -/// Test step +/// Test Step type +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum TestStepType { + Regular(TestStep), + Macro { + name: String, + file: std::path::PathBuf, + } +} + +/// Regular test step #[derive(Serialize, Deserialize, Debug, Clone)] pub struct TestStep { pub tab: TabDescriptor,