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

nordisk/holger

xtask/tests/build_verb.rs

//! # `cargo run -p xtask build` — holger's first-party build-tier command
//!
//! Drives the real xtask binary's `build` verb end-to-end and asserts the
//! observable output. `build --suite MVP --list` is holger's ONE product list
//! (HOL-PRODUCTS): the four products (holger, holger-downstream, holger-cli,
//! holger-ui) and the estate's own builds, each with its materialized cargo
//! command; `--features full` goes to the four products only. Each listing emits
//! one `functional_status` row (the `xtask/build`::`build_list` cell) into the
//! matrix sink, exactly like every other xtask verb.
//!
//! LAW: inject a real subprocess run, assert the real stdout + the real emitted
//! row — never merely "it compiled".

use std::path::{Path, PathBuf};
use std::process::Command;

/// Workspace root = the dir above `xtask/`.
fn workspace_root() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .expect("xtask has a parent (workspace root)")
        .to_path_buf()
}

/// A private path for this test's `NORNIR_TESTMATRIX_OUT` sink.
///
/// Deliberately NOT `<repo>/target/` — the nordisk build policy pins
/// `target-dir = /home/rickard/cargo-target` in `~/.cargo/config.toml`, so
/// `<repo>/target/` does not exist and the sink writer cannot create a file
/// under it. Both tests here used to hardcode `<repo>/target/…` and therefore
/// failed at the sink-read step with `No such file or directory` on any machine
/// honouring that policy — the stdout assertions passed, so the failure was
/// purely about where the row was asked to land. `temp_dir()` always exists.
fn sink_path(tag: &str) -> PathBuf {
    std::env::temp_dir().join(format!("xtask-{tag}-{}.jsonl", std::process::id()))
}

/// Run the xtask binary's `build` verb with `args` (and the testmatrix sink at `sink`).
fn build_verb(args: &[&str], sink: &Path) -> (bool, String, String) {
    let out = Command::new(env!("CARGO"))
        .args([
            "run",
            "--quiet",
            "--manifest-path",
            "xtask/Cargo.toml",
            "--features",
            "testmatrix",
            "--",
            "build",
        ])
        .args(args)
        .current_dir(workspace_root())
        .env("NORNIR_TESTMATRIX_OUT", sink)
        .env("NORNIR_TESTMATRIX_REPO", "holger")
        .output()
        .unwrap_or_else(|e| panic!("spawn cargo run -p xtask build {args:?}: {e}"));
    (
        out.status.success(),
        String::from_utf8_lossy(&out.stdout).into_owned(),
        String::from_utf8_lossy(&out.stderr).into_owned(),
    )
}

/// The sink got the `xtask/build::build_list` row, scoped to repo=holger.
fn assert_list_row(sink: &Path) {
    let text = std::fs::read_to_string(sink)
        .unwrap_or_else(|e| panic!("read functional sink {}: {e}", sink.display()));
    let _ = std::fs::remove_file(sink);
    assert!(
        text.contains("xtask/build") && text.contains("build_list"),
        "the build verb emitted its xtask/build::build_list functional row:\n{text}"
    );
    assert!(
        text.contains("\"repo\":\"holger\""),
        "row scoped to repo=holger:\n{text}"
    );
}

/// The four products, in the order the docs name them (`holger_recipe::variants::PRODUCTS`).
const PRODUCTS: [&str; 4] = ["holger", "holger-downstream", "holger-ui", "holger-cli"];

/// Every row holger declares: the products, then the estate's own builds.
const ALL_VARIANTS: [&str; 24] = [
    "holger",
    "holger-downstream",
    "holger-cli",
    "holger-ui", // the declaration's order
    "mvp-server-musl",
    "mvp-server-metered",
    "mvp-server-musl-metered",
    "mvp-server-musl-metered-narrate",
    "holger-mvp-appliance",
    "holger-installer",
    "holger-installer-metered",
    "holger-appliance-iso",
    "plugin-allocation",
    "plugin-skidbladnir",
    "plugin-rust-toolchain",
    "estate-front-musl-metered",
    "estate-front-musl-metered-narrate",
    "holger-front-installer-metered",
    "holger-keep",
    "holger-front-keep",
    "downstream-musl",
    "holger-downstream-oneway-installer",
    "downstream-fetch-musl",
    "holger-downstream-installer",
];

