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

nordisk/holger

holger-recipe/tests/dev_narrate_symbols.rs

//! **A RELEASE Holger contains no narrate sender at all — read off the symbol table, and held
//! by the declaration** (owner 2026-10-19: Holger's test media are DEV "EXACTLY like gunnar";
//! gunnar-recipe `tests/dev_narrate_symbols.rs` is the pattern).
//!
//! 1. **The artifact.** holger's server built with a release row's switches has ZERO
//!    `nordisk_narrate` symbols; built with `dev-narrate` it has the sender — or the clean table
//!    proves nothing. Same reader as the composer's `OptionAudit`.
//! 2. **The declaration.** Only the two `…-narrate` rows name the feature; no crate's `default`
//!    list does; every estate medium's `dev-narrate` option defaults OFF and forbids
//!    `nordisk_narrate`; ON resolves to the `…-narrate` row and OFF to the payload row.

use std::path::PathBuf;
use std::process::Command;

use holger_recipe::packaging::{DEV_NARRATE_FORBIDDEN, DEV_NARRATE_OPTION};
use holger_recipe::variants::{DEV_NARRATE, NARRATE_SUFFIX};
use nornir_build_thing::linkage::{count_families, demangled_symbols};

fn workspace_root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .expect("one below the root")
        .to_path_buf()
}

