crates/mimir-room/tests/room/api.rs
//! The reader API over the `shop` log (see `shop.rs`): attributes and spans,
//! name lookup, bounded and mask-seeded walks, paths, edge export, quotient
//! graphs, symbols, repeated keys, the bottleneck semiring, serialisable
//! evidence, the sync door and the typed "not consumed" error — every one
//! with a known answer.
use mimir_ingest::{EventLog, PayloadSpec, Specs};
use mimir_room::{AnswerState, AttrValue, EdgeSel, GroupBy, MaskSpec, NameMatch, NameQuery, Room, Scope, ScopeError, Span, Weights};
use mimir_schema::{EdgeKind, EffectiveVisibility, Level, Provenance};
use super::common::{open_log, serial};
use super::shop::*;
fn o(r: &Room, d: &str) -> u64 {
r.ordinal(&m(d)).unwrap_or_else(|| panic!("{d} is not in the room"))
}
/// Readable paths of `ords`, in the order given.
fn paths(r: &Room, ords: &[u64]) -> Vec<String> {
ords.iter().map(|x| r.display_path(*x)).collect()
}
fn sorted(mut v: Vec<String>) -> Vec<String> {
v.sort();
v
}
fn s(v: &[&str]) -> Vec<String> {
v.iter().map(|x| x.to_string()).collect()
}
async fn current(log: &EventLog) -> Room {
Room::at(log, &Scope::current()).await.unwrap()
}
/// ONE runtime and ONE `shop` log for every read-only test here: writing the
/// log (git commits + the consumer) is most of a test's wall clock, and no
/// test below writes to it. The log lives as long as the runtime it was
/// opened on.
static RT: std::sync::LazyLock<tokio::runtime::Runtime> =
std::sync::LazyLock::new(|| tokio::runtime::Builder::new_multi_thread().worker_threads(2).enable_all().build().unwrap());
static SHOP_LOG: tokio::sync::OnceCell<(tempfile::TempDir, EventLog)> = tokio::sync::OnceCell::const_new();
async fn shared() -> &'static EventLog {
&SHOP_LOG
.get_or_init(|| async {
let tmp = tempfile::tempdir().unwrap();
let log = shop_log(tmp.path()).await;
(tmp, log)
})
.await
.1
}
/// Run a test body on the shared runtime, one at a time (the room cache is
/// per process).
fn on_shop<F: Future<Output = ()>>(body: impl FnOnce(&'static EventLog) -> F) {
RT.block_on(async {
let _g = serial().await;
body(shared().await).await
})
}
fn lines(v: &[Span<'_>]) -> Vec<(String, u32)> {
v.iter().map(|s| (s.file.to_string(), s.start_line)).collect()
}
// ---------------------------------------------------------------- R1 + R2
#[test]
fn a_node_carries_its_payload_its_path_and_its_span() {
on_shop(|log| async move {
let r = current(log).await;
let add = r.node(o(&r, ADD));
assert_eq!((add.name, add.kind, add.level, add.krate, add.version), (Some("add"), Some("method"), Some(Level::Item), Some("shop"), Some("0.1.0")));
assert_eq!(add.parent, Some(o(&r, IMPL)));
assert_eq!((add.file, add.start_line, add.end_line), (Some("src/cart.rs"), Some(9), Some(14)));
assert!(!add.is_test && add.defined && add.cfg.is_none());
assert_eq!(r.display_path(add.ordinal), "shop::cart::Cart::add", "the impl block reads as its self type");
assert_eq!(r.display_path(o(&r, IMPL)), "shop::cart::Cart");
assert_eq!(r.display_path(o(&r, ADD_ITEM)), "shop::cart::Cart::add::item");
assert_eq!(r.display_path(o(&r, CRATE)), "shop");
let t = r.node(o(&r, ADDS_UP));
assert!(t.is_test);
assert_eq!(r.display_path(t.ordinal), "shop::cart::tests::adds_up");
assert_eq!(r.node(o(&r, CHARGE)).cfg, Some("feature = \"stripe\""));
assert_eq!(r.node(o(&r, ITEMS)).ty, Some("Vec<Item>"));
assert_eq!(r.node(o(&r, ITEMS)).level, Some(Level::Part));
// a reference stub: its module is not in the room, the moniker is read instead
let send = r.node(r.ordinal(&send()).unwrap());
assert!(send.stub_only && send.parent.is_none() && send.file.is_none(), "{send:?}");
assert_eq!(r.display_path(send.ordinal), "stripe::Client::send");
// a binary-only node has no name chain and no moniker of ours: itself
assert_eq!(r.display_path(r.ordinal("extern write").unwrap()), "write");
// by column name: the typed ones, and honestly nothing for a column no producer writes
assert_eq!(r.attr(add.ordinal, "name"), Some(AttrValue::Str("add")));
assert_eq!(r.attr(add.ordinal, "parent").and_then(|v| v.as_str().map(str::to_string)), Some(m(IMPL)));
assert_eq!(r.attr(t.ordinal, "is_test"), Some(AttrValue::Bool(true)));
// what the producer says about the API surface: nominal and effective visibility, docs
assert_eq!((add.visibility, add.effective_visibility, add.has_doc, add.doc), (Some("pub"), Some(EffectiveVisibility::Direct), Some(false), None));
let charge = r.node(o(&r, CHARGE));
assert_eq!((charge.visibility, charge.effective_visibility), (Some("pub"), Some(EffectiveVisibility::Internal)), "`pub` in a private module is not API");
let co = r.node(o(&r, CHECKOUT));
assert_eq!((co.has_doc, co.doc), (Some(true), Some("Check a cart out and charge it.")));
assert_eq!(r.node(o(&r, IMPL)).visibility, None, "an impl block has none");
assert_eq!(r.attr(add.ordinal, "visibility"), Some(AttrValue::Str("pub")), "the same column, by name");
assert!(!r.has_attr("arity") && r.attr(add.ordinal, "arity").is_none(), "a column no producer writes is honestly absent");
// the attributes rustc has on the item: `#[ignore]` only means something on a test
assert_eq!((r.attr(t.ordinal, "ignored"), r.attr(add.ordinal, "ignored")), (Some(AttrValue::Bool(false)), None));
assert_eq!(r.attr(add.ordinal, "doc_hidden"), Some(AttrValue::Bool(false)));
assert!(r.has_attr("cfg") && r.attr(add.ordinal, "cfg").is_none(), "the column exists, this node has no gate");
assert_eq!(r.module_of(add.ordinal), Some(o(&r, CART)));
assert_eq!(r.module_of(o(&r, CHECKOUT)), Some(o(&r, CRATE)), "an item at the root: the crate");
assert_eq!(r.module_of(send.ordinal), None);
assert_eq!(r.impl_self(o(&r, IMPL)), Some(o(&r, CART_T)));
let st = r.stats();
assert_eq!((st.tests, st.files, st.symbols, st.orphan_spans), (2, 4, 4, 0));
assert_eq!(st.spans, 16 + 3 + 2, "lib 16 + harness 3 + web 2 node spans");
assert_eq!(st.sites, 10 + 4 + 1, "lib 10 + harness 4 new (charge→audit is the lib's) + web 1");
assert_eq!(st.extra_columns, s(&["visibility", "effective_visibility", "doc", "has_doc", "ignored", "doc_hidden", "link_name"]));
assert!(st.unread_columns.is_empty(), "{st:?}");
})
}
#[test]
fn spans_and_sites_follow_the_seq_and_never_touch_identity() {
on_shop(|log| async move {
let at = |n: i64| Scope::repo(SHOP, Some(n));
let (r1, r2) = (Room::at(log, &at(1)).await.unwrap(), Room::at(log, &at(2)).await.unwrap());
let sp = |r: &Room, d: &str| r.span(o(r, d)).map(|s| (s.repo.to_string(), s.file.to_string(), s.start_line, s.end_line));
assert_eq!(sp(&r1, CHARGE), Some((SHOP.into(), "src/pay.rs".into(), 5, 20)));
assert_eq!(sp(&r2, CHARGE), Some((SHOP.into(), "src/pay.rs".into(), 7, 22)), "seq 2 moved it");
assert_eq!(sp(&r2, AUDIT), Some((SHOP.into(), "src/pay.rs".into(), 22, 30)));
assert_eq!(r2.spans_of(o(&r2, CHARGE)).len(), 1, "the old position is closed, not kept beside the new");
let d = r1.diff(&r2).unwrap().value;
assert!(d.added_nodes.is_empty() && d.changed.is_empty() && d.edges.is_empty(), "moved code is spans only: {d:?}");
use EdgeKind::*;
let pay = |l: u32| ("src/pay.rs".to_string(), l);
let lib = |l: u32| ("src/lib.rs".to_string(), l);
assert_eq!(lines(&r1.sites(o(&r1, CHECKOUT), o(&r1, CHARGE), Calls)), vec![lib(14), lib(15), lib(18)]);
assert_eq!(lines(&r1.sites(o(&r1, CHARGE), o(&r1, AUDIT), Calls)), vec![pay(11), pay(17)]);
assert_eq!(lines(&r2.sites(o(&r2, CHARGE), o(&r2, AUDIT), Calls)), vec![pay(13), pay(19)]);
assert_eq!(lines(&r1.sites(o(&r1, CHECKOUT), o(&r1, CART_T), UsesType)), vec![lib(11)]);
assert!(r1.sites(o(&r1, CHECKOUT), o(&r1, CART_T), Calls).is_empty(), "sites are per kind");
assert!(r1.sites(o(&r1, CRATE), o(&r1, CART), Contains).is_empty(), "CONTAINS has no positioned site");
// the count column: positioned or not; two units carrying one edge do not add up
assert_eq!(r1.site_count(o(&r1, CHECKOUT), o(&r1, CHARGE), Calls).unwrap(), Some(3));
assert_eq!(r1.site_count(o(&r1, CHARGE), o(&r1, AUDIT), Calls).unwrap(), Some(2), "lib and lib+test both carry it with 2 sites: 2, not 4");
assert_eq!(r1.site_count(o(&r1, CRATE), o(&r1, CART), Contains).unwrap(), Some(1));
assert_eq!(r1.site_count(o(&r1, AUDIT), o(&r1, CHARGE), Calls).unwrap(), None, "no such edge");
// file → nodes, in line order; the same path in two repos stays apart
let r = current(log).await;
assert_eq!(
paths(&r, &r.nodes_in_file(None, "src/pay.rs")),
s(&["shop::pay", "shop::pay::charge", "shop::pay::audit", "shop::pay::open", "shop::pay::charges_once", "shop::pay::libc_write"])
);
assert_eq!(paths(&r, &r.nodes_in_file(Some(WEB), "src/lib.rs")), s(&["web", "web::render"]));
assert_eq!(paths(&r, &r.nodes_in_file(Some(SHOP), "src/lib.rs")), s(&["shop", "shop::checkout"]));
assert_eq!(r.nodes_in_file(None, "src/lib.rs").len(), 4);
assert!(r.nodes_in_file(None, "src/nope.rs").is_empty());
let files: Vec<(String, String, u64)> = r.files().iter().map(|f| (f.repo.to_string(), f.path.to_string(), f.nodes)).collect();
assert_eq!(
files,
vec![(SHOP.into(), "src/cart.rs".into(), 11), (SHOP.into(), "src/lib.rs".into(), 2), (SHOP.into(), "src/pay.rs".into(), 6), (WEB.into(), "src/lib.rs".into(), 2)]
);
let m = r.mask(&MaskSpec { files: vec!["src/lib.rs".into()], node_kinds: vec!["fn".into()], ..Default::default() }).unwrap();
assert_eq!(sorted(paths(&r, m.value.ordinals())), s(&["shop::checkout", "web::render"]));
})
}
/// What an UNCOMMITTED tree would change is the ingest's dry delta (nothing
/// is written); its touched nodes are the seeds of the dependents walk — the
/// release doctor's question, asked here of the log and the room directly.
#[test]
fn a_dry_delta_of_an_uncommitted_tree_seeds_the_dependents_walk() {
on_shop(|log| async move {
let r = current(log).await;
// the tree as it was consumed: nothing would change
let same = log.dry_delta(SHOP, &shop_tree("0.1.0", |_| true)).await.unwrap();
assert!(same.is_semantically_empty() && same.spans.is_empty(), "{same:?}");
// `checkout` is gone from the tree: web still calls it. The delta
// speaks in STABLE ids; the walk resolves them to the room's
// (versioned) nodes.
let gone = m(CHECKOUT);
let stable_checkout = mimir_schema::moniker::stable(&gone).into_owned();
let d = log.dry_delta(SHOP, &shop_tree("0.1.0", |n| n.id != gone)).await.unwrap();
assert_eq!((d.nodes.removed.clone(), d.nodes.added.len(), d.nodes.changed.len()), (vec![stable_checkout.clone()], 0, 0));
let walk = r.dependents(&d.touched_nodes(), None).unwrap();
assert_eq!(paths(&r, &walk.value.reached), s(&["web::render"]));
// a version bump keeps every identity: the payloads re-emit as
// CHANGED on the same stable ids. Only a COLLIDING key (`helper`,
// whose identity hangs on its payload hash) moves between dup
// identities.
let bumped = log.dry_delta(SHOP, &shop_tree("0.2.0", |_| true)).await.unwrap();
let dup_only = |v: &[String]| v.iter().all(|k| k.contains("\u{1f}#"));
assert!(dup_only(&bumped.nodes.removed) && dup_only(&bumped.nodes.added), "{bumped:?}");
assert!(bumped.nodes.changed.contains(&stable_checkout), "{bumped:?}");
assert!(bumped.edges.removed.is_empty() && bumped.edges.added.is_empty(), "{bumped:?}");
assert_eq!(log.offset(SHOP).await.unwrap().unwrap().seq, 2, "a dry delta moves no offset");
})
}
/// A column a producer adds later (here `arity`) is readable by
/// name the moment the log's schema has it — no change in the room. That is
/// how `visibility`, the doc columns and `ignored` / `doc_hidden` arrived.
#[tokio::test(flavor = "current_thread")]
async fn a_column_added_by_a_producer_is_readable_by_name() {
let _g = serial().await;
let tmp = tempfile::tempdir().unwrap();
let rows = extras_rows();
let batch = nodes_with_extras(&rows);
let mut specs = Specs::mimir().unwrap();
specs.nodes = PayloadSpec::with_log_owned(batch.schema(), &["id"], &["repo", "valid_from_sha", "valid_to_sha", "file", "start_line", "end_line"]).unwrap();
let wh = skade::open(tmp.path().join("lake")).await.unwrap();
let log = EventLog::open(wh, specs).await.unwrap();
let dump = tiny_dump(batch);
let log = consume_dumps(log, tmp.path(), "v", vec![dump]).await;
let r = current(&log).await;
assert_eq!(r.stats().extra_columns, s(&["visibility", "effective_visibility", "doc", "has_doc", "ignored", "doc_hidden", "link_name", "arity"]));
assert!(r.has_attr("ignored") && r.has_attr("arity"));
assert!(r.attr_names().contains(&"arity"));
let id = |d: &str| r.ordinal(&v(d)).unwrap();
assert_eq!(r.attr(id("slow_test()."), "ignored"), Some(AttrValue::Bool(true)));
assert_eq!(r.attr(id("api()."), "ignored"), None, "not a test: no such notion");
assert_eq!(r.attr(id("api()."), "arity"), Some(AttrValue::Int(2)));
assert_eq!(r.attr(id("inner()."), "arity"), Some(AttrValue::Int(0)), "0 is a value, not null");
assert_eq!(r.attr(id("crate/"), "ignored"), None, "null for this node");
assert_eq!(r.attr(id("api()."), "name"), Some(AttrValue::Str("api")));
}
/// mimir-schema 2 in the room: the API surface (visibility, docs) as masks,
/// cargo features as nodes, GATED_BY with its `negated` / `partial` flags,
/// and the items the configuration compiled OUT.
#[tokio::test(flavor = "current_thread")]
async fn the_api_surface_and_the_feature_gates() {
let _g = serial().await;
let tmp = tempfile::tempdir().unwrap();
let log = consume_dumps(open_log(tmp.path()).await, tmp.path(), "v", vec![tiny_commit()]).await;
let r = current(&log).await;
use EdgeKind::*;
let mask = |spec: MaskSpec| sorted(paths(&r, r.mask(&spec).unwrap().value.ordinals()));
assert_eq!(mask(MaskSpec { public_api: Some(true), ..Default::default() }), s(&["v::api"]));
assert_eq!(mask(MaskSpec { public_api: Some(false), ..Default::default() }), s(&["v::inner", "v::slow_test"]), "the stub and the feature have no effective visibility");
assert_eq!(mask(MaskSpec { has_doc: Some(false), is_test: Some(false), ..Default::default() }), s(&["v::inner"]));
assert_eq!(mask(MaskSpec::kinds(&["feature"])), s(&["v::fast"]));
// what a feature gates: the item itself, a part of one, and — negated — what it switches OFF
let fast = v_fast();
let gated = r.dependents_by(&[fast.as_str()], &[GatedBy], Some(1), None).unwrap();
assert_eq!(sorted(paths(&r, &gated.value.reached)), s(&["v::api", "v::inner", "v::legacy"]));
assert!(gated.evidence.is_complete(), "{}", gated.evidence);
let e = r.edges(&EdgeSel::kinds(&[GatedBy])).unwrap().value;
let flags: Vec<(String, bool, bool)> = e.edges.iter().map(|x| (r.display_path(x.from), x.negated, x.partial)).collect();
assert_eq!(sorted(flags.iter().map(|f| format!("{} negated={} partial={}", f.0, f.1, f.2)).collect()), s(&[
"v::api negated=false partial=false",
"v::inner negated=false partial=true",
"v::legacy negated=true partial=false",
]));
assert_eq!(lines(&r.sites(r.ordinal(&v("api().")).unwrap(), r.ordinal(&fast).unwrap(), GatedBy)), vec![("src/lib.rs".to_string(), 3)]);
// an item rustc never saw: a stub with what the SOURCE says, never a resolved item
let legacy = r.node(r.ordinal(&v_legacy()).unwrap());
assert_eq!((legacy.kind, legacy.cfg, legacy.ty, legacy.visibility, legacy.effective_visibility), (Some("cfg_gated_out"), Some("not(feature = \"fast\")"), Some("fn"), Some("pub"), None));
assert_eq!((legacy.file, legacy.start_line), (Some("src/lib.rs"), Some(16)));
// the build's own selection
let root = v("crate/");
assert_eq!(paths(&r, &r.reach(&[root.as_str()], &[Enables], None, None).unwrap().value.reached), s(&["v::fast"]));
// a kind the scope has no edge of names the two possible reasons
let weak = r.reach(&[root.as_str()], &[EnablesWeak], None, None).unwrap();
assert!(weak.evidence.missing_input("ENABLES_WEAK edges")[0].why.contains("mimir-schema 2"), "{}", weak.evidence);
}
// ---------------------------------------------------------------- R3
#[test]
fn names_paths_and_symbols_find_their_nodes() {
on_shop(|log| async move {
let r = current(log).await;
let find = |q: NameQuery| sorted(paths(&r, &r.lookup(&q).unwrap().value));
let q = |t: &str, mode| NameQuery::new(t).mode(mode);
assert_eq!(find(q("open", NameMatch::Name)), s(&["shop::cart::open", "shop::pay::open"]));
assert_eq!(find(q("pay::open", NameMatch::Path)), s(&["shop::pay::open"]));
assert_eq!(find(q("Cart::add", NameMatch::Path)), s(&["shop::cart::Cart::add"]));
assert_eq!(find(q("shop::cart::Cart::total", NameMatch::Path)), s(&["shop::cart::Cart::total"]));
assert_eq!(find(q("open", NameMatch::Path)), s(&["shop::cart::open", "shop::pay::open"]), "one segment = the name");
assert!(find(q("art::open", NameMatch::Path)).is_empty(), "a suffix matches on segment borders only");
assert!(find(q("cart::add", NameMatch::Path)).is_empty(), "add is Cart's, not the module's");
// a stub is found by the path its moniker spells
assert_eq!(find(q("Client::send", NameMatch::Path)), s(&["stripe::Client::send"]));
// auto: the moniker itself, a linker symbol, then the path
let auto = |t: &str| {
let a = r.lookup(&NameQuery::new(t)).unwrap();
(paths(&r, &a.value), a.evidence.notes.iter().find(|n| n.starts_with("matched as")).cloned().unwrap_or_default())
};
assert_eq!(auto(&m(TOTAL)), (s(&["shop::cart::Cart::total"]), "matched as: moniker".into()));
assert_eq!(auto("_Rcharge"), (s(&["shop::pay::charge"]), "matched as: linker symbol".into()));
assert_eq!(auto("pay::audit"), (s(&["shop::pay::audit"]), "matched as: path suffix".into()));
assert_eq!(find(q("shop::checkout", NameMatch::Symbol)), s(&["shop::checkout"]), "the demangled form");
assert_eq!(find(q("_Rcharge_closure0", NameMatch::Symbol)), s(&["shop::pay::charge"]), "a closure's symbol is its item's");
assert_eq!(find(q("CHARGE", NameMatch::Substring)), s(&["shop::pay::charge", "shop::pay::charges_once"]));
// filters
assert_eq!(find(q("open", NameMatch::Name).kinds(&["method"])), Vec::<String>::new());
assert_eq!(find(NameQuery { defined_only: true, ..q("checkout", NameMatch::Name) }), s(&["shop::checkout"]));
let web_only = r.mask(&MaskSpec { repos: vec![WEB.into()], ..Default::default() }).unwrap().value;
assert!(find(NameQuery { within: Some(web_only), ..q("checkout", NameMatch::Name) }).is_empty(), "the definition is shop's row");
let capped = r.lookup(&q("open", NameMatch::Name).limit(1)).unwrap();
assert_eq!(capped.value.len(), 1);
assert_eq!(capped.evidence.capped.map(|c| (c.shown, c.total)), Some((1, 2)));
// nothing by that name: an empty FACT, said so
let none = r.lookup(&q("nope", NameMatch::Name)).unwrap();
assert!(none.value.is_empty());
assert_eq!(none.evidence.state, AnswerState::EmptyFact, "{}", none.evidence);
// a symbol asked of a room without a binary stream: the gap, not "no such symbol"
let web = Room::at(log, &Scope::repo(WEB, None)).await.unwrap();
let gap = web.lookup(&q("_Rcharge", NameMatch::Symbol)).unwrap();
assert!(gap.value.is_empty() && !gap.evidence.missing_input("linked symbols").is_empty(), "{}", gap.evidence);
assert_eq!(gap.evidence.state, AnswerState::EmptyGap);
})
}
// ---------------------------------------------------------------- R4 + R5 + R6
#[test]
fn bounded_and_mask_seeded_walks_and_hop_layers() {
on_shop(|log| async move {
let r = current(log).await;
use EdgeKind::*;
let audit = m(AUDIT);
// direct callers only
let one = r.dependents_by(&[audit.as_str()], &[Calls], Some(1), None).unwrap();
assert_eq!(paths(&r, &one.value.reached), s(&["shop::pay::charge"]));
let two = r.dependents_by(&[audit.as_str()], &[Calls], Some(2), None).unwrap();
assert_eq!(sorted(paths(&r, &two.value.reached)), s(&["shop::checkout", "shop::pay::charge", "shop::pay::charges_once"]));
let all = r.dependents_by(&[audit.as_str()], &[Calls], None, None).unwrap();
assert_eq!(sorted(paths(&r, &all.value.reached)), s(&["shop::checkout", "shop::pay::charge", "shop::pay::charges_once", "web::render"]), "across repos");
// seeds as a mask: everything the tests reach
let tests = r.mask(&MaskSpec { is_test: Some(true), ..Default::default() }).unwrap().value;
assert_eq!(sorted(paths(&r, tests.ordinals())), s(&["shop::cart::tests::adds_up", "shop::pay::charges_once"]));
let reached = r.reach(&tests, &[Calls], None, None).unwrap();
assert_eq!(
sorted(paths(&r, &reached.value.reached)),
s(&["shop::cart::Cart::add", "shop::cart::Cart::total", "shop::cart::open", "shop::pay::audit", "shop::pay::charge", "shop::pay::libc_write", "stripe::Client::send"])
);
assert_eq!(reached.value.seeds.len(), 2);
// seeds as ordinals: the same walk as by moniker
let by_ord = r.reach(&[o(&r, CHECKOUT)], &[Calls], Some(1), None).unwrap();
let checkout = m(CHECKOUT);
let by_name = r.reach(&[checkout.as_str()], &[Calls], Some(1), None).unwrap();
assert_eq!(by_ord.value, by_name.value);
assert_eq!(sorted(paths(&r, &by_ord.value.reached)), s(&["shop::cart::Cart::total", "shop::pay::charge"]));
// an ordinal of another room is said, not silently dropped
let far = r.reach(&[9_999u64], &[Calls], None, None).unwrap();
assert!(far.value.seeds.is_empty() && !far.evidence.missing_input("seed").is_empty(), "{}", far.evidence);
// mask set algebra
let fns = r.mask(&MaskSpec::kinds(&["fn"])).unwrap().value;
assert_eq!(fns.intersect(&tests).len(), 2);
assert_eq!(fns.minus(&tests).len(), fns.len() - 2);
assert_eq!(tests.union(&r.mask_of(&[o(&r, CHECKOUT)])).len(), 3);
let gated = r.mask(&MaskSpec { with_cfg: true, ..Default::default() }).unwrap().value;
assert_eq!(sorted(paths(&r, gated.ordinals())), s(&["shop::cart::tests", "shop::pay::charge"]));
// the undocumented API: nameable from outside (direct or re-exported), no doc comment
let undoc = r.mask(&MaskSpec { public_api: Some(true), has_doc: Some(false), node_kinds: vec!["fn".into(), "method".into()], ..Default::default() }).unwrap().value;
assert_eq!(sorted(paths(&r, undoc.ordinals())), s(&["shop::cart::Cart::add", "shop::cart::open"]), "charge is `pub` but internal; total and checkout are documented");
let mods = r.mask(&MaskSpec { levels: vec![Level::Module], ..Default::default() }).unwrap().value;
assert_eq!(sorted(paths(&r, mods.ordinals())), s(&["shop::cart", "shop::cart::tests", "shop::pay"]));
// hop layers from an entry point
let layers = r.hops_from(&[checkout.as_str()], &[Calls], None, None).unwrap();
let got: Vec<(String, u64)> = layers.value.iter().map(|(n, h)| (r.display_path(*n), *h)).collect();
let mut want = vec![
("shop::cart::Cart::total".to_string(), 1),
("shop::pay::charge".into(), 1),
("shop::cart::open".into(), 2),
("shop::pay::audit".into(), 2),
("stripe::Client::send".into(), 2),
("shop::pay::libc_write".into(), 3),
];
let mut got_sorted = got.clone();
got_sorted.sort_by(|a, b| a.1.cmp(&b.1).then(a.0.cmp(&b.0)));
want.sort_by(|a, b| a.1.cmp(&b.1).then(a.0.cmp(&b.0)));
assert_eq!(got_sorted, want);
assert!(got.windows(2).all(|w| w[0].1 <= w[1].1), "by hops: {got:?}");
let two = r.hops_from(&[checkout.as_str()], &[Calls], Some(1), None).unwrap();
assert_eq!(two.value.len(), 2);
})
}
#[test]
fn a_shortest_path_names_the_kind_of_every_hop() {
on_shop(|log| async move {
let r = current(log).await;
use EdgeKind::*;
let (render, write, checkout) = (render(), m(WRITE), m(CHECKOUT));
let p = r.path(&[render.as_str()], &[write.as_str()], &[Calls], None, None).unwrap();
let found = p.value.expect("render reaches libc_write");
assert_eq!(paths(&r, &found.nodes), s(&["web::render", "shop::checkout", "shop::pay::charge", "shop::pay::audit", "shop::pay::libc_write"]));
assert_eq!(found.kinds, vec![Calls; 4]);
assert_eq!(found.hops(), 4);
assert_eq!(p.evidence.state, AnswerState::Complete, "{}", p.evidence);
// two kinds: each hop says which one it walked
let imp = m(IMPL);
let open = m(CART_OPEN);
let mixed = r.path(&[imp.as_str()], &[open.as_str()], &[Calls, Contains], None, None).unwrap().value.unwrap();
assert_eq!(paths(&r, &mixed.nodes), s(&["shop::cart::Cart", "shop::cart::Cart::total", "shop::cart::open"]));
assert_eq!(mixed.kinds, vec![Contains, Calls]);
// the SHORTEST: crate → pay → libc_write (2 CONTAINS), not the 3-hop call chain from checkout
let root = m(CRATE);
let short = r.path(&[root.as_str()], &[write.as_str()], &[Calls, Contains], None, None).unwrap().value.unwrap();
assert_eq!(paths(&r, &short.nodes), s(&["shop", "shop::pay", "shop::pay::libc_write"]));
assert_eq!(short.kinds, vec![Contains, Contains]);
// sets on both ends: the nearest pair
let tests = r.mask(&MaskSpec { is_test: Some(true), ..Default::default() }).unwrap().value;
let ffi = r.mask(&MaskSpec::kinds(&["ffi"])).unwrap().value;
let near = r.path(&tests, &ffi, &[Calls], None, None).unwrap().value.unwrap();
assert_eq!(paths(&r, &near.nodes), s(&["shop::pay::charges_once", "shop::pay::charge", "shop::pay::audit", "shop::pay::libc_write"]));
// a node in both sets: zero hops
let zero = r.path(&[checkout.as_str()], &[checkout.as_str()], &[Calls], None, None).unwrap().value.unwrap();
assert_eq!((zero.nodes.len(), zero.hops()), (1, 0));
// no path: against the direction, past the hop bound, outside the mask
let back = r.path(&[write.as_str()], &[checkout.as_str()], &[Calls], None, None).unwrap();
assert!(back.value.is_none());
assert_eq!(back.evidence.state, AnswerState::EmptyFact, "edges are there, the path is not: {}", back.evidence);
assert!(r.path(&[render.as_str()], &[write.as_str()], &[Calls], Some(3), None).unwrap().value.is_none(), "4 hops needed");
assert!(r.path(&[render.as_str()], &[write.as_str()], &[Calls], Some(4), None).unwrap().value.is_some());
let shop_only = r.mask(&MaskSpec { crates: vec!["shop".into()], ..Default::default() }).unwrap().value;
let masked = r.path(&[render.as_str()], &[write.as_str()], &[Calls], None, Some(&shop_only)).unwrap();
assert!(masked.value.is_none() && !masked.evidence.missing_input("seed").is_empty(), "render lies outside the mask: {}", masked.evidence);
// an end the room does not know: the gap is named
let unknown = r.path(&["nope"], &[write.as_str()], &[Calls], None, None).unwrap();
assert!(unknown.value.is_none());
assert_eq!(unknown.evidence.state, AnswerState::EmptyGap);
// a kind nobody produces
let gated = r.path(&[render.as_str()], &[write.as_str()], &[GatedBy], None, None).unwrap();
assert!(gated.value.is_none() && !gated.evidence.missing_input("GATED_BY edges").is_empty());
})
}
// ---------------------------------------------------------------- R7 + R8
#[test]
fn edges_come_out_with_their_site_counts() {
on_shop(|log| async move {
let r = current(log).await;
use EdgeKind::*;
let all = r.edges(&EdgeSel::kinds(&[Calls])).unwrap();
assert_eq!((all.value.total, all.value.edges.len()), (10, 10), "lib 6 + harness 3 + web 1 (charge→audit once)");
assert_eq!(all.value.sites, 1 + 3 + 1 + 2 + 1 + 1 + 2 + 1 + 1 + 1);
assert_eq!(all.evidence.kinds, vec![(Calls, 10)]);
let row = |f: &str, t: &str| all.value.edges.iter().find(|e| e.from == o(&r, f) && e.to == o(&r, t)).map(|e| e.count);
assert_eq!(row(CHECKOUT, CHARGE), Some(3));
assert_eq!(row(CHARGE, AUDIT), Some(2));
assert_eq!(row(ADDS_UP, ADD), Some(2));
assert_eq!(row(TOTAL, CART_OPEN), Some(1));
let top = r.edges(&EdgeSel { heaviest_first: true, ..EdgeSel::kinds(&[Calls]).cap(1) }).unwrap();
assert_eq!(top.value.edges.len(), 1);
assert_eq!((top.value.edges[0].from, top.value.edges[0].to, top.value.edges[0].count), (o(&r, CHECKOUT), o(&r, CHARGE), 3));
assert_eq!((top.value.total, top.value.sites), (10, 14), "the cap cuts the list, not the totals");
assert_eq!(top.evidence.capped.map(|c| (c.shown, c.total)), Some((1, 10)));
let tests = r.mask(&MaskSpec { is_test: Some(true), ..Default::default() }).unwrap().value;
let from_tests = r.edges(&EdgeSel { from: Some(&tests), ..EdgeSel::kinds(&[Calls, Tests]) }).unwrap();
assert_eq!(from_tests.value.total, 6, "3 CALLS + 3 TESTS");
let pay = r.mask(&MaskSpec { files: vec!["src/pay.rs".into()], ..Default::default() }).unwrap().value;
let inside = r.edges(&EdgeSel::kinds(&[Calls]).inside(&pay)).unwrap();
let got: Vec<(String, String, u32)> = inside.value.edges.iter().map(|e| (r.display_path(e.from), r.display_path(e.to), e.count)).collect();
let mut got = got;
got.sort();
assert_eq!(
got,
vec![
("shop::pay::audit".into(), "shop::pay::libc_write".into(), 1),
("shop::pay::charge".into(), "shop::pay::audit".into(), 2),
("shop::pay::charges_once".into(), "shop::pay::charge".into(), 1)
]
);
let none = r.edges(&EdgeSel::kinds(&[TouchesTable])).unwrap();
assert_eq!(none.value.total, 0);
assert_eq!(none.evidence.state, AnswerState::EmptyGap, "no producer: {}", none.evidence);
})
}
#[test]
fn the_quotient_graph_by_crate_module_repo_and_file() {
on_shop(|log| async move {
let r = current(log).await;
use EdgeKind::*;
let edges = |q: &mimir_room::Quotient| -> Vec<(String, String, u64, u64)> {
q.edges.iter().map(|e| (q.groups[e.from].key.clone(), q.groups[e.to].key.clone(), e.edges, e.sites)).collect()
};
let group = |q: &mimir_room::Quotient, key: &str| q.groups.iter().find(|g| g.key == key).cloned().unwrap_or_else(|| panic!("no group {key}"));
// ---- by crate
let q = r.quotient(GroupBy::Crate, &[Calls], None).unwrap().value;
assert_eq!(q.groups.iter().map(|g| g.key.as_str()).collect::<Vec<_>>(), vec!["extern@0", "shop@0.1.0", "stripe@2.0.0", "web@0.2.0"]);
assert_eq!(edges(&q), vec![("shop@0.1.0".into(), "stripe@2.0.0".into(), 1, 1), ("web@0.2.0".into(), "shop@0.1.0".into(), 1, 1)]);
let shop = group(&q, "shop@0.1.0");
assert_eq!((shop.label.as_str(), shop.nodes, shop.internal, shop.fan_out, shop.fan_in, shop.files), ("shop", 19, 8, 1, 1, 3));
assert_eq!(shop.node, Some(o(&r, CRATE)), "the crate group knows its crate node");
assert_eq!(shop.node_kinds["fn"], 8);
assert_eq!(shop.node_kinds["method"], 2);
assert_eq!(q.ungrouped, 0);
// ---- by module: the board's nodes
let q = r.quotient(GroupBy::Module, &[Calls], None).unwrap();
assert_eq!(
q.value.groups.iter().map(|g| (g.key.as_str(), g.nodes)).collect::<Vec<_>>(),
vec![("shop", 2), ("shop::cart", 9), ("shop::cart::tests", 2), ("shop::pay", 6), ("stripe", 1), ("web", 2)]
);
assert_eq!(
edges(&q.value),
vec![
("shop".into(), "shop::cart".into(), 1, 1),
("shop".into(), "shop::pay".into(), 1, 3),
("shop::cart::tests".into(), "shop::cart".into(), 2, 3),
("web".into(), "shop".into(), 1, 1),
]
);
let pay = group(&q.value, "shop::pay");
assert_eq!((pay.label.as_str(), pay.internal, pay.fan_in, pay.fan_out, pay.node), ("pay", 3, 1, 0, Some(o(&r, PAY))));
assert_eq!(q.value.ungrouped, 2, "the stripe stub's module is not in the room; `extern write` has no parent");
assert!(q.evidence.notes.iter().any(|n| n.contains("2 node(s) belong to no group")), "{}", q.evidence);
// several kinds at once, per kind inside an edge
let q2 = r.quotient(GroupBy::Module, &[Calls, UsesType, Tests], None).unwrap().value;
let e = q2.edges.iter().find(|e| q2.groups[e.from].key == "shop::cart::tests" && q2.groups[e.to].key == "shop::cart").unwrap();
assert_eq!((e.edges, e.kinds.get(&Calls), e.kinds.get(&Tests)), (4, Some(&2), Some(&2)));
let e = q2.edges.iter().find(|e| q2.groups[e.from].key == "shop" && q2.groups[e.to].key == "shop::cart").unwrap();
assert_eq!((e.edges, e.kinds.get(&Calls), e.kinds.get(&UsesType)), (2, Some(&1), Some(&1)));
// ---- by repo
let q = r.quotient(GroupBy::Repo, &[Calls], None).unwrap().value;
assert_eq!(q.groups.iter().map(|g| (g.key.as_str(), g.nodes)).collect::<Vec<_>>(), vec![(SHOP, 22), (WEB, 2)]);
assert_eq!(edges(&q), vec![(WEB.into(), SHOP.into(), 1, 1)]);
// ---- by file
let q = r.quotient(GroupBy::File, &[Calls], None).unwrap().value;
assert_eq!(q.groups.iter().map(|g| (g.key.as_str(), g.label.as_str())).collect::<Vec<_>>(), vec![
("shop:src/cart.rs", "cart.rs"),
("shop:src/lib.rs", "lib.rs"),
("shop:src/pay.rs", "pay.rs"),
("web:src/lib.rs", "lib.rs")
]);
assert_eq!(
edges(&q),
vec![
("shop:src/lib.rs".into(), "shop:src/cart.rs".into(), 1, 1),
("shop:src/lib.rs".into(), "shop:src/pay.rs".into(), 1, 3),
("web:src/lib.rs".into(), "shop:src/lib.rs".into(), 1, 1),
]
);
assert_eq!(group(&q, "shop:src/cart.rs").internal, 3, "total→open, adds_up→add, adds_up→total: inside one file");
// ---- inside a mask: only its nodes form groups
let shop_only = r.mask(&MaskSpec { repos: vec![SHOP.into()], defined_only: true, ..Default::default() }).unwrap().value;
let q = r.quotient(GroupBy::Module, &[Calls], Some(&shop_only)).unwrap().value;
assert!(q.groups.iter().all(|g| g.key.starts_with("shop")), "{:?}", q.groups.iter().map(|g| &g.key).collect::<Vec<_>>());
assert_eq!(q.edges.len(), 3);
})
}
// ---------------------------------------------------------------- R9 + R10 + R11
#[test]
fn linked_symbols_with_sizes_and_the_repeated_keys() {
on_shop(|log| async move {
let r = current(log).await;
let charge = r.symbols_of(o(&r, CHARGE));
assert_eq!(
charge.iter().map(|s| (s.symbol, s.role, s.size, s.artifact, s.section, s.demangled)).collect::<Vec<_>>(),
vec![
("_Rcharge", Some("item"), Some(300), ARTIFACT, Some(".text"), Some("shop::pay::charge")),
("_Rcharge_closure0", Some("closure"), Some(40), ARTIFACT, Some(".text"), None)
]
);
assert_eq!(r.linked_bytes(o(&r, CHARGE)), 340);
assert_eq!(r.linked_bytes(o(&r, AUDIT)), 0, "not linked");
assert_eq!(r.node(o(&r, CHARGE)).provenance, Some(Provenance::Both));
let all = r.symbols(None, None).unwrap();
assert_eq!(all.value.len(), 4);
let import = all.value.iter().find(|s| s.symbol == "write").unwrap();
assert_eq!((import.role, import.size, import.binary), (Some("import"), None, Some("shop")));
let fns = r.mask(&MaskSpec::kinds(&["fn"])).unwrap().value;
assert_eq!(r.symbols(Some(&fns), None).unwrap().value.len(), 3, "checkout + charge ×2");
let cut = r.symbols(None, Some(1)).unwrap();
assert_eq!((cut.value.len(), cut.evidence.capped.map(|c| c.total)), (1, Some(4)));
// a repo without a binary stream: the gap is named
let web = Room::at(log, &Scope::repo(WEB, None)).await.unwrap();
let none = web.symbols(None, None).unwrap();
assert!(none.value.is_empty() && !none.evidence.missing_input("binary stream").is_empty(), "{}", none.evidence);
// ---- repeated keys
let d = r.duplicate_keys(None).unwrap();
let got: Vec<(String, &str, u32, u32, u32, Vec<String>)> =
d.value.iter().map(|k| (r.display_path(k.node), k.rows_of, k.rows, k.distinct_payloads, k.key_dup_rows, k.owners.clone())).collect();
assert_eq!(
sorted(got.iter().map(|g| g.0.clone()).collect()),
s(&["shop::cart::helper", "shop::pay::audit"]),
"add / total / charge are described twice too, identically: not listed. {got:?}"
);
// owners surface as the STABLE unit names (the events own by them)
let helper = got.iter().find(|g| g.0 == "shop::cart::helper").unwrap();
assert_eq!((helper.1, helper.2, helper.3, helper.4, helper.5.clone()), ("definition", 2, 2, 2, s(&["shop:lib"])), "ONE unit repeated the key");
let audit = got.iter().find(|g| g.0 == "shop::pay::audit").unwrap();
assert_eq!((audit.1, audit.2, audit.3, audit.4, audit.5.clone()), ("definition", 2, 2, 0, s(&["shop:lib", "shop:lib+test"])), "two units disagree");
assert_eq!(r.stats().duplicate_keys, 2);
// the ledger counted the same producer collision
assert_eq!(log.runs(SHOP).await.unwrap()[0].key_collisions, 1);
let web_dups = web.duplicate_keys(None).unwrap();
assert!(web_dups.value.is_empty());
assert_eq!(web_dups.evidence.state, AnswerState::EmptyFact);
})
}
#[test]
fn the_bottleneck_walk_is_the_best_weakest_hop() {
on_shop(|log| async move {
let r = current(log).await;
use EdgeKind::*;
let send = r.ordinal(&send()).unwrap();
let w = Weights { default: 10, set: vec![(o(&r, CHARGE), 4), (o(&r, AUDIT), 7), (send, 9), (o(&r, WRITE), 8)] };
let named = |v: &[(u64, u64)]| {
let mut x: Vec<(String, u64)> = v.iter().map(|(n, t)| (r.display_path(*n), *t)).collect();
x.sort();
x
};
let checkout = m(CHECKOUT);
let b = r.bottleneck(&[checkout.as_str()], &[Calls], &w, None).unwrap();
assert_eq!(
named(&b.value),
vec![
("shop::cart::Cart::total".into(), 10),
("shop::cart::open".into(), 10),
("shop::checkout".into(), 10),
("shop::pay::audit".into(), 4),
("shop::pay::charge".into(), 4),
("shop::pay::libc_write".into(), 4),
("stripe::Client::send".into(), 4),
],
"everything behind charge (4) is capped at 4, however clean it is itself"
);
// a second entry: audit seeds itself at 7, and its callee rises with it (max over paths)
let audit = m(AUDIT);
let two = r.bottleneck(&[checkout.as_str(), audit.as_str()], &[Calls], &w, None).unwrap();
let v = named(&two.value);
assert_eq!(v.iter().find(|x| x.0 == "shop::pay::audit").unwrap().1, 7);
assert_eq!(v.iter().find(|x| x.0 == "shop::pay::libc_write").unwrap().1, 7, "min(7, 8) beats the 4 through charge");
assert_eq!(v.iter().find(|x| x.0 == "stripe::Client::send").unwrap().1, 4);
// a node nobody reaches is absent — no provenance is not a weight
assert!(!b.value.iter().any(|(n, _)| *n == o(&r, PAY_OPEN)));
// no edge of the kind: the seeds keep their own weight, and the gap is said
let none = r.bottleneck(&[o(&r, CHARGE)], &[TouchesTable], &w, None).unwrap();
assert_eq!(none.value, vec![(o(&r, CHARGE), 4)]);
assert_eq!(none.evidence.state, AnswerState::Partial, "{}", none.evidence);
// worst value on any path FROM a node
let worst = r.worst_on_path(&[Calls], &[(o(&r, WRITE), 5), (send, 9)], None).unwrap();
assert_eq!(
named(&worst.value),
vec![
("shop::checkout".into(), 9),
("shop::pay::audit".into(), 5),
("shop::pay::charge".into(), 9),
("shop::pay::charges_once".into(), 9),
("shop::pay::libc_write".into(), 5),
("stripe::Client::send".into(), 9),
("web::render".into(), 9),
]
);
})
}
// ---------------------------------------------------------------- R12 + R14 + R15
#[test]
fn evidence_travels_as_json_and_back() {
on_shop(|log| async move {
let r = current(log).await;
let checkout = m(CHECKOUT);
let a = r.reach(&[checkout.as_str(), "nope"], &[EdgeKind::Calls, EdgeKind::GatedBy], None, None).unwrap();
let j = serde_json::to_value(&a.evidence).unwrap();
assert_eq!(j["state"], "partial", "a value, with a kind and a seed missing");
assert_eq!(j["kinds"], serde_json::json!([{"kind": "CALLS", "edges": 10}, {"kind": "GATED_BY", "edges": 0}]));
assert!(j["missing"].as_array().unwrap().iter().any(|m| m["input"] == "GATED_BY edges"));
assert!(j["missing"].as_array().unwrap().iter().any(|m| m["input"] == "seed" && m["why"].as_str().unwrap().contains("nope")));
assert_eq!(j["inputs"][0]["table"], "code_node_events");
assert!(j.get("capped").is_none(), "absent unless a cap cut the answer");
let back: mimir_room::Evidence = serde_json::from_value(j).unwrap();
assert_eq!(back, a.evidence);
// the whole answer, and the values a front door prints
let whole = serde_json::to_value(&a).unwrap();
assert_eq!(whole["value"]["seeds"].as_array().unwrap().len(), 1);
let node = serde_json::to_value(r.node(o(&r, CHARGE))).unwrap();
assert_eq!((node["name"].as_str(), node["level"].as_str(), node["provenance"].as_str(), node["crate"].as_str()), (Some("charge"), Some("item"), Some("both"), Some("shop")));
assert_eq!(node["file"], "src/pay.rs");
let stats = serde_json::to_value(r.stats()).unwrap();
assert_eq!(stats["edges"]["CALLS"], 10);
assert_eq!(stats["provenance"]["both"], 2);
let p = r.path(&[checkout.as_str()], &[o(&r, AUDIT)], &[EdgeKind::Calls], None, None).unwrap();
assert_eq!(serde_json::to_value(&p.value).unwrap()["kinds"], serde_json::json!(["CALLS", "CALLS"]));
let q = serde_json::to_value(r.quotient(GroupBy::Crate, &[EdgeKind::Calls], None).unwrap().value).unwrap();
assert_eq!(q["by"], "crate");
assert_eq!(q["edges"][0]["kinds"]["CALLS"], 1);
// every state has its word
for (state, word) in [(AnswerState::Complete, "complete"), (AnswerState::Partial, "partial"), (AnswerState::EmptyFact, "empty_fact"), (AnswerState::EmptyGap, "empty_gap")] {
assert_eq!(serde_json::to_value(state).unwrap(), word);
assert_eq!(state.as_str(), word);
}
})
}
#[test]
fn the_build_phases_are_named_for_the_log() {
on_shop(|log| async move {
let r = current(log).await;
let note = r.stats().build.phase_note();
for part in ["fold ", "index ", "sort ", "fill ", "symbols ", "provenance ", "matrices ", "spans ", " ms"] {
assert!(note.contains(part), "`{note}` misses `{part}`");
}
})
}
#[test]
fn a_repo_that_was_never_consumed_is_a_typed_state() {
on_shop(|log| async move {
let err = Room::at(log, &Scope::repo("nobody", None)).await.err().expect("no offset");
match err.downcast_ref::<ScopeError>() {
Some(ScopeError::NotConsumed { repo, known }) => {
assert_eq!(repo, "nobody");
assert_eq!(known, &s(&[SHOP, WEB]));
}
other => panic!("expected NotConsumed, got {other:?} ({err:#})"),
}
// a seq the repo never had is another state, not "not consumed"
let past = Room::at(log, &Scope::repo(SHOP, Some(9))).await.err().expect("past the offset");
assert!(matches!(past.downcast_ref::<ScopeError>(), Some(ScopeError::PastOffset { seq: 9, offset: 2, .. })), "{past:#}");
let neg = Room::at(log, &Scope::repo(SHOP, Some(-1))).await.err().expect("negative");
assert!(matches!(neg.downcast_ref::<ScopeError>(), Some(ScopeError::NegativeSeq { .. })), "{neg:#}");
// an empty log: every repo is unconsumed, and a scope of "all" is an empty room that says so
let tmp2 = tempfile::tempdir().unwrap();
let empty = open_log(tmp2.path()).await;
let none = Room::at(&empty, &Scope::repo(SHOP, None)).await.err().expect("nothing consumed");
assert!(matches!(none.downcast_ref::<ScopeError>(), Some(ScopeError::NotConsumed { known, .. }) if known.is_empty()), "{none:#}");
let nothing = Room::at(&empty, &Scope::current()).await.unwrap();
assert!(nothing.is_empty() && nothing.scope().repos.is_empty());
let q = nothing.lookup(&NameQuery::new("x")).unwrap();
assert!(!q.evidence.missing_input("repos").is_empty(), "{}", q.evidence);
})
}
/// The sync door on the shared log: the same answers, the same cache.
#[test]
fn the_sync_door_gives_the_same_room_as_the_async_one() {
on_shop(|log| async move {
Room::clear_cache();
// called here INSIDE a runtime context (block_on of the shared runtime)
let opened = Room::open_blocking(log, &Scope::repo(SHOP, None)).unwrap();
assert!(!opened.cached);
assert_eq!(opened.room.display_path(o(&opened.room, ADD)), "shop::cart::Cart::add");
let again = Room::open(log, &Scope::repo(SHOP, None)).await.unwrap();
assert!(again.cached && again.room.same_snapshot(&opened.room), "one process cache behind both doors");
let err = Room::at_blocking(log, &Scope::repo("nobody", None)).err().expect("no offset");
assert!(matches!(err.downcast_ref::<ScopeError>(), Some(ScopeError::NotConsumed { .. })), "{err:#}");
})
}
/// From plain sync code, no runtime on the thread at all.
#[test]
fn the_sync_door_opens_with_no_runtime_on_the_thread() {
let log = RT.block_on(async {
let _g = serial().await;
shared().await
});
assert!(tokio::runtime::Handle::try_current().is_err(), "this thread has no runtime");
let room = Room::at_blocking(log, &Scope::current()).unwrap();
assert_eq!(room.scope().repos.len(), 2);
assert_eq!(room.lookup(&NameQuery::new("Cart::total")).unwrap().value, vec![o(&room, TOTAL)]);
}
/// On a CURRENT-THREAD runtime — where blocking on the runtime itself would
/// panic ("cannot start a runtime from within a runtime") and blocking on
/// its handle would hang — with a log opened on that very runtime.
#[tokio::test(flavor = "current_thread")]
async fn the_sync_door_opens_on_a_current_thread_runtime() {
let _g = serial().await;
let tmp = tempfile::tempdir().unwrap();
let log = super::shop::consume_dumps(open_log(tmp.path()).await, tmp.path(), "v", vec![tiny_commit()]).await;
let room = Room::at_blocking(&log, &Scope::current()).unwrap();
assert_eq!(room.len(), 6);
assert_eq!(room.display_path(room.lookup(&NameQuery::new("api")).unwrap().value[0]), "v::api");
}
/// The room over the READ-FAST zone is the same room: materialize both
/// repos (its own lake — the shared one stays event-only for the other
/// tests), drop the cache, build again — the same nodes, edges, spans and
/// answers, now read as interval rows + tail instead of the event history.
#[test]
fn the_room_over_the_read_fast_zone_is_the_same_room() {
RT.block_on(async {
let _g = serial().await;
let tmp = tempfile::tempdir().unwrap();
let log = super::shop::shop_log(tmp.path()).await;
let before = Room::at(&log, &Scope::current()).await.unwrap();
let monikers = sorted((0..before.len() as u64).map(|o| before.moniker(o).to_string()).collect());
for repo in [SHOP, WEB] {
let r = log.materialize(repo).await.unwrap();
assert!(!r.skipped && r.rows > 0, "{r:?}");
}
Room::clear_cache();
let after = Room::at(&log, &Scope::current()).await.unwrap();
assert_eq!(sorted((0..after.len() as u64).map(|o| after.moniker(o).to_string()).collect()), monikers, "the same nodes");
let (b, a) = (before.stats(), after.stats());
assert_eq!((a.nodes, &a.edges, &a.provenance, &a.node_kinds), (b.nodes, &b.edges, &b.provenance, &b.node_kinds));
assert_eq!((a.spans, a.files, a.sites, a.endpoint_only), (b.spans, b.files, b.sites, b.endpoint_only));
// an answer through the walk, as before
assert_eq!(
paths(&after, &after.dependents(&[m(CHECKOUT).as_str()][..], None).unwrap().value.reached),
paths(&before, &before.dependents(&[m(CHECKOUT).as_str()][..], None).unwrap().value.reached)
);
// and time travel: the room as of seq 1 is the seq-1 room either way
Room::clear_cache();
let early = Room::at(&log, &Scope::repo(SHOP, Some(1))).await.unwrap();
assert!(early.len() > 0 && early.len() < after.len(), "seq 1 is a smaller shop");
})
}
/// **The commit window from the READ-FAST zone is the as-of rooms' diff.**
/// Four commits of `shop` without a binary stream: an item dropped and back,
/// a payload change, a visibility change, an item dropped while an edge still
/// names it (an endpoint on the newer side), a version bump that moves every
/// id — two units describe most items (the first among equals wins) and the
/// dump repeats a key. For every window, every moniker the diff of the two
/// as-of rooms lists with a node row on a side is in the window, in the same
/// list, with the node the room holds; nothing else is.
#[test]
fn the_commit_window_is_the_diff_of_the_two_as_of_rooms() {
RT.block_on(async {
let _g = serial().await;
let tmp = tempfile::tempdir().unwrap();
let is = |n: &mimir_schema::NodeRow, name: &str| n.name == name && n.level == Level::Item;
let commits = vec![
shop_tree("0.1.0", |_| true),
shop_tree("0.1.0", |n| {
if is(n, "checkout") {
n.ty = Some("fn() -> u64".into());
}
if is(n, "total") {
n.effective_visibility = Some(EffectiveVisibility::Direct);
}
!(is(n, "open") && n.id.contains("cart"))
}),
shop_tree("0.1.0", |n| !is(n, "audit") && !is(n, "adds_up")),
shop_tree("0.2.0", |n| !is(n, "charge")),
];
let log = consume_dumps(open_log(tmp.path()).await, tmp.path(), SHOP, commits).await;
assert!(Room::commit_window(&log, SHOP, 2).await.unwrap().is_none(), "no zone yet: the caller folds");
log.materialize(SHOP).await.unwrap();
let view = |r: &Room, o: u64| {
let n = r.node(o);
mimir_room::WindowNode {
krate: n.krate.map(str::to_string),
level: n.level,
origin: n.origin,
defined: n.defined,
effective_visibility: r.attr(o, "effective_visibility").and_then(|v| v.as_str()).map(str::to_string),
}
};
let mut seen = [0usize; 3];
for seq in 2..=4 {
let older = Room::build_uncached(&log, &Scope::repo(SHOP, Some(seq - 1))).await.unwrap();
let newer = Room::build_uncached(&log, &Scope::repo(SHOP, Some(seq))).await.unwrap();
let d = older.diff(&newer).unwrap().value;
let rows = |r: &Room, m: &str| r.ordinal(m).is_some_and(|o| !r.node(o).endpoint_only);
let pick = |ms: &[String], side: &Room| -> Vec<(String, mimir_room::WindowNode)> {
ms.iter().filter(|m| rows(&older, m) || rows(&newer, m)).map(|m| (m.clone(), view(side, side.ordinal(m).unwrap()))).collect()
};
let want = (pick(&d.added_nodes, &newer), pick(&d.removed_nodes, &older), pick(&d.changed, &newer));
let w = Room::commit_window(&log, SHOP, seq).await.unwrap().expect("inside the zone, no binary stream");
assert_eq!((&w.added, &w.removed, &w.changed), (&want.0, &want.1, &want.2), "window {}→{seq}", seq - 1);
seen[0] += w.added.len();
seen[1] += w.removed.len();
seen[2] += w.changed.len();
assert!(w.changed.iter().chain(&w.removed).chain(&w.added).all(|(m, _)| older.ordinal(m).is_some() || newer.ordinal(m).is_some()));
}
assert!(seen.iter().all(|n| *n > 0), "every kind of change was exercised: {seen:?}");
})
}