221 lines
6.8 KiB
Rust
221 lines
6.8 KiB
Rust
//! Host-runnable validation tests for the watchdog binary.
|
|
//!
|
|
//! Validates that the watchdog correctly:
|
|
//! - Detects clean exits (PASS)
|
|
//! - Detects non-zero exits (FAIL)
|
|
//! - Detects timeouts (HANG)
|
|
//! - Generates correct exit codes
|
|
|
|
use std::process::Command;
|
|
|
|
/// Helper: run the watchdog in host mode and return exit code + stderr text.
|
|
fn run_watchdog(args: &[&str], timeout_secs: u64) -> (i32, String) {
|
|
let mut cmd_args = vec!["host"];
|
|
cmd_args.extend(args);
|
|
// Add timeout.
|
|
let timeout_str = timeout_secs.to_string();
|
|
cmd_args.push("--timeout");
|
|
cmd_args.push(&timeout_str);
|
|
|
|
let output = Command::new(
|
|
std::env::current_dir()
|
|
.unwrap()
|
|
.join("target/debug/redbear-threadtest-watchdog"),
|
|
)
|
|
.args(&cmd_args)
|
|
.output();
|
|
|
|
match output {
|
|
Ok(o) => {
|
|
let code = o.status.code().unwrap_or(-1);
|
|
let stderr = String::from_utf8_lossy(&o.stderr).to_string();
|
|
(code, stderr)
|
|
}
|
|
Err(_e) => {
|
|
// Try cargo run as fallback.
|
|
let output = Command::new("cargo")
|
|
.args(["run", "--bin", "redbear-threadtest-watchdog", "--"])
|
|
.args(&cmd_args)
|
|
.output()
|
|
.expect("watchdog binary not found; build with `cargo build` first");
|
|
|
|
let code = output.status.code().unwrap_or(-1);
|
|
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
|
(code, stderr)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Run the in-guest binary directly and capture its output for watchdog parsing.
|
|
fn run_threadtest(layer: &str) -> (i32, String, String) {
|
|
let output = Command::new(
|
|
std::env::current_dir()
|
|
.unwrap()
|
|
.join("target/debug/redbear-threadtest"),
|
|
)
|
|
.arg(layer)
|
|
.output();
|
|
|
|
match output {
|
|
Ok(o) => {
|
|
let code = o.status.code().unwrap_or(-1);
|
|
let stdout = String::from_utf8_lossy(&o.stdout).to_string();
|
|
let stderr = String::from_utf8_lossy(&o.stderr).to_string();
|
|
(code, stdout, stderr)
|
|
}
|
|
Err(_e) => {
|
|
let output = Command::new("cargo")
|
|
.args(["run", "--bin", "redbear-threadtest", "--", layer])
|
|
.output()
|
|
.expect("threadtest binary not found; build with `cargo build` first");
|
|
|
|
let code = output.status.code().unwrap_or(-1);
|
|
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
|
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
|
(code, stdout, stderr)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn watchdog_detects_clean_exit() {
|
|
let (code, stderr) = run_watchdog(&["true"], 10);
|
|
assert_eq!(code, 0, "watchdog should report PASS for exit 0: stderr={stderr}");
|
|
assert!(
|
|
stderr.contains("[WATCHDOG-PASS]"),
|
|
"watchdog should emit PASS marker: stderr={stderr}",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn watchdog_detects_non_zero_exit() {
|
|
let (code, stderr) = run_watchdog(&["false"], 10);
|
|
assert_eq!(code, 1, "watchdog should report FAIL for non-zero exit: stderr={stderr}");
|
|
assert!(
|
|
stderr.contains("[WATCHDOG-FAIL]"),
|
|
"watchdog should emit FAIL marker: stderr={stderr}",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn watchdog_detects_timeout_hang() {
|
|
// Use `sleep` to simulate a hanging process.
|
|
let (code, stderr) = run_watchdog(&["sleep", "30"], 2);
|
|
assert_eq!(code, 2, "watchdog exit code 2 = hang detected: stderr={stderr}");
|
|
assert!(
|
|
stderr.contains("[WATCHDOG-HANG]"),
|
|
"watchdog should emit HANG marker: stderr={stderr}",
|
|
);
|
|
assert!(
|
|
stderr.contains("[WATCHDOG-ATTRIBUTION]"),
|
|
"watchdog should emit ATTRIBUTION marker: stderr={stderr}",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn watchdog_reports_spawn_failure() {
|
|
let (code, stderr) = run_watchdog(&["/nonexistent/binary"], 10);
|
|
assert_eq!(code, 1, "watchdog should report FAIL for spawn failure");
|
|
assert!(
|
|
stderr.contains("[WATCHDOG-FAIL]"),
|
|
"watchdog should emit FAIL marker: stderr={stderr}",
|
|
);
|
|
assert!(
|
|
stderr.contains("spawn"),
|
|
"watchdog should report spawn error: stderr={stderr}",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn threadtest_l3_rwlock_produces_pass_marker() {
|
|
let (code, stdout, _stderr) = run_threadtest("l3");
|
|
assert_eq!(code, 0, "L3 RwLock host run must exit 0");
|
|
assert!(
|
|
stdout.contains("[THREADTEST-PASS]"),
|
|
"L3 must produce PASS marker: stdout={stdout}",
|
|
);
|
|
assert!(
|
|
stdout.contains("L3-rwlock"),
|
|
"L3 output must name the layer: stdout={stdout}",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn threadtest_l4_scope_produces_pass_marker() {
|
|
let (code, stdout, _stderr) = run_threadtest("l4");
|
|
assert_eq!(code, 0, "L4 scope host run must exit 0");
|
|
assert!(
|
|
stdout.contains("[THREADTEST-PASS]"),
|
|
"L4 must produce PASS marker: stdout={stdout}",
|
|
);
|
|
assert!(
|
|
stdout.contains("L4-scope"),
|
|
"L4 output must name the layer: stdout={stdout}",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn threadtest_l1_futex_produces_pass_marker() {
|
|
let (code, stdout, _stderr) = run_threadtest("l1");
|
|
assert_eq!(code, 0, "L1 futex host mock must exit 0");
|
|
assert!(
|
|
stdout.contains("[THREADTEST-PASS]"),
|
|
"L1 must produce PASS marker: stdout={stdout}",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn threadtest_l2_pthread_produces_pass_marker() {
|
|
let (code, stdout, _stderr) = run_threadtest("l2");
|
|
assert_eq!(code, 0, "L2 pthread host run must exit 0");
|
|
assert!(
|
|
stdout.contains("[THREADTEST-PASS]"),
|
|
"L2 must produce PASS marker: stdout={stdout}",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn threadtest_emits_begin_and_done_markers() {
|
|
let (code, stdout, _stderr) = run_threadtest("l3");
|
|
assert_eq!(code, 0);
|
|
assert!(
|
|
stdout.contains("[THREADTEST-BEGIN]"),
|
|
"must emit BEGIN marker"
|
|
);
|
|
// Single-layer runs don't emit DONE (only `all` mode does).
|
|
// That's fine — the watchdog reads per-layer PASS/FAIL markers.
|
|
}
|
|
|
|
#[test]
|
|
fn watchdog_can_supervise_threadtest_directly() {
|
|
// Full integration: watchdog watches threadtest directly.
|
|
// Use L4 scope which completes fast on host.
|
|
let output = Command::new("cargo")
|
|
.args([
|
|
"run",
|
|
"--bin",
|
|
"redbear-threadtest-watchdog",
|
|
"--",
|
|
"host",
|
|
"--timeout",
|
|
"60",
|
|
"cargo",
|
|
"run",
|
|
"--bin",
|
|
"redbear-threadtest",
|
|
"--",
|
|
"l4",
|
|
])
|
|
.output()
|
|
.expect("cargo run failed");
|
|
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
let code = output.status.code().unwrap_or(-1);
|
|
|
|
assert_eq!(code, 0, "watchdog supervising L4 must PASS: stderr={stderr}");
|
|
assert!(
|
|
stderr.contains("[WATCHDOG-PASS]"),
|
|
"watchdog must emit PASS: stderr={stderr}",
|
|
);
|
|
}
|