/// The first token of every line after the header, kept when it is a declared row name.
fn listed_rows(stdout: &str, header: &str) -> Vec<String> {
    stdout
        .lines()
        .skip_while(|l| !l.contains(header))
        .skip(1)
        .filter_map(|l| l.split_whitespace().next())
        .map(|t| t.trim_end_matches(':').to_string())
        .filter(|t| ALL_VARIANTS.contains(&t.as_str()))
        .collect()
}

/// `build --suite MVP --list` resolves the ONE product list from the DECLARATION: every
/// row, the four products first, each bin named, and no retired row (the ORIGINAL and OPEN
/// suites, the tier bins, the folded tools).
#[test]
fn build_list_resolves_the_one_product_list_and_emits_a_row() {
    let sink = sink_path("build-mvp-list");
    let _ = std::fs::remove_file(&sink);
    let (ok, stdout, stderr) = build_verb(&["--suite", "MVP", "--list"], &sink);
    assert!(
        ok,
        "build --suite MVP --list must exit 0.\n--- stdout ---\n{stdout}\n--- stderr ---\n{stderr}"
    );
    assert!(
        stdout.contains("delivery 'MVP' — 24 variant(s):"),
        "MVP resolves the 24 rows:\n{stdout}"
    );
    let listed = listed_rows(&stdout, "variant(s):");
    let mut dedup = listed.clone();
    dedup.dedup();
    assert_eq!(
        dedup, ALL_VARIANTS,
        "every row once, the products first:\n{stdout}"
    );
    for bin in [
        "holger-downstream",
        "holger-cli",
        "holger-ui",
        "holger-mvp",
        "holger-mvp-metered",
    ] {
        assert!(
            stdout.contains(bin),
            "build --list names the '{bin}' bin:\n{stdout}"
        );
    }
    for gone in [
        "holger-embedded",
        "holger-mikro",
        "holger-full",
        "holger-bundle",
        "holger-verify",
        "holger-agent",
    ] {
        assert!(
            !stdout.contains(gone),
            "the retired `{gone}` is listed:\n{stdout}"
        );
    }
    assert_list_row(&sink);
}

/// The retired deliveries are refused by name.
#[test]
fn the_retired_suites_are_no_delivery() {
    let sink = sink_path("build-original");
    for gone in ["ORIGINAL", "OPEN"] {
        let (ok, stdout, stderr) = build_verb(&["--suite", gone, "--list"], &sink);
        assert!(!ok, "`--suite {gone}` must be refused:\n{stdout}");
        assert!(
            stderr.contains("no such delivery") && stderr.contains("MVP"),
            "the refusal names the one delivery:\n{stderr}"
        );
    }
    let _ = std::fs::remove_file(&sink);
}

/// **`--suite MVP --features full`** is the four products with every extra: the features go
/// to the product rows only (the estate's builds, the media and the wasm guests are
/// declared builds and are never re-featured), and `--list` shows exactly what it builds.
#[test]
fn the_products_with_full_are_the_four_products_each_with_full() {
    let sink = sink_path("build-full-list");
    let _ = std::fs::remove_file(&sink);
    let (ok, stdout, stderr) =
        build_verb(&["--suite", "MVP", "--features", "full", "--list"], &sink);
    assert!(ok, "build --suite MVP --features full --list must exit 0.\n--- stdout ---\n{stdout}\n--- stderr ---\n{stderr}");
    assert!(
        stdout.contains("the products with `--features full` — 4 variant(s):"),
        "{stdout}"
    );
    let lines: Vec<&str> = stdout
        .lines()
        .filter(|l| l.starts_with("  ") && l.contains(": cargo "))
        .collect();
    let names: Vec<&str> = lines
        .iter()
        .map(|l| l.trim_start().split(':').next().unwrap())
        .collect();
    assert_eq!(
        names, PRODUCTS,
        "the four products and nothing else:\n{stdout}"
    );
    for l in &lines {
        assert!(
            l.contains("--features full"),
            "each product carries `full`: {l}"
        );
        assert!(
            !l.contains("--no-default-features"),
            "on top of its defaults: {l}"
        );
    }
    assert_list_row(&sink);
}

