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

nordisk/knut

crates/knut-build-thing/tests/the_rows_are_the_matrix.rs

//! **The rows ARE the matrix** — knut's fourteen build points, asserted as a set and
//! not as a count.
//!
//! Every arm here is a property the hand-written file could not state: which rows
//! exist (exactly, in both directions), that each is tagged with a delivery, that
//! the repo names its base suite, that exactly one row per role is the normal
//! case, that the `required-features` trap is enforced rather than described, and
//! that the excluded wasm package is reachable at all.

use std::collections::BTreeSet;

use knut_build_thing::{KnutBuild, REQUIRED_FEATURES, SUITE};
use nornir_build_thing::plugin::{BuildRequest, BuildThing, Role};
use nornir_build_thing::variant::{ArtifactKind, ServiceShape};

/// **The fourteen atoms are exactly the fourteen** — both inclusions, so neither a new row
/// nobody mentioned nor a deleted row nobody missed can pass.
#[test]
fn the_fourteen_rows_are_exactly_these_fourteen() {
    let declared: BTreeSet<String> = KnutBuild
        .variants()
        .variants
        .iter()
        .map(|v| v.name.clone())
        .collect();
    let expected: BTreeSet<String> = [
        "bifrost",
        "bifrost-doris",
        "bifrost-falkordb",
        "bifrost-falkordb-aistor",
        "bifrost-falkordb-gravitino",
        "bifrost-falkordb-grpc",
        "bifrost-falkordb-polaris",
        "bifrost-falkordb-unity",
        "bifrost-full",
        "graphar-flight",
        "knut-bifrost-wasm-guest",
        "knut-popviz",
        "knut-spark-server",
        "knutpipe",
    ]
    .into_iter()
    .map(str::to_string)
    .collect();
    assert_eq!(
        declared, expected,
        "the declared rows are not the fourteen knut ships"
    );
}

/// **Every row carries a delivery tag, and the repo names its base suite.**
///
/// All six were UNTAGGED until 2026-09-20 — six of the 27 rows the 2026-09-17
/// census counted across the constellation. Untagged is not a third flavour; it
/// is a row `--suite ORIGINAL` cannot select, and a build verb that needs a
/// different flag per repo is not one verb.
#[test]
fn every_row_is_tagged_and_the_base_suite_is_declared() {
    let set = KnutBuild.variants();
    assert_eq!(
        set.base_suite.as_deref(),
        Some(SUITE),
        "the repo must name the delivery `--suite ORIGINAL` resolves to"
    );
    for v in &set.variants {
        assert_eq!(
            v.suites,
            vec![SUITE.to_string()],
            "variant '{}' is not tagged {SUITE}",
            v.name
        );
    }
    assert!(set.declares_suites(), "the suite axis must be live");
    assert_eq!(set.suites(), vec![SUITE]);
    assert_eq!(
        set.in_suite(SUITE).len(),
        14,
        "all fourteen rows must be selectable by delivery"
    );
}

/// **Exactly one normal case per role.**
///
/// The hand-written file marked TWO tools default (`knutpipe` and `bifrost`) and
/// NO client at all, and nothing anywhere read the file closely enough to say so:
/// `validate_all` does not call `check_defaults`. `knutpipe` keeps the tool
/// default (it is the binary a plain `cargo build` produces) and `knut-popviz`
/// takes the client default as the only client row.
#[test]
fn each_role_names_exactly_one_normal_case() {
    let set = KnutBuild.variants();
    let problems = set.check_defaults();
    assert!(
        problems.is_empty(),
        "the declaration cannot say which row is normal: {problems:#?}"
    );
    let menu = KnutBuild.menu();
    assert_eq!(menu.default_for(Role::Tool), Some("knutpipe"));
    assert_eq!(menu.default_for(Role::Server), Some("graphar-flight"));
    assert_eq!(menu.default_for(Role::Client), Some("knut-popviz"));
}

/// The whole declaration passes the generic shape rules — kind × target × bin ×
/// linkage, the offers closure, the exclusive groups and the required-features
/// mirror.
#[test]
fn the_declaration_validates() {
    KnutBuild.variants().validate_all().expect("shape-valid");
}