/// Build `holger-mvp` with exactly `features`; its path from cargo's own `compiler-artifact`
/// message (never a guessed target dir).
fn build_server(features: &str) -> PathBuf {
    let out = Command::new(env!("CARGO"))
        .current_dir(workspace_root())
        .env_remove("CARGO_MANIFEST_DIR")
        .args([
            "build",
            "-q",
            "--message-format=json-render-diagnostics",
            "-p",
            "holger",
            "--bin",
            "holger-mvp",
            "--no-default-features",
            "--features",
            features,
        ])
        .output()
        .expect("cargo runs");
    assert!(
        out.status.success(),
        "cargo build --features {features} failed ({}):\n{}",
        out.status,
        String::from_utf8_lossy(&out.stderr)
            .lines()
            .rev()
            .take(40)
            .collect::<Vec<_>>()
            .into_iter()
            .rev()
            .collect::<Vec<_>>()
            .join("\n")
    );
    let stdout = String::from_utf8_lossy(&out.stdout);
    let bin = stdout
        .lines()
        .filter(|l| {
            l.contains(r#""compiler-artifact""#)
                && l.contains(r#""executable":""#)
                && l.contains("holger-mvp")
        })
        .filter_map(|l| {
            const KEY: &str = r#""executable":""#;
            let i = l.find(KEY)? + KEY.len();
            let rest = &l[i..];
            Some(PathBuf::from(&rest[..rest.find('"')?]))
        })
        .next_back()
        .unwrap_or_else(|| {
            panic!("cargo reported no executable for holger-mvp --features {features}")
        });
    assert!(bin.is_file(), "no binary at {}", bin.display());
    bin
}

fn narrate_symbols(features: &str) -> Vec<(String, usize)> {
    let bin = build_server(features);
    let symbols = demangled_symbols(&bin).unwrap_or_else(|| {
        panic!(
            "{} has no readable symbol table: UNMEASURED is not a pass",
            bin.display()
        )
    });
    assert!(
        symbols.iter().any(|s| s.contains("holger_server_lib::")),
        "{} names no holger code: a clean read would be vacuous",
        bin.display()
    );
    let c = count_families(&symbols, &[DEV_NARRATE_FORBIDDEN]);
    println!(
        "symbol audit  --features {features}: {} symbols; found {:?}; glue-only {:?}",
        symbols.len(),
        c.found,
        c.glue_only
    );
    let mut all = c.found;
    all.extend(c.glue_only);
    all
}

#[test]
fn the_release_server_has_zero_narrate_symbols_and_the_dev_one_has_the_sender() {
    // A box's switches (the appliance, the pairing loop the sender rides beside), host target.
    let release = narrate_symbols("mvp-server,appliance,paired");
    assert!(release.is_empty(), "a RELEASE holger CONTAINS narrate code: {release:?}. Off means never compiled in, not declining to send");
    let dev = narrate_symbols("mvp-server,appliance,paired,dev-narrate");
    assert!(dev.iter().any(|(f, _)| f == DEV_NARRATE_FORBIDDEN), "the DEV holger has no `{DEV_NARRATE_FORBIDDEN}` symbols, so the release table's emptiness proves nothing: {dev:?}");
}

#[test]
fn only_the_narrate_rows_name_the_feature_and_every_estate_medium_defaults_it_off() {
    let set = holger_recipe::declared();
    let mut rows: Vec<String> = Vec::new();
    for v in &set.variants {
        if v.features.iter().any(|f| f == DEV_NARRATE) {
            assert!(
                v.name.ends_with(NARRATE_SUFFIX) && !v.is_default,
                "`{}` names `{DEV_NARRATE}` and is not a `…-narrate` dev row",
                v.name
            );
            rows.push(v.name.clone());
        } else {
            assert!(
                !v.name.ends_with(NARRATE_SUFFIX),
                "`{}` is called a narrate row and does not build the feature",
                v.name
            );
        }
    }
    rows.sort();
    assert_eq!(
        rows,
        [
            "estate-front-musl-metered-narrate",
            "mvp-server-musl-metered-narrate"
        ]
    );
    let mut media: Vec<String> = Vec::new();
    for i in &set.installers {
        for o in i.options.iter().filter(|o| o.name == DEV_NARRATE_OPTION) {
            assert!(!o.default, "`{}` turns DEV-NARRATE on by default", i.name);
            assert_eq!(o.feature, DEV_NARRATE);
            assert!(
                o.forbid_symbols.iter().any(|s| s == DEV_NARRATE_FORBIDDEN),
                "`{}`'s option does not forbid `{DEV_NARRATE_FORBIDDEN}` when off",
                i.name
            );
            media.push(i.name.clone());
        }
    }
    media.sort();
    assert_eq!(
        media,
        [
            "holger-front-installer-metered",
            "holger-front-keep",
            "holger-installer-metered",
            "holger-keep"
        ],
        "the estate's four media carry the option"
    );
    for krate in ["server/cli/Cargo.toml", "holger-estate-front/Cargo.toml"] {
        let text = std::fs::read_to_string(workspace_root().join(krate)).unwrap();
        let default = text
            .lines()
            .find(|l| l.trim_start().starts_with("default ="))
            .unwrap_or("");
        assert!(
            !default.contains("dev-narrate"),
            "{krate}: `{default}` puts DEV-NARRATE in every default build"
        );
    }
}

/// ON and OFF both resolve to a DECLARED build point: ON exactly the payload's `…-narrate`
/// row, OFF exactly the payload row, and the payload keeps `dev-narrate` a switch.
#[test]
fn the_dev_narrate_option_on_and_off_resolves_to_a_declared_row() {
    use std::collections::{BTreeMap, BTreeSet};
    let set = holger_recipe::declared();
    let mut seen = 0;
    for i in set
        .installers
        .iter()
        .filter(|i| i.options.iter().any(|o| o.name == DEV_NARRATE_OPTION))
    {
        let app = set
            .variants
            .iter()
            .find(|v| v.name == i.variant)
            .unwrap_or_else(|| panic!("{}: no payload row {}", i.name, i.variant));
        assert!(app.no_default_features, "{}: `{}` must build no_default_features, or the crate's default decides behind the option", i.name, app.name);
        assert!(
            app.open.contains(DEV_NARRATE) && !app.features.iter().any(|f| f == DEV_NARRATE),
            "{}: `{}` must keep dev-narrate a switch",
            i.name,
            app.name
        );
        let dev = set
            .variants
            .iter()
            .find(|v| v.name == format!("{}{NARRATE_SUFFIX}", app.name))
            .unwrap_or_else(|| panic!("{}: no `{}{NARRATE_SUFFIX}` row", i.name, app.name));
        for on in [true, false] {
            let supplied: BTreeMap<String, bool> =
                BTreeMap::from([(DEV_NARRATE_OPTION.to_string(), on)]);
            let r = nornir_build_thing::options::resolve_options(&i.options, &i.params, &supplied)
                .unwrap_or_else(|e| panic!("{} dev-narrate={on}: {e:#}", i.name));
            let got: BTreeSet<String> = r.app_features(app).into_iter().collect();
            let want: BTreeSet<String> = if on {
                dev.features.iter().cloned().collect()
            } else {
                app.features.iter().cloned().collect()
            };
            assert_eq!(
                got, want,
                "{} dev-narrate={on} resolves to no declared row",
                i.name
            );
        }
        seen += 1;
    }
    assert_eq!(seen, 4);
}