use std::cell::RefCell;
use std::marker::PhantomData;
use std::rc::Rc;
#[derive(Default)]
struct State {
target: Option<usize>,
trace: Vec<String>,
killed: bool,
}
thread_local! {
static STATE: RefCell<Option<State>> = const { RefCell::new(None) };
}
#[doc(hidden)]
#[derive(Debug)]
pub struct Crash;
#[doc(hidden)]
pub struct Scope(PhantomData<Rc<()>>);
impl Scope {
#[doc(hidden)]
pub fn new(target: Option<usize>) -> Self {
STATE.with(|s| {
let mut s = s.borrow_mut();
assert!(s.is_none(), "nested fault scope");
*s = Some(State {
target,
..State::default()
});
});
Self(PhantomData)
}
#[doc(hidden)]
pub fn trace(&self) -> Vec<String> {
STATE.with(|s| s.borrow().as_ref().unwrap().trace.clone())
}
}
impl Drop for Scope {
fn drop(&mut self) {
STATE.with(|s| *s.borrow_mut() = None);
}
}
pub(crate) fn point(kind: &str, name: &str, phase: &str) -> std::io::Result<()> {
let crash = STATE.with(|s| {
let mut s = s.borrow_mut();
let Some(s) = s.as_mut() else {
return Ok(false);
};
if s.killed {
return Err(std::io::Error::other("simulated process has ended"));
}
s.trace.push(format!("{kind}:{name}:{phase}"));
s.killed = s.target == Some(s.trace.len());
Ok(s.killed)
})?;
if crash {
std::panic::resume_unwind(Box::new(Crash));
}
Ok(())
}