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

nordisk/nornir

src/code_room/reader/fixture.rs

//! A code room written the way production writes one — mimir-ingest's own
//! consumer over scratch git repos — then read through the handle every front
//! door holds. The dumps are the `shop` fixture of `mimir-room`'s tests
//! (ONE source: the same file, included here), so what the reader is tested
//! against is what the room is tested against.

use std::path::Path;
use std::sync::{Arc, LazyLock};

use mimir_ingest::{EventLog, Specs};

use crate::code_room::CodeRoom;

/// What `shop.rs` expects beside it.
pub mod common {
    use std::path::Path;

    use mimir_ingest::{EventLog, RepoSpec, Specs};

    /// A scratch repo with `n` commits that each touch a `.rs` file.
    pub fn scratch(dir: &Path, n: usize) {
        crate::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();
            crate::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()
    }
}

#[path = "../../../crates/mimir-room/tests/room/shop.rs"]
pub mod shop;

/// A workspace whose code room holds what `write` consumed into it.
pub struct Workspace {
    /// The process's ONE handle of this lake ([`super::local`]): a pane that
    /// asks for the same `builds` gets this one.
    pub handle: Arc<CodeRoom>,
    /// The warehouse root the lake lives under.
    pub builds: std::path::PathBuf,
    _dir: tempfile::TempDir,
}

fn written(write: impl AsyncFnOnce(&Path) -> EventLog) -> Workspace {
    let dir = tempfile::tempdir().unwrap();
    let handle = super::local("ws", dir.path());
    let base = handle.layout().base().to_path_buf();
    std::fs::create_dir_all(&base).unwrap();
    let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
    // the writer's log is closed before the handle opens the lake (one owner)
    drop(rt.block_on(write(&base)));
    Workspace { handle, builds: dir.path().to_path_buf(), _dir: dir }
}

/// The Mímir driver's own fixture (`crates/mimir-driver/tests/fixture`: crates
/// `a`, `b` with its bin, a second version of `a`, `c`) as the git repo
/// `fixture`, dumped by the REAL driver (the pinned nightly, built here on
/// first use, as mimir-ingest's `real.rs` does) and consumed — a room over a
/// real small crate, with the consumer's own clone where the source reader
/// reads it.
pub fn real() -> &'static Workspace {
    static WS: LazyLock<Workspace> = LazyLock::new(|| {
        let bin = real_driver::build();
        written(async move |base| {
            let src = base.join("src-fixture");
            real_driver::copy_dir(&real_driver::dir().join("tests/fixture"), &src);
            crate::gitio::init(&src).unwrap();
            crate::gitio::commit_all(&src, "c1 fixture").unwrap();
            let state = base.join("driver-state");
            let mut d = mimir_ingest::DriverDumper::new(bin.join("cargo-mimir-dump"), real_driver::nightly(), state.join("target"), state.join("out"), "fixture".into());
            d.driver = Some(bin.join("mimir-driver"));
            let spec = mimir_ingest::RepoSpec {
                name: "fixture".into(),
                source: src.to_string_lossy().into_owned(),
                branch: "main".into(),
                // where the code room's source reader looks (`Layout::clone_of`)
                worktree: base.join("clones").join("fixture"),
                initial_offset: None,
            };
            let mut c = mimir_ingest::Consumer::boot(common::open_log(base).await, d).await.unwrap();
            let rep = c.run(&spec).await.unwrap();
            assert!(rep.iter().all(|r| r.status == "ok"), "{rep:?}");
            c.into_parts().0
        })
    });
    &WS
}