/// **The `--all-targets` trap, as data and as enforcement.**
///
/// Each entry of `REQUIRED_FEATURES` must have a row that builds its bin WITH the
/// feature — a hand-kept copy nothing compares is how a table drifts — and the
/// set's own `required` table must contain it, so the emitted file carries the
/// same trap the code enforces.
#[test]
fn required_table_matches_declaration() {
    let set = KnutBuild.variants();
    for (bin, need) in REQUIRED_FEATURES {
        let v = set
            .variants
            .iter()
            .find(|v| v.bin.as_deref() == Some(*bin))
            .unwrap_or_else(|| panic!("no variant builds bin '{bin}'"));
        assert!(
            v.features.iter().any(|f| f == need),
            "variant '{}' builds bin '{bin}' but does not enable its required feature \
             '{need}' — the declaration would produce no binary",
            v.name
        );
        assert_eq!(
            set.required.get(*bin).map(Vec::as_slice),
            Some(&[need.to_string()][..]),
            "[build.required] and REQUIRED_FEATURES disagree about '{bin}'"
        );
    }
    // The fifth required-features bin: declared, built by nothing, excused BY
    // NAME. The hand-written file did not know it existed.
    assert_eq!(
        set.required.get("export-iceberg").map(Vec::as_slice),
        Some(&["skade".to_string()][..])
    );
    assert!(
        set.allow_unbuilt_bins
            .get("export-iceberg")
            .and_then(|e| e.reason())
            .is_some_and(|r| !r.trim().is_empty()),
        "an unbuilt bin with no written reason is a gap, not a decision"
    );
}

/// The trap, as a test: asking for a `required-features` binary without its
/// feature must be REFUSED, not silently accepted. Without this rule cargo exits
/// 0 and emits nothing.
#[test]
fn refuses_a_required_features_bin_without_its_feature() {
    let mut set = KnutBuild.variants();
    let v = set
        .variants
        .iter_mut()
        .find(|v| v.bin.as_deref() == Some("bifrost"))
        .unwrap();
    v.features.retain(|f| f != "cli");
    let name = v.name.clone();
    // The REAL rule, run against a deliberately-broken declaration — knut's own
    // declaration is correct, so the failing case has to be constructed, and
    // constructing it must not mean re-implementing the rule.
    let verdict = knut_build_thing::validate_against(
        &set,
        &BuildRequest {
            variant: Some(name),
            ..Default::default()
        },
    );
    assert!(
        !verdict.ok,
        "a variant building `bifrost` without `cli` must be refused"
    );
    let why = verdict.reasons.join(" ");
    assert!(
        why.contains("cli") && why.contains("required-features"),
        "the refusal must name the missing feature and why: {why}"
    );
}

/// …and the same request WITH the feature must pass, or the rule is just a
/// blanket ban and the test above proves nothing.
#[test]
fn accepts_a_required_features_bin_with_its_feature() {
    assert!(
        KnutBuild
            .validate(&BuildRequest {
                variant: Some("bifrost".to_string()),
                ..Default::default()
            })
            .ok,
        "`bifrost` as declared (with `cli`) must build"
    );
}

/// An undeclared variant is refused by name, with the roster.
#[test]
fn an_undeclared_variant_is_refused_with_the_roster() {
    let v = KnutBuild.validate(&BuildRequest {
        variant: Some("knut-does-not-exist".to_string()),
        ..Default::default()
    });
    assert!(!v.ok);
    let why = v.reasons.join(" ");
    assert!(why.contains("knut-does-not-exist") && why.contains("knutpipe"), "{why}");
}

