summaryrefslogtreecommitdiff
path: root/subplotlib
diff options
context:
space:
mode:
authorDaniel Silverstone <dsilvers@digital-scurf.org>2020-12-27 10:11:51 +0000
committerDaniel Silverstone <dsilvers@digital-scurf.org>2020-12-27 10:11:51 +0000
commitbfae38d9d1263c8d5dca00dd27d7f28db4751db4 (patch)
tree4c0acd5b3ad2a91f5426969913f6cf24fa5cd584 /subplotlib
parenta8ba0f2474553a5c4a212ec1225aa4c375741114 (diff)
downloadsubplot-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.rs138
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
}