#![cfg(unix)]
#![allow(clippy::expect_used, clippy::panic)]
use std::path::Path;
use std::sync::mpsc;
use std::time::{Duration, Instant};
use super::{DeathNote, FatalAction};
fn note_content(path: &Path) -> String {
std::fs::read_to_string(path).unwrap_or_default()
}
fn wait_for_entry(path: &Path, needle: &str) -> String {
let deadline = Instant::now() + Duration::from_secs(10);
loop {
let content = note_content(path);
if content.contains(needle) {
return content;
}
assert!(
Instant::now() < deadline,
"timed out waiting for note entry `{needle}`; note so far:\n{content}"
);
std::thread::sleep(Duration::from_millis(20));
}
}
#[test]
fn the_death_note_records_every_observable_path_once_armed() {
let home = tempfile::tempdir().expect("create a temporary Aion home");
super::breadcrumb("pre-arm breadcrumb must never reach a note");
let (fatal_tx, fatal_rx) = mpsc::channel();
let recorder: FatalAction = Box::new(move |signal| {
let _ = fatal_tx.send(signal);
});
let note = DeathNote::arm_with_fatal_action(home.path(), Some(recorder))
.expect("arm the death note under the temporary home");
let path = note.path().to_path_buf();
let content = note_content(&path);
let framed = format!("pid={} ARMED ", std::process::id());
assert!(
content.contains(&framed),
"the note must open with a pid-framed ARMED line ({framed}), got:\n{content}"
);
let second = DeathNote::arm(home.path());
assert!(
matches!(second, Err(crate::ServerError::DeathNote { .. })),
"a second arm must refuse with the DeathNote error"
);
super::breadcrumb("declared-action start action=probe workflow_id=wf run_id=r1");
let content = wait_for_entry(&path, "BREADCRUMB declared-action start action=probe");
assert!(
!content.contains("pre-arm breadcrumb"),
"a pre-arm breadcrumb must never be written, got:\n{content}"
);
let unwound = std::panic::catch_unwind(|| panic!("death-note test panic"));
assert!(unwound.is_err(), "the probe panic must actually unwind");
let content = wait_for_entry(&path, "PANIC");
assert!(
content.contains("death-note test panic"),
"the PANIC entry must carry the payload, got:\n{content}"
);
assert!(
content.contains("death_note_tests.rs"),
"the PANIC entry must carry the location, got:\n{content}"
);
signal_hook::low_level::raise(signal_hook::consts::SIGTERM).expect("raise SIGTERM");
let delivered = fatal_rx
.recv_timeout(Duration::from_secs(10))
.expect("a termination signal before the drain is watching must be answered");
assert_eq!(delivered, signal_hook::consts::SIGTERM);
let content = wait_for_entry(&path, "SIGNAL SIGTERM received during boot");
assert!(
content.contains("the boot is abandoned"),
"the entry must say what was done about it, got:\n{content}"
);
let content = wait_for_entry(&path, "DISARMED SIGTERM received before the doors opened");
assert!(
content.contains("nothing serving and nothing draining"),
"the closing line must carry the reason, got:\n{content}"
);
note.drain_owns_termination();
signal_hook::low_level::raise(signal_hook::consts::SIGTERM).expect("raise SIGTERM");
wait_for_entry(&path, "SIGNAL SIGTERM observed");
assert!(
fatal_rx.try_recv().is_err(),
"once the drain is watching, SIGTERM must never reach the fatal action"
);
signal_hook::low_level::raise(signal_hook::consts::SIGHUP).expect("raise SIGHUP");
let delivered = fatal_rx
.recv_timeout(Duration::from_secs(10))
.expect("the fatal action must be invoked for SIGHUP");
assert_eq!(delivered, signal_hook::consts::SIGHUP);
wait_for_entry(&path, "SIGNAL SIGHUP received; terminating");
note.disarm("test disarm: clean exit");
let content = wait_for_entry(&path, "DISARMED test disarm: clean exit");
let settled_len = content.len();
let unwound = std::panic::catch_unwind(|| panic!("post-disarm panic must not be recorded"));
assert!(unwound.is_err(), "the post-disarm probe panic must unwind");
let content = note_content(&path);
assert_eq!(
content.len(),
settled_len,
"a disarmed note must not accept panic entries, got:\n{content}"
);
super::breadcrumb("post-disarm breadcrumb must not be recorded");
let content = note_content(&path);
assert_eq!(
content.len(),
settled_len,
"a disarmed note must not accept breadcrumb entries, got:\n{content}"
);
}