#![cfg(unix)]
#[test]
fn recovery_child() {
let Ok(case) = std::env::var("MANT_RECOVERY_TEST_CASE") else {
return;
};
let content = mant_loader::load_markdown_text("# Recovery\n\nBody.\n", None).unwrap();
if case == "panic" {
let mut discover = |_: &mant_protocol::CatalogQuery| panic!("injected host callback panic");
super::run_reader(
mant_ui::ReaderOptions::new(std::sync::Arc::new(content)),
&mut mant_ui::ReaderServices {
discover_documents: Some(&mut discover),
..Default::default()
},
)
.unwrap();
panic!("expected discovery callback to panic");
} else {
assert_eq!(case, "initialization");
initialization_failure(&content);
}
}
#[test]
fn panic_and_initialization_failure_restore_a_real_pty() {
let harness = include_str!("../../../tests/support/display_pty.py");
let program = format!(
"__name__ = 'recovery_harness'\n{harness}\n{}",
r#"
environment = dict(os.environ, TERM="xterm-256color", NO_COLOR="1")
for case in ["panic", "initialization"]:
env = dict(environment, MANT_RECOVERY_TEST_CASE=case)
in_session(lambda: check_in_session(
[sys.argv[1], "--exact", "delivery::terminal::tests::recovery_child", "--nocapture"], True, env,
action=lambda _process, master: os.write(master, b"\x0f") if case == "panic" else None,
returncodes=(101,) if case == "panic" else (0,),
diagnostic=b"injected host callback panic" if case == "panic" else None,
))
print("Rust UI recovery", case, "passed", flush=True)
"#
);
let mut child = std::process::Command::new("python3")
.args(["-c", &program])
.arg(std::env::current_exe().unwrap())
.spawn()
.expect("Python 3 PTY harness");
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(65);
loop {
if let Some(status) = child.try_wait().unwrap() {
assert!(status.success(), "PTY recovery failed: {status}");
break;
}
if std::time::Instant::now() >= deadline {
let _ = child.kill();
let _ = child.wait();
panic!("PTY recovery supervisor timed out");
}
std::thread::sleep(std::time::Duration::from_millis(50));
}
}
#[allow(unsafe_code)] fn initialization_failure(content: &mant_ir::ResolvedContent) {
use std::os::fd::AsRawFd;
unsafe extern "C" {
fn dup2(old: std::ffi::c_int, new: std::ffi::c_int) -> std::ffi::c_int;
}
let tty = std::fs::OpenOptions::new()
.write(true)
.open("/dev/tty")
.unwrap();
let (broken, peer) = std::os::unix::net::UnixStream::pair().unwrap();
drop(peer);
assert_eq!(unsafe { dup2(broken.as_raw_fd(), 1) }, 1);
let result = super::run_reader(
mant_ui::ReaderOptions::new(std::sync::Arc::new(content.clone())),
&mut mant_ui::ReaderServices::default(),
);
assert_eq!(unsafe { dup2(tty.as_raw_fd(), 1) }, 1);
assert_eq!(result.unwrap_err().kind(), std::io::ErrorKind::BrokenPipe);
}