/// **The excluded wasm guest is reachable.**
///
/// It is `exclude`d from the root workspace on purpose, so `-p
/// knut-bifrost-wasm-guest` from the repo root reaches NOTHING. The row therefore
/// has to carry `manifest_dir`, which `cargo_args` renders as `--manifest-path`;
/// without it the row named a build cargo could not run, and that is how it stood
/// from 2026-07-23 until today.
#[test]
fn the_wasm_guest_row_names_the_manifest_that_can_build_it() {
    let set = KnutBuild.variants();
    let v = set.get("knut-bifrost-wasm-guest").expect("the guest row");
    assert_eq!(v.kind, ArtifactKind::Wasm);
    assert_eq!(v.target.as_deref(), Some("wasm32-unknown-unknown"));
    assert_eq!(v.manifest_dir.as_deref(), Some("crates/knut-bifrost/wasm-guest"));
    let argv = v.cargo_args();
    assert!(
        argv.iter().any(|a| a == "--manifest-path"),
        "the guest's argv does not carry --manifest-path: {argv:?}"
    );
    assert!(
        argv.iter()
            .any(|a| a.ends_with("crates/knut-bifrost/wasm-guest/Cargo.toml")),
        "{argv:?}"
    );
    // And the directory it names is really in this checkout.
    assert!(knut_build_thing::emit::repo_root()
        .join("crates/knut-bifrost/wasm-guest/Cargo.toml")
        .is_file());
}

/// **Every row is a cargo row, and each says its linkage out loud.**
///
/// knut declares no `container`/`iso`/`installer` row, so `cargo xtask build`
/// would have nothing to refuse — the day one appears, `--list` must name the
/// door that builds it rather than skipping it. An omitted `linkage` reads as
/// `fat`, a positive claim of self-containment, so every row states it; and
/// `embeds`/`reaches` stay EMPTY because no symbol count was ever taken here.
#[test]
fn every_row_is_cargo_and_declares_its_linkage() {
    for v in KnutBuild.variants().variants {
        assert!(
            v.kind.is_cargo(),
            "variant '{}' is kind {} — a non-cargo row must be listed and refused by name \
             with the door that builds it",
            v.name,
            v.kind.as_str()
        );
        assert_eq!(v.shape, ServiceShape::FatService, "variant '{}'", v.name);
        assert!(
            v.embeds.is_empty() && v.reaches.is_empty(),
            "variant '{}' claims a composition nobody measured",
            v.name
        );
    }
}

/// **The pinned environment.** `CargoToolchain::validate` refuses an empty
/// `image_sha256` BY NAME, and the wasm target the guest (and this recipe's own
/// plugin module) needs must be one the image carries.
#[test]
fn the_toolchain_is_pinned_by_digest_and_carries_every_declared_target() {
    let set = KnutBuild.variants();
    let tc = set.toolchain.as_ref().expect("knut pins its build image");
    tc.validate().expect("the pin is well-formed");
    assert_eq!(
        tc.image_sha256,
        "668468e775ad80f32d700cbc9709d41cac726f40d00649461a0932f30d1b2067"
    );
    assert!(tc.targets.iter().any(|t| t == "wasm32-unknown-unknown"));
    for v in &set.variants {
        if let Some(t) = v.target.as_deref() {
            assert!(
                tc.targets.iter().any(|x| x == t),
                "variant '{}' targets '{t}', which the pinned image does not carry",
                v.name
            );
        }
    }
}

/// A GUI build is allowed, and must SAY that it starts a database — the
/// transitive `gui → autostart → graphar-falkordb/autostart` chain is the thing a
/// reader misses.
#[test]
fn gui_is_allowed_but_discloses_the_autostart() {
    let v = KnutBuild.validate(&BuildRequest {
        variant: Some("knut-popviz".to_string()),
        ..Default::default()
    });
    assert!(v.ok, "the popviz GUI is a legitimate build");
    assert!(
        v.reasons.join(" ").to_lowercase().contains("falkordb"),
        "a build that starts a database must say so: {:?}",
        v.reasons
    );
}

#[test]
fn wasm_guest_may_not_link_a_native_graph_client() {
    use nornir_build_thing::plugin::ArtifactFacts;
    let facts = ArtifactFacts {
        file_name: "knut_bifrost_wasm_guest.wasm".to_string(),
        linked_crates: vec!["arrow-ipc".to_string(), "redis".to_string()],
        ..Default::default()
    };
    assert!(!KnutBuild.judge_artifact(&facts).ok);

    let clean = ArtifactFacts {
        file_name: "knut_bifrost_wasm_guest.wasm".to_string(),
        linked_crates: vec!["arrow-ipc".to_string()],
        ..Default::default()
    };
    assert!(KnutBuild.judge_artifact(&clean).ok);
}