#[cfg(test)]
pub(crate) fn point(name: &'static str) {
let hung = MEANWHILE.with(|meanwhile| {
let mut meanwhile = meanwhile.borrow_mut();
match meanwhile.as_ref() {
Some((at, _)) if at == name => meanwhile.take().map(|(_, run)| run),
_ => None,
}
});
if let Some(run) = hung {
run();
}
ARMED.with(|armed| {
if armed.borrow().as_deref() == Some(name) {
*armed.borrow_mut() = None;
panic!("{KILLED}{name}");
}
});
}
#[cfg(test)]
pub(crate) fn meanwhile<T>(
name: &'static str,
elsewhere: impl FnOnce() + 'static,
change: impl FnOnce() -> T,
) -> T {
MEANWHILE
.with(|meanwhile| *meanwhile.borrow_mut() = Some((name.to_string(), Box::new(elsewhere))));
let outcome = change();
MEANWHILE.with(|meanwhile| *meanwhile.borrow_mut() = None);
outcome
}
#[cfg(not(test))]
#[inline(always)]
pub(crate) fn point(_name: &'static str) {}
#[cfg(test)]
const KILLED: &str = "pitboard fault: killed at ";
#[cfg(test)]
type Elsewhere = Box<dyn FnOnce()>;
#[cfg(test)]
thread_local! {
static ARMED: std::cell::RefCell<Option<String>> = const { std::cell::RefCell::new(None) };
static MEANWHILE: std::cell::RefCell<Option<(String, Elsewhere)>> =
const { std::cell::RefCell::new(None) };
}
#[cfg(test)]
pub(crate) fn killing<T>(name: &'static str, change: impl FnOnce() -> T) -> Result<T, String> {
ARMED.with(|armed| *armed.borrow_mut() = Some(name.to_string()));
let quiet = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(change));
std::panic::set_hook(quiet);
ARMED.with(|armed| *armed.borrow_mut() = None);
outcome.map_err(|panic| {
let said = panic
.downcast_ref::<String>()
.cloned()
.or_else(|| panic.downcast_ref::<&str>().map(|s| (*s).to_string()))
.unwrap_or_default();
match said.strip_prefix(KILLED) {
Some(point) => point.to_string(),
None => panic!("{said}"),
}
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_run_dies_at_the_point_it_was_armed_for() {
let reached = std::cell::Cell::new(0);
let died = killing("second", || {
point("first");
reached.set(1);
point("second");
reached.set(2);
});
assert_eq!(died.unwrap_err(), "second");
assert_eq!(reached.get(), 1, "it stopped where it was told to");
}
#[test]
fn a_point_nobody_armed_is_nothing_at_all() {
let done = killing("never reached", || {
point("first");
point("second");
"finished"
});
assert_eq!(done.expect("no kill"), "finished");
}
#[test]
fn the_arming_does_not_survive_the_run() {
let _ = killing("first", || point("first"));
let done = killing("nothing", || {
point("first");
"finished"
});
assert_eq!(done.expect("no kill"), "finished");
}
}