#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CrashPoint {
AfterStart,
AfterStep,
BeforeFinish,
AfterFinish,
}
#[cfg(any(test, feature = "fault-injection"))]
mod armed {
use std::cell::Cell;
use super::CrashPoint;
use crate::error::{Error, Result};
thread_local! {
static ARMED: Cell<Option<CrashPoint>> = const { Cell::new(None) };
}
pub fn inject_crash(point: Option<CrashPoint>) {
ARMED.with(|armed| armed.set(point));
}
pub(crate) fn crash_seam(point: CrashPoint) -> Result<()> {
let tripped = ARMED.with(|armed| {
if armed.get() == Some(point) {
armed.set(None);
true
} else {
false
}
});
if tripped {
return Err(Error::Migration(
"fault-injection: simulated crash at a migration seam".to_string(),
));
}
Ok(())
}
}
#[cfg(any(test, feature = "fault-injection"))]
pub(crate) use armed::crash_seam;
#[cfg(any(test, feature = "fault-injection"))]
pub use armed::inject_crash;
#[cfg(not(any(test, feature = "fault-injection")))]
#[inline]
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn crash_seam(_point: CrashPoint) -> crate::error::Result<()> {
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn armed_point_trips_its_seam_once() {
inject_crash(Some(CrashPoint::AfterStart));
assert!(crash_seam(CrashPoint::AfterStart).is_err());
assert!(crash_seam(CrashPoint::AfterStart).is_ok());
}
#[test]
fn unarmed_seam_is_a_noop() {
inject_crash(None);
assert!(crash_seam(CrashPoint::AfterFinish).is_ok());
}
#[test]
fn other_points_do_not_trip() {
inject_crash(Some(CrashPoint::BeforeFinish));
assert!(crash_seam(CrashPoint::AfterStep).is_ok());
}
}