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

nordisk/gunnar

xtask/tests/fast_console_two_runs.rs

//! ★ **Two Gunnar fast runs of ONE checkout build their consoles apart** (FAST-REL, 2026-10-25).
//!
//! The fast verb built gunnar-ui's paid console inside gunnar-ui's SOURCE tree
//! (`crates/gunnar-ui-web/dist-metered`). Two runs of one checkout that started together both
//! found it missing and ran trunk into that one directory: one run's finalize removed the other's
//! staging ("error from asset pipeline: No such file or directory"; "Text file busy" on the
//! CI-green lane's runs). Each run now builds into its OWN directory, under its `--out`, and the
//! front row embeds that one.
//!
//! The real build, on the host: trunk and a gunnar-ui checkout beside gunnar are needed. Without
//! them (the toolchain container has no trunk) the test says so by name and checks nothing.

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

fn skip(why: &str) {
    eprintln!("[skip: fast-console-two-runs] {why}");
}

#[test]
fn two_runs_of_one_checkout_build_their_consoles_apart() {
    let root = Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .to_path_buf();
    let ui = root.parent().unwrap().join("gunnar-ui");
    if Path::new("/run/.containerenv").exists() || Path::new("/.dockerenv").exists() {
        return skip("in a container: the console is a host trunk build");
    }
    if !ui.join("crates/gunnar-ui-web/Trunk.toml").is_file() {
        return skip("no gunnar-ui checkout beside gunnar");
    }
    if std::process::Command::new("trunk")
        .arg("--version")
        .output()
        .map(|o| !o.status.success())
        .unwrap_or(true)
    {
        return skip("no trunk on PATH");
    }
    std::env::remove_var("GUNNAR_FRONT_CONSOLE_DIST"); // this test binary runs this one test
    let base =
        std::env::temp_dir().join(format!("gunnar-fast-two-consoles-{}", std::process::id()));
    let _ = std::fs::remove_dir_all(&base);
    let (out_a, out_b) = (base.join("a"), base.join("b"));
    let (a, b): (Result<PathBuf, String>, Result<PathBuf, String>) = std::thread::scope(|s| {
        let ta = s.spawn(|| xtask::fast::build_console(&root, &out_a));
        let tb = s.spawn(|| xtask::fast::build_console(&root, &out_b));
        (ta.join().unwrap(), tb.join().unwrap())
    });
    let (a, b) = (
        a.expect("run a's console builds while run b builds its own"),
        b.expect("run b's console builds while run a builds its own"),
    );
    assert_ne!(a, b, "two runs, two console directories");
    for (d, out) in [(&a, &out_a), (&b, &out_b)] {
        assert!(
            d.starts_with(out),
            "{} is the run's own (under {})",
            d.display(),
            out.display()
        );
        assert!(
            !d.starts_with(&ui),
            "{} is not in gunnar-ui's source tree",
            d.display()
        );
        assert!(
            d.join("index.html").is_file(),
            "{} holds a whole console",
            d.display()
        );
    }
    let _ = std::fs::remove_dir_all(&base);
}