use super::RestoreOnDrop;
use std::cell::Cell;
#[test]
fn restore_runs_on_normal_scope_exit() {
let ran = Cell::new(0);
{
let _g = RestoreOnDrop {
restore: || ran.set(ran.get() + 1),
};
}
assert_eq!(ran.get(), 1, "restore must run exactly once on normal exit");
}
#[test]
fn restore_runs_when_an_inner_question_mark_returns_early() {
let ran = Cell::new(0);
fn splash_body(ran: &Cell<u32>) -> std::io::Result<()> {
let _g = RestoreOnDrop {
restore: || ran.set(ran.get() + 1),
};
Err(std::io::Error::other("splash step failed"))?;
unreachable!("the ? above returns");
}
assert!(splash_body(&ran).is_err());
assert_eq!(
ran.get(),
1,
"restore must run on the error path — this is the #1411 leak: an I/O \
error inside the splash used to skip disable_raw_mode + \
LeaveAlternateScreen and strand the operator in a hidden-cursor, \
non-echoing terminal"
);
}
#[test]
fn restore_runs_while_unwinding_a_panic() {
let ran = std::sync::Arc::new(std::sync::Mutex::new(0u32));
let seen = std::sync::Arc::clone(&ran);
let result = std::panic::catch_unwind(move || {
let _g = RestoreOnDrop {
restore: || *seen.lock().unwrap() += 1,
};
panic!("splash panicked");
});
assert!(result.is_err(), "the panic propagates");
assert_eq!(
*ran.lock().unwrap(),
1,
"restore must run during unwind — Drop is the only mechanism that \
covers this path at all"
);
}
#[test]
fn the_pre_fix_shape_skips_restore_on_the_error_path() {
fn pre_fix_splash_body(restored: &Cell<u32>) -> std::io::Result<()> {
Err(std::io::Error::other("splash step failed"))?;
restored.set(restored.get() + 1);
Ok(())
}
let restored = Cell::new(0);
assert!(pre_fix_splash_body(&restored).is_err());
assert_eq!(
restored.get(),
0,
"this is the bug #1411 fixes: the terminal was never restored on the \
error path, so the operator was left in the alternate screen with \
raw mode on and the cursor hidden"
);
}
#[test]
fn nested_guards_restore_in_reverse_order_exactly_once() {
let order = std::cell::RefCell::new(Vec::new());
{
let _outer = RestoreOnDrop {
restore: || order.borrow_mut().push("outer"),
};
{
let _inner = RestoreOnDrop {
restore: || order.borrow_mut().push("inner"),
};
}
}
assert_eq!(*order.borrow(), vec!["inner", "outer"]);
}