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

nordisk/gunnar

xtask/tests/lone_clone.rs

//! **A lone clone of gunnar loads, and `cargo build --release` gives the server**
//! (owner order 2026-10-22).
//!
//! Somebody who clones gunnar ALONE, with no `../edda`, `../Skidbladnir`,
//! `../monetize`, `../znippy`, `../tunnr` or `../gunnar-ui` beside it, must be able
//! to run plain `cargo build --release` at the root and get `target/release/gunnar`.
//! cargo refuses to load a workspace when ANY member's dependency, or any `[patch]`
//! entry, points at a path that is not there, optional and dev edges included. So:
//!
//! * no root-workspace member may have a path dependency that leaves the repo;
//! * the root `[patch]` tables may only point inside the repo;
//! * the root takes no sibling patch at all, not through `.cargo/config.toml` either, and its
//!   committed lock is the crates.io lock (G-B7, owner order G-BOTH-1); the estate workspaces
//!   (gunnar-front, the guest, the benches, installtest) carry the sibling patches they take
//!   in their own manifests;
//! * `default-members` is the `gunnar` crate alone.
//!
//! The end-to-end proof (copy only this repo into an empty directory, `cargo build
//! --release`, boot, `/healthz`) is `xtask/lone-clone-check.sh`; it needs crates.io (and,
//! until the publish script has run, the packaged crates it is pointed at).

use std::path::{Component, Path, PathBuf};
use std::process::Command;

use xtask::json::Json;

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

/// Lexical normalisation (no filesystem): what cargo itself does with a declared path.
fn normalise(p: &Path) -> PathBuf {
    let mut out = PathBuf::new();
    for c in p.components() {
        match c {
            Component::ParentDir => {
                out.pop();
            }
            Component::CurDir => {}
            other => out.push(other.as_os_str()),
        }
    }
    out
}

/// Pure: the `path = "…"` values under every `[patch.*]` table of a manifest's text.
fn patch_paths(manifest: &str) -> Vec<String> {
    let mut out = Vec::new();
    let mut in_patch = false;
    for line in manifest.lines().map(str::trim) {
        if line.starts_with('#') || line.is_empty() {
            continue;
        }
        if line.starts_with('[') {
            in_patch = line.starts_with("[patch.") || line.starts_with("[patch]");
            continue;
        }
        if in_patch {
            if let Some(rest) = line.split("path").nth(1) {
                if let Some(v) = rest.split('"').nth(1) {
                    out.push(v.to_string());
                }
            }
        }
    }
    out
}

#[test]
fn patch_paths_reads_every_entry() {
    let m = "[workspace]\nmembers=[]\n[patch.crates-io]\n# x = { path = \"../no\" }\na = { path = \"../edda/a\" }\nb = { path = \"vendor/b\" }\n[profile.release]\nc = { path = \"../not-a-patch\" }\n";
    assert_eq!(patch_paths(m), vec!["../edda/a", "vendor/b"]);
}

#[test]
fn the_root_patch_table_stays_inside_the_repo() {
    let root = root();
    let text = std::fs::read_to_string(root.join("Cargo.toml")).expect("root Cargo.toml");
    let outside: Vec<String> = patch_paths(&text)
        .into_iter()
        .filter(|p| !normalise(&root.join(p)).starts_with(&root))
        .collect();
    assert!(
        outside.is_empty(),
        "the root [patch] points out of the repo at {outside:?}: a lone clone cannot load the \
         workspace. Sibling patches belong in the estate workspaces' own manifests \
         (xtask::siblings::ESTATE_WORKSPACES), never at the root"
    );
}

#[test]
fn no_member_of_the_root_workspace_reaches_a_sibling_checkout() {
    let root = root();
    let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
    let out = Command::new(cargo)
        .args(["metadata", "--no-deps", "--format-version", "1"])
        .current_dir(&root)
        .output()
        .expect("run cargo metadata");
    assert!(
        out.status.success(),
        "cargo metadata: {}",
        String::from_utf8_lossy(&out.stderr)
    );
    let json = Json::parse(&String::from_utf8_lossy(&out.stdout)).expect("json");
    let mut outside = Vec::new();
    for package in json.get("packages").expect("packages").arr() {
        let name = package.get("name").and_then(Json::str).unwrap_or("?");
        for dep in package.get("dependencies").map(Json::arr).unwrap_or(&[]) {
            if let Some(path) = dep.get("path").and_then(Json::str) {
                if !normalise(Path::new(path)).starts_with(&root) {
                    let dep_name = dep.get("name").and_then(Json::str).unwrap_or("?");
                    outside.push(format!("{name} -> {dep_name} ({path})"));
                }
            }
        }
    }
    assert!(
        outside.is_empty(),
        "root-workspace members depend on sibling checkouts by path, so a lone clone cannot \
         load the workspace:\n  {}",
        outside.join("\n  ")
    );
}

#[test]
fn the_default_member_is_the_gunnar_server_alone() {
    let text = std::fs::read_to_string(root().join("Cargo.toml")).expect("root Cargo.toml");
    assert!(
        text.lines()
            .any(|l| l.trim() == "default-members = [\"crates/gunnar\"]"),
        "the root workspace must say `default-members = [\"crates/gunnar\"]`, so a plain \
         `cargo build --release` builds the server and nothing else"
    );
}

