use crate::DetRng;
#[derive(Debug, Clone)]
pub enum FaultCheck {
Pass,
Inject { message: String },
Delay { us: u64 },
}
pub struct PluginContext<'a> {
pub rng: &'a mut DetRng,
pub seed: u64,
pub metadata: &'a std::collections::HashMap<String, String>,
}
pub trait VortexPlugin: Send + Sync {
fn name(&self) -> &str;
fn check_fault(&self, ctx: &mut PluginContext<'_>, operation: &str) -> FaultCheck;
fn on_register(&self) {}
fn on_shutdown(&self) {}
}
pub struct PluginRegistry {
plugins: Vec<Box<dyn VortexPlugin>>,
}
impl PluginRegistry {
pub fn new() -> Self {
Self {
plugins: Vec::new(),
}
}
pub fn register(&mut self, plugin: Box<dyn VortexPlugin>) {
plugin.on_register();
self.plugins.push(plugin);
}
pub fn check_all(
&self,
rng: &mut DetRng,
seed: u64,
operation: &str,
metadata: &std::collections::HashMap<String, String>,
) -> FaultCheck {
for plugin in &self.plugins {
let mut ctx = PluginContext {
rng,
seed,
metadata,
};
match plugin.check_fault(&mut ctx, operation) {
FaultCheck::Pass => continue,
fault => return fault,
}
}
FaultCheck::Pass
}
pub fn shutdown(&self) {
for plugin in &self.plugins {
plugin.on_shutdown();
}
}
pub fn len(&self) -> usize {
self.plugins.len()
}
pub fn is_empty(&self) -> bool {
self.plugins.is_empty()
}
}
impl Default for PluginRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct TestPlugin {
probability: f64,
}
impl VortexPlugin for TestPlugin {
fn name(&self) -> &str {
"test"
}
fn check_fault(&self, ctx: &mut PluginContext<'_>, _operation: &str) -> FaultCheck {
if ctx.rng.chance(self.probability) {
FaultCheck::Inject {
message: "test fault".into(),
}
} else {
FaultCheck::Pass
}
}
}
#[test]
fn test_plugin_registry() {
let mut registry = PluginRegistry::new();
assert!(registry.is_empty());
registry.register(Box::new(TestPlugin { probability: 1.0 }));
assert_eq!(registry.len(), 1);
let mut rng = DetRng::new(42);
let metadata = std::collections::HashMap::new();
let result = registry.check_all(&mut rng, 42, "test:op", &metadata);
assert!(matches!(result, FaultCheck::Inject { .. }));
}
#[test]
fn test_plugin_pass() {
let mut registry = PluginRegistry::new();
registry.register(Box::new(TestPlugin { probability: 0.0 }));
let mut rng = DetRng::new(42);
let metadata = std::collections::HashMap::new();
let result = registry.check_all(&mut rng, 42, "test:op", &metadata);
assert!(matches!(result, FaultCheck::Pass));
}
#[test]
fn test_multiple_plugins() {
let mut registry = PluginRegistry::new();
registry.register(Box::new(TestPlugin { probability: 0.0 })); registry.register(Box::new(TestPlugin { probability: 1.0 }));
let mut rng = DetRng::new(42);
let metadata = std::collections::HashMap::new();
let result = registry.check_all(&mut rng, 42, "test:op", &metadata);
assert!(matches!(result, FaultCheck::Inject { .. }));
}
}