diff --git a/src/lib/descriptor/mod.rs b/src/lib/descriptor/mod.rs index 0a84fe4b..6cde3b01 100755 --- a/src/lib/descriptor/mod.rs +++ b/src/lib/descriptor/mod.rs @@ -638,5 +638,13 @@ pub fn load( } } + for (name, _) in config.tasks.iter() { + match name.as_str() { + "before_each" => config.config.before_each_task = Some(name.to_string()), + "after_each" => config.config.after_each_task = Some(name.to_string()), + _ => {} + } + } + Ok(config) } diff --git a/src/lib/execution_plan.rs b/src/lib/execution_plan.rs index 6292d707..f5bb026d 100644 --- a/src/lib/execution_plan.rs +++ b/src/lib/execution_plan.rs @@ -19,6 +19,7 @@ use fsio::path::{get_basename, get_parent_directory}; use glob::Pattern; use indexmap::IndexMap; use regex::Regex; +use std::borrow::Cow; use std::collections::HashSet; use std::env; use std::path::Path; @@ -481,8 +482,8 @@ impl<'a> ExecutionPlanBuilder<'a> { skip_tasks_pattern, skip_init_end_tasks, } = *self; - let mut task_names = HashSet::new(); - let mut steps = Vec::new(); + let mut task_names = HashSet::::new(); + let mut steps = Vec::::new(); let default_crate_info = CrateInfo::new(); let crate_info = crate_info.unwrap_or(&default_crate_info); let skip_init_end_tasks = skip_init_end_tasks || sub_flow; @@ -549,6 +550,76 @@ impl<'a> ExecutionPlanBuilder<'a> { }; } - Ok(ExecutionPlan { steps }) + Ok(ExecutionPlan { + steps: Self::handle_before_each_and_after_each_tasks(&config, &steps)?.into_owned(), + }) + } + + /// `before_each` and `after_each` interspersal for the steps vector + fn handle_before_each_and_after_each_tasks<'b>( + config: &Config, + steps: &'b Vec, + ) -> Result>, CargoMakeError> { + let mut after_and_before_each = Vec::::with_capacity(2); + let mut handle_some = |name: &String| -> Result { + let task_config = get_normalized_task(config, name, false)?; + if !task_config.disabled.unwrap_or(false) { + after_and_before_each.push(Step { + name: name.to_string(), + config: task_config, + }); + Ok(1) + } else { + Ok(0) + } + }; + let has_after_each: u8 = match &config.config.after_each_task { + None => 0, + Some(after_each) => handle_some(&after_each)?, + }; + let has_before_each: u8 = match &config.config.before_each_task { + None => 0, + Some(before_each) => handle_some(&before_each)?, + }; + let init_task_opt = &config.config.init_task; + let end_task_opt = &config.config.end_task; + let scale: u8 = has_before_each + has_after_each; + if scale > 0 { + let before_and_after_each_len = scale as usize + 1; + let mut interspersed_steps = + Vec::::with_capacity(steps.len() * before_and_after_each_len); + let mut zeroth = true; + interspersed_steps.extend(steps.into_iter().flat_map(|step| -> Vec { + let mut _steps = Vec::::with_capacity(before_and_after_each_len + 1); + if zeroth { + zeroth = false; + } else if init_task_opt == &Some(step.name.to_string()) { + if has_after_each == 1 { + _steps.push(after_and_before_each.last().unwrap().to_owned()); + } + } else if end_task_opt == &Some(step.name.to_string()) { + if has_before_each == 1 { + _steps.push(after_and_before_each.first().unwrap().to_owned()); + } + } else { + _steps.extend(after_and_before_each.clone()); + } + _steps.push(step.to_owned()); + _steps + })); + if let Some(last_step) = interspersed_steps.last() { + if has_after_each == 1 { + let after_each = after_and_before_each.first().unwrap().to_owned(); + if last_step.name != after_each.name + && Some(&last_step.name) != Option::from(end_task_opt) + { + interspersed_steps.push(after_each); + } + } + } + Ok(Cow::Owned(interspersed_steps)) + } else { + Ok(Cow::Borrowed(&steps)) + } } } diff --git a/src/lib/execution_plan_test.rs b/src/lib/execution_plan_test.rs index 86447afd..edd37bdc 100644 --- a/src/lib/execution_plan_test.rs +++ b/src/lib/execution_plan_test.rs @@ -1639,3 +1639,123 @@ fn respect_skip_init_end_tasks() { assert_eq!(execution_plan.steps.len(), 1); assert_eq!(execution_plan.steps[0].name, "test"); } + +#[test] +fn respect_before_each_and_after_each_tasks() { + let config = Config { + config: ConfigSection { + init_task: None, + before_each_task: Some("before_each".to_string()), + after_each_task: Some("after_each".to_string()), + end_task: None, + ..ConfigSection::default() + }, + env_files: vec![], + env: IndexMap::new(), + env_scripts: vec![], + tasks: indexmap::indexmap! { + "before_each".to_string() => Task::new(), + "test2".to_string() => Task { + dependencies: Some(vec![DependencyIdentifier::Definition(TaskIdentifier { + name: "test1".to_string(), + path: Some("./examples/workspace".to_string()), + })]), + ..Task::default() + }, + "test1".to_string() => Task { + dependencies: Some(vec![DependencyIdentifier::Definition(TaskIdentifier { + name: "test0".to_string(), + path: Some("./examples/workspace".to_string()), + })]), + ..Task::default() + }, + "test0".to_string() => Task::new(), + "after_each".to_string() => Task::new(), + }, + plugins: None, + }; + + let execution_plan = ExecutionPlanBuilder { + allow_private: true, + skip_init_end_tasks: true, + ..ExecutionPlanBuilder::new(&config, "test2") + } + .build() + .unwrap(); + assert_eq!(execution_plan.steps.len(), 5); + assert_eq!( + execution_plan + .steps + .iter() + .map(|step| step.name.as_str()) + .collect::>(), + vec![ + "test1_proxy", + "after_each", + "before_each", + "test2", + "after_each" + ] + ); +} + +#[test] +fn respect_init_and_end_and_before_each_and_after_each_tasks() { + let config = Config { + config: ConfigSection { + init_task: Some("init".to_string()), + before_each_task: Some("before_each".to_string()), + after_each_task: Some("after_each".to_string()), + end_task: Some("end".to_string()), + ..ConfigSection::default() + }, + env_files: vec![], + env: IndexMap::new(), + env_scripts: vec![], + tasks: indexmap::indexmap! { + "init".to_string() => Task::new(), + "before_each".to_string() => Task::new(), + "test2".to_string() => Task { + dependencies: Some(vec![DependencyIdentifier::Definition(TaskIdentifier { + name: "test1".to_string(), + path: Some("./examples/workspace".to_string()), + })]), + ..Task::default() + }, + "test1".to_string() => Task { + dependencies: Some(vec![DependencyIdentifier::Definition(TaskIdentifier { + name: "test0".to_string(), + path: Some("./examples/workspace".to_string()), + })]), + ..Task::default() + }, + "test0".to_string() => Task::new(), + "after_each".to_string() => Task::new(), + "end".to_string() => Task::new(), + }, + plugins: None, + }; + + let execution_plan = ExecutionPlanBuilder { + allow_private: true, + skip_init_end_tasks: true, + ..ExecutionPlanBuilder::new(&config, "test2") + } + .build() + .unwrap(); + assert_eq!(execution_plan.steps.len(), 5); + assert_eq!( + execution_plan + .steps + .iter() + .map(|step| step.name.as_str()) + .collect::>(), + vec![ + "test1_proxy", + "after_each", + "before_each", + "test2", + "after_each" + ] + ); +} diff --git a/src/lib/types.rs b/src/lib/types.rs index 4582c09e..70783c9e 100755 --- a/src/lib/types.rs +++ b/src/lib/types.rs @@ -2150,6 +2150,10 @@ pub struct ConfigSection { pub init_task: Option, /// End task name which will be invoked at the end of every run pub end_task: Option, + /// before_each task name which will be invoked before each task (except `init_task`) + pub before_each_task: Option, + /// after_each task name which will be invoked after each task (except `end_task`) + pub after_each_task: Option, /// The name of the task to run in case of any error during the invocation of the flow pub on_error_task: Option, /// The name of the task which runs legacy migration flows @@ -2210,6 +2214,20 @@ impl ConfigSection { )); } + if self.before_each_task.is_some() { + self.before_each_task = Some(get_namespaced_task_name( + namespace, + &self.before_each_task.clone().unwrap(), + )); + } + + if self.after_each_task.is_some() { + self.after_each_task = Some(get_namespaced_task_name( + namespace, + &self.after_each_task.clone().unwrap(), + )); + } + if self.on_error_task.is_some() { self.on_error_task = Some(get_namespaced_task_name( namespace, @@ -2250,6 +2268,14 @@ impl ConfigSection { self.end_task = extended.end_task.clone(); } + if let Some(before_each_task) = &extended.before_each_task { + self.before_each_task = Some(before_each_task.clone()); + } + + if let Some(after_each_task) = &extended.after_each_task { + self.after_each_task = Some(after_each_task.clone()); + } + if extended.on_error_task.is_some() { self.on_error_task = extended.on_error_task.clone(); }