/// ★ G-B7 (owner order G-BOTH-1): **the root takes NO sibling patch.** Not in its manifest and
/// not through `.cargo/config.toml` either: a config is found from the directory cargo runs in,
/// so an included sibling table rewrote the ROOT's resolution in every estate checkout, and the
/// committed root lock was the estate's (29 sibling crates by path) instead of the one a lone
/// clone resolves. The estate workspaces carry the sibling patches they take in their OWN
/// manifests ([`xtask::siblings::ESTATE_WORKSPACES`]).
#[test]
fn the_root_cargo_config_includes_no_sibling_table() {
    let root = root();
    let config =
        std::fs::read_to_string(root.join(".cargo/config.toml")).expect(".cargo/config.toml");
    let live: Vec<&str> = config
        .lines()
        .map(str::trim)
        .filter(|l| !l.starts_with('#'))
        .collect();
    assert!(
        !live
            .iter()
            .any(|l| l.starts_with("include") || l.contains("siblings.toml")),
        ".cargo/config.toml still includes a sibling table: every cargo run from the repository \
         root (the root workspace's included) builds against the sibling checkouts, so its lock is \
         not the lock a lone clone resolves"
    );
    for gone in [".cargo/estate-siblings.toml", ".cargo/estate-siblings.sh"] {
        assert!(
            !root.join(gone).exists(),
            "{gone} is still here: the estate's sibling patches live in the estate workspaces' own \
             manifests now, and lockbot switches a repository's patches on when it finds this file"
        );
    }
    let ignore = std::fs::read_to_string(root.join(".gitignore")).expect(".gitignore");
    assert!(
        ignore.lines().any(|l| l.trim() == "/.cargo/siblings.toml"),
        "a stale .cargo/siblings.toml from an older checkout must stay ignored"
    );
}

/// The root workspace's member names and the crates its own `[patch]` supplies from inside the
/// repository (`vendor/gix-parallel`): the path packages a crates.io lock may hold.
fn in_repo_packages(root: &Path) -> std::collections::BTreeSet<String> {
    let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
    let mut own = xtask::both_ways::member_names(root, &cargo);
    assert!(
        own.contains("gunnar"),
        "cargo metadata --no-deps read no members: {own:?}"
    );
    let text = std::fs::read_to_string(root.join("Cargo.toml")).expect("root Cargo.toml");
    own.extend(xtask::siblings::patch_names(&text));
    own
}

/// ★ G-B7: **the committed root lock is the crates.io lock.** Every package in it is a member,
/// an in-repo patch, or comes from a registry: a lone clone's `cargo build --locked` takes it
/// as it is. RED on master e95cc71a: 29 sibling crates by path (appliance-pid1 0.1.8,
/// disk-snapshot 0.1.1, draupnir 0.2.0, edda-admit 0.1.0, …).
#[test]
fn the_committed_root_lock_names_no_sibling_by_path() {
    let root = root();
    let lock = std::fs::read_to_string(root.join("Cargo.lock")).expect("Cargo.lock");
    let bad = xtask::both_ways::lock_path_entries(&lock, &in_repo_packages(&root));
    assert!(
        bad.is_empty(),
        "the committed root Cargo.lock names {} crate(s) by path that are neither members nor an \
         in-repo patch, so a lone clone's `cargo build --locked` refuses it: {bad:?}. Relock the \
         root with no sibling patch (`cargo metadata` at the root) and commit that lock.",
        bad.len()
    );
}

/// ★ G-B7: **each estate workspace patches the siblings its own lock takes by path**, in its own
/// manifest: with the root include gone, nothing else turns them on, and a lock that names a
/// sibling crate by path its manifest does not patch is refused by `--locked`.
#[test]
fn each_estate_workspace_patches_the_siblings_its_lock_takes() {
    let root = root();
    let estate: std::collections::BTreeSet<String> = xtask::siblings::ESTATE_PATCHES
        .iter()
        .map(|s| s.to_string())
        .collect();
    let mut missing = Vec::new();
    for ws in xtask::siblings::ESTATE_WORKSPACES {
        let manifest = std::fs::read_to_string(root.join(ws).join("Cargo.toml"))
            .unwrap_or_else(|e| panic!("{ws}/Cargo.toml: {e}"));
        let own = xtask::siblings::patch_names(&manifest);
        let lock = std::fs::read_to_string(root.join(ws).join("Cargo.lock"))
            .unwrap_or_else(|e| panic!("{ws}/Cargo.lock: {e}"));
        for entry in xtask::both_ways::lock_path_entries(&lock, &Default::default()) {
            let name = entry.split(' ').next().unwrap_or("").to_string();
            if estate.contains(&name) && !own.contains(&name) {
                missing.push(format!("{ws}: {entry}"));
            }
        }
    }
    assert!(
        missing.is_empty(),
        "these estate workspaces' locks take a sibling crate by path that their own manifest does \
         not patch (it came from the root's include before G-B7):\n  {}",
        missing.join("\n  ")
    );
}

#[test]
fn the_readme_documents_the_lone_clone_build() {
    let readme = std::fs::read_to_string(root().join("README.md")).expect("README.md");
    for needle in ["cargo build --release", "target/release/gunnar"] {
        assert!(
            readme.contains(needle),
            "README.md does not document `{needle}`"
        );
    }
}