/// HEAVY: `build --variant holger-downstream` shells the product's build in the pinned
/// container and prints the assembled attestation. Ignored by default (a release build).
#[test]
#[ignore = "heavy: a release build of holger-downstream in the toolchain container"]
fn build_variant_presses_a_product_end_to_end() {
    let sink = sink_path("build-downstream");
    let (ok, stdout, stderr) =
        build_verb(&["--suite", "MVP", "--variant", "holger-downstream"], &sink);
    let _ = std::fs::remove_file(&sink);
    assert!(ok, "build --variant holger-downstream must exit 0.\n--- stdout ---\n{stdout}\n--- stderr ---\n{stderr}");
    assert!(
        stdout.contains("building variant 'holger-downstream'"),
        "{stdout}"
    );
    assert!(
        stdout.contains("digest   :"),
        "prints the attestation digest:\n{stdout}"
    );
    assert!(
        stdout.contains("holger-downstream ("),
        "hashes the real holger-downstream binary:\n{stdout}"
    );
}

/// HEAVY: `build --suite MVP --features full` builds the four products with everything.
#[test]
#[ignore = "heavy: four release builds with every extra (holger-ui needs ../holger-ui beside)"]
fn the_products_build_with_full() {
    let sink = sink_path("build-full");
    let (ok, stdout, stderr) = build_verb(&["--suite", "MVP", "--features", "full"], &sink);
    let _ = std::fs::remove_file(&sink);
    assert!(ok, "build --suite MVP --features full must exit 0.\n--- stdout ---\n{stdout}\n--- stderr ---\n{stderr}");
    for p in PRODUCTS {
        assert!(
            stdout.contains(&format!("building variant '{p}'")),
            "{p}:\n{stdout}"
        );
    }
    assert!(
        stdout.contains("the four products with `--features full` assembled"),
        "{stdout}"
    );
}

/// **An unknown verb runs nothing.** Until 2026-10-20 any verb the dispatch did
/// not match (a typo, a retired verb such as `build-mvp`) fell through to the
/// whole local suite: wasm build, `cargo test --workspace` and a 500 000-crate
/// bench. Now it prints the verb list and exits 2, and so does no verb at all.
#[test]
fn an_unknown_verb_prints_the_verbs_and_exits_2() {
    let root = workspace_root();
    for argv in [&["build-mvp"][..], &["tset"][..], &[][..]] {
        let t0 = std::time::Instant::now();
        let out = Command::new(env!("CARGO"))
            .args([
                "run",
                "--quiet",
                "--manifest-path",
                "xtask/Cargo.toml",
                "--",
            ])
            .args(argv)
            .current_dir(&root)
            .output()
            .expect("spawn cargo run -p xtask");
        let stdout = String::from_utf8_lossy(&out.stdout);
        let stderr = String::from_utf8_lossy(&out.stderr);
        assert_eq!(
            out.status.code(),
            Some(2),
            "{argv:?} must exit 2:\n{stdout}\n{stderr}"
        );
        let text = format!("{stdout}{stderr}");
        for verb in ["test", "build", "gate", "rows-check", "bench", "gen-proto"] {
            assert!(
                text.contains(&format!("  {verb} ")),
                "{argv:?}: the verb list names `{verb}`:\n{text}"
            );
        }
        assert!(
            !text.contains("=== workspace tests") && !text.contains("=== wasm"),
            "{argv:?} ran a suite:\n{text}"
        );
        assert!(
            t0.elapsed() < std::time::Duration::from_secs(120),
            "{argv:?} took {:?}: it ran something",
            t0.elapsed()
        );
    }
}