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

nordisk/edda

crates/nornir-feature-graph/tests/same_name_path_packages.rs

//! **Two first-party packages with one name are two packages** (CI-RED's red at edda
//! 01f6d63c, lane OSSF-SILVER-CODE, 2026-10-31).
//!
//! nornir's own recipe crate is `nornir-build-thing` 0.1.0 (the forge finds a repo's
//! recipe by the name `<repo>-build-thing`), and it depends on edda's evaluator, also
//! `nornir-build-thing`, 0.2.x, by path. Both are path packages in one lock. The
//! constellation scan keyed first-party manifests by NAME, so the evaluator's locked id
//! got the recipe's manifest, its dependencies did not match its own lock edges, and the
//! closure came back truncated (`nornir-build-thing <unlocked>`). Here the same shape in
//! three temporary crates.

use std::collections::BTreeSet;
use std::path::Path;

use nornir_feature_graph::registry;
use nornir_feature_graph::ResolveConfig;

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

#[test]
fn a_recipe_and_the_evaluator_it_names_resolve_as_two_packages() {
    let tmp = std::env::temp_dir().join(format!("fg-same-name-{}", std::process::id()));
    let _ = std::fs::remove_dir_all(&tmp);
    // the recipe: `twin` 0.1.0, which names the evaluator under another key
    write(&tmp, "recipe/Cargo.toml", "[package]\nname = \"twin\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\nnbt = { package = \"twin\", path = \"../evaluator\", version = \"0.2.0\" }\n");
    write(&tmp, "recipe/src/lib.rs", "");
    // the evaluator: `twin` 0.2.0, with a dependency of its own
    write(&tmp, "evaluator/Cargo.toml", "[package]\nname = \"twin\"\nversion = \"0.2.0\"\nedition = \"2021\"\n\n[dependencies]\nleaf = { path = \"../leaf\", version = \"0.3.0\" }\n");
    write(&tmp, "evaluator/src/lib.rs", "");
    write(
        &tmp,
        "leaf/Cargo.toml",
        "[package]\nname = \"leaf\"\nversion = \"0.3.0\"\nedition = \"2021\"\n",
    );
    write(&tmp, "leaf/src/lib.rs", "");
    write(
        &tmp,
        "recipe/Cargo.lock",
        "version = 4\n\n[[package]]\nname = \"leaf\"\nversion = \"0.3.0\"\n\n[[package]]\nname = \"twin\"\nversion = \"0.1.0\"\ndependencies = [\n \"twin 0.2.0\",\n]\n\n[[package]]\nname = \"twin\"\nversion = \"0.2.0\"\ndependencies = [\n \"leaf\",\n]\n",
    );

    let roots = [
        ("recipe", tmp.join("recipe")),
        ("evaluator", tmp.join("evaluator")),
        ("leaf", tmp.join("leaf")),
    ];
    let roots: Vec<(&str, &Path)> = roots.iter().map(|(r, p)| (*r, p.as_path())).collect();
    let (u, load) =
        registry::universe_from_lock(&roots, &tmp.join("recipe/Cargo.lock")).expect("the universe");
    assert!(load.missing.is_empty(), "missing: {:?}", load.missing);

    let res = nornir_feature_graph::resolve(
        &u,
        "twin 0.1.0",
        &BTreeSet::new(),
        &ResolveConfig::default(),
    );
    assert!(res.is_complete(), "TRUNCATED closure: {:?}", res.unresolved);
    let got: BTreeSet<&str> = res.packages.iter().map(String::as_str).collect();
    assert_eq!(
        got,
        BTreeSet::from(["twin 0.1.0", "twin 0.2.0", "leaf 0.3.0"]),
        "the evaluator's own dependency is in the closure"
    );
    let _ = std::fs::remove_dir_all(&tmp);
}