crates/nornir-build-thing/src/measure/tests.rs
//! The measure step against a FAKE `heimdall` (a scripted [`ProfileTool`]):
//! what Build Thing writes beside the row, what it hands the tool, how it
//! reads the tool's JSON back, and how a drift is named. The real tool runs
//! in the lane's measured run (privileged rootless podman, USER_NOTIF).
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use super::*;
const PIN_VERSION: &str = "0.2.0";
/// One recorded call: (args, extra env).
type Call = (Vec<String>, Vec<(String, String)>);
/// A scripted `heimdall`: it records every call and writes what the real
/// tool writes (probe → `.probe.json`; gen → `.rs`, `.json`, `.oci.json`).
struct FakeTool {
path: PathBuf,
version: String,
calls: RefCell<Vec<Call>>,
/// The calls each probe "sees" per program.
seen: BTreeMap<String, Vec<&'static str>>,
/// What `check` answers: (exit code, stderr).
check: (i32, String),
}
impl FakeTool {
fn new(dir: &Path) -> FakeTool {
let path = dir.join("fake-heimdall");
std::fs::write(&path, b"#!/bin/false\nfake heimdall\n").unwrap();
FakeTool {
path,
version: PIN_VERSION.into(),
calls: RefCell::new(Vec::new()),
seen: BTreeMap::new(),
check: (0, String::new()),
}
}
fn sha(&self) -> String {
nornir_hash::sha256_hex(&std::fs::read(&self.path).unwrap())
}
fn verbs(&self) -> Vec<String> {
self.calls
.borrow()
.iter()
.map(|(a, _)| a.iter().take(3).cloned().collect::<Vec<_>>().join(" "))
.collect()
}
}
fn arg_after(args: &[String], flag: &str) -> String {
let i = args
.iter()
.position(|a| a == flag)
.unwrap_or_else(|| panic!("{flag} in {args:?}"));
args[i + 1].clone()
}
impl ProfileTool for FakeTool {
fn path(&self) -> &Path {
&self.path
}
fn call(&self, args: &[String], env: &[(String, String)]) -> anyhow::Result<ToolOutput> {
self.calls.borrow_mut().push((args.to_vec(), env.to_vec()));
if args == ["--version"] {
return Ok(ToolOutput {
code: 0,
stdout: format!("heimdall {}\n", self.version),
stderr: String::new(),
});
}
assert_eq!(args[0], "profile", "{args:?}");
let prog = args[2].clone();
let dir = PathBuf::from(arg_after(args, "--profiles")).join(&prog);
let libc = arg_after(args, "--libc");
// The real tool refuses a workload.toml it cannot read: so must this one.
let w: WorkloadToml =
toml::from_str(&std::fs::read_to_string(dir.join("workload.toml")).unwrap()).unwrap();
assert_eq!(w.workload, prog);
let arch = std::env::consts::ARCH;
let base = dir.join(format!("{arch}-{libc}"));
match args[1].as_str() {
"probe" => {
let runs: Vec<_> = w
.run
.iter()
.map(|r| serde_json::json!({"name": r.name, "error_path": r.error_path, "exit": "exit 0"}))
.collect();
let names: Vec<_> = w.run.iter().map(|r| r.name.clone()).collect();
let calls: Vec<_> = self.seen[&prog]
.iter()
.map(|c| serde_json::json!({"syscall": c, "runs": names}))
.collect();
let j = serde_json::json!({"workload": prog, "arch": arch, "libc": libc, "runs": runs, "calls": calls});
std::fs::write(
base.with_extension("probe.json"),
serde_json::to_string_pretty(&j).unwrap(),
)
.unwrap();
Ok(ToolOutput {
code: 0,
stdout: "probe: ok\n".into(),
stderr: String::new(),
})
}
"gen" => {
let rules: Vec<_> = self.seen[&prog]
.iter()
.map(|c| serde_json::json!({"syscall": c, "action": "allow"}))
.collect();
let j = serde_json::json!({
"workload": prog, "arch": arch, "libc": libc, "rules": rules,
"review": {"runs": [], "near_miss": [{"call": "rt_sigreturn", "runs": ["x"]}], "wx": [], "bare": ["ioctl"], "unknown": [], "fixed": [], "notes": []}
});
std::fs::write(
base.with_extension("json"),
serde_json::to_string_pretty(&j).unwrap(),
)
.unwrap();
std::fs::write(base.with_extension("rs"), "// generated\n").unwrap();
std::fs::write(base.with_extension("oci.json"), "{}\n").unwrap();
Ok(ToolOutput {
code: 0,
stdout: "gen: ok\n".into(),
stderr: String::new(),
})
}
"check" => Ok(ToolOutput {
code: self.check.0,
stdout: String::new(),
stderr: self.check.1.clone(),
}),
other => panic!("unexpected verb {other}"),
}
}
}
/// A repo with `.nornir/profiles/node/measure.toml`, the binaries built
/// under `target/`, and a fake tool pinned by its real sha256.
fn repo(manifest_extra: &str) -> (tempfile::TempDir, FakeTool) {
let d = tempfile::tempdir().unwrap();
let tool = FakeTool::new(d.path());
let bin = d.path().join("target/x86_64-unknown-linux-musl/release");
std::fs::create_dir_all(&bin).unwrap();
std::fs::write(bin.join("node-aya"), b"\x7fELF fake").unwrap();
std::fs::write(bin.join("node"), b"\x7fELF fake").unwrap();
let row = d.path().join(".nornir/profiles/node");
std::fs::create_dir_all(&row).unwrap();
std::fs::write(
row.join("measure.toml"),
format!(
r#"row = "node"
[tool]
version = "{PIN_VERSION}"
sha256 = "{sha}"
[[program]]
name = "tunnr-cni"
about = "the CNI plugin: the aya PID 1 under argv0 tunnr-cni"
binary = {{ musl = "{{target}}/x86_64-unknown-linux-musl/release/node-aya" }}
install_as = "tunnr-cni"
later = ["ADD success: the BPF attach to the pinned programs"]
[program.env]
RUST_BACKTRACE = "0"
[[program.run]]
name = "version"
env = {{ CNI_COMMAND = "VERSION" }}
stdin = "{{dir}}/corpus/conf.json"
expect_exit = 0
[[program.run]]
name = "add_without_netns"
error_path = true
env = {{ CNI_COMMAND = "ADD" }}
expect_exit = 1
[[program]]
name = "pid1"
binary = {{ musl = "{{target}}/x86_64-unknown-linux-musl/release/node" }}
repeat = 2
[[program.run]]
name = "not_pid_1"
error_path = true
expect_exit = 78
[[program]]
name = "rusternetes"
needs_vm = "runs only as the node's child, after PID 1 laid the archive (KVM proof)"
{manifest_extra}"#,
sha = tool.sha()
),
)
.unwrap();
let mut tool = tool;
tool.seen
.insert("tunnr-cni".into(), vec!["read", "write", "socket"]);
tool.seen.insert("pid1".into(), vec!["write", "exit_group"]);
(d, tool)
}
/// [`repo`], measured once (the profiles exist), the call log cleared.
fn measured(extra: &str) -> (tempfile::TempDir, FakeTool) {
let (d, tool) = repo(extra);
run_measure_verb(&vt("syscalls node", d.path()), d.path(), &tool).unwrap();
tool.calls.borrow_mut().clear();
(d, tool)
}
fn verb(args: &str) -> MeasureVerb {
parse_measure_verb(
&args
.split_whitespace()
.map(String::from)
.collect::<Vec<_>>(),
)
.unwrap()
}
/// The verb with `--target <repo>/target` (the test process's own
/// `CARGO_TARGET_DIR` must not leak in).
fn vt(args: &str, root: &Path) -> MeasureVerb {
verb(&format!(
"{args} --target {}",
root.join("target").display()
))
}
// ── the verb ────────────────────────────────────────────────────────────────
#[test]
fn the_verb_parses_row_check_program_and_libc() {
let v = verb(
"syscalls rusternetes-node --check --program tunnr-cni --program pid1 --libc musl --target /t",
);
assert_eq!(v.row, "rusternetes-node");
assert!(v.check);
assert_eq!(v.programs, ["tunnr-cni", "pid1"]);
assert_eq!(v.libcs, ["musl"]);
assert_eq!(v.target, Some(PathBuf::from("/t")));
let plain = verb("syscalls node");
assert!(!plain.check && plain.programs.is_empty() && plain.libcs.is_empty());
}
#[test]
fn the_verb_refuses_what_it_does_not_measure() {
let p =
|s: &str| parse_measure_verb(&s.split_whitespace().map(String::from).collect::<Vec<_>>());
let e = p("coverage node").unwrap_err().to_string();
assert!(e.contains("coverage") && e.contains("syscalls"), "{e}");
assert!(p("syscalls").is_err());
assert!(p("syscalls --check").is_err());
assert!(
p("syscalls node --libc uclibc")
.unwrap_err()
.to_string()
.contains("uclibc")
);
assert!(
p("syscalls node --frobnicate")
.unwrap_err()
.to_string()
.contains("--frobnicate")
);
}
// ── the manifest ────────────────────────────────────────────────────────────
#[test]
fn a_row_without_a_measure_manifest_is_refused_by_path() {
let d = tempfile::tempdir().unwrap();
let e = MeasureManifest::load(d.path(), "node")
.unwrap_err()
.to_string();
assert!(e.contains(".nornir/profiles/node/measure.toml"), "{e}");
}
#[test]
fn the_manifest_refuses_bad_names_duplicate_runs_and_empty_programs() {
let bad = |extra: &str| {
let (d, _t) = repo(extra);
MeasureManifest::load(d.path(), "node")
.unwrap_err()
.to_string()
};
let e = bad("[[program]]\nname = \"../evil\"\n[[program.run]]\nname = \"x\"\n");
assert!(e.contains("../evil"), "{e}");
let e = bad(
"[[program]]\nname = \"dup\"\nbinary = { musl = \"/x\" }\n[[program.run]]\nname = \"a\"\n[[program.run]]\nname = \"a\"\n",
);
assert!(e.contains("dup") && e.contains("\"a\""), "{e}");
let e = bad("[[program]]\nname = \"empty\"\nbinary = { musl = \"/x\" }\n");
assert!(e.contains("empty") && e.contains("needs_vm"), "{e}");
let e = bad("[[program]]\nname = \"pid1\"\nneeds_vm = \"twice\"\n");
assert!(e.contains("pid1") && e.contains("twice"), "{e}");
let (d, _t) = repo("");
std::fs::write(
d.path().join(".nornir/profiles/node/measure.toml"),
"row = \"other\"\n[tool]\nversion = \"0.2.0\"\nsha256 = \"00\"\n",
)
.unwrap();
let e = MeasureManifest::load(d.path(), "node")
.unwrap_err()
.to_string();
assert!(e.contains("other") && e.contains("node"), "{e}");
}
#[test]
fn the_workload_toml_is_heimdalls_format_with_the_target_placeholder_kept() {
let (d, _t) = repo("");
let m = MeasureManifest::load(d.path(), "node").unwrap();
let p = &m.programs[0];
let text = workload_toml(&m, p).unwrap();
assert!(text.starts_with("# GENERATED by Build Thing"), "{text}");
assert!(text.contains("measure.toml"), "{text}");
let w: WorkloadToml = toml::from_str(&text).unwrap();
assert_eq!(w.workload, "tunnr-cni");
// install_as: the tool runs the staged copy, under its argv0 name.
assert_eq!(
w.binary["musl"],
"{target}/heimdall-measure/node/musl/tunnr-cni"
);
assert_eq!(w.run.len(), 2);
assert_eq!(w.run[0].env["CNI_COMMAND"], "VERSION");
assert_eq!(w.run[0].stdin.as_deref(), Some("{dir}/corpus/conf.json"));
assert!(w.run[1].error_path);
assert_eq!(w.env["RUST_BACKTRACE"], "0");
// Deterministic: the same manifest, the same bytes.
assert_eq!(text, workload_toml(&m, p).unwrap());
let pid1 = workload_toml(&m, &m.programs[1]).unwrap();
let w: WorkloadToml = toml::from_str(&pid1).unwrap();
assert_eq!(
w.binary["musl"],
"{target}/x86_64-unknown-linux-musl/release/node"
);
assert_eq!(w.repeat, Some(2));
}
// ── the tool: located by name, pinned by version and sha256 ─────────────────
#[test]
fn a_missing_tool_is_refused_by_name_with_the_install_line() {
let pin = ToolPin {
version: PIN_VERSION.into(),
sha256: "00".into(),
};
let e = locate_in(None, Some("/nonexistent-a:/nonexistent-b".into()), &pin)
.unwrap_err()
.to_string();
for w in [
"heimdall",
"nordisk-heimdall-cli",
PIN_VERSION,
"cargo install",
"HEIMDALL_TOOL",
] {
assert!(e.contains(w), "{w} missing from: {e}");
}
let e = locate_in(Some("/nonexistent/heimdall".into()), None, &pin)
.unwrap_err()
.to_string();
assert!(
e.contains("/nonexistent/heimdall") && e.contains("HEIMDALL_TOOL"),
"{e}"
);
}
#[test]
fn the_tool_is_found_on_path_or_by_heimdall_tool() {
let d = tempfile::tempdir().unwrap();
let pin = ToolPin {
version: PIN_VERSION.into(),
sha256: "00".into(),
};
let bin = d.path().join("bin");
std::fs::create_dir_all(&bin).unwrap();
std::fs::write(bin.join("heimdall"), b"x").unwrap();
let path = format!("/nonexistent:{}", bin.display());
assert_eq!(
locate_in(None, Some(path.into()), &pin).unwrap().path(),
bin.join("heimdall")
);
let other = d.path().join("h2");
std::fs::write(&other, b"y").unwrap();
assert_eq!(
locate_in(Some(other.clone().into()), None, &pin)
.unwrap()
.path(),
other
);
}
#[test]
fn the_pin_refuses_another_version_and_another_build_by_name() {
let d = tempfile::tempdir().unwrap();
let mut t = FakeTool::new(d.path());
let good = ToolPin {
version: PIN_VERSION.into(),
sha256: t.sha(),
};
let id = verify_pin(&t, &good).unwrap();
assert_eq!(id.version, PIN_VERSION);
assert_eq!(id.sha256, t.sha());
let other_sha = ToolPin {
version: PIN_VERSION.into(),
sha256: "ab".repeat(32),
};
let e = verify_pin(&t, &other_sha).unwrap_err().to_string();
assert!(e.contains(&"ab".repeat(32)) && e.contains(&t.sha()), "{e}");
t.version = "0.1.0".into();
let e = verify_pin(&t, &good).unwrap_err().to_string();
assert!(
e.contains("0.1.0") && e.contains(PIN_VERSION) && e.contains("nordisk-heimdall-cli"),
"{e}"
);
}
// ── measure ─────────────────────────────────────────────────────────────────
#[test]
fn measure_probes_and_generates_every_program_and_writes_the_evidence() {
let (d, tool) = repo("");
let r = run_measure_verb(&vt("syscalls node", d.path()), d.path(), &tool).unwrap();
assert!(r.ok(), "{}", r.render());
let arch = std::env::consts::ARCH;
// The tool was asked probe then gen, per program, with the libc and the
// row's profiles dir; it never saw the VM-only program.
assert_eq!(
tool.verbs(),
[
"--version",
"profile probe tunnr-cni",
"profile gen tunnr-cni",
"profile probe pid1",
"profile gen pid1"
]
);
let calls = tool.calls.borrow();
let (args, env) = &calls[1];
assert_eq!(arg_after(args, "--libc"), "musl");
assert_eq!(
PathBuf::from(arg_after(args, "--profiles")),
d.path().join(".nornir/profiles/node")
);
assert!(
env.contains(&(
"CARGO_TARGET_DIR".into(),
d.path().join("target").display().to_string()
)),
"{env:?}"
);
drop(calls);
// Beside the row: workload.toml + the four files of each program.
let row = d.path().join(".nornir/profiles/node");
for p in ["tunnr-cni", "pid1"] {
for f in [
"workload.toml".to_string(),
format!("{arch}-musl.probe.json"),
format!("{arch}-musl.json"),
format!("{arch}-musl.rs"),
format!("{arch}-musl.oci.json"),
] {
assert!(row.join(p).join(&f).is_file(), "{p}/{f}");
}
}
assert!(!row.join("rusternetes").exists());
// install_as: the staged copy carries the argv0 name.
assert!(
d.path()
.join("target/heimdall-measure/node/musl/tunnr-cni")
.is_file()
);
// The counts are read back from the tool's JSON.
let cni = r
.programs
.iter()
.find(|p| p.program == "tunnr-cni")
.unwrap();
assert_eq!(
(cni.runs, cni.calls, cni.rules, cni.near_miss, cni.bare),
(2, 3, 3, 1, 1)
);
assert_eq!(cni.libc, "musl");
assert_eq!(
r.vm_later,
[
(
"tunnr-cni".to_string(),
"not covered yet: ADD success: the BPF attach to the pinned programs".to_string()
),
(
"rusternetes".to_string(),
"runs only as the node's child, after PID 1 laid the archive (KVM proof)"
.to_string()
)
]
);
// The evidence file: tool pin + counts + what waits for a VM; no dates.
let ev: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(row.join("measure.json")).unwrap()).unwrap();
assert_eq!(ev["tool"]["crate"], "nordisk-heimdall-cli");
assert_eq!(ev["tool"]["version"], PIN_VERSION);
assert_eq!(ev["tool"]["sha256"], tool.sha());
assert_eq!(ev["programs"][0]["program"], "tunnr-cni");
assert_eq!(ev["programs"][0]["calls"], 3);
assert_eq!(ev["vm_later"][1]["program"], "rusternetes");
let text = r.render();
// Usable output: what was probed, which calls, the near-miss, where the
// profile went, how to check it, what waits for a VM.
for w in [
"tunnr-cni (",
"version, add_without_netns",
"calls (3): read socket write",
"rt_sigreturn (by x)",
&format!(".nornir/profiles/node/tunnr-cni/{arch}-musl.json"),
"measure syscalls node --check",
"rusternetes: not probed, needs a VM",
] {
assert!(text.contains(w), "{w:?} missing from:\n{text}");
}
}
#[test]
fn measure_filters_by_program_and_refuses_an_unknown_one() {
let (d, tool) = repo("");
run_measure_verb(
&vt("syscalls node --program pid1", d.path()),
d.path(),
&tool,
)
.unwrap();
assert_eq!(
tool.verbs(),
["--version", "profile probe pid1", "profile gen pid1"]
);
let e = run_measure_verb(
&vt("syscalls node --program nope", d.path()),
d.path(),
&tool,
)
.unwrap_err()
.to_string();
assert!(e.contains("nope") && e.contains("tunnr-cni"), "{e}");
}
#[test]
fn an_unbuilt_binary_is_refused_naming_the_program_and_the_path() {
let (d, tool) = repo("");
std::fs::remove_file(
d.path()
.join("target/x86_64-unknown-linux-musl/release/node"),
)
.unwrap();
let e = run_measure_verb(
&vt("syscalls node --program pid1", d.path()),
d.path(),
&tool,
)
.unwrap_err()
.to_string();
assert!(e.contains("pid1") && e.contains("release/node"), "{e}");
}
#[test]
fn a_pin_mismatch_stops_before_any_probe() {
let (d, mut tool) = repo("");
tool.version = "0.1.0".into();
assert!(run_measure_verb(&vt("syscalls node", d.path()), d.path(), &tool).is_err());
assert_eq!(tool.verbs(), ["--version"]);
}
#[test]
fn a_failing_probe_is_refused_with_the_tools_own_words() {
struct Failing(FakeTool);
impl ProfileTool for Failing {
fn path(&self) -> &Path {
self.0.path()
}
fn call(&self, args: &[String], env: &[(String, String)]) -> anyhow::Result<ToolOutput> {
if args.get(1).map(String::as_str) == Some("probe") {
return Ok(ToolOutput {
code: 3,
stdout: String::new(),
stderr:
"heimdall profile probe: probe run version: expected exit 0, got exit 1\n"
.into(),
});
}
self.0.call(args, env)
}
}
let (d, tool) = repo("");
let e = run_measure_verb(&vt("syscalls node", d.path()), d.path(), &Failing(tool))
.unwrap_err()
.to_string();
assert!(
e.contains("tunnr-cni") && e.contains("expected exit 0, got exit 1"),
"{e}"
);
}
// ── check ───────────────────────────────────────────────────────────────────
#[test]
fn check_runs_heimdall_check_per_program_and_is_ok_when_it_is() {
let (d, tool) = measured("");
let r = run_measure_verb(&vt("syscalls node --check", d.path()), d.path(), &tool).unwrap();
assert!(r.ok(), "{}", r.render());
assert_eq!(
tool.verbs(),
["--version", "profile check tunnr-cni", "profile check pid1"]
);
// check writes nothing.
let ev = d.path().join(".nornir/profiles/node/measure.json");
let before = std::fs::metadata(&ev).unwrap().modified().unwrap();
run_measure_verb(&vt("syscalls node --check", d.path()), d.path(), &tool).unwrap();
assert_eq!(std::fs::metadata(&ev).unwrap().modified().unwrap(), before);
}
#[test]
fn check_before_any_measure_is_refused_with_the_measure_line() {
let (d, tool) = repo("");
let e = run_measure_verb(&vt("syscalls node --check", d.path()), d.path(), &tool)
.unwrap_err()
.to_string();
assert!(
e.contains("tunnr-cni") && e.contains("measure syscalls node"),
"{e}"
);
}
#[test]
fn check_names_a_workload_toml_that_no_longer_matches_measure_toml() {
let (d, tool) = measured("");
let m = d.path().join(".nornir/profiles/node/measure.toml");
let t = std::fs::read_to_string(&m)
.unwrap()
.replace("expect_exit = 78", "expect_exit = 77");
std::fs::write(&m, t).unwrap();
let r = run_measure_verb(
&vt("syscalls node --check --program pid1", d.path()),
d.path(),
&tool,
)
.unwrap();
assert!(!r.ok());
assert_eq!(
r.out_of_sync,
[(
"pid1".to_string(),
".nornir/profiles/node/pid1/workload.toml".to_string()
)]
);
}
#[test]
fn check_fails_on_drift_naming_the_call_and_the_test() {
let (d, mut tool) = measured("");
tool.check = (
1,
"heimdall profile check: new call sysinfo (kill_process), made by test version\n\
heimdall profile check: new call openat arg2=0x241 (kill_process), made by test add_without_netns, version\n\
fix: heimdall profile probe tunnr-cni && heimdall profile gen tunnr-cni, then review the diff (never widen a profile by hand)\n"
.into(),
);
let r = run_measure_verb(
&vt("syscalls node --check --program tunnr-cni", d.path()),
d.path(),
&tool,
)
.unwrap();
assert!(!r.ok());
assert_eq!(r.drift.len(), 2);
assert_eq!(r.drift[0].call, "sysinfo");
assert_eq!(r.drift[0].tests, ["version"]);
assert_eq!(r.drift[1].call, "openat arg2=0x241");
assert_eq!(r.drift[1].tests, ["add_without_netns", "version"]);
let text = r.render();
assert!(
text.contains("DRIFT")
&& text.contains("tunnr-cni")
&& text.contains("new call sysinfo")
&& text.contains("made by test version"),
"{text}"
);
}
#[test]
fn check_reports_a_hand_edited_profile() {
let (d, mut tool) = measured("");
tool.check = (1, "heimdall profile check: /r/x86_64-musl.oci.json does not match /r/x86_64-musl.json (hand-edited or stale)\n".into());
let r = run_measure_verb(
&vt("syscalls node --check --program pid1", d.path()),
d.path(),
&tool,
)
.unwrap();
assert!(!r.ok());
assert_eq!(
r.out_of_sync,
[("pid1".to_string(), "/r/x86_64-musl.oci.json".to_string())]
);
}
#[test]
fn check_refuses_when_the_tool_itself_fails() {
let (d, mut tool) = measured("");
tool.check = (
3,
"heimdall profile check: no such file x86_64-musl.json\n".into(),
);
let e = run_measure_verb(
&vt("syscalls node --check --program pid1", d.path()),
d.path(),
&tool,
)
.unwrap_err()
.to_string();
assert!(e.contains("pid1") && e.contains("no such file"), "{e}");
}
#[test]
fn the_check_stderr_parser_reads_heimdalls_lines_exactly() {
let (drift, oos) = parse_check_stderr(
"p",
"musl",
"heimdall profile check: new call clone arg0=0x50f00 (kill_process), made by test a, b\n\
heimdall profile check: new call nr 451 (kill_process), made by test c\n\
something else\n",
);
assert_eq!(drift.len(), 2);
assert_eq!(
(drift[1].call.as_str(), drift[1].tests.as_slice()),
("nr 451", &["c".to_string()][..])
);
assert_eq!(drift[0].call, "clone arg0=0x50f00");
assert_eq!(drift[0].action, "kill_process");
assert_eq!(drift[0].tests, ["a", "b"]);
assert!(oos.is_empty());
}
#[test]
fn a_row_without_the_rust_table_keeps_only_json_and_its_check_does_not_ask_for_it() {
let (d, mut tool) = repo("");
let m = d.path().join(".nornir/profiles/node/measure.toml");
let t =
std::fs::read_to_string(&m)
.unwrap()
.replacen("[tool]", "rust_table = false\n\n[tool]", 1);
std::fs::write(&m, t).unwrap();
let r = run_measure_verb(
&vt("syscalls node --program pid1", d.path()),
d.path(),
&tool,
)
.unwrap();
let arch = std::env::consts::ARCH;
let base = d
.path()
.join(format!(".nornir/profiles/node/pid1/{arch}-musl"));
assert!(base.with_extension("json").is_file() && base.with_extension("oci.json").is_file());
assert!(!base.with_extension("rs").exists());
assert!(!r.render().contains(", .rs"), "{}", r.render());
// heimdall's check names the missing .rs as out of sync: not drift here.
let rs = base.with_extension("rs").display().to_string();
let json = base.with_extension("json").display().to_string();
tool.check = (
1,
format!("heimdall profile check: {rs} does not match {json} (hand-edited or stale)\n"),
);
let r = run_measure_verb(
&vt("syscalls node --check --program pid1", d.path()),
d.path(),
&tool,
)
.unwrap();
assert!(r.ok(), "{}", r.render());
// ...but a hand-edited .oci.json still is.
let oci = base.with_extension("oci.json").display().to_string();
tool.check = (
1,
format!(
"heimdall profile check: {rs} does not match {json} (hand-edited or stale)\nheimdall profile check: {oci} does not match {json} (hand-edited or stale)\n"
),
);
let r = run_measure_verb(
&vt("syscalls node --check --program pid1", d.path()),
d.path(),
&tool,
)
.unwrap();
assert_eq!(r.out_of_sync, [("pid1".to_string(), oci)]);
}
// ── the drift gate (`measure gate`, Heimdall tooling plan step 6) ───────────
/// A second row `edge` beside `node`, same tool, one program.
fn add_row(root: &Path, tool: &FakeTool, row: &str) {
let dir = root.join(PROFILES_DIR).join(row);
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(
dir.join(MANIFEST),
format!(
r#"row = "{row}"
[tool]
version = "{PIN_VERSION}"
sha256 = "{sha}"
[[program]]
name = "pid1"
binary = {{ musl = "{{target}}/x86_64-unknown-linux-musl/release/node" }}
[[program.run]]
name = "not_pid_1"
error_path = true
expect_exit = 78
"#,
sha = tool.sha()
),
)
.unwrap();
}
/// The builder for a gate without `--build`: never called.
fn no_build(_root: &Path, argv: &[String], _target: &Path) -> anyhow::Result<()> {
panic!("no build expected, got cargo {argv:?}")
}
fn gate_verb(args: &str, root: &Path) -> GateVerb {
parse_gate_verb(
&format!("gate {args} --target {}", root.join("target").display())
.split_whitespace()
.map(String::from)
.collect::<Vec<_>>(),
)
.unwrap()
}
#[test]
fn the_gate_verb_parses_rows_target_and_json() {
let v = parse_gate_verb(
&"gate --row a --row b --json out/drift.json --target /t"
.split_whitespace()
.map(String::from)
.collect::<Vec<_>>(),
)
.unwrap();
assert_eq!(v.rows, vec!["a", "b"]);
assert_eq!(v.json, Some(PathBuf::from("out/drift.json")));
assert_eq!(v.target, Some(PathBuf::from("/t")));
let e = parse_gate_verb(&["gate".into(), "--bogus".into()]).unwrap_err();
assert!(format!("{e:#}").contains("unknown argument `--bogus`"), "{e:#}");
// `measure gate` is named in the syscalls verb's refusal of another word.
let e = parse_measure_verb(&["kernel".into()]).unwrap_err();
assert!(format!("{e:#}").contains("measure gate"), "{e:#}");
}
#[test]
fn the_gate_finds_every_row_with_a_manifest_sorted() {
let (d, tool) = measured("");
add_row(d.path(), &tool, "edge");
// a dir without a manifest is not a row
std::fs::create_dir_all(d.path().join(PROFILES_DIR).join("notes")).unwrap();
assert_eq!(rows_with_profiles(d.path()).unwrap(), vec!["edge", "node"]);
}
#[test]
fn a_repo_without_any_profiled_row_fails_the_gate_by_path() {
let d = tempfile::tempdir().unwrap();
let tool = FakeTool::new(d.path());
let r = run_gate(&gate_verb("", d.path()), d.path(), &|_| Ok(&tool), &no_build);
assert!(!r.ok);
assert!(r.refused.as_deref().unwrap().contains(".nornir/profiles/<row>/measure.toml"), "{r:?}");
}
#[test]
fn the_gate_checks_every_row_and_passes_when_none_drift() {
let (d, tool) = measured("");
add_row(d.path(), &tool, "edge");
run_measure_verb(&vt("syscalls edge", d.path()), d.path(), &tool).unwrap();
tool.calls.borrow_mut().clear();
let r = run_gate(&gate_verb("", d.path()), d.path(), &|_| Ok(&tool), &no_build);
assert!(r.ok, "{}", r.render());
assert_eq!(r.rows.iter().map(|g| g.row.as_str()).collect::<Vec<_>>(), vec!["edge", "node"]);
// only checks were run: never a probe that rewrites a profile
assert!(tool.verbs().iter().all(|v| v == "--version" || v.starts_with("profile check")), "{:?}", tool.verbs());
let node = r.rows.iter().find(|g| g.row == "node").unwrap();
assert_eq!(node.checked.len(), 2);
assert_eq!(node.vm_later.iter().filter(|v| v.program == "rusternetes").count(), 1);
assert!(r.render().contains("GATE OK"), "{}", r.render());
}
#[test]
fn the_gate_fails_on_drift_and_names_row_program_call_and_test() {
let (d, mut tool) = measured("");
tool.check = (
1,
"heimdall profile check: new call sysinfo (kill_process), made by test version, add_without_netns\n".into(),
);
let r = run_gate(&gate_verb("", d.path()), d.path(), &|_| Ok(&tool), &no_build);
assert!(!r.ok);
let text = r.render();
assert!(text.contains("DRIFT node/tunnr-cni (musl): new call sysinfo (kill_process), made by test version, add_without_netns"), "{text}");
assert!(text.contains("GATE FAILED"), "{text}");
// the JSON names the same thing, field by field
let j: serde_json::Value = serde_json::from_str(&r.to_json()).unwrap();
assert_eq!(j["schema"], GATE_SCHEMA);
assert_eq!(j["ok"], false);
assert_eq!(j["rows"][0]["drift"][0]["call"], "sysinfo");
assert_eq!(j["rows"][0]["drift"][0]["tests"][1], "add_without_netns");
}
#[test]
fn a_refused_row_fails_the_gate_but_the_other_rows_are_still_checked() {
let (d, tool) = measured("");
add_row(d.path(), &tool, "edge"); // never measured: no workload.toml
let r = run_gate(&gate_verb("", d.path()), d.path(), &|_| Ok(&tool), &no_build);
assert!(!r.ok);
let edge = r.rows.iter().find(|g| g.row == "edge").unwrap();
assert!(edge.refused.as_deref().unwrap().contains("measure syscalls edge"), "{edge:?}");
let node = r.rows.iter().find(|g| g.row == "node").unwrap();
assert!(node.ok && node.refused.is_none(), "{node:?}");
}
#[test]
fn the_gate_takes_only_the_named_rows_and_refuses_an_unknown_one() {
let (d, tool) = measured("");
add_row(d.path(), &tool, "edge");
let r = run_gate(&gate_verb("--row node", d.path()), d.path(), &|_| Ok(&tool), &no_build);
assert_eq!(r.rows.len(), 1);
assert!(r.ok, "{}", r.render());
let r = run_gate(&gate_verb("--row nope", d.path()), d.path(), &|_| Ok(&tool), &no_build);
assert!(!r.ok);
assert!(r.rows[0].refused.as_deref().unwrap().contains("nope"), "{:?}", r.rows[0]);
}
#[test]
fn a_tool_that_cannot_be_located_refuses_the_row_with_the_install_line() {
let (d, _tool) = measured("");
let r = run_gate(&gate_verb("", d.path()), d.path(), &|pin| {
Err::<&FakeTool, _>(anyhow!("not on PATH: {}", install_line(pin)))
}, &no_build);
assert!(!r.ok);
assert!(r.rows[0].refused.as_deref().unwrap().contains("cargo install nordisk-heimdall-cli --version 0.2.0 --locked"), "{:?}", r.rows[0]);
}
#[test]
fn the_manifest_takes_build_lines_and_refuses_one_that_is_not_cargo_build() {
let (d, _tool) = repo("\n[[build]]\ncargo = [\"build\", \"--release\", \"-p\", \"node\"]\n");
let m = MeasureManifest::load(d.path(), "node").unwrap();
assert_eq!(m.build[0].cargo, vec!["build", "--release", "-p", "node"]);
let (d, _tool) = repo("\n[[build]]\ncargo = [\"run\", \"-p\", \"node\"]\n");
let e = MeasureManifest::load(d.path(), "node").unwrap_err();
assert!(format!("{e:#}").contains("[[build]] cargo must start with `build`"), "{e:#}");
}
#[test]
fn gate_build_builds_each_row_first_and_a_failed_build_refuses_only_that_row() {
let (d, tool) = measured("\n[[build]]\ncargo = [\"build\", \"-p\", \"node\"]\n");
add_row(d.path(), &tool, "edge");
let edge_manifest = d.path().join(PROFILES_DIR).join("edge").join(MANIFEST);
std::fs::write(
&edge_manifest,
std::fs::read_to_string(&edge_manifest).unwrap()
+ "\n[[build]]\ncargo = [\"build\", \"-p\", \"broken\"]\n",
)
.unwrap();
let built = RefCell::new(Vec::new());
let builder = |_root: &Path, argv: &[String], target: &Path| -> anyhow::Result<()> {
built.borrow_mut().push((argv.join(" "), target.to_path_buf()));
if argv.iter().any(|a| a == "broken") {
bail!("exited 101");
}
Ok(())
};
let r = run_gate(&gate_verb("--build", d.path()), d.path(), &|_| Ok(&tool), &builder);
let built = built.into_inner();
assert_eq!(built.len(), 2, "{built:?}");
assert_eq!(built[1].0, "build -p node");
assert_eq!(built[1].1, d.path().join("target"));
let edge = r.rows.iter().find(|g| g.row == "edge").unwrap();
assert!(edge.refused.as_deref().unwrap().contains("cargo build -p broken: exited 101"), "{edge:?}");
let node = r.rows.iter().find(|g| g.row == "node").unwrap();
assert!(node.ok, "{node:?}");
// without --build nothing is built (no_build panics if called)
let r = run_gate(&gate_verb("--row node", d.path()), d.path(), &|_| Ok(&tool), &no_build);
assert!(r.ok);
}
// ── the tune verbs' library half (dwarves `tune probe|gen|check|approve`) ──
const SMOKE: &str = r#"
[[driver]]
name = "smoke"
program = "pid1"
about = "drive PID 1 through its status page"
[[driver.run]]
name = "status"
args = ["status"]
expect_exit = 0
[[driver.run]]
name = "bad_flag"
args = ["--nope"]
error_path = true
"#;
#[test]
fn the_verb_parses_gen_and_workload_and_refuses_gen_with_check() {
let v = verb("syscalls node --gen --workload smoke --workload load");
assert!(v.gen_only && !v.check);
assert_eq!(v.workloads, ["smoke", "load"]);
let e = parse_measure_verb(
&"syscalls node --gen --check"
.split_whitespace()
.map(String::from)
.collect::<Vec<_>>(),
)
.unwrap_err()
.to_string();
assert!(e.contains("--gen") && e.contains("--check"), "{e}");
}
#[test]
fn gen_regenerates_from_the_probe_evidence_without_probing() {
let (d, tool) = measured("");
let r = run_measure_verb(&vt("syscalls node --gen", d.path()), d.path(), &tool).unwrap();
assert!(r.ok(), "{}", r.render());
assert_eq!(
tool.verbs(),
["--version", "profile gen tunnr-cni", "profile gen pid1"]
);
assert_eq!(r.programs.len(), 2);
assert!(r.render().contains("measure syscalls node --gen"), "{}", r.render());
}
#[test]
fn gen_without_probe_evidence_is_refused_with_the_probe_line() {
let (d, tool) = repo("");
let e = format!(
"{:#}",
run_measure_verb(&vt("syscalls node --gen", d.path()), d.path(), &tool).unwrap_err()
);
assert!(
e.contains("no probe evidence") && e.contains("tunnr-cni") && e.contains("probe"),
"{e}"
);
assert_eq!(tool.verbs(), ["--version"]);
}
#[test]
fn a_workload_driver_adds_its_runs_to_its_program_and_is_remembered() {
let (d, tool) = repo(SMOKE);
let row = d.path().join(".nornir/profiles/node");
// Without --workload the driver's runs are not probed.
run_measure_verb(&vt("syscalls node", d.path()), d.path(), &tool).unwrap();
let w = std::fs::read_to_string(row.join("pid1/workload.toml")).unwrap();
assert!(!w.contains("smoke/status"), "{w}");
// With it, they are: named <driver>/<run>, on the driver's program only.
let r = run_measure_verb(
&vt("syscalls node --workload smoke", d.path()),
d.path(),
&tool,
)
.unwrap();
let w: WorkloadToml =
toml::from_str(&std::fs::read_to_string(row.join("pid1/workload.toml")).unwrap()).unwrap();
let names: Vec<_> = w.run.iter().map(|r| r.name.as_str()).collect();
assert_eq!(names, ["not_pid_1", "smoke/status", "smoke/bad_flag"]);
assert_eq!(w.run[1].args, ["status"]);
assert!(w.run[2].error_path);
let cni = std::fs::read_to_string(row.join("tunnr-cni/workload.toml")).unwrap();
assert!(!cni.contains("smoke"), "{cni}");
assert_eq!(r.workloads, ["smoke"]);
let pid1 = r.programs.iter().find(|p| p.program == "pid1").unwrap();
assert_eq!(pid1.runs, 3);
// The evidence remembers the drivers, so check and gen reuse them.
let ev: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(row.join("measure.json")).unwrap()).unwrap();
assert_eq!(ev["workloads"][0], "smoke");
let c = run_measure_verb(&vt("syscalls node --check", d.path()), d.path(), &tool).unwrap();
assert!(c.ok(), "{}", c.render());
assert_eq!(c.workloads, ["smoke"]);
tool.calls.borrow_mut().clear();
let g = run_measure_verb(&vt("syscalls node --gen", d.path()), d.path(), &tool).unwrap();
assert_eq!(g.workloads, ["smoke"]);
}
#[test]
fn a_row_without_drivers_writes_the_evidence_without_a_workloads_key() {
let (d, _tool) = measured("");
let ev = std::fs::read_to_string(d.path().join(".nornir/profiles/node/measure.json")).unwrap();
assert!(!ev.contains("workloads"), "{ev}");
}
#[test]
fn an_unknown_driver_is_refused_listing_the_rows_drivers() {
let (d, tool) = repo(SMOKE);
let e = run_measure_verb(
&vt("syscalls node --workload load", d.path()),
d.path(),
&tool,
)
.unwrap_err()
.to_string();
assert!(e.contains("no workload driver `load`") && e.contains("smoke"), "{e}");
let (d, tool) = repo("");
let e = run_measure_verb(
&vt("syscalls node --workload load", d.path()),
d.path(),
&tool,
)
.unwrap_err()
.to_string();
assert!(e.contains("no workload driver `load`") && e.contains("[[driver]]"), "{e}");
}
#[test]
fn a_driver_must_name_a_probed_program_and_give_runs() {
for (extra, want) in [
(
"[[driver]]\nname = \"x\"\nprogram = \"nope\"\n[[driver.run]]\nname = \"a\"\n",
"program `nope`",
),
(
"[[driver]]\nname = \"x\"\nprogram = \"rusternetes\"\n[[driver.run]]\nname = \"a\"\n",
"needs_vm",
),
("[[driver]]\nname = \"x\"\nprogram = \"pid1\"\n", "no [[driver.run]]"),
(
"[[driver]]\nname = \"x\"\nprogram = \"pid1\"\n[[driver.run]]\nname = \"a\"\n[[driver]]\nname = \"x\"\nprogram = \"pid1\"\n[[driver.run]]\nname = \"a\"\n",
"twice",
),
] {
let (d, _tool) = repo(extra);
let e = format!("{:#}", MeasureManifest::load(d.path(), "node").unwrap_err());
assert!(e.contains(want), "{want:?} not in {e}");
}
}
#[test]
fn an_unbuilt_binary_names_the_rows_build_lines() {
let (d, tool) = repo("\n[[build]]\ncargo = [\"build\", \"--release\", \"-p\", \"node\"]\n");
std::fs::remove_file(d.path().join("target/x86_64-unknown-linux-musl/release/node")).unwrap();
let e = run_measure_verb(&vt("syscalls node --program pid1", d.path()), d.path(), &tool)
.unwrap_err()
.to_string();
assert!(e.contains("is not built") && e.contains("cargo build --release -p node"), "{e}");
}
// ── approval ────────────────────────────────────────────────────────────────
#[test]
fn a_measured_row_is_not_approved_until_approve_records_the_review() {
let (d, _tool) = measured("");
let arch = std::env::consts::ARCH;
let s = approval_status(d.path(), "node").unwrap();
assert!(!s.ok() && !s.exists);
// .json and .oci.json of each probed program; never the .rs or .probe.json.
let files: Vec<_> = s.files.iter().map(|f| f.file.clone()).collect();
assert_eq!(
files,
[
format!(".nornir/profiles/node/pid1/{arch}-musl.json"),
format!(".nornir/profiles/node/pid1/{arch}-musl.oci.json"),
format!(".nornir/profiles/node/tunnr-cni/{arch}-musl.json"),
format!(".nornir/profiles/node/tunnr-cni/{arch}-musl.oci.json"),
]
);
assert!(s.files.iter().all(|f| f.state == ApprovalState::NotApproved));
assert!(
s.problems()[0].starts_with(&format!(
"not approved: pid1 .nornir/profiles/node/pid1/{arch}-musl.json was never approved"
)),
"{:?}",
s.problems()
);
let s = approve(d.path(), "node", "Rickard", "2026-11-01", "first review").unwrap();
assert!(s.ok() && s.exists, "{:?}", s.problems());
let t = std::fs::read_to_string(d.path().join(".nornir/profiles/node/approved.toml")).unwrap();
for w in ["reviewer = \"Rickard\"", "date = \"2026-11-01\"", "note = \"first review\"", "sha256 = \"", "program = \"tunnr-cni\"", "libc = \"musl\""] {
assert!(t.contains(w), "{w:?} missing from:\n{t}");
}
let f = &s.files[0];
assert_eq!(f.state, ApprovalState::Approved);
assert_eq!(f.reviewer.as_deref(), Some("Rickard"));
assert_eq!(f.sha256, f.approved_sha256);
}
#[test]
fn a_profile_changed_after_approval_is_named_with_the_approval_date() {
let (d, _tool) = measured("");
let arch = std::env::consts::ARCH;
approve(d.path(), "node", "Rickard", "2026-11-01", "").unwrap();
let p = d.path().join(format!(".nornir/profiles/node/pid1/{arch}-musl.json"));
std::fs::write(&p, "{\"rules\": []}\n").unwrap();
let s = approval_status(d.path(), "node").unwrap();
assert!(!s.ok());
assert_eq!(
s.problems(),
[format!(
"not approved: pid1 .nornir/profiles/node/pid1/{arch}-musl.json changed since 2026-11-01"
)]
);
// A re-approval keeps the unchanged entries' reviewer and date.
let s = approve(d.path(), "node", "Partner", "2026-11-02", "").unwrap();
assert!(s.ok());
let by: Vec<_> = s
.files
.iter()
.map(|f| (f.file.rsplit('/').nth(1).unwrap().to_string(), f.date.clone().unwrap()))
.collect();
assert!(by.contains(&("pid1".into(), "2026-11-02".into())), "{by:?}");
assert!(by.contains(&("tunnr-cni".into(), "2026-11-01".into())), "{by:?}");
}
#[test]
fn an_approved_profile_that_vanished_is_named() {
let (d, _tool) = measured("");
let arch = std::env::consts::ARCH;
approve(d.path(), "node", "Rickard", "2026-11-01", "").unwrap();
std::fs::remove_file(d.path().join(format!(".nornir/profiles/node/pid1/{arch}-musl.oci.json")))
.unwrap();
let s = approval_status(d.path(), "node").unwrap();
assert_eq!(
s.problems(),
[format!(
"not approved: pid1 .nornir/profiles/node/pid1/{arch}-musl.oci.json was approved on 2026-11-01 and is gone"
)]
);
}
#[test]
fn approve_refuses_an_email_as_reviewer_and_a_row_with_nothing_to_approve() {
let (d, _tool) = measured("");
let e = approve(d.path(), "node", "someone@example.org", "2026-11-01", "")
.unwrap_err()
.to_string();
assert!(e.contains("never an e-mail"), "{e}");
assert!(approve(d.path(), "node", " ", "2026-11-01", "").is_err());
assert!(!d.path().join(".nornir/profiles/node/approved.toml").exists());
let (d, _tool) = repo("");
let e = approve(d.path(), "node", "Rickard", "2026-11-01", "")
.unwrap_err()
.to_string();
assert!(e.contains("nothing to approve") && e.contains("measure"), "{e}");
}
#[test]
fn a_served_driver_carries_its_fields_as_data_and_adds_no_corpus_runs() {
let served = r#"
[[driver]]
name = "pg"
program = "pid1"
command = ["scripts/drive-pg.sh", "{channel}"]
serve = ["{target}/x86_64-unknown-linux-musl/release/node", "serve"]
ready = { socket = "{channel}/.s.PGSQL.5432" }
timeout_secs = 30
stop_signal = "INT"
env = { PGDATA = "{tmp}/pg" }
image = { layout = "oci/pg", digest = "sha256:00" }
channel = true
"#;
let (d, tool) = repo(served);
let m = MeasureManifest::load(d.path(), "node").unwrap();
let pg = &m.drivers[0];
assert!(pg.served());
assert_eq!(pg.ready, Some(Ready::Socket("{channel}/.s.PGSQL.5432".into())));
assert_eq!(pg.image.as_ref().unwrap().digest, "sha256:00");
assert!(pg.channel && pg.timeout_secs == Some(30));
let r = run_measure_verb(&vt("syscalls node --workload pg", d.path()), d.path(), &tool).unwrap();
assert_eq!(r.workloads, ["pg"]);
let w = std::fs::read_to_string(d.path().join(".nornir/profiles/node/pid1/workload.toml")).unwrap();
assert!(!w.contains("pg"), "{w}");
}
#[test]
fn the_gate_holds_a_reviewed_row_to_its_approval_and_names_an_unreviewed_one() {
let (d, tool) = measured("");
let arch = std::env::consts::ARCH;
// never reviewed: the gate passes and says so
let r = run_gate(&gate_verb("", d.path()), d.path(), &|_| Ok(&tool), &no_build);
assert!(r.ok, "{}", r.render());
assert!(!r.rows[0].reviewed);
assert!(r.render().contains("node: not reviewed yet (dwarves tune approve node)"), "{}", r.render());
// reviewed and unchanged: ok
approve(d.path(), "node", "Rickard", "2026-11-01", "").unwrap();
let r = run_gate(&gate_verb("", d.path()), d.path(), &|_| Ok(&tool), &no_build);
assert!(r.ok && r.rows[0].reviewed, "{}", r.render());
// a reviewed file changed after approval: the gate fails, naming file and date
let f = d.path().join(format!(".nornir/profiles/node/pid1/{arch}-musl.oci.json"));
std::fs::write(&f, "{\"changed\": true}\n").unwrap();
let r = run_gate(&gate_verb("", d.path()), d.path(), &|_| Ok(&tool), &no_build);
assert!(!r.ok);
let text = r.render();
assert!(
text.contains(&format!("node: not approved: pid1 .nornir/profiles/node/pid1/{arch}-musl.oci.json changed since 2026-11-01")),
"{text}"
);
let j: serde_json::Value = serde_json::from_str(&r.to_json()).unwrap();
assert_eq!(j["rows"][0]["not_approved"].as_array().unwrap().len(), 1);
}