/// [`real`] with a SECOND commit: `a::dead` removed (nothing calls it),
/// `a::norm1` gains a doc line (a changed payload), and `a::born` is added
/// calling `a::tiny` (an added node and an added CALLS edge) — the history
/// the 3D commit diff is asked about, with the READ-FAST zone materialized.
pub fn real_diff() -> &'static Workspace {
    static WS: LazyLock<Workspace> = LazyLock::new(|| {
        let bin = real_driver::build();
        written(async move |base| {
            let src = base.join("src-fixture");
            real_driver::copy_dir(&real_driver::dir().join("tests/fixture"), &src);
            crate::gitio::init(&src).unwrap();
            crate::gitio::commit_all(&src, "c1 fixture").unwrap();
            // the second commit, edited on the COPY (the driver's own
            // fixture tree stays what it is)
            let lib = src.join("a/src/lib.rs");
            let text = std::fs::read_to_string(&lib).unwrap();
            let text = text.replace(
                "/// called by nothing: the linker drops it from b\'s binaries.\npub fn dead(x: i32) -> i32 {\n    x.rotate_left(7) ^ 0x5a\n}\n",
                "",
            );
            let text = text.replace("pub fn norm1(", "/// Norm as city distance.\n    pub fn norm1(");
            // a REWRITTEN body (same signature: invisible to the node diff,
            // visible to the links) and a REFORMATTED one
            let text = text.replace("    format!(\"{t:?}\")\n", "    let s = format!(\"{t:?}\");\n    s\n");
            let text = text.replace("    x * 3\n", "    x*3\n");
            let text = format!("{text}\n/// born in commit two: the added head.\npub fn born(x: i32) -> i32 {{\n    tiny(x) + 41\n}}\n");
            std::fs::write(&lib, text).unwrap();
            crate::gitio::commit_all(&src, "c2 dead goes, norm1 documented, born arrives").unwrap();
            let state = base.join("driver-state");
            let mut d = mimir_ingest::DriverDumper::new(bin.join("cargo-mimir-dump"), real_driver::nightly(), state.join("target"), state.join("out"), "fixture".into());
            d.driver = Some(bin.join("mimir-driver"));
            let spec = mimir_ingest::RepoSpec {
                name: "fixture".into(),
                source: src.to_string_lossy().into_owned(),
                branch: "main".into(),
                worktree: base.join("clones").join("fixture"),
                initial_offset: None,
            };
            let mut c = mimir_ingest::Consumer::boot(common::open_log(base).await, d).await.unwrap();
            let rep = c.run(&spec).await.unwrap();
            assert!(rep.len() == 2 && rep.iter().all(|r| r.status == "ok"), "{rep:?}");
            let log = c.into_parts().0;
            // the diff answers from the zone: anchor at the tip
            let m = log.materialize("fixture").await.unwrap();
            assert!(!m.skipped, "{m:?}");
            log
        })
    });
    &WS
}

/// The driver's fixture as the git repo `fixture`, its history written by
/// `commits` (each edits the working tree, then it is committed), every
/// commit dumped by the REAL driver and consumed, the READ-FAST zone
/// materialized at the tip — a room per commit, with the consumer's own
/// clone where a reader diffs commits. Returns the workspace and the shas,
/// oldest first. Not shared: each caller gets its own.
pub fn real_history(commits: &[&dyn Fn(&Path)]) -> (Workspace, Vec<String>) {
    let bin = real_driver::build();
    let shas = std::sync::Mutex::new(Vec::new());
    let ws = written(async |base| {
        let src = base.join("src-fixture");
        real_driver::copy_dir(&real_driver::dir().join("tests/fixture"), &src);
        crate::gitio::init(&src).unwrap();
        for (i, edit) in commits.iter().enumerate() {
            edit(&src);
            shas.lock().unwrap().push(crate::gitio::commit_all(&src, &format!("c{i}")).unwrap());
        }
        let state = base.join("driver-state");
        let mut d = mimir_ingest::DriverDumper::new(bin.join("cargo-mimir-dump"), real_driver::nightly(), state.join("target"), state.join("out"), "fixture".into());
        d.driver = Some(bin.join("mimir-driver"));
        let spec = mimir_ingest::RepoSpec {
            name: "fixture".into(),
            source: src.to_string_lossy().into_owned(),
            branch: "main".into(),
            worktree: base.join("clones").join("fixture"),
            initial_offset: None,
        };
        let mut c = mimir_ingest::Consumer::boot(common::open_log(base).await, d).await.unwrap();
        let rep = c.run(&spec).await.unwrap();
        assert!(rep.len() == commits.len() && rep.iter().all(|r| r.status == "ok"), "{rep:?}");
        let log = c.into_parts().0;
        let m = log.materialize("fixture").await.unwrap();
        assert!(!m.skipped, "{m:?}");
        log
    });
    (ws, shas.into_inner().unwrap())
}

/// Building and finding the real Mímir driver (its own workspace, pinned nightly).
mod real_driver {
    use std::path::{Path, PathBuf};
    use std::process::Command;

    pub fn dir() -> PathBuf {
        PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/crates/mimir-driver"))
    }

    /// `cargo` with the outer `cargo test`'s env scrubbed (its own target dir, inside this tree).
    fn scrubbed_cargo() -> Command {
        let mut c = Command::new("cargo");
        for (k, _) in std::env::vars() {
            if (k.starts_with("CARGO_") && k != "CARGO_HOME") || matches!(k.as_str(), "RUSTFLAGS" | "RUSTC_WRAPPER" | "RUSTC_WORKSPACE_WRAPPER" | "RUSTUP_TOOLCHAIN" | "RUSTC") {
                c.env_remove(k);
            }
        }
        c
    }

