dwarves-gui/tests/robot_version_stamp.rs
//! **VERSION STAMP (LAW): `dwarves-ui --version` prints the build stamp and EXITS before boot.**
//!
//! An operator must always be able to tell WHICH build is running. `dwarves-ui`'s `main`
//! short-circuits `--version`/`-V` (before any robot mount or window bring-up), prints
//! `dwarves v<ver> (<hash>)` and returns. This test runs the REAL compiled binary
//! (`CARGO_BIN_EXE_dwarves-ui`) and asserts:
//!
//! * it exits 0 with the exact `dwarves_gui::version_line()` stamp on stdout, and
//! * it EXITS — a regression that fell through to `NativeDemo::run` would try to open a
//! window (and, headless, hang or error). A guarded wait proves the process finished on
//! its own; the stamp being the WHOLE of stdout proves nothing booted after it.
//!
//! Emits a `nornir-testmatrix` cell under `--features testmatrix`.
use std::process::Command;
use std::time::{Duration, Instant};
#[allow(unused_variables)]
fn matrix(check: &str, ok: bool, detail: &str) {
#[cfg(feature = "testmatrix")]
nornir_testmatrix::functional_status("dwarves-gui", check, ok, detail);
}
/// Run `dwarves-ui <flag>` with a hard timeout. Returns (exit-ok, stdout). A binary that
/// booted a window instead of exiting would blow the timeout — we kill it and fail, so
/// "exits before boot" is a REAL assertion, not an assumption.
fn run_version(flag: &str) -> (bool, String) {
let mut child = Command::new(env!("CARGO_BIN_EXE_dwarves-ui"))
.arg(flag)
// A headless CI box has no display; make sure a regression that DID try to boot
// can't accidentally find one and hang differently.
.env_remove("DISPLAY")
.env_remove("WAYLAND_DISPLAY")
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::null())
.spawn()
.expect("spawn dwarves-ui");
let deadline = Instant::now() + Duration::from_secs(30);
loop {
match child.try_wait().expect("try_wait") {
Some(status) => {
let mut out = String::new();
if let Some(mut so) = child.stdout.take() {
use std::io::Read;
let _ = so.read_to_string(&mut out);
}
return (status.success(), out);
}
None => {
if Instant::now() >= deadline {
let _ = child.kill();
let _ = child.wait();
panic!(
"`dwarves-ui {flag}` did not exit — it must print the stamp and \
return BEFORE booting a window"
);
}
std::thread::sleep(Duration::from_millis(50));
}
}
}
}
#[test]
fn dwarves_ui_version_prints_the_stamp_and_exits_before_boot() {
// The stamp names THE ARTIFACT that prints it. `dwarves_gui::version_line()` is the
// `dwarves-ui` spelling of `version_line_for`, and the binary passes its own
// `env!("CARGO_BIN_NAME")`.
let expected = dwarves_gui::version_line_for("dwarves-ui");
assert_eq!(
expected,
dwarves_gui::version_line(),
"the compatibility spelling must be the dwarves-ui variant, or the two answers \
to `--version` drift"
);
// ── T7: the stamp must name the VARIANT, not the family ──────────────────────
// MEASURED on t14s 2026-08-27: `/usr/local/bin/dwarves-ui` and
// `/usr/local/bin/dwarves-ui-gpu` were BYTE-IDENTICAL — both sha256
// `6eb9ec98717b270e…`, both equal to that box's `target/release/dwarves-ui-gpu`,
// which had no `dwarves-ui` beside it. ONE artifact under TWO names.
//
// It was invisible because this stamp used to read `dwarves v0.1.4 (<hash>)` for
// EVERY `[[bin]]` off this crate's one `src/main.rs`. An operator holding both files
// could ask each what it was and be told the same thing twice.
assert!(
expected.starts_with("dwarves-ui v"),
"the stamp must lead with the ARTIFACT name, not the family: {expected:?}"
);
// The discriminator is a real function of the name, not decoration: two declared
// variants must produce two stamps.
for other in ["dwarves-ui-gpu", "dwarves-solo"] {
let o = dwarves_gui::version_line_for(other);
assert_ne!(
o, expected,
"`{other}` and `dwarves-ui` must not print the same stamp — that identity is \
exactly the t14s duplicate"
);
assert!(o.starts_with(&format!("{other} v")), "{o:?}");
}
// And it is NOT derivable from a cargo feature. Feature resolution is per-PACKAGE:
// building `dwarves-ui` and `dwarves-ui-gpu` in ONE cargo invocation turns `gpu` on
// for the package, so both binaries see it. Proven on korp 2026-08-27, where
// `cfg!(feature = "map3d-gpu")` answered "gpu" for BOTH artifacts of the pair. This
// asserts the function depends on its ARGUMENT and on nothing ambient: whatever
// features this test binary was compiled with, the answer for a given name is fixed.
assert_eq!(
dwarves_gui::version_line_for("dwarves-ui-gpu"),
format!("dwarves-ui-gpu {}", dwarves_gui::build_stamp()),
"the variant half comes from the caller's string; the build half is the crate's. \
A `cfg!(feature = ...)` anywhere in this path would make the answer depend on \
how the OTHER binary was built."
);
assert!(
expected.contains(env!("CARGO_PKG_VERSION")),
"version_line carries the crate version: {expected:?}"
);
let hash = expected
.rsplit_once('(')
.and_then(|(_, r)| r.split_once(')'))
.map(|(h, _)| h.trim())
.unwrap_or_default();
assert!(
!hash.is_empty(),
"version_line carries a git hash in parens: {expected:?}"
);
let mut ok = true;
for flag in ["--version", "-V"] {
let (exit_ok, stdout) = run_version(flag);
assert!(exit_ok, "`dwarves-ui {flag}` exits 0");
// The stamp is the WHOLE of stdout — nothing booted after it.
assert_eq!(
stdout.trim(),
expected,
"`dwarves-ui {flag}` prints exactly the build stamp and nothing else"
);
ok &= exit_ok && stdout.trim() == expected;
}
matrix(
"dwarves_ui_version_stamp_exits_before_boot",
ok,
"`dwarves-ui --version` and `-V` each print exactly `dwarves v<ver> (<hash>)` and exit \
0 within the timeout — the stamp is the whole of stdout and the process returns on \
its own, proving it short-circuits BEFORE any window/robot boot",
);
}