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

nordisk/nornir

crates/mimir-room/tests/room/common.rs

//! A small hand-built log, written through mimir-ingest's own consumer (a
//! scratch git repo + a scripted dumper emitting real mimir-schema rows), so
//! the room reads exactly what production writes.
//!
//! repo `room` (crate k, binary layer ON), universe crate `dep@1.0.0`:
//!
//! ```text
//! seq 1  a→b  b→c  c→x(ffi)  a→dep h(stub)  CALLS        d→T USES_TYPE
//!        I→T FOR_TYPE   I→tr IMPLEMENTS
//!        binary: a, b, dep h linked; k t<u32> INSTANCE_OF k t; extern write
//! seq 2  + e   + e→a  + e→b   − c→x
//! seq 3  − d (and d→T)   + b→T USES_TYPE   binary no longer links b
//!        k c's type changes (payload only)   + k DEPENDS_ON dep2 (new ring crate)
//! every seq: crate k DEPENDS_ON dep · I CONTAINS m (method) · u CALLS m
//! universe: dep h → dep w(ffi) CALLS; dep2 (dumped at seq 3): dep2 z;
//!        rt (seq 1): rt unwind, linked by the binary, no DEPENDS_ON names rt
//! ```
//!
//! repo `plain` (crate p, NO binary stream), seq 1: p1→p2, p2→k a (stub) CALLS.

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

use mimir_ingest::{BinaryDump, CommitDump, Consumer, CrateDump, Dumper, EventLog, RepoSpec, Specs};
use mimir_schema::{EdgeKind, EdgeRow, Level, NodeRow, Origin, Provenance, SymbolRow, role};

pub const ROOM: &str = "room";
pub const PLAIN: &str = "plain";
pub const ARTIFACT: &str = "k@x86_64-fixture:dev";

pub fn node(id: &str, kind: &str, krate: &str, origin: Origin, defined: bool) -> NodeRow {
    let version = if krate.starts_with("dep") { "1.0.0" } else { "0.1.0" };
    let mut row = NodeRow::new(id, kind, Level::Item, id.rsplit(' ').next().unwrap_or(id), krate, version, origin);
    row.defined_here = defined;
    row
}

pub fn k(id: &str, kind: &str) -> NodeRow {
    node(&format!("k {id}"), kind, "k", Origin::Local, true)
}

pub fn edge(from: &str, to: &str, kind: EdgeKind) -> EdgeRow {
    EdgeRow::new(from, to, kind)
}

fn sym(id: &str, symbol: &str, r: &str, krate: &str) -> SymbolRow {
    SymbolRow {
        id: id.into(),
        symbol: symbol.into(),
        demangled: None,
        role: r.into(),
        krate: krate.into(),
        version: "0.1.0".into(),
        binary: Some("k".into()),
        section: (r != role::IMPORT).then(|| ".text".into()),
        addr: (r != role::IMPORT).then_some(0x1000),
        size: (r != role::IMPORT).then_some(16),
        provenance: Provenance::BinaryOnly,
        valid_from_sha: None,
        valid_to_sha: None,
    }
}

