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

nordisk/dwarves

ci/verbs/tests/runner_uses_detached_verbs.rs

//! The runner's lock check and status page come from this detached crate, never from
//! dwarves' workspace xtask: a sibling's version move that breaks dwarves' graph must not
//! freeze STATUS.md (2026-10-24: frozen at 13:11Z for ten hours, the cron running on).

use std::path::Path;

fn runner() -> String {
    let p = Path::new(env!("CARGO_MANIFEST_DIR")).join("../runner.sh");
    std::fs::read_to_string(&p).unwrap_or_else(|e| panic!("read {}: {e}", p.display()))
}

fn code_lines(s: &str) -> Vec<&str> {
    s.lines().filter(|l| !l.trim_start().starts_with('#')).collect()
}

#[test]
fn the_runner_writes_the_status_page_and_the_lock_check_with_the_detached_verbs() {
    let script = runner();
    let code = code_lines(&script);
    for verb in ["status", "lockcheck"] {
        assert!(
            !code.iter().any(|l| l.contains(&format!("xtask ci {verb}")) || (l.contains("run_verb dwarves") && l.contains(verb))),
            "the runner still runs `{verb}` through dwarves' workspace xtask"
        );
        assert!(
            code.iter().any(|l| l.contains(&format!("cargo run --quiet --manifest-path \"$VERBS_MANIFEST\" -- {verb} "))),
            "the runner does not run `{verb}` from ci/verbs"
        );
    }
    assert!(code.iter().any(|l| l.trim() == "VERBS_MANIFEST=$W/src/dwarves/ci/verbs/Cargo.toml"), "VERBS_MANIFEST is not ci/verbs");
}

#[test]
fn the_crate_is_detached_and_links_no_dwarves_crate() {
    let m = std::fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join("Cargo.toml")).unwrap();
    assert!(m.lines().any(|l| l.trim() == "[workspace]"), "ci/verbs is not its own workspace");
    let deps = m.split("[dependencies]").nth(1).unwrap_or("");
    let names: Vec<&str> = deps.lines().filter_map(|l| l.split('=').next()).map(str::trim).filter(|n| !n.is_empty() && !n.starts_with('#')).collect();
    // nornir-xtask-support, serde_json for the nightly Scorecard step's JSON and ureq for
    // the nightly OpenSSF badge copy (BADGE-7): crates.io crates, so no sibling and no
    // dwarves crate can freeze them
    assert_eq!(names, ["nornir-xtask-support", "serde_json", "ureq"], "ci/verbs links more than nornir-xtask-support + serde_json + ureq");
    for leaf in ["serde_json", "ureq"] {
        assert!(!deps.lines().any(|l| l.starts_with(leaf) && l.contains("path")), "{leaf} comes from crates.io");
    }
}

#[test]
fn the_status_page_is_written_from_the_runs_alone() {
    let dir = std::env::temp_dir().join(format!("ci-verbs-status-{}", std::process::id()));
    let run = dir.join("runs/20261024T230001Z/holger");
    std::fs::create_dir_all(&run).unwrap();
    std::fs::write(
        run.join("receipt.json"),
        r#"{"schema": "nornir-ci/1", "repo": "holger", "head": "55ed5ed3aaaa", "mode": "quick", "finished_unix": 1792890000, "green": true, "failing_step": null, "steps": []}"#,
    )
    .unwrap();
    let out = dir.join("STATUS.md");
    let args: Vec<String> = ["--runs", dir.join("runs").to_str().unwrap(), "--out", out.to_str().unwrap()].iter().map(|s| s.to_string()).collect();
    assert_eq!(ci_verbs::status_verb(&args), 0);
    let md = std::fs::read_to_string(&out).unwrap();
    assert!(md.contains("| holger | quick | 55ed5ed3"), "{md}");
    let _ = std::fs::remove_dir_all(&dir);
}

#[test]
fn the_runner_copies_itself_only_under_its_lock() {
    // The cron starts a runner every 5 minutes. A start that copied runner.sh over
    // .runner-copy.sh BEFORE it found the lock taken overwrote the script a running run
    // was still reading (bash reads a script as it goes): 2026-10-24 23:30, mid-run.
    let script = runner();
    let code = code_lines(&script);
    let lock = code.iter().position(|l| l.contains("flock -n 9")).expect("the runner takes its lock");
    let copy = code.iter().position(|l| l.contains(".runner-copy.sh")).expect("the runner runs from a copy");
    assert!(lock < copy, "the runner copies itself before it holds its lock");
    let open = code.iter().position(|l| l.contains("exec 9>")).expect("the lock's descriptor");
    assert_eq!(code.iter().filter(|l| l.contains("exec 9>")).count(), 1, "the copy must keep the inherited lock, not open a second one");
    assert!(open < copy);
}