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

nordisk/gunnar

xtask/tests/staging_guard.rs

//! Red-first proofs for [`xtask::staging_guard`] — the I6 guard, over the one
//! directory git is *configured* not to watch.
//!
//! # Why every fixture is built at run time
//!
//! `xtask` has **zero dependencies** by construction, so
//! there is no `tempfile` here; the [`Scratch`] guard below is the same shape
//! `xtask/tests/bench_guard.rs` uses, and it removes its tree on drop.
//!
//! # What has to be red before the green means anything
//!
//! The leak this guard exists for was **invisible to git by design**: the
//! staging tree's own `.gitignore` is `*`, so `git status` was clean while 4.4
//! MB of another repository's forge-test fixtures sat on the initramfs root of
//! every ISO this box built, from 2026-08-13 to 2026-08-27. A guard that read
//! git would have been green throughout. So the red case here is the real
//! shape — `control.redb`, `control.git/`, `repos/` — and there is a third
//! test for the guard's OWN blindness: a tree it cannot find must not read as
//! a clean one.

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

use xtask::staging_guard::{scan, ALLOWED, STAGING_TREE};

/// A directory that removes itself. `xtask` has no `tempfile`.
struct Scratch(PathBuf);

impl Drop for Scratch {
    fn drop(&mut self) {
        let _ = std::fs::remove_dir_all(&self.0);
    }
}

/// A root holding a staging tree with the two declared files in it.
fn declared_tree(name: &str) -> Scratch {
    let dir =
        Scratch(std::env::temp_dir().join(format!("gunnar-staging-{name}-{}", std::process::id())));
    let _ = std::fs::remove_dir_all(&dir.0);
    let data = dir.0.join(STAGING_TREE).join("data");
    std::fs::create_dir_all(&data).expect("mkdir data");
    std::fs::write(data.join(".keep"), "/data ships EMPTY\n").expect("write .keep");
    std::fs::write(data.join(".gitignore"), "*\n!.keep\n!.gitignore\n").expect("write .gitignore");
    dir
}

fn names(root: &Path) -> Vec<String> {
    scan(root).0.into_iter().map(|s| s.rel).collect()
}

/// The two files in [`ALLOWED`] are the two files the tree carries, and the
/// walk says how many entries it saw so that "clean" is distinguishable from
/// "found nothing".
#[test]
fn the_declared_tree_is_clean_and_the_walk_is_not_silent() {
    let dir = declared_tree("clean");
    let (strays, seen) = scan(&dir.0);
    assert!(strays.is_empty(), "{strays:?}");
    assert_eq!(seen, 3, "data/ and its two declared files");
    assert_eq!(ALLOWED.len(), 2, "both of them are what ALLOWED names");
}

/// **RED-FIRST, the leak as measured.** dwarves' `gunnar_appliance_iso` forge
/// test — another repository entirely — bootstraps `control.redb`,
/// `control.git/` and `repos/` into this directory. Every one of them is
/// named, and the redb's size is in the report because "4 MB on the initramfs
/// root" is the sentence that makes somebody act.
#[test]
fn the_forge_tests_fixture_state_is_caught_by_name_and_carries_its_size() {
    let dir = declared_tree("leak");
    let data = dir.0.join(STAGING_TREE).join("data");
    std::fs::write(data.join("control.redb"), vec![0u8; 4096]).expect("write redb");
    std::fs::create_dir_all(data.join("control.git")).expect("mkdir control.git");
    std::fs::write(
        data.join("control.git").join("HEAD"),
        "ref: refs/heads/main\n",
    )
    .expect("write HEAD");
    std::fs::create_dir_all(data.join("repos").join("demo.git")).expect("mkdir repos");
    std::fs::write(data.join("repos").join("demo.git").join("HEAD"), "x\n").expect("write demo");

    let (strays, seen) = scan(&dir.0);
    let found = names(&dir.0);
    for want in [
        "data/control.redb",
        "data/control.git/HEAD",
        "data/repos/demo.git/HEAD",
    ] {
        assert!(
            found.contains(&want.to_string()),
            "{want} missing from {found:?}"
        );
    }
    assert!(
        seen >= 7,
        "the walk descended into both directories: {seen}"
    );

    let redb = strays
        .iter()
        .find(|s| s.rel == "data/control.redb")
        .expect("the redb is reported");
    assert_eq!(redb.bytes, 4096, "the size is measured, not assumed");
    assert!(!redb.is_dir);
    let said = redb.to_string();
    assert!(said.contains("initramfs ROOT"), "explains itself: {said}");
    assert!(
        said.contains("gitignore"),
        "names why no diff shows it: {said}"
    );
}

/// **The guard's own blindness, asserted rather than assumed.** A root with no
/// staging tree produces no strays — identical in shape to a clean verdict —
/// so `scan` returns the walked count and every caller refuses on zero. Without
/// this, renaming the directory would leave a guard that is green forever.
#[test]
fn a_tree_that_cannot_be_found_walks_zero_and_is_not_a_clean_verdict() {
    let dir = Scratch(
        std::env::temp_dir().join(format!("gunnar-staging-missing-{}", std::process::id())),
    );
    let _ = std::fs::remove_dir_all(&dir.0);
    std::fs::create_dir_all(&dir.0).expect("mkdir root");
    let (strays, seen) = scan(&dir.0);
    assert!(strays.is_empty(), "nothing to report: {strays:?}");
    assert_eq!(seen, 0, "and nothing walked, which is the caller's refusal");
}

/// An undeclared EMPTY directory is a stray too: on an initramfs root it is a
/// mount point nobody declared, and it is the shape a half-cleaned leak leaves.
#[test]
fn an_undeclared_empty_directory_is_a_stray() {
    let dir = declared_tree("emptydir");
    std::fs::create_dir_all(dir.0.join(STAGING_TREE).join("etc")).expect("mkdir etc");
    let (strays, _) = scan(&dir.0);
    assert_eq!(strays.len(), 1, "{strays:?}");
    assert_eq!(strays[0].rel, "etc");
    assert!(strays[0].is_dir);
}

/// **This checkout's own staging tree**, walked. The fixtures above prove the
/// scanner can go red; this is the assertion that keeps the leak closed here.
#[test]
fn this_repositorys_own_staging_tree_is_clean() {
    let root = xtask::guard::workspace_root();
    let (strays, seen) = scan(&root);
    assert!(
        seen > 0,
        "walked nothing under {}/{STAGING_TREE} — the tree moved, and a guard that cannot \
         find what it watches must not report clean",
        root.display()
    );
    assert!(
        strays.is_empty(),
        "the staging tree carries what it does not declare:\n{}",
        strays
            .iter()
            .map(ToString::to_string)
            .collect::<Vec<_>>()
            .join("\n")
    );
}