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

nordisk/nornir

crates/mimir-ingest/tests/ingest/owners.rs

//! Owner ids from REAL cargo output: a dependency bump changes the
//! dependent's cargo hash, never its owner id, so an unchanged dependent
//! writes no node / edge events.

use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::Arc;

use anyhow::Result;
use mimir_ingest::driver::{cargo_build, workspace_targets};
use mimir_ingest::{CommitDump, Consumer, CrateDump, Dumper, Kind};
use skade::arrow_array::{RecordBatch, StringArray};

use super::common::{Scratch, edge_spec, events, node_spec, open_log, shape};

/// Runs real `cargo check` (stable) on the worktree; owners and targets come
/// from mimir-ingest's own cargo parsing; rows are one node per `pub fn` line
/// of each local lib (ids carry no version), no edges.
struct CargoDumper {
    target: PathBuf,
    /// `(owner, cargo hash)` of `d`'s lib, per commit.
    d_units: Vec<(String, String)>,
}

impl Dumper for CargoDumper {
    fn dump(&mut self, worktree: &Path, _sha: &str) -> Result<CommitDump> {
        let o = Command::new("cargo")
            .args(["check", "--workspace", "--message-format=json", "--offline"])
            .current_dir(worktree)
            .env("CARGO_TARGET_DIR", &self.target)
            .env("RUSTC_WRAPPER", "")
            .env_remove("RUSTC_WORKSPACE_WRAPPER")
            .output()?;
        anyhow::ensure!(o.status.success(), "{}", String::from_utf8_lossy(&o.stderr));
        let build = cargo_build(&String::from_utf8_lossy(&o.stdout), worktree)?;
        let toolchain = std::env::var("RUSTUP_TOOLCHAIN").unwrap_or_else(|_| "stable".into());
        let targets = workspace_targets(worktree, &toolchain)?;
        let mut crates = Vec::new();
        for (hash, unit) in build.owners.iter().filter(|(_, u)| u.local) {
            let pkg = unit.target.split(':').next().unwrap();
            if pkg == "d" {
                self.d_units.push((unit.owner.clone(), hash.clone()));
            }
            let src = std::fs::read_to_string(worktree.join(pkg).join("src/lib.rs"))?;
            let ids: Vec<String> = src.lines().filter_map(|l| l.strip_prefix("pub fn ")).map(|l| format!("{pkg} {}", l.split('(').next().unwrap())).collect();
            let n = ids.len();
            let s = |v: Vec<String>| Arc::new(StringArray::from(v));
            crates.push(CrateDump {
                krate: unit.owner.clone(),
                universe: false,
                nodes: vec![RecordBatch::try_new(node_spec().schema.clone(), vec![s(ids), s(vec!["fn".into(); n]), s(vec!["()".into(); n]), s(vec![String::new(); n])])?],
                edges: vec![RecordBatch::new_empty(edge_spec().schema.clone())],
                spans: vec![],
            });
        }
        Ok(CommitDump {
            toolchain,
            workspace_crates: build.local_owners(),
            workspace_targets: targets.declared,
            failed_units: BTreeSet::new(),
            owner_disambiguations: build.disambiguations,
            crates,
            binary: false,
            binaries: vec![],
        })
    }
}

#[tokio::test(flavor = "current_thread")]
async fn a_dependency_version_bump_writes_no_events_for_an_unchanged_dependent() {
    let tmp = tempfile::tempdir().unwrap();
    let src = Scratch::new(tmp.path().join("src"));
    src.write("Cargo.toml", "[workspace]\nmembers = [\"a\", \"d\"]\nresolver = \"3\"\n")
        .write("a/Cargo.toml", "[package]\nname = \"a\"\nversion = \"0.1.0\"\nedition = \"2024\"\n")
        .write("a/src/lib.rs", "pub fn a() -> u32 { 1 }\n")
        .write("d/Cargo.toml", "[package]\nname = \"d\"\nversion = \"0.1.0\"\nedition = \"2024\"\n[dependencies]\na = { path = \"../a\" }\n")
        .write("d/src/lib.rs", "pub fn d() -> u32 { a::a() }\n");
    src.commit("c1");
    // Bump the DEPENDENCY's version; not one byte of d changes.
    src.write("a/Cargo.toml", "[package]\nname = \"a\"\nversion = \"0.2.0\"\nedition = \"2024\"\n");
    src.commit("c2 bump a");

    let log = open_log(&tmp.path().join("lake")).await;
    let dumper = CargoDumper { target: tmp.path().join("target"), d_units: Vec::new() };
    let mut c = Consumer::boot(log, dumper).await.unwrap();
    let rs = mimir_ingest::RepoSpec {
        name: "fx".into(),
        source: src.root.to_string_lossy().into_owned(),
        branch: "main".into(),
        worktree: tmp.path().join("clone"),
        initial_offset: None,
    };
    let report = c.run(&rs).await.unwrap();
    assert_eq!(report.iter().map(|r| r.status.as_str()).collect::<Vec<_>>(), vec!["ok", "ok"]);

    // cargo DID give d a new hash …
    let d = c.dumper_mut().d_units.clone();
    assert_eq!(d.len(), 2);
    assert_ne!(d[0].1, d[1].1, "the bump must move d's cargo hash, or this test proves nothing");
    // … but not a new owner id …
    assert_eq!((d[0].0.as_str(), d[1].0.as_str()), ("d@0.1.0:lib", "d@0.1.0:lib"));
    // … but owners are STABLE ids, so the version-only bump writes NOTHING:
    // not for d, and not for a either (no row's payload moved). Before stable
    // identities this was a close + reopen of every key a held.
    let nodes = shape(&events(c.log(), Kind::Nodes).await);
    let at2: Vec<_> = nodes.iter().filter(|e| e.1 == 2).map(|e| (e.2.as_str(), e.3.as_str())).collect();
    assert_eq!(at2, Vec::<(&str, &str)>::new());
    let owners: Vec<&str> = nodes.iter().filter(|e| e.1 == 1).map(|e| e.3.as_str()).collect();
    assert_eq!(owners, vec!["a:lib", "d:lib"], "the log's owners carry no version");
    assert!(events(c.log(), Kind::Edges).await.is_empty());
}