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

nordisk/gunnar

xtask/tests/front_tests_run_in_ci.rs

//! ★ **`cargo xtask ci` runs gunnar-front's tests** (OX-WIRED 2026-10-28).
//!
//! gunnar-front is a DETACHED workspace (the root excludes it: it takes crates that live only
//! in sibling checkouts), so `cargo test --workspace` never reaches it, and until this file no
//! CI step did either: `tests/grpc_hole.rs` (the CLI to the main's control plane through the
//! front's 443) and every other gunnar-front test ran nowhere. `xtask ci` runs each DETACHED
//! entry by manifest path; this holds that the front is one of them, once at its default
//! features and once with every feature a front test requires, so a test gated on a feature
//! nobody turns on cannot hide either.

fn root() -> std::path::PathBuf {
    std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .expect("xtask has a parent")
        .to_path_buf()
}

fn text(rel: &str) -> String {
    std::fs::read_to_string(root().join(rel)).unwrap_or_else(|e| panic!("{rel}: {e}"))
}

/// The `DETACHED` table of `xtask/src/main.rs`, as written.
fn detached() -> String {
    let main = text("xtask/src/main.rs");
    let at = main
        .find("const DETACHED")
        .expect("xtask/src/main.rs declares DETACHED");
    let end = main[at..].find("];").expect("DETACHED ends") + at;
    main[at..end].to_string()
}

/// The names in a one-line `key = ["a", "b"]` of gunnar-front's Cargo.toml, every such line.
fn listed(key: &str) -> std::collections::BTreeSet<String> {
    let manifest = text("crates/gunnar-front/Cargo.toml");
    let mut out = std::collections::BTreeSet::new();
    for line in manifest.lines().filter(|l| {
        l.trim_start()
            .strip_prefix(key)
            .is_some_and(|r| r.trim_start().starts_with('='))
    }) {
        let list = line
            .split_once('[')
            .and_then(|(_, r)| r.split_once(']'))
            .map(|(l, _)| l)
            .unwrap_or_default();
        out.extend(
            list.split(',')
                .map(|f| f.trim().trim_matches('"').to_string())
                .filter(|f| !f.is_empty()),
        );
    }
    out
}

/// Every feature a `[[test]]` of gunnar-front names in `required-features` that its default
/// features do not already turn on (a featured run keeps the defaults).
fn required_by_the_front_tests() -> std::collections::BTreeSet<String> {
    let defaults = listed("default");
    listed("required-features")
        .into_iter()
        .filter(|f| !defaults.contains(f))
        .collect()
}

#[test]
fn xtask_ci_runs_the_fronts_tests_at_its_defaults() {
    let table = detached();
    assert!(
        table.contains(r#""crates/gunnar-front/Cargo.toml", &[])"#),
        "`cargo xtask ci` must run gunnar-front's tests at its default features (a DETACHED entry \
         with no extra arguments):\n{table}"
    );
}

#[test]
fn xtask_ci_runs_every_feature_gated_front_test() {
    let table = detached();
    let required = required_by_the_front_tests();
    assert!(
        !required.is_empty(),
        "gunnar-front's Cargo.toml names required-features (metered, mcp, …)"
    );
    let featured: Vec<&str> = table
        .split("(\"")
        .filter(|e| e.contains(r#""crates/gunnar-front/Cargo.toml", &["--features""#))
        .collect();
    assert!(
        !featured.is_empty(),
        "no DETACHED entry runs gunnar-front with --features:\n{table}"
    );
    for f in &required {
        assert!(
            featured.iter().any(|e| e.split('"').any(|w| w.split(',').any(|x| x == f))),
            "a gunnar-front test requires `{f}`, and no `cargo xtask ci` step turns it on:\n{table}"
        );
    }
}