1use core::fmt;
8use std::fmt::Debug;
9use std::{cell::RefCell, marker::PhantomData, sync::Mutex};
10
11use state::TypeMap;
12
13use crate::step::ScenarioStep;
14use crate::types::{StepError, StepResult};
15
16pub trait ContextElement: Debug + Default + Send + 'static {
24 #[allow(unused_variables)]
35 fn created(&mut self, scenario: &Scenario) {
36 }
38
39 fn scenario_starts(&mut self) -> StepResult {
46 Ok(())
47 }
48
49 fn scenario_stops(&mut self) -> StepResult {
58 Ok(())
59 }
60
61 #[allow(unused_variables)]
80 fn step_starts(&mut self, step_title: &str) -> StepResult {
81 Ok(())
82 }
83
84 #[allow(unused_variables)]
93 fn step_stops(&mut self) -> StepResult {
94 Ok(())
95 }
96}
97
98struct ScenarioContextItem<C>(Mutex<C>);
100
101struct ScenarioContextHook<C>(PhantomData<C>);
104
105impl<C> ScenarioContextHook<C>
106where
107 C: ContextElement,
108{
109 fn new() -> Self {
110 Self(PhantomData)
111 }
112}
113
114trait ScenarioContextHookKind {
116 fn scenario_starts(&self, contexts: &ScenarioContext) -> StepResult;
118
119 fn scenario_stops(&self, contexts: &ScenarioContext) -> StepResult;
121
122 fn step_starts(&self, contexts: &ScenarioContext, step_name: &str) -> StepResult;
124
125 fn step_stops(&self, contexts: &ScenarioContext) -> StepResult;
127
128 fn debug(&self, contexts: &ScenarioContext, dc: &mut DebuggedContext, alternate: bool);
130}
131
132impl<C> ScenarioContextHookKind for ScenarioContextHook<C>
133where
134 C: ContextElement,
135{
136 fn scenario_starts(&self, contexts: &ScenarioContext) -> StepResult {
137 contexts.with_mut(|c: &mut C| c.scenario_starts(), false)
138 }
139
140 fn scenario_stops(&self, contexts: &ScenarioContext) -> StepResult {
141 contexts.with_mut(|c: &mut C| c.scenario_stops(), true)
142 }
143
144 fn step_starts(&self, contexts: &ScenarioContext, step_name: &str) -> StepResult {
145 contexts.with_mut(|c: &mut C| c.step_starts(step_name), false)
146 }
147
148 fn step_stops(&self, contexts: &ScenarioContext) -> StepResult {
149 contexts.with_mut(|c: &mut C| c.step_stops(), true)
150 }
151
152 fn debug(&self, contexts: &ScenarioContext, dc: &mut DebuggedContext, alternate: bool) {
153 contexts.with_generic(|c: &C| dc.add(c, alternate));
154 }
155}
156
157pub struct ScenarioContext {
161 title: String,
162 location: &'static str,
163 inner: TypeMap![],
164 hooks: RefCell<Vec<Box<dyn ScenarioContextHookKind>>>,
165}
166
167#[derive(Default)]
168struct DebuggedContext {
169 body: Vec<String>,
170}
171
172impl DebuggedContext {
173 fn add<C>(&mut self, obj: &C, alternate: bool)
174 where
175 C: Debug,
176 {
177 let body = if alternate {
178 format!("{obj:#?}")
179 } else {
180 format!("{obj:?}")
181 };
182 self.body.push(body);
183 }
184}
185
186struct DebugContextString<'a>(&'a str);
187
188impl Debug for DebugContextString<'_> {
189 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190 f.write_str(self.0)
191 }
192}
193
194impl Debug for DebuggedContext {
195 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196 f.debug_list()
197 .entries(self.body.iter().map(|s| DebugContextString(s)))
198 .finish()
199 }
200}
201
202impl Debug for ScenarioContext {
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204 let mut contexts = DebuggedContext::default();
205 for hook in self.hooks.borrow().iter() {
206 hook.debug(self, &mut contexts, f.alternate());
207 }
208 f.debug_struct("ScenarioContext")
209 .field("title", &self.title)
210 .field("contexts", &contexts)
211 .finish()
212 }
213}
214
215impl ScenarioContext {
216 fn new(title: &str, location: &'static str) -> Self {
217 Self {
218 title: title.to_string(),
219 location,
220 inner: <TypeMap![]>::new(),
221 hooks: RefCell::new(Vec::new()),
222 }
223 }
224
225 fn title(&self) -> &str {
227 &self.title
228 }
229
230 pub(crate) fn register_context_type<C>(&self) -> bool
232 where
233 C: ContextElement,
234 {
235 let sci: Option<&ScenarioContextItem<C>> = self.inner.try_get();
236 if sci.is_none() {
237 let ctx = ScenarioContextItem(Mutex::new(C::default()));
238 self.inner.set(ctx);
239 self.hooks
240 .borrow_mut()
241 .push(Box::new(ScenarioContextHook::<C>::new()));
242 true
243 } else {
244 false
245 }
246 }
247
248 fn with_generic<C, F>(&self, func: F)
249 where
250 F: FnOnce(&C),
251 C: ContextElement,
252 {
253 let sci: &ScenarioContextItem<C> = self
254 .inner
255 .try_get()
256 .expect("Scenario Context item not initialised");
257 let lock = match sci.0.lock() {
258 Ok(lock) => lock,
259 Err(pe) => pe.into_inner(),
260 };
261 func(&lock)
262 }
263
264 pub fn with<C, F, R>(&self, func: F, defuse_poison: bool) -> Result<R, StepError>
266 where
267 F: FnOnce(&C) -> Result<R, StepError>,
268 C: ContextElement,
269 {
270 self.with_mut(|c: &mut C| func(&*c), defuse_poison)
271 }
272
273 pub fn with_mut<C, F, R>(&self, func: F, defuse_poison: bool) -> Result<R, StepError>
275 where
276 F: FnOnce(&mut C) -> Result<R, StepError>,
277 C: ContextElement,
278 {
279 let sci: &ScenarioContextItem<C> = self
280 .inner
281 .try_get()
282 .ok_or("required context type not registered with scenario")?;
283 let mut lock = match sci.0.lock() {
284 Ok(lock) => lock,
285 Err(pe) => {
286 if defuse_poison {
287 pe.into_inner()
288 } else {
289 return Err("context poisoned by panic".into());
290 }
291 }
292 };
293 func(&mut lock)
294 }
295}
296
297pub struct Scenario {
318 contexts: ScenarioContext,
319 steps: Vec<(ScenarioStep, Option<ScenarioStep>)>,
320}
321
322impl Scenario {
323 pub fn new(title: &str, location: &'static str) -> Self {
325 Self {
326 contexts: ScenarioContext::new(title, location),
327 steps: Vec::new(),
328 }
329 }
330
331 pub fn title(&self) -> &str {
333 self.contexts.title()
334 }
335
336 pub fn add_step(&mut self, step: ScenarioStep, cleanup: Option<ScenarioStep>) {
338 step.register_contexts(self);
339 if let Some(s) = cleanup.as_ref() {
340 s.register_contexts(self)
341 }
342 self.steps.push((step, cleanup));
343 }
344
345 pub fn register_context_type<C>(&self)
347 where
348 C: ContextElement,
349 {
350 if self.contexts.register_context_type::<C>() {
351 self.contexts
352 .with_mut(
353 |c: &mut C| {
354 c.created(self);
355 Ok(())
356 },
357 false,
358 )
359 .unwrap();
360 }
361 }
362
363 pub fn run(self) -> Result<(), StepError> {
374 let mut ret = Ok(());
376 let mut highest_start = None;
377 println!(
378 "{}: scenario: {}",
379 self.contexts.location,
380 self.contexts.title()
381 );
382 for (i, hook) in self.contexts.hooks.borrow().iter().enumerate() {
383 let res = hook.scenario_starts(&self.contexts);
384 if res.is_err() {
385 ret = res;
386 break;
387 }
388 highest_start = Some(i);
389 }
390 if ret.is_err() {
391 println!("*** Context hooks returned failure",);
392 }
393 if ret.is_ok() {
394 let mut highest = None;
395 for (i, step) in self.steps.iter().map(|(step, _)| step).enumerate() {
396 println!("{}: step: {}", step.location(), step.step_text());
397 let mut highest_prep = None;
398 for (i, prep) in self.contexts.hooks.borrow().iter().enumerate() {
399 let res = prep.step_starts(&self.contexts, step.step_text());
400 if res.is_err() {
401 ret = res;
402 break;
403 }
404 highest_prep = Some(i);
405 }
406 if ret.is_err() {
407 println!("*** Context hooks returned failure",);
408 }
409 if ret.is_ok() {
410 let res = step.call(&self.contexts, false);
411 if res.is_err() {
412 ret = res;
413 break;
414 }
415 highest = Some(i);
416 }
417 if let Some(n) = highest_prep {
418 for hookn in (0..=n).rev() {
419 let res = self.contexts.hooks.borrow()[hookn].step_stops(&self.contexts);
420 ret = ret.and(res)
421 }
422 }
423 }
424 if let Some(n) = highest {
425 for stepn in (0..=n).rev() {
426 if let (_, Some(cleanup)) = &self.steps[stepn] {
427 println!(" cleanup: {}", cleanup.step_text());
428 let res = cleanup.call(&self.contexts, true);
429 if res.is_err() {
430 println!("*** Cleanup returned failure",);
431 }
432 ret = ret.and(res);
433 }
434 }
435 }
436 }
437
438 if let Some(n) = highest_start {
439 for hookn in (0..=n).rev() {
440 let res = self.contexts.hooks.borrow()[hookn].scenario_stops(&self.contexts);
441 ret = ret.and(res);
442 }
443 }
444 println!(" return: {}", if ret.is_ok() { "OK" } else { "Failure" });
445 ret
446 }
447}