    /// Build mimir-driver once; its release dir.
    pub fn build() -> PathBuf {
        let st = scrubbed_cargo().current_dir(dir()).args(["build", "--release", "--bins"]).status().unwrap();
        assert!(st.success(), "building mimir-driver failed");
        dir().join("target/release")
    }

    pub fn nightly() -> String {
        let t = std::fs::read_to_string(dir().join("rust-toolchain.toml")).unwrap();
        t.lines().find_map(|l| l.trim().strip_prefix("channel = ").map(|v| v.trim_matches('"').to_string())).unwrap()
    }

    pub fn copy_dir(from: &Path, to: &Path) {
        std::fs::create_dir_all(to).unwrap();
        for e in std::fs::read_dir(from).unwrap().flatten() {
            let (p, name) = (e.path(), e.file_name());
            if name == "target" || name == "Cargo.lock" {
                continue;
            }
            if p.is_dir() { copy_dir(&p, &to.join(&name)) } else { std::fs::copy(&p, to.join(&name)).map(|_| ()).unwrap() }
        }
    }
}

/// `shop` (seq 1..2, binary layer on) and `web` (seq 1, no binary layer),
/// written once for every test of this process.
pub fn shop() -> &'static Workspace {
    static WS: LazyLock<Workspace> = LazyLock::new(|| written(async |base| shop::shop_log(base).await));
    &WS
}

/// `shop` in a lake of its own (its own rooms, its own kept answers): for a
/// test that must see a room NOT built yet.
pub fn fresh_shop() -> Workspace {
    written(async |base| shop::shop_log(base).await)
}

/// The tiny crate `v`, consumed WITHOUT the binary layer.
pub fn tiny() -> &'static Workspace {
    static WS: LazyLock<Workspace> =
        LazyLock::new(|| written(async |base| shop::consume_dumps(common::open_log(base).await, base, "v", vec![shop::tiny_commit()]).await));
    &WS
}

/// [`tiny`] in a lake of its own — for a test that writes into it (a build
/// landed beside the consumed repo).
pub fn fresh_tiny() -> Workspace {
    written(async |base| shop::consume_dumps(common::open_log(base).await, base, "v", vec![shop::tiny_commit()]).await)
}

/// A room over the tiny crate `v` in a log whose node schema carries a column
/// NO producer writes yet (`arity`) — what a next producer step adds. Opened
/// straight from the log: the handle opens a lake with the CURRENT payload
/// schema and refuses one that has a column of its own.
pub fn extras_room() -> mimir_room::Room {
    let dir = tempfile::tempdir().unwrap();
    let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
    rt.block_on(async {
        let batch = shop::nodes_with_extras(&shop::extras_rows());
        let mut specs = Specs::mimir().unwrap();
        specs.nodes =
            mimir_ingest::PayloadSpec::with_log_owned(batch.schema(), &["id"], &["repo", "valid_from_sha", "valid_to_sha", "file", "start_line", "end_line"]).unwrap();
        let wh = skade::open(dir.path().join("lake")).await.unwrap();
        let log = EventLog::open(wh, specs).await.unwrap();
        let log = shop::consume_dumps(log, dir.path(), "v", vec![shop::tiny_dump(batch)]).await;
        mimir_room::Room::build_uncached(&log, &mimir_room::Scope::current()).await.unwrap()
    })
}

/// The `depot` workspace: code that TOUCHES warehouse tables, behind a gRPC
/// service — what the board's Table nodes, the table lineage and the 🚇
/// termini are read from.
///
/// ```text
/// repo `depot`, crate depot@0.1.0
/// svc/   StockSvc + `impl StockTrait for StockSvc`      src/svc.rs
///        list()   CALLS core::plan
///        put()    CALLS store::append_stock · store::log_move
///        browse() CALLS store::preview
///        ping()                                          (reaches no table)
/// core/  plan()   CALLS store::query_stock              src/core.rs
/// store/ query_stock()  READS  stock_levels   :11       src/store.rs
///        append_stock() WRITES stock_levels   :21, :24
///        log_move()     WRITES stock_moves    :31
///        audit()        READS + WRITES stock_moves :41  (nobody calls it)
///        preview()      READS  a table named at runtime ×2  :51, :53
/// repo `till`, crate till@0.3.0: sale() READS stock_levels :7 — the SAME table node
/// ```
pub fn depot() -> &'static Workspace {
    static WS: LazyLock<Workspace> = LazyLock::new(|| {
        written(async |base| {
            let log = common::open_log(base).await;
            let log = shop::consume_dumps(log, base, depot::DEPOT, vec![depot::depot_commit(true)]).await;
            shop::consume_dumps(log, base, depot::TILL, vec![depot::till_commit()]).await
        })
    });
    &WS
}

