Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/dwarves

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",
    );
}