wsb 0.291.8591

Workspace tools for every project under $PRJ: git across submodules, template stamping, GitHub, and the workbench
Documentation
//! Analysis roots of a real fleet: a repository with its Cargo workspace below its root, a
//! submodule that is a Cargo workspace itself, and the places a walk must not look into.

use super::*;

fn git(dir: &Path, args: &[&str]) {
    let output = crate::git_backend::git_command()
        .arg("-C")
        .arg(dir)
        .args(["-c", "user.name=Test", "-c", "user.email=test@example.com", "-c", "protocol.file.allow=always"])
        .args(args)
        .output()
        .expect("git runs");
    assert!(output.status.success(), "git {args:?}: {}", String::from_utf8_lossy(&output.stderr));
}

fn write(dir: &Path, rel: &str, text: &str) {
    let p = dir.join(rel);
    std::fs::create_dir_all(p.parent().expect("a parent")).expect("mkdir");
    std::fs::write(p, text).expect("a file");
}

/// A worktree `top` with `wsb/` (a Cargo workspace, holding a crate of its own), `docs/`,
/// an ignored `target/` and a locally excluded `scratch/` each holding a stray `Cargo.toml`,
/// and a submodule `lib` that is a Cargo workspace at its root.
fn fleet() -> (tempfile::TempDir, PathBuf) {
    let scratch = tempfile::tempdir().expect("a scratch directory");
    let base = crate::platform::canonicalize(scratch.path()).expect("resolves");
    let lib = base.join("lib-origin");
    std::fs::create_dir_all(&lib).expect("mkdir");
    git(&lib, &["init", "--quiet", "-b", "main"]);
    write(&lib, "Cargo.toml", "[package]\nname = \"lib\"\n");
    write(&lib, "src/lib.rs", "pub fn f() {}\n");
    git(&lib, &["add", "-A"]);
    git(&lib, &["commit", "--quiet", "-m", "lib"]);

    let top = base.join("top");
    std::fs::create_dir_all(&top).expect("mkdir");
    git(&top, &["init", "--quiet", "-b", "main"]);
    write(&top, ".gitignore", "target/\n");
    write(&top, "wsb/Cargo.toml", "[workspace]\nmembers = [\"daemon\"]\n");
    write(&top, "wsb/src/lib.rs", "pub fn g() {}\n");
    write(&top, "wsb/daemon/Cargo.toml", "[package]\nname = \"daemon\"\n");
    write(&top, "wsb/daemon/src/main.rs", "fn main() {}\n");
    write(&top, "docs/tool.py", "def tool():\n    pass\n");
    write(&top, "target/stray/Cargo.toml", "[package]\nname = \"stray\"\n");
    git(&top, &["add", "-A"]);
    git(&top, &["submodule", "--quiet", "add", lib.to_str().expect("utf-8"), "lib"]);
    git(&top, &["commit", "--quiet", "-m", "top"]);
    write(&top, "scratch/Cargo.toml", "[package]\nname = \"scratch\"\n");
    let exclude = top.join(".git/info/exclude");
    let mut excluded = std::fs::read_to_string(&exclude).unwrap_or_default();
    excluded.push_str("scratch/\n");
    std::fs::write(&exclude, excluded).expect("an exclude");
    (scratch, top)
}

/// TEST20080: a file's analysis is made at the shallowest Cargo workspace above it in its
/// repository, else at the repository; an analysis at a repository's root leaves out the
/// Cargo workspaces below it, and no other place.
///
/// omnicode understands Rust only with a Cargo workspace at the root it analyzes, and leaves
/// submodules out. A worktree analyzed at its own root would get its Rust as syntax only,
/// and a submodule's files not at all.
#[test]
fn test20080_files_are_analyzed_at_their_repository_or_cargo_workspace() {
    let (_scratch, top) = fleet();
    let fleet = Fleet::discover(&top).expect("the fleet");
    assert_eq!(fleet.repositories(), [top.clone(), top.join("lib")]);

    let root = |rel: &str| fleet.analysis_root(&top.join(rel)).expect("a root");
    assert_eq!(root("wsb/src/lib.rs"), AnalysisRoot { dir: top.join("wsb"), exclude: vec![] });
    assert_eq!(
        root("wsb/daemon/src/main.rs"),
        AnalysisRoot { dir: top.join("wsb"), exclude: vec![] },
        "a crate inside a Cargo workspace is that workspace's"
    );
    assert_eq!(
        root("docs/tool.py"),
        AnalysisRoot { dir: top.clone(), exclude: vec!["wsb/**".to_owned()] },
        "the repository's own analysis leaves out its Cargo workspace, and nothing ignored"
    );
    assert_eq!(root("lib/src/lib.rs"), AnalysisRoot { dir: top.join("lib"), exclude: vec![] });

    let mut all = vec![
        AnalysisRoot { dir: top.clone(), exclude: vec!["wsb/**".to_owned()] },
        AnalysisRoot { dir: top.join("lib"), exclude: vec![] },
        AnalysisRoot { dir: top.join("wsb"), exclude: vec![] },
    ];
    all.sort();
    assert_eq!(fleet.analysis_roots(None).expect("the roots"), all);
    assert_eq!(
        fleet.analysis_roots(Some(&top.join("lib"))).expect("one repository's roots"),
        vec![AnalysisRoot { dir: top.join("lib"), exclude: vec![] }]
    );
}