/// `depot` with NO fn touching a table (the producer ran and found none).
pub fn depot_untouched() -> &'static Workspace {
    static WS: LazyLock<Workspace> =
        LazyLock::new(|| written(async |base| shop::consume_dumps(common::open_log(base).await, base, depot::DEPOT, vec![depot::depot_commit(false)]).await));
    &WS
}

/// The dumps of [`depot`].
pub mod depot {
    use std::collections::BTreeSet;

    use mimir_ingest::{CommitDump, CrateDump};
    use mimir_schema::{Access, EdgeKind, EdgeRow, Level, NodeRow, Origin, SPAN_EDGE, SPAN_NODE, SpanRow, TABLE_UNRESOLVED, edge_key, kind, moniker};

    pub const DEPOT: &str = "depot";
    pub const TILL: &str = "till";
    pub const STOCK_LEVELS: &str = "stock_levels";
    pub const STOCK_MOVES: &str = "stock_moves";

    pub fn d(desc: &str) -> String {
        moniker::moniker("depot", "0.1.0", desc)
    }
    fn till(desc: &str) -> String {
        moniker::moniker("till", "0.3.0", desc)
    }

    const IMPL: &str = "svc/impl#[StockSvc][StockTrait]";

    fn node(id: String, k: &str, level: Level, name: &str, parent: Option<String>) -> NodeRow {
        let (krate, version) = moniker::parse(&id).map(|(p, v, _)| (p.to_string(), v.to_string())).unwrap();
        let mut row = NodeRow::new(id, k, level, name, krate, version, Origin::Local);
        row.parent = parent;
        row
    }
    fn table(name: &str) -> NodeRow {
        NodeRow::new(moniker::table(name), kind::TABLE, Level::Item, name, moniker::TABLE_PACKAGE, moniker::TABLE_VERSION, Origin::Local)
    }
    fn e(from: &str, to: &str, k: EdgeKind) -> EdgeRow {
        EdgeRow::new(from, to, k)
    }
    fn touch(from: &str, table: &str, access: Access, count: u32) -> EdgeRow {
        EdgeRow { access: Some(access), count, ..EdgeRow::new(from, moniker::table(table), EdgeKind::TouchesTable) }
    }
    fn span(id: &str, file: &str, start: u32, end: u32) -> SpanRow {
        SpanRow { target: SPAN_NODE, key: id.into(), file: file.into(), start_line: start, end_line: end }
    }
    fn site(from: &str, table: &str, file: &str, line: u32) -> SpanRow {
        SpanRow { target: SPAN_EDGE, key: edge_key(from, &moniker::table(table), EdgeKind::TouchesTable), file: file.into(), start_line: line, end_line: line }
    }

    fn commit(owner: &str, target: &str, nodes: Vec<NodeRow>, edges: Vec<EdgeRow>, spans: Vec<SpanRow>) -> CommitDump {
        CommitDump {
            toolchain: "nightly-fixture".into(),
            workspace_crates: [owner.to_string()].into(),
            workspace_targets: [target.to_string()].into(),
            failed_units: BTreeSet::new(),
            owner_disambiguations: 0,
            crates: vec![CrateDump {
                krate: owner.into(),
                universe: false,
                nodes: vec![mimir_schema::nodes_batch(&nodes).unwrap()],
                edges: vec![mimir_schema::edges_batch(&edges).unwrap()],
                spans: vec![mimir_schema::spans_batch(&spans).unwrap()],
            }],
            binary: false,
            binaries: vec![],
        }
    }

