diff options
author | Daniel Silverstone <dsilvers@digital-scurf.org> | 2020-12-27 10:11:51 +0000 |
---|---|---|
committer | Daniel Silverstone <dsilvers@digital-scurf.org> | 2020-12-27 10:11:51 +0000 |
commit | bfae38d9d1263c8d5dca00dd27d7f28db4751db4 (patch) | |
tree | 4c0acd5b3ad2a91f5426969913f6cf24fa5cd584 /subplotlib | |
parent | a8ba0f2474553a5c4a212ec1225aa4c375741114 (diff) | |
download | subplot-bfae38d9d1263c8d5dca00dd27d7f28db4751db4.tar.gz |
subplotlib: Support entry/exit of scenario and steps
Signed-off-by: Daniel Silverstone <dsilvers@digital-scurf.org>
Diffstat (limited to 'subplotlib')
-rw-r--r-- | subplotlib/src/scenario.rs | 138 |
1 files changed, 123 insertions, 15 deletions
diff --git a/subplotlib/src/scenario.rs b/subplotlib/src/scenario.rs index 0028af9..36e113e 100644 --- a/subplotlib/src/scenario.rs +++ b/subplotlib/src/scenario.rs @@ -28,6 +28,64 @@ pub trait ContextElement: Send + 'static { /// There will be an impl of this for Default capable types should the name /// be unnecessary. fn create(scenario_title: &str) -> Self; + + /// Scenario starts + /// + /// When a scenario starts, this function is called to permit setup. + /// + /// If this returns an error, scenario setup is stopped and `scenario_stops` + /// will be called for anything which succeeded at startup. + fn scenario_starts(&mut self) -> StepResult { + Ok(()) + } + + /// Scenario stops + /// + /// When a scenario finishes, this function is called to permit teardown. + /// + /// If this returns an error, and the scenario would otherwise have passed, + /// then the error will be used. The first encountered error in stopping + /// a scenario will be used, rather than the last. All contexts which + /// succeeded at starting will be stopped. + fn scenario_stops(&mut self) -> StepResult { + Ok(()) + } + + /// Entry to a step function + /// + /// In order to permit elements which for example work on disk, this + /// function will be invoked with the step's name to permit the creation of + /// suitably named temporary directories, logging, etc. + /// + /// The default implementation of this does nothing. + /// + /// Calls to this function *will* be paired with calls to the step exit + /// function providing nothing panics or calls exit without unwinding. + /// + /// If you wish to be resilient to step functions panicing then you will + /// need to be careful to cope with a new step being entered without a + /// previous step exiting. Particularly if you're handing during cleanup + /// of a failed scenario. + /// + /// If this returns an error then the step function is not run, nor is the + /// corresponding `exit_step()` called. + #[allow(unused_variables)] + fn step_starts(&mut self, step_title: &str) -> StepResult { + Ok(()) + } + + /// Exit from a step function + /// + /// See [the `enter_step` function][ContextElement::enter_step] for most + /// details of this. + /// + /// Any error returned from this will be masked if the step function itself + /// returned an error. However if the step function succeeded then this + /// function's error will make it out. + #[allow(unused_variables)] + fn step_stops(&mut self) -> StepResult { + Ok(()) + } } impl<T> ContextElement for T @@ -56,23 +114,37 @@ where /// A trait used to permit the holding of multiple hooks in one vector trait ScenarioContextHookKind { + /// Start scenario + fn scenario_starts(&self, contexts: &ScenarioContext) -> StepResult; + + /// Stop scenario + fn scenario_stops(&self, contexts: &ScenarioContext) -> StepResult; + /// Enter a step - fn enter_step(&self); + fn step_starts(&self, contexts: &ScenarioContext, step_name: &str) -> StepResult; /// Leave a step - fn exit_step(&self); + fn step_stops(&self, contexts: &ScenarioContext) -> StepResult; } impl<C> ScenarioContextHookKind for ScenarioContextHook<C> where C: ContextElement, { - fn enter_step(&self) { - // + fn scenario_starts(&self, contexts: &ScenarioContext) -> StepResult { + contexts.with_mut(|c: &mut C| c.scenario_starts(), false) + } + + fn scenario_stops(&self, contexts: &ScenarioContext) -> StepResult { + contexts.with_mut(|c: &mut C| c.scenario_stops(), true) } - fn exit_step(&self) { - // + fn step_starts(&self, contexts: &ScenarioContext, step_name: &str) -> StepResult { + contexts.with_mut(|c: &mut C| c.step_starts(step_name), false) + } + + fn step_stops(&self, contexts: &ScenarioContext) -> StepResult { + contexts.with_mut(|c: &mut C| c.step_stops(), true) } } @@ -197,24 +269,60 @@ impl Scenario { /// If any of the cleanup functions error, this will immediately panic. /// pub fn run(self) -> Result<(), StepError> { - let mut highest = None; + // Firstly, we start all the contexts let mut ret = Ok(()); - for (i, step) in self.steps.iter().map(|(step, _)| step).enumerate() { - let res = step.call(&self.contexts, false); + let mut highest_start = None; + for (i, hook) in self.contexts.hooks.iter().enumerate() { + let res = hook.scenario_starts(&self.contexts); if res.is_err() { ret = res; break; } - highest = Some(i); + highest_start = Some(i); } - if let Some(n) = highest { - for stepn in (0..=n).rev() { - if let (_, Some(cleanup)) = &self.steps[stepn] { - if let Err(e) = cleanup.call(&self.contexts, true) { - panic!("Failure during cleanup: {:?}", e); + + if ret.is_ok() { + let mut highest = None; + for (i, step) in self.steps.iter().map(|(step, _)| step).enumerate() { + let mut highest_prep = None; + for (i, prep) in self.contexts.hooks.iter().enumerate() { + let res = prep.step_starts(&self.contexts, step.name()); + if res.is_err() { + ret = res; + break; + } + highest_prep = Some(i); + } + if ret.is_ok() { + let res = step.call(&self.contexts, false); + if res.is_err() { + ret = res; + break; + } + highest = Some(i); + } + if let Some(n) = highest_prep { + for hookn in (0..=n).rev() { + let res = self.contexts.hooks[hookn].step_stops(&self.contexts); + ret = ret.and(res) } } } + if let Some(n) = highest { + for stepn in (0..=n).rev() { + if let (_, Some(cleanup)) = &self.steps[stepn] { + let res = cleanup.call(&self.contexts, true); + ret = ret.and(res); + } + } + } + } + + if let Some(n) = highest_start { + for hookn in (0..=n).rev() { + let res = self.contexts.hooks[hookn].scenario_stops(&self.contexts); + ret = ret.and(res); + } } ret } |