pub fn armed() -> Option<String> {
std::env::var("KNOT_FAILPOINT")
.ok()
.filter(|name| !name.is_empty())
}
fn is_armed(name: &str) -> bool {
armed().as_deref() == Some(name)
}
/// tells the test which failpoint fired, through `KNOT_FAILPOINT_FILE`
fn signal(name: &str) -> Option<String> {
let file = std::env::var("KNOT_FAILPOINT_FILE")
.ok()
.filter(|file| !file.is_empty())?;
let path = file.as_str();
let _ = std::fs::write(path, format!("{name}\n"));
Some(file)
}
/// armed, it waits until the test removes the file it signalled through
pub fn pause(name: &str) {
if !is_armed(name) {
return;
}
let Some(file) = signal(name) else {
return;
};
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(60);
while std::path::Path::new(&file).exists() && std::time::Instant::now() < deadline {
std::thread::sleep(std::time::Duration::from_millis(20));
}
}
pub fn check(name: &str) -> anyhow::Result<()> {
if !is_armed(name) {
return Ok(());
}
signal(name);
anyhow::bail!("failpoint {name} armed (KNOT_FAILPOINT)")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unarmed_by_default_and_matched_by_name() {
let ambient = armed();
match ambient.as_deref() {
Some(name) => {
assert!(is_armed(name));
assert!(check(name).is_err());
assert!(check(&format!("{name}-other")).is_ok());
}
None => {
assert!(check("route_emission").is_ok());
assert!(!is_armed("route_emission"));
}
}
}
}