1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
//! Scenario steps
//!
//! In general you will not need to interact with these types, they're simply
//! here to provide wrappers to make the scenarios easier to work with.
//!
use std::any::Any;
use std::panic::{catch_unwind, AssertUnwindSafe};
use crate::scenario::{Scenario, ScenarioContext};
use crate::types::StepResult;
/// A ScenarioStep is one step in a scenario.
///
/// In essence, a scenario step is a named closure. Its name can be used when
/// reporting an error encountered in running a scenario.
///
/// Scenario steps are typically constructed from step builders, rather than
/// directly. This permits the step builder to correctly register context types
/// etc.
///
/// ```
/// # use subplotlib::prelude::*;
///
/// let step = ScenarioStep::new(
/// "when everything works".to_string(), |ctx, ok| Ok(()), |scen| ()
/// );
/// ```
pub struct ScenarioStep {
step_text: String,
func: Box<dyn Fn(&ScenarioContext, bool) -> StepResult>,
reg: Box<dyn Fn(&Scenario)>,
}
impl ScenarioStep {
/// Create a new scenario step taking the scenario context
///
/// This is used to construct a scenario step from a function which
/// takes the scenario context container. This will generally be
/// called from the generated build method for the step.
pub fn new<F, R>(step_text: String, func: F, reg: R) -> Self
where
F: Fn(&ScenarioContext, bool) -> StepResult + 'static,
R: Fn(&Scenario) + 'static,
{
Self {
step_text,
func: Box::new(func),
reg: Box::new(reg),
}
}
/// Attempt to render a message.
/// If something panics with a type other than a static string or
/// a formatted string then we won't be able to render it sadly.
fn render_panic(name: &str, err: Box<dyn Any + Send>) -> String {
if let Some(msg) = err.downcast_ref::<&str>() {
format!("step {} panic'd: {}", name, msg)
} else if let Some(msg) = err.downcast_ref::<String>() {
format!("step {} panic'd: {}", name, msg)
} else {
format!("step {} panic'd", name)
}
}
/// Call the step function
///
/// This simply calls the encased step function
pub fn call(&self, context: &ScenarioContext, defuse_poison: bool) -> StepResult {
// Note, panic here will be absorbed and so there's a risk that
// subsequent step calls may not be sound. There's not a lot we can
// do to ensure things are good except try.
let func = AssertUnwindSafe(|| (*self.func)(context, defuse_poison));
catch_unwind(func).map_err(|e| Self::render_panic(self.step_text(), e))?
}
/// Return the full text of this step
pub fn step_text(&self) -> &str {
&self.step_text
}
/// Register any context types needed by this step
pub(crate) fn register_contexts(&self, scenario: &Scenario) {
(*self.reg)(scenario);
}
}
|