    /// `touches` = with the TOUCHES_TABLE edges and table nodes.
    pub fn depot_commit(touches: bool) -> CommitDump {
        use EdgeKind::*;
        let m = |name: &str| format!("{IMPL}{name}().");
        let f = |module: &str, name: &str| node(d(&format!("{module}/{name}().")), kind::FN, Level::Item, name, Some(d(&format!("{module}/"))));
        let mut nodes = vec![
            node(d("crate/"), kind::CRATE, Level::Crate, "depot", None),
            node(d("svc/"), kind::MOD, Level::Module, "svc", Some(d("crate/"))),
            node(d("core/"), kind::MOD, Level::Module, "core", Some(d("crate/"))),
            node(d("store/"), kind::MOD, Level::Module, "store", Some(d("crate/"))),
            node(d("svc/StockSvc#"), kind::STRUCT, Level::Item, "StockSvc", Some(d("svc/"))),
            node(d("svc/StockTrait#"), kind::TRAIT, Level::Item, "StockTrait", Some(d("svc/"))),
            node(d(IMPL), kind::IMPL, Level::Item, "_", Some(d("svc/"))),
            f("core", "plan"),
            f("store", "query_stock"),
            f("store", "append_stock"),
            f("store", "log_move"),
            f("store", "audit"),
            f("store", "preview"),
        ];
        for name in ["list", "put", "browse", "ping"] {
            nodes.push(node(d(&m(name)), kind::METHOD, Level::Part, name, Some(d(IMPL))));
        }
        let mut edges = vec![
            e(&d(IMPL), &d("svc/StockSvc#"), ForType),
            e(&d(IMPL), &d("svc/StockTrait#"), Implements),
            e(&d(&m("list")), &d("core/plan()."), Calls),
            e(&d("core/plan()."), &d("store/query_stock()."), Calls),
            e(&d(&m("put")), &d("store/append_stock()."), Calls),
            e(&d(&m("put")), &d("store/log_move()."), Calls),
            e(&d(&m("browse")), &d("store/preview()."), Calls),
        ];
        let mut spans = vec![
            span(&d("crate/"), "src/lib.rs", 1, 5),
            span(&d("svc/"), "src/svc.rs", 1, 60),
            span(&d("core/"), "src/core.rs", 1, 20),
            span(&d("store/"), "src/store.rs", 1, 60),
            span(&d(&m("list")), "src/svc.rs", 10, 14),
            span(&d(&m("put")), "src/svc.rs", 16, 22),
            span(&d(&m("browse")), "src/svc.rs", 24, 28),
            span(&d(&m("ping")), "src/svc.rs", 30, 32),
            span(&d("core/plan()."), "src/core.rs", 3, 9),
            span(&d("store/query_stock()."), "src/store.rs", 9, 13),
            span(&d("store/append_stock()."), "src/store.rs", 19, 26),
            span(&d("store/log_move()."), "src/store.rs", 29, 33),
            span(&d("store/audit()."), "src/store.rs", 39, 43),
            span(&d("store/preview()."), "src/store.rs", 49, 55),
        ];
        if touches {
            nodes.extend([table(STOCK_LEVELS), table(STOCK_MOVES), table(TABLE_UNRESOLVED)]);
            edges.extend([
                touch(&d("store/query_stock()."), STOCK_LEVELS, Access::Read, 1),
                touch(&d("store/append_stock()."), STOCK_LEVELS, Access::Write, 2),
                touch(&d("store/log_move()."), STOCK_MOVES, Access::Write, 1),
                touch(&d("store/audit()."), STOCK_MOVES, Access::Both, 1),
                touch(&d("store/preview()."), TABLE_UNRESOLVED, Access::Read, 2),
            ]);
            spans.extend([
                site(&d("store/query_stock()."), STOCK_LEVELS, "src/store.rs", 11),
                site(&d("store/append_stock()."), STOCK_LEVELS, "src/store.rs", 21),
                site(&d("store/append_stock()."), STOCK_LEVELS, "src/store.rs", 24),
                site(&d("store/log_move()."), STOCK_MOVES, "src/store.rs", 31),
                site(&d("store/audit()."), STOCK_MOVES, "src/store.rs", 41),
                site(&d("store/preview()."), TABLE_UNRESOLVED, "src/store.rs", 51),
                site(&d("store/preview()."), TABLE_UNRESOLVED, "src/store.rs", 53),
            ]);
        }
        commit("depot@0.1.0:lib", "depot:lib", nodes, edges, spans)
    }

    pub fn till_commit() -> CommitDump {
        let sale = till("sale().");
        let nodes = vec![node(till("crate/"), kind::CRATE, Level::Crate, "till", None), node(sale.clone(), kind::FN, Level::Item, "sale", Some(till("crate/"))), table(STOCK_LEVELS)];
        let edges = vec![touch(&sale, STOCK_LEVELS, Access::Read, 1)];
        let spans = vec![span(&till("crate/"), "src/lib.rs", 1, 9), span(&sale, "src/lib.rs", 5, 9), site(&sale, STOCK_LEVELS, "src/lib.rs", 7)];
        commit("till@0.3.0:lib", "till:lib", nodes, edges, spans)
    }
}

/// A workspace nothing was consumed into.
pub fn unconsumed() -> &'static Workspace {
    static WS: LazyLock<Workspace> = LazyLock::new(|| {
        let dir = tempfile::tempdir().unwrap();
        Workspace { handle: super::local("fresh", dir.path()), builds: dir.path().to_path_buf(), _dir: dir }
    });
    &WS
}