#![cfg(unix)]
use std::fs::File;
use std::process::{Command, Stdio};
use std::time::{Duration, Instant};
use assert_cmd::cargo::CommandCargoExt as _;
const PROBE_TIMEOUT: Duration = Duration::from_secs(5);
#[test]
fn the_restore_escape_lands_before_the_panic_backtrace() {
let dir = tempfile::tempdir().expect("tempdir for the probe's merged output");
let path = dir.path().join("probe.out");
let sink = File::create(&path).expect("create probe output file");
let sink_for_stderr = sink.try_clone().expect("clone probe output handle");
let mut cmd = Command::cargo_bin("shep").expect("locate the built shep binary");
cmd.env("SHEP_TERM_PANIC_PROBE", "1")
.env("RUST_BACKTRACE", "0")
.stdin(Stdio::null())
.stdout(Stdio::from(sink))
.stderr(Stdio::from(sink_for_stderr));
let status = wait_bounded(cmd, PROBE_TIMEOUT);
assert!(
!status.success(),
"the probe is supposed to panic, not exit cleanly"
);
let merged = std::fs::read(&path).expect("read the probe's merged output");
let restore_at = find(&merged, b"\x1b[?1049l").expect("the restore escape must appear at all");
let panic_at = find(&merged, b"panicked at").expect("the panic backtrace must appear at all");
assert!(
restore_at < panic_at,
"restore escape at byte {restore_at} must precede the panic backtrace at byte \
{panic_at}; merged output:\n{}",
String::from_utf8_lossy(&merged)
);
}
fn wait_bounded(mut cmd: Command, timeout: Duration) -> std::process::ExitStatus {
let mut child = cmd.spawn().expect("spawn the probe subprocess");
let deadline = Instant::now() + timeout;
loop {
if let Some(status) = child.try_wait().expect("poll the probe subprocess") {
return status;
}
if Instant::now() >= deadline {
let _ = child.kill();
let _ = child.wait();
panic!("probe subprocess did not exit within {timeout:?}");
}
std::thread::sleep(Duration::from_millis(20));
}
}
fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack
.windows(needle.len())
.position(|window| window == needle)
}