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
}