use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
use crate::error::{Error, Result};
#[derive(Debug, Default)]
pub struct Faults {
any: AtomicBool,
armed: Mutex<HashMap<String, u32>>,
}
impl Faults {
pub fn arm(&self, point: &str) {
self.arm_after(point, 0);
}
pub fn arm_after(&self, point: &str, skip: u32) {
if let Ok(mut a) = self.armed.lock() {
a.insert(point.to_string(), skip);
self.any.store(true, Ordering::SeqCst);
}
}
pub fn hit(&self, point: &str) -> bool {
if !self.any.load(Ordering::SeqCst) {
return false;
}
let Ok(mut a) = self.armed.lock() else {
return false;
};
match a.get_mut(point) {
Some(0) => {
a.remove(point);
if a.is_empty() {
self.any.store(false, Ordering::SeqCst);
}
true
}
Some(n) => {
*n -= 1;
false
}
None => false,
}
}
pub fn check(&self, point: &str) -> Result<()> {
if self.hit(point) {
return Err(Error::Db(rusqlite::Error::SqliteFailure(
rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_IOERR),
Some(format!("injected fault at {point}")),
)));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fires_once_after_the_skipped_hits() {
let f = Faults::default();
assert!(!f.hit("x"));
f.arm_after("x", 2);
assert!(!f.hit("x"));
assert!(!f.hit("x"));
assert!(f.hit("x"));
assert!(!f.hit("x"));
f.arm("y");
assert!(f.check("y").is_err());
assert!(f.check("y").is_ok());
}
}