/// One commit's dump: local nodes + edges of crate `owner`, binary rows.
pub struct Step {
    pub owner: &'static str,
    pub nodes: Vec<NodeRow>,
    pub edges: Vec<EdgeRow>,
    pub binary: Option<BinaryDump>,
    /// Ring crates this commit dumps besides `dep@1.0.0` (owner, nodes, edges).
    pub ring: Vec<(&'static str, Vec<NodeRow>, Vec<EdgeRow>)>,
}

pub struct Scripted(pub Vec<Step>);

impl Dumper for Scripted {
    fn dump(&mut self, _: &Path, _: &str) -> anyhow::Result<CommitDump> {
        let s = self.0.remove(0);
        let pkg = s.owner.split('@').next().unwrap();
        let mut targets: BTreeSet<String> = [format!("{pkg}:lib")].into();
        if let Some(b) = &s.binary {
            // the artifact's bin is declared (else the consumer closes it)
            targets.insert(format!("{pkg}:bin:{}", BinaryDump::name_of(&b.artifact)));
        }
        let universe = CrateDump {
            krate: "dep@1.0.0:lib".into(),
            universe: true,
            nodes: vec![mimir_schema::nodes_batch(&[
                node("dep", "crate", "dep", Origin::Library, true),
                node("dep h", "fn", "dep", Origin::Library, true),
                node("dep w", "ffi", "dep", Origin::Library, true),
            ])?],
            edges: vec![mimir_schema::edges_batch(&[edge("dep h", "dep w", EdgeKind::Calls)])?],
            spans: vec![],
        };
        Ok(CommitDump {
            toolchain: "nightly-fixture".into(),
            workspace_crates: [s.owner.to_string()].into(),
            workspace_targets: targets,
            failed_units: BTreeSet::new(),
            owner_disambiguations: 0,
            crates: [
                vec![
                    CrateDump {
                        krate: s.owner.into(),
                        universe: false,
                        nodes: vec![mimir_schema::nodes_batch(&s.nodes)?],
                        edges: vec![mimir_schema::edges_batch(&s.edges)?],
                        spans: vec![],
                    },
                    universe,
                ],
                s.ring
                    .iter()
                    .map(|(owner, n, e)| CrateDump {
                        krate: owner.to_string(),
                        universe: true,
                        nodes: vec![mimir_schema::nodes_batch(n).unwrap()],
                        edges: vec![mimir_schema::edges_batch(e).unwrap()],
                        spans: vec![],
                    })
                    .collect(),
            ]
            .concat(),
            binary: s.binary.is_some(),
            binaries: s.binary.into_iter().collect(),
        })
    }

    /// A binary-only re-ingest (the artifact relinked): links `b` again.
    fn binaries(&mut self, _: &Path, _: &str) -> anyhow::Result<Vec<BinaryDump>> {
        Ok(vec![binary(true)])
    }
}

fn binary(link_b: bool) -> BinaryDump {
    let mut syms = vec![
        sym("k a", "_Rka", role::ITEM, "k"),
        sym("dep h", "_Rdeph", role::ITEM, "dep"),
        sym("k t<u32>", "_Rktu32", role::MONO, "k"),
        sym("extern write", "write", role::IMPORT, "extern"),
        // a ring crate the compiler links though nothing DEPENDS_ON it (a panic runtime)
        sym("rt unwind", "_Rrtunwind", role::ITEM, "rt"),
    ];
    if link_b {
        syms.push(sym("k b", "_Rkb", role::ITEM, "k"));
    }
    let mut mono = node("k t<u32>", "mono_instance", "k", Origin::Local, true);
    mono.provenance = Provenance::BinaryOnly;
    let mut imp = node("extern write", "ffi_import", "extern", Origin::Library, true);
    imp.provenance = Provenance::BinaryOnly;
    let mut inst = edge("k t<u32>", "k t", EdgeKind::InstanceOf);
    inst.provenance = Provenance::BinaryOnly;
    BinaryDump {
        artifact: ARTIFACT.into(),
        nodes: vec![mimir_schema::nodes_batch(&[mono, imp]).unwrap()],
        edges: vec![mimir_schema::edges_batch(&[inst]).unwrap()],
        symbols: vec![mimir_schema::symbols_batch(&syms).unwrap()],
        ledger: None,
        ms: 0,
    }
}

pub fn room_steps() -> Vec<Step> {
    use EdgeKind::*;
    let base = || {
        vec![
            k("a", "fn"),
            k("b", "fn"),
            k("c", "fn"),
            k("t", "fn"),
            k("T", "struct"),
            k("I", "impl"),
            k("tr", "trait"),
            k("x", "ffi"),
            k("m", "method"),
            k("u", "fn"),
            node("k", "crate", "k", Origin::Local, true),
            node("dep", "crate", "dep", Origin::Library, false),
            node("dep h", "fn", "dep", Origin::Library, false),
        ]
    };
    let e = |f: &str, t: &str, kd| edge(&format!("k {f}"), &if t.starts_with("dep ") { t.to_string() } else { format!("k {t}") }, kd);
    let s1_nodes = [base(), vec![k("d", "fn")]].concat();
    let s1_edges = vec![
        e("a", "b", Calls),
        e("b", "c", Calls),
        e("c", "x", Calls),
        e("a", "dep h", Calls),
        e("d", "T", UsesType),
        e("I", "T", ForType),
        e("I", "tr", Implements),
        e("I", "m", Contains),
        e("u", "m", Calls),
        edge("k", "dep", DependsOn),
    ];
    let s2_nodes = [base(), vec![k("d", "fn"), k("e", "fn")]].concat();
    let mut s2_edges: Vec<EdgeRow> = s1_edges.iter().filter(|x| !(x.from == "k c" && x.to == "k x")).cloned().collect();
    s2_edges.extend([e("e", "a", Calls), e("e", "b", Calls)]);
    let mut c3 = k("c", "fn");
    c3.ty = Some("fn() -> u64".into());
    let s3_nodes = [base().into_iter().filter(|n| n.id != "k c").collect(), vec![c3, k("e", "fn"), node("dep2", "crate", "dep2", Origin::Library, false)]].concat();
    let mut s3_edges: Vec<EdgeRow> = s2_edges.iter().filter(|x| x.from != "k d").cloned().collect();
    s3_edges.push(e("b", "T", UsesType));
    s3_edges.push(edge("k", "dep2", DependsOn));
    let rt = ("rt@1.0.0:lib", vec![node("rt", "crate", "rt", Origin::Lang, true), node("rt unwind", "fn", "rt", Origin::Lang, true)], vec![]);
    let dep2 = ("dep2@1.0.0:lib", vec![node("dep2", "crate", "dep2", Origin::Library, true), node("dep2 z", "fn", "dep2", Origin::Library, true)], vec![]);
    vec![
        Step { owner: "k@0.1.0:lib", nodes: s1_nodes, edges: s1_edges, binary: Some(binary(true)), ring: vec![rt] },
        Step { owner: "k@0.1.0:lib", nodes: s2_nodes, edges: s2_edges, binary: Some(binary(true)), ring: vec![] },
        Step { owner: "k@0.1.0:lib", nodes: s3_nodes, edges: s3_edges, binary: Some(binary(false)), ring: vec![dep2] },
    ]
}

pub fn plain_steps() -> Vec<Step> {
    vec![Step {
        owner: "p@0.1.0:lib",
        nodes: vec![
            node("p p1", "fn", "p", Origin::Local, true),
            node("p p2", "fn", "p", Origin::Local, true),
            node("k a", "fn", "k", Origin::Library, false),
        ],
        edges: vec![edge("p p1", "p p2", EdgeKind::Calls), edge("p p2", "k a", EdgeKind::Calls)],
        binary: None,
        ring: vec![],
    }]
}

/// A scratch repo with `n` commits that each touch a `.rs` file.
pub fn scratch(dir: &Path, n: usize) {
    nornir_git::gitio::init(dir).unwrap();
    for i in 0..n {
        std::fs::create_dir_all(dir.join("src")).unwrap();
        std::fs::write(dir.join("src/lib.rs"), format!("// commit {i}\n")).unwrap();
        nornir_git::gitio::commit_all(dir, &format!("c{i}")).unwrap();
    }
}

pub fn repo_spec(tmp: &Path, name: &str) -> RepoSpec {
    RepoSpec {
        name: name.into(),
        source: tmp.join(format!("src-{name}")).to_string_lossy().into_owned(),
        branch: "main".into(),
        worktree: tmp.join(format!("clone-{name}")),
        initial_offset: None,
    }
}

pub async fn open_log(tmp: &Path) -> EventLog {
    let wh = skade::open(tmp.join("lake")).await.unwrap();
    EventLog::open(wh, Specs::mimir().unwrap()).await.unwrap()
}

/// Consume `steps` for repo `name` (its scratch repo gets one commit per step).
pub async fn consume(log: EventLog, tmp: &Path, name: &str, steps: Vec<Step>) -> EventLog {
    scratch(&tmp.join(format!("src-{name}")), steps.len());
    let mut c = Consumer::boot(log, Scripted(steps)).await.unwrap();
    let rep = c.run(&repo_spec(tmp, name)).await.unwrap();
    assert!(rep.iter().all(|r| r.status == "ok"), "{rep:?}");
    c.into_parts().0
}

/// The whole fixture: `room` seq 1..3, `plain` seq 1.
pub async fn fixture(tmp: &Path) -> EventLog {
    let log = open_log(tmp).await;
    let log = consume(log, tmp, ROOM, room_steps()).await;
    consume(log, tmp, PLAIN, plain_steps()).await
}

/// The room cache is per process: tests that open rooms run one at a time so
/// the cache test sees only its own entries.
static SERIAL: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());

pub async fn serial() -> tokio::sync::MutexGuard<'static, ()> {
    SERIAL.lock().await
}