Add file-in-file (macro) support to json test runner

This commit is contained in:
NGnius (Graham) 2023-01-21 16:22:31 -05:00
parent ba7e99f482
commit bd91de42f0
3 changed files with 64 additions and 12 deletions

View file

@ -1,4 +1,5 @@
/// Harness information for a test runner /// Harness information for a test runner
#[derive(Clone)]
pub enum Feedback { pub enum Feedback {
/// Start of run (no feedback to provide) /// Start of run (no feedback to provide)
Start, Start,

View file

@ -1,5 +1,7 @@
use std::path::{Path, PathBuf};
use super::super::{Instruction, Feedback, TestRunner, TestMetadata}; use super::super::{Instruction, Feedback, TestRunner, TestMetadata};
use super::{Test, FailureMode}; use super::{Test, FailureMode, TestStepType};
/// Test runner for specific JSON data structures. /// Test runner for specific JSON data structures.
pub struct JsonRunner { pub struct JsonRunner {
@ -7,11 +9,13 @@ pub struct JsonRunner {
step_i: usize, step_i: usize,
op_i: usize, op_i: usize,
success: bool, success: bool,
filepath: Option<PathBuf>,
current_macro: Option<Box<Self>>,
} }
impl JsonRunner { impl JsonRunner {
/// Load test information from file /// Load test information from file
pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> std::io::Result<Self> { pub fn from_file<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
let file = std::io::BufReader::new(std::fs::File::open(path.as_ref())?); let file = std::io::BufReader::new(std::fs::File::open(path.as_ref())?);
let test = serde_json::from_reader(file)?; let test = serde_json::from_reader(file)?;
Ok(Self { Ok(Self {
@ -19,6 +23,8 @@ impl JsonRunner {
step_i: 0, step_i: 0,
op_i: 0, op_i: 0,
success: true, success: true,
filepath: Some(path.as_ref().into()),
current_macro: None,
}) })
} }
@ -29,6 +35,8 @@ impl JsonRunner {
step_i: 0, step_i: 0,
op_i: 0, op_i: 0,
success: true, success: true,
filepath: None,
current_macro: None,
} }
} }
} }
@ -40,18 +48,50 @@ impl TestRunner for JsonRunner {
if matches!(fail_mode, FailureMode::FastFail) && !feedback.is_ok() { if matches!(fail_mode, FailureMode::FastFail) && !feedback.is_ok() {
return None; 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)] #[allow(clippy::never_loop)]
'step_loop: while self.step_i < self.test_data.test.len() { 'step_loop: while self.step_i < self.test_data.test.len() {
let step = &self.test_data.test[self.step_i]; let step = &self.test_data.test[self.step_i];
'op_loop: while self.op_i < step.operations.len() { match step {
if matches!(fail_mode, FailureMode::SkipInstructions) && !feedback.is_ok() { TestStepType::Regular(step) => {
log::info!("{:?} Failing instruction, going to next step", fail_mode); 'op_loop: while self.op_i < step.operations.len() {
break 'op_loop; 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 { if matches!(fail_mode, FailureMode::SkipSteps) && !self.success {
log::info!("{:?} Failing step complete, ending test", fail_mode); log::info!("{:?} Failing step complete, ending test", fail_mode);

View file

@ -8,7 +8,7 @@ use super::super::{TabSelector, ElementSelector, ElementOpType, ElementOp, TabOp
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct Test { pub struct Test {
pub(super) info: TestInfo, pub(super) info: TestInfo,
pub(super) test: Vec<TestStep>, pub(super) test: Vec<TestStepType>,
} }
/// Test metadata /// Test metadata
@ -41,7 +41,18 @@ impl std::convert::From<TestInfo> 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)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TestStep { pub struct TestStep {
pub tab: TabDescriptor, pub tab: TabDescriptor,