crates/mimir-driver/tests/fixture_dump.rs
//! Integration: run `cargo mimir-dump` on the two-crate fixture (tests/fixture)
//! and assert the graph. b calls a's fn (two versions of a), a trait impl, a
//! method call through a trait (static + dyn), a std call, an extern "C" fn.
use std::collections::{BTreeMap, BTreeSet, HashSet};
use std::path::{Path, PathBuf};
use std::process::Command;
use mimir_driver::read_tables;
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 = e.path();
let name = 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)).unwrap();
}
}
}
struct Graph {
nodes: BTreeMap<String, BTreeMap<String, String>>, // id -> col -> value (defined_here preferred)
edges: HashSet<(String, String, String)>, // (kind, from, to)
}
fn load(out: &Path) -> Graph {
let n = read_tables(out, "nodes").unwrap();
let e = read_tables(out, "edges").unwrap();
let mut nodes: BTreeMap<String, BTreeMap<String, String>> = BTreeMap::new();
let (id, here) = (n.col("id"), n.col("defined_here"));
for r in &n.rows {
let row: BTreeMap<String, String> =
n.columns.iter().cloned().zip(r.iter().cloned()).collect();
let keep = match nodes.get(&r[id]) {
None => true,
Some(old) => old["defined_here"] != "true" && r[here] == "true",
};
if keep {
nodes.insert(r[id].clone(), row);
}
}
let (f, t, k) = (e.col("from"), e.col("to"), e.col("kind"));
let edges = e.rows.iter().map(|r| (r[k].clone(), r[f].clone(), r[t].clone())).collect();
Graph { nodes, edges }
}
const A: &str = "rust-analyzer cargo a 0.1.0 ";
const A_OLD: &str = "rust-analyzer cargo a 0.0.9 ";
const B: &str = "rust-analyzer cargo b 0.2.0 ";
#[test]
fn fixture_graph() {
let src = PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixture"));
let work = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join(format!("fixture-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&work);
copy_dir(&src, &work);
let out = work.join("out");
let st = Command::new(env!("CARGO_BIN_EXE_cargo-mimir-dump"))
.current_dir(&work)
.args(["--out", out.to_str().unwrap(), "--driver", env!("CARGO_BIN_EXE_mimir-driver")])
.status()
.unwrap();
assert!(st.success(), "cargo mimir-dump failed");
assert!(!out.join(".errors").exists(), "walk errors: {:?}", std::fs::read_dir(out.join(".errors")).ok().map(|d| d.flatten().map(|e| e.path()).collect::<Vec<_>>()));
let g = load(&out);
let m = |p: &str, d: &str| format!("{p}{d}");
let mut results: Vec<(String, bool)> = Vec::new();
let mut check = |name: String, ok: bool| results.push((name, ok));
let has_edge = |k: &str, f: &str, t: &str| g.edges.contains(&(k.to_string(), f.to_string(), t.to_string()));
let node = |id: &str| g.nodes.get(id);
let run = m(B, "run().");
// the headline: b → a CALLS carries a's real moniker WITH version
check(format!("CALLS {run} -> {A}add()."), has_edge("CALLS", &run, &m(A, "add().")));
check(format!("CALLS {run} -> {A_OLD}add()."), has_edge("CALLS", &run, &m(A_OLD, "add().")));
check("CALLS run -> b Dog#Greet#greet() (static dispatch resolved to impl)".into(), has_edge("CALLS", &run, &m(B, "impl#[Dog][Greet]greet().")));
check("CALLS run -> a Greet#greet() (dyn dispatch, trait method)".into(), has_edge("CALLS", &run, &m(A, "Greet#greet().")));
check("CALLS run -> a Point#norm1() (inherent method)".into(), has_edge("CALLS", &run, &m(A, "impl#[Point]norm1().")));
check("CALLS run -> a Point#new() (assoc fn path call)".into(), has_edge("CALLS", &run, &m(A, "impl#[Point]new().")));
let std_len = g.edges.iter().any(|(k, f, t)| {
k == "CALLS" && *f == run && t.starts_with("rust-analyzer cargo alloc ") && t.ends_with(" string/impl#[String]len().")
});
check("CALLS run -> alloc String#len() (std, origin lang)".into(), std_len);
let alloc_len = g.nodes.iter().find(|(id, _)| id.starts_with("rust-analyzer cargo alloc ") && id.ends_with("string/impl#[String]len()."));
check("alloc String#len node origin=lang, defined_here=false".into(), alloc_len.is_some_and(|(_, r)| r["origin"] == "lang" && r["defined_here"] == "false"));
check("CALLS run -> a abs() (extern \"C\")".into(), has_edge("CALLS", &run, &m(A, "abs().")));
check("CALLS by_value -> a tiny() (a fn named as a value, called through a generic)".into(), has_edge("CALLS", &m(B, "by_value()."), &m(A, "tiny().")));
check("a abs() kind=ffi".into(), node(&m(A, "abs().")).is_some_and(|r| r["kind"] == "ffi"));
check("IMPLEMENTS b impl#[Dog][Greet] -> a Greet#".into(), has_edge("IMPLEMENTS", &m(B, "impl#[Dog][Greet]"), &m(A, "Greet#")));
check("FOR_TYPE b impl#[Dog][Greet] -> b Dog#".into(), has_edge("FOR_TYPE", &m(B, "impl#[Dog][Greet]"), &m(B, "Dog#")));
check("CONTAINS b impl#[Dog][Greet] -> Dog#Greet#greet()".into(), has_edge("CONTAINS", &m(B, "impl#[Dog][Greet]"), &m(B, "impl#[Dog][Greet]greet().")));
check("CONTAINS b crate/ -> Dog#".into(), has_edge("CONTAINS", &m(B, "crate/"), &m(B, "Dog#")));
check("READS_FIELD run -> a Point#x.".into(), has_edge("READS_FIELD", &run, &m(A, "Point#x.")));
check("USES_TYPE run -> a Point#".into(), has_edge("USES_TYPE", &run, &m(A, "Point#")));
check("DEPENDS_ON b crate/ -> a 0.1.0 crate/".into(), has_edge("DEPENDS_ON", &m(B, "crate/"), &m(A, "crate/")));
check("DEPENDS_ON b crate/ -> a 0.0.9 crate/".into(), has_edge("DEPENDS_ON", &m(B, "crate/"), &m(A_OLD, "crate/")));
check("EXPANDS a impl#[Point][Debug] -> core Debug: derive".into(), g.edges.iter().any(|(k, f, t)| k == "EXPANDS" && *f == m(A, "impl#[Point][Debug]") && t.ends_with("Debug:")));
check("a tests/adds() is_test".into(), node(&m(A, "tests/adds().")).is_some_and(|r| r["is_test"] == "true"));
check("TESTS a tests/adds() -> a add()".into(), has_edge("TESTS", &m(A, "tests/adds()."), &m(A, "add().")));
check("a tests/ cfg = cfg(test)".into(), node(&m(A, "tests/")).is_some_and(|r| r["cfg"] == "cfg(test)"));
check("param a add().(x) ty=i32".into(), node(&m(A, "add().(x)")).is_some_and(|r| r["ty"] == "i32" && r["kind"] == "param" && r["level"] == "part"));
// what the 🏛 code room hangs under a function head: its return type and its
// `let` bindings (a shadowed name is two locals, one per line)
let ret = m(B, "run().`->`.");
check("ret b run() -> i32: a part node under run()".into(), node(&ret).is_some_and(|r| r["ty"] == "i32" && r["kind"] == "ret" && r["level"] == "part" && r["parent"] == run));
check("CONTAINS run -> its ret".into(), has_edge("CONTAINS", &run, &ret));
let local = |name: &str, line: u32| m(B, &format!("run().`{name}@{line}`."));
check("local b run() s@18 ty=String".into(), node(&local("s", 18)).is_some_and(|r| r["ty"] == "String" && r["kind"] == "local" && r["level"] == "part" && r["start_line"] == "18" && r["file"] == "b/src/lib.rs" && r["name"] == "s"));
check("locals v@21 and v@22 (shadowing: two)".into(), node(&local("v", 21)).is_some() && node(&local("v", 22)).is_some());
check("local g@19 ty=&dyn Greet".into(), node(&local("g", 19)).is_some_and(|r| r["ty"].contains("dyn Greet")));
check("CONTAINS run -> local q@25".into(), has_edge("CONTAINS", &run, &local("q", 25)));
check("no local for `let _`".into(), !g.nodes.keys().any(|id| id.starts_with(&run) && id.contains("`_@")));
check("no ret for a unit fn".into(), !g.nodes.keys().any(|id| id.ends_with("`->`.") && g.nodes[id]["ty"] == "()"));
check("field a Point#x. ty=i32 level=part".into(),node(&m(A, "Point#x.")).is_some_and(|r| r["ty"] == "i32" && r["level"] == "part"));
check("bin b `bin=bctl`:main() (target-prefixed)".into(), node(&m(B, "`bin=bctl`:main().")).is_some_and(|r| r["defined_here"] == "true"));
check("bin b's crate root `bin=bctl`:crate/, not the lib's crate/".into(), node(&m(B, "`bin=bctl`:crate/")).is_some_and(|r| r["kind"] == "crate" && r["file"] == "b/src/bin/bctl.rs"));
check("a add() span a/src/lib.rs".into(), node(&m(A, "add().")).is_some_and(|r| r["file"] == "a/src/lib.rs" && r["start_line"] != ""));
check("a/b nodes origin=local, repo set".into(), node(&run).is_some_and(|r| r["origin"] == "local" && !r["repo"].is_empty()));
check("a 0.0.9 add() origin=library (excluded path dep)".into(), node(&m(A_OLD, "add().")).is_some_and(|r| r["origin"] == "library"));
check("no version '?' anywhere".into(), g.nodes.values().all(|r| r["version"] != "?"));
check("provenance = source_only everywhere".into(), g.nodes.values().all(|r| r["provenance"] == "source_only"));
// Semantic edges: one row per (from, to, kind), `count` = distinct sites;
// the sites themselves are `code_spans` rows, not edge identity.
let e = read_tables(&out, "edges").unwrap();
let (ef, et, ek, ec) = (e.col("from"), e.col("to"), e.col("kind"), e.col("count"));
// Unique WITHIN each crate's dump (a lib and its test harness each dump
// their own copy of shared edges).
let mut unique = true;
for f in std::fs::read_dir(&out).unwrap().flatten().map(|e| e.path()) {
if !f.to_string_lossy().ends_with(".edges.arrow") {
continue;
}
let one = f.with_extension("").with_extension("").file_name().unwrap().to_string_lossy().into_owned();
let dir = out.join(".uniq").join(&one);
std::fs::create_dir_all(&dir).unwrap();
std::fs::copy(&f, dir.join(f.file_name().unwrap())).unwrap();
let t = read_tables(&dir, "edges").unwrap();
let mut seen = HashSet::new();
unique &= t.rows.iter().all(|r| seen.insert((r[ef].clone(), r[et].clone(), r[ek].clone())));
}
check("edges unique per (from, to, kind) in every crate dump".into(), unique);
let add = m(A, "add().");
check("CALLS b run() -> a add() count = 2 (two sites)".into(), e.rows.iter().any(|r| r[ek] == "CALLS" && r[ef] == run && r[et] == add && r[ec] == "2"));
let sp = read_tables(&out, "spans").unwrap();
let (st, sk, sf, ss) = (sp.col("target"), sp.col("key"), sp.col("file"), sp.col("start_line"));
let site_key = mimir_schema::edge_key(&run, &add, mimir_schema::EdgeKind::Calls);
let sites: BTreeSet<String> = sp.rows.iter().filter(|r| r[st] == "edge" && r[sk] == site_key).map(|r| format!("{}:{}", r[sf], r[ss])).collect();
check(format!("2 edge-site spans for run -> add ({sites:?})"), sites.len() == 2);
check("node span a add() in code_spans".into(), sp.rows.iter().any(|r| r[st] == "node" && r[sk] == add && r[sf] == "a/src/lib.rs"));
let mut kinds: BTreeMap<&str, usize> = BTreeMap::new();
for (k, _, _) in &g.edges {
*kinds.entry(k.as_str()).or_default() += 1;
}
eprintln!("fixture: {} nodes, {} edges {:?}", g.nodes.len(), g.edges.len(), kinds);
eprintln!("| check | result |\n|---|---|");
for (n, ok) in &results {
eprintln!("| {n} | {} |", if *ok { "PASS" } else { "FAIL" });
}
let failed: Vec<_> = results.iter().filter(|(_, ok)| !ok).map(|(n, _)| n).collect();
assert!(failed.is_empty(), "failed: {failed:#?}");
}
/// Schema 2 (producer gaps P1 + P2) on the same fixture: visibility as rustc
/// resolves it (nominal + effective), doc summary / `has_doc`, feature nodes
/// with ENABLES / ENABLES_WEAK, GATED_BY from kept items (rustc's `CfgTrace`)
/// and from `cfg_gated_out` stubs (the token scan — items rustc never saw).
#[test]
fn fixture_visibility_docs_features_and_gates() {
let src = PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixture"));
let work = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join(format!("fixture-s2-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&work);
copy_dir(&src, &work);
let out = work.join("out");
let st = Command::new(env!("CARGO_BIN_EXE_cargo-mimir-dump"))
.current_dir(&work)
.args(["--out", out.to_str().unwrap(), "--driver", env!("CARGO_BIN_EXE_mimir-driver")])
.status()
.unwrap();
assert!(st.success(), "cargo mimir-dump failed");
assert!(!out.join(".errors").exists(), "walk errors");
let g = load(&out);
let m = |p: &str, d: &str| format!("{p}{d}");
let mut results: Vec<(String, bool)> = Vec::new();
let mut check = |name: String, ok: bool| results.push((name, ok));
let has_edge = |k: &str, f: &str, t: &str| g.edges.contains(&(k.to_string(), f.to_string(), t.to_string()));
let col = |id: &str, c: &str| g.nodes.get(id).map(|r| r[c].clone()).unwrap_or_else(|| "<no node>".into());
const C: &str = "rust-analyzer cargo c 0.3.0 ";
// ---- P1: visibility — (item, nominal, effective)
for (d, vis, eff) in [
("add().", "pub", "direct"),
("private_fn().", "private", "internal"),
// at the crate root `pub(crate)` IS the item's own module: one meaning
("crate_root_scoped().", "private", "internal"),
("inner/", "private", "internal"),
("inner/crate_wide().", "pub(crate)", "internal"),
// `pub(super)` one level down resolves to the crate
("inner/for_parent().", "pub(crate)", "internal"),
("inner/deep/scoped().", "pub(in crate::inner)", "internal"),
("inner/deep/up().", "pub(in crate::inner)", "internal"),
// `pub`, but nothing outside the crate can reach it
("inner/hidden_pub().", "pub", "internal"),
("inner/deep/", "pub", "internal"),
// nameable only through `pub use inner::reexported`
("inner/reexported().", "pub", "reexported"),
// not nameable, leaks through `leak()`'s signature
("inner/Leaked#", "pub", "reachable"),
("leak().", "pub", "direct"),
("Point#", "pub", "direct"),
("Point#x.", "pub", "direct"),
("Greet#greet().", "pub", "direct"),
("impl#[Point]new().", "pub", "direct"),
("abs().", "pub", "direct"),
("twice!", "private", "internal"),
("crate/", "pub", "direct"),
("tests/adds().", "private", "internal"),
] {
let id = m(A, d);
let got = (col(&id, "visibility"), col(&id, "effective_visibility"));
check(format!("visibility a {d} = {vis} / {eff} (got {} / {})", got.0, got.1), got == (vis.to_string(), eff.to_string()));
}
for d in ["impl#[Point]", "ident().[T]", "add().(x)"] {
let id = m(A, d);
check(format!("no visibility on a {d} (impl block / generic / param)"), col(&id, "visibility").is_empty() && col(&id, "effective_visibility").is_empty());
}
let alloc_len = g.nodes.iter().find(|(id, _)| id.starts_with("rust-analyzer cargo alloc ") && id.ends_with("string/impl#[String]len()."));
check(
"reference stub (alloc String#len): visibility / doc / has_doc all NULL".into(),
alloc_len.is_some_and(|(_, r)| r["visibility"].is_empty() && r["effective_visibility"].is_empty() && r["doc"].is_empty() && r["has_doc"].is_empty()),
);
// ---- P1: docs — (item, has_doc, summary)
for (d, has, doc) in [
("tiny().", "true", "always inlined: a source node with no linked symbol of its own."),
// only the FIRST line of a multi-line doc comment
("ident().", "true", "generic + `#[inline(never)]`: b instantiates it twice (`u32`, `&str`) →"),
("abs().", "true", "libc `abs` — the extern \"C\" border."),
("inner/reexported().", "true", "Re-exported from a private module."),
("crate/", "true", "fixture crate a 0.1.0"),
// `//` comments are not docs
("add().", "false", ""),
("Point#", "false", ""),
("impl#[Point]", "false", ""),
] {
let id = m(A, d);
let got = (col(&id, "has_doc"), col(&id, "doc"));
check(format!("doc a {d}: has_doc={has} doc={doc:?} (got {} {:?})", got.0, got.1), got == (has.to_string(), doc.to_string()));
}
check("has_doc NULL on a param / generic".into(), col(&m(A, "add().(x)"), "has_doc").is_empty() && col(&m(A, "ident().[T]"), "has_doc").is_empty());
// ---- schema 3: attributes — `ignored` on tests only, `doc_hidden` on the
// item that carries the attribute
for (d, ignored) in [("tests/adds().", "false"), ("tests/slow().", "true"), ("tests/slow_with_reason().", "true"), ("add().", ""), ("tests/", ""), ("Point#", "")] {
let id = m(A, d);
check(format!("ignored a {d} = {ignored:?} (got {:?})", col(&id, "ignored")), col(&id, "ignored") == ignored);
}
for (d, hidden) in [
("hidden_api().", "true"),
("hidden_mod/", "true"),
// its module is hidden, the fn itself carries nothing
("hidden_mod/inside().", "false"),
("add().", "false"),
("crate/", "false"),
("impl#[Point]", "false"),
("tests/slow().", "false"),
// no doc notion: a param, a generic param
("add().(x)", ""),
("ident().[T]", ""),
] {
let id = m(A, d);
check(format!("doc_hidden a {d} = {hidden:?} (got {:?})", col(&id, "doc_hidden")), col(&id, "doc_hidden") == hidden);
}
check(
"reference stub (alloc String#len): ignored / doc_hidden NULL".into(),
alloc_len.is_some_and(|(_, r)| r["ignored"].is_empty() && r["doc_hidden"].is_empty()),
);
let stub = g.nodes.values().find(|r| r["kind"] == "cfg_gated_out");
check("a cfg_gated_out stub: ignored / doc_hidden NULL (nothing resolved)".into(), stub.is_some_and(|r| r["ignored"].is_empty() && r["doc_hidden"].is_empty()));
// ---- schema 4: the link name of a foreign item (rustc's symbol name of
// it: its own name here, no `#[link_name]` in the fixture); null on
// everything that is not a foreign item
for (d, link) in [("abs().", "abs"), ("getpid().", "getpid"), ("add().", ""), ("Point#", "")] {
let id = m(A, d);
check(format!("link_name a {d} = {link:?} (got {:?})", col(&id, "link_name")), col(&id, "link_name") == link);
}
check("reference stub (alloc String#len): link_name NULL".into(), alloc_len.is_some_and(|(_, r)| r["link_name"].is_empty()));
// ---- P2: feature nodes, one per crate@version feature
let f = |p: &str, name: &str| format!("{p}`feature={name}`:");
for (id, here, what) in [
(f(A, "default"), "true", "a default (declared)"),
(f(A, "on"), "true", "a on (declared)"),
(f(A, "extra"), "true", "a extra (declared, never built)"),
(f(A, "ghost"), "false", "a ghost (in a cfg, no manifest declares it)"),
(f(A_OLD, "legacy"), "true", "a 0.0.9 legacy (its own node: per crate@VERSION)"),
(f(B, "fast"), "true", "b fast"),
(f(B, "c-turbo"), "true", "b c-turbo"),
(f(C, "turbo"), "false", "c turbo (c is never compiled: a reference)"),
] {
check(
format!("feature node {what}: kind=feature defined_here={here}"),
g.nodes.get(&id).is_some_and(|r| r["kind"] == "feature" && r["level"] == "item" && r["defined_here"] == here && r["cfg"].is_empty()),
);
}
check("feature a on: file a/Cargo.toml + line, parent = a crate/".into(), g.nodes.get(&f(A, "on")).is_some_and(|r| r["file"] == "a/Cargo.toml" && r["start_line"] == "10" && r["parent"] == m(A, "crate/")));
check("CONTAINS a crate/ -> feature on".into(), has_edge("CONTAINS", &m(A, "crate/"), &f(A, "on")));
check("c 0.3.0 crate/ is a reference stub (off: no rustc compiled it)".into(), g.nodes.get(&m(C, "crate/")).is_some_and(|r| r["kind"] == "crate" && r["defined_here"] == "false"));
// ---- P2: ENABLES — what switching something on switches on
check("ENABLES a extra -> a on (feature -> feature)".into(), has_edge("ENABLES", &f(A, "extra"), &f(A, "on")));
check("ENABLES a default -> a on".into(), has_edge("ENABLES", &f(A, "default"), &f(A, "on")));
check("ENABLES b fast -> a 0.1.0 extra (`a/extra`: the dep's feature)".into(), has_edge("ENABLES", &f(B, "fast"), &f(A, "extra")));
check("ENABLES b fast -> a 0.1.0 crate/ (`a/extra`: and the dep)".into(), has_edge("ENABLES", &f(B, "fast"), &m(A, "crate/")));
check("ENABLES b old -> a 0.0.9 legacy (renamed dep `a_old` -> package a 0.0.9)".into(), has_edge("ENABLES", &f(B, "old"), &f(A_OLD, "legacy")));
check("ENABLES b with-c -> c 0.3.0 crate/ (`dep:c`, version from Cargo.lock)".into(), has_edge("ENABLES", &f(B, "with-c"), &m(C, "crate/")));
check("ENABLES_WEAK b c-turbo -> c turbo (`c?/turbo`)".into(), has_edge("ENABLES_WEAK", &f(B, "c-turbo"), &f(C, "turbo")));
check("`c?/turbo` is NOT a plain ENABLES and does not pull c".into(), !has_edge("ENABLES", &f(B, "c-turbo"), &f(C, "turbo")) && !has_edge("ENABLES", &f(B, "c-turbo"), &m(C, "crate/")));
// the build's own selection: crate -> the features it was compiled with
check("ENABLES a crate/ -> default, on (compiled in)".into(), has_edge("ENABLES", &m(A, "crate/"), &f(A, "default")) && has_edge("ENABLES", &m(A, "crate/"), &f(A, "on")));
let built = |feature: &str| g.edges.iter().any(|(k, from, to)| k == "ENABLES" && to == feature && from.ends_with("crate/"));
check("a extra / b fast: no crate ENABLES them = never built".into(), !built(&f(A, "extra")) && !built(&f(B, "fast")) && built(&f(A, "on")));
// ---- P2 (a): GATED_BY from items rustc KEPT
let e = read_tables(&out, "edges").unwrap();
let (ef, et, ek, en, ep, ec) = (e.col("from"), e.col("to"), e.col("kind"), e.col("negated"), e.col("partial"), e.col("count"));
// `negated` of the GATED_BY edge from -> to (the lib and its test harness
// both dump it: they must agree)
let gate_col = |from: &str, to: &str, c: usize| -> String {
let mut v: Vec<&str> = e.rows.iter().filter(|r| r[ek] == "GATED_BY" && r[ef] == from && r[et] == to).map(|r| r[c].as_str()).collect();
v.sort();
v.dedup();
if v.is_empty() { "<no edge>".into() } else { v.join("|") }
};
let negated = |from: &str, to: &str| gate_col(from, to, en);
// (partial, negated, count)
let gate = |from: &str, to: &str| (gate_col(from, to, ep), gate_col(from, to, en), gate_col(from, to, ec));
for (d, feature, neg) in [
("gated_on().", "on", "false"),
("not_extra().", "extra", "true"),
("on_not_extra().", "on", "false"),
("on_not_extra().", "extra", "true"),
("pick().", "extra", "true"),
("Config#on_field.", "on", "false"),
] {
let got = negated(&m(A, d), &f(A, feature));
check(format!("GATED_BY a {d} -> {feature} negated={neg} (got {got})"), got == neg);
}
check("kept item: cfg column is the canonical predicate".into(), col(&m(A, "on_not_extra()."), "cfg") == "cfg(all(feature = \"on\", not(feature = \"extra\")))");
check("kept item: a resolved node (kind fn, defined, no stub twin)".into(), col(&m(A, "gated_on()."), "kind") == "fn" && !g.nodes.contains_key(&m(A, "gated_on().`cfg(feature = \"on\")`:")));
check("negated / partial are NULL on every edge that is not GATED_BY, set on every GATED_BY".into(), e.rows.iter().all(|r| (r[ek] == "GATED_BY") != r[en].is_empty() && (r[ek] == "GATED_BY") != r[ep].is_empty()));
check("an item's OWN gate is not partial".into(), gate(&m(A, "gated_on()."), &f(A, "on")) == ("false".into(), "false".into(), "1".into()));
// ---- P2: a gate INSIDE an item — partial = true, the item is a resolved
// node with no cfg of its own
let t = |p: &str, n: &str, c: &str| (p.to_string(), n.to_string(), c.to_string());
check("partial: body_gate() -> extra (`cfg!(feature = \"extra\")` in its body)".into(), gate(&m(A, "body_gate()."), &f(A, "extra")) == t("true", "false", "1"));
check("partial: stmt_gates() -> on (one gated block)".into(), gate(&m(A, "stmt_gates()."), &f(A, "on")) == t("true", "false", "1"));
check("partial: stmt_gates() -> extra, 2 sites, NOT negated (one `not`, one plain)".into(), gate(&m(A, "stmt_gates()."), &f(A, "extra")) == t("true", "false", "2"));
check("partial gate: stmt_gates() itself has no cfg and is a plain fn".into(), col(&m(A, "stmt_gates()."), "cfg").is_empty() && col(&m(A, "stmt_gates()."), "kind") == "fn");
check("partial: a crate/ -> extra (the gated `use` is a part of the module it stands in)".into(), gate(&m(A, "crate/"), &f(A, "extra")) == t("true", "false", "1"));
check("own gate + a gate inside, same feature: ONE edge, not partial, 2 sites".into(), gate(&m(A, "own_and_inside()."), &f(A, "on")) == t("false", "false", "2"));
let ewi = format!("{A}extra_with_inside().`cfg(feature = \"extra\")`:");
check("inside a compiled-out item: stub -> extra is its own gate, stub -> on is partial".into(), gate(&ewi, &f(A, "extra")) == t("false", "false", "1") && gate(&ewi, &f(A, "on")) == t("true", "false", "1"));
check("cfg_attr switches an attribute: attr_only() has no GATED_BY".into(), !e.rows.iter().any(|r| r[ek] == "GATED_BY" && r[ef] == m(A, "attr_only().")));
let stmt_site = mimir_schema::edge_key(&m(A, "stmt_gates()."), &f(A, "extra"), mimir_schema::EdgeKind::GatedBy);
let sp = read_tables(&out, "spans").unwrap();
let (st_, sk, sf) = (sp.col("target"), sp.col("key"), sp.col("file"));
let gate_site = mimir_schema::edge_key(&m(A, "gated_on()."), &f(A, "on"), mimir_schema::EdgeKind::GatedBy);
check("GATED_BY site (the attribute's line) is a code_spans row".into(), sp.rows.iter().any(|r| r[st_] == "edge" && r[sk] == gate_site && r[sf] == "a/src/lib.rs"));
let stmt_lines: BTreeSet<&str> = sp.rows.iter().filter(|r| r[st_] == "edge" && r[sk] == stmt_site).map(|r| r[sp.col("start_line")].as_str()).collect();
check(format!("the two partial sites of stmt_gates() -> extra are two span rows ({stmt_lines:?})"), stmt_lines.len() == 2);
// ---- P2 (b): items the build compiled OUT — stubs from the token scan
let stub = |d: &str, pred: &str| format!("{A}{d}`{pred}`:");
let x = "cfg(feature = \"extra\")";
let extra = stub("extra().", x);
check(
"stub a extra(): kind=cfg_gated_out ty=fn cfg=predicate file+line, parent a crate/".into(),
g.nodes.get(&extra).is_some_and(|r| {
r["kind"] == "cfg_gated_out" && r["ty"] == "fn" && r["cfg"] == x && r["file"] == "a/src/lib.rs" && !r["start_line"].is_empty() && r["parent"] == m(A, "crate/") && r["name"] == "extra"
}),
);
check("stub a extra(): what the source says — written visibility + doc line, nothing resolved".into(), g.nodes.get(&extra).is_some_and(|r| r["visibility"] == "pub" && r["effective_visibility"].is_empty() && r["doc"] == "Compiled OUT: nothing enables `extra`." && r["is_test"] == "false"));
check("the compiled-out extra() has NO resolved node".into(), !g.nodes.contains_key(&m(A, "extra().")));
check("GATED_BY stub extra() -> extra, not negated".into(), negated(&extra, &f(A, "extra")) == "false");
check("CONTAINS a crate/ -> stub extra()".into(), has_edge("CONTAINS", &m(A, "crate/"), &extra));
// the on/off pair: the resolved pick() (not(extra)) AND the stub pick() (extra)
let pick = stub("pick().", x);
check("pair: resolved pick() + stub pick() are two ids".into(), g.nodes.get(&m(A, "pick().")).is_some_and(|r| r["kind"] == "fn") && g.nodes.get(&pick).is_some_and(|r| r["kind"] == "cfg_gated_out"));
check("stub field Config#extra_field (ty=field, level part), parent the REAL Config#".into(), g.nodes.get(&stub("Config#extra_field.", x)).is_some_and(|r| r["ty"] == "field" && r["level"] == "part" && r["parent"] == m(A, "Config#")));
check("stub method impl#[Config]extra_method (ty=method), parent the REAL impl".into(), g.nodes.get(&stub("impl#[Config]extra_method().", x)).is_some_and(|r| r["ty"] == "method" && r["parent"] == m(A, "impl#[Config]")));
// a module FILE rustc never loaded
let extra_mod = stub("extra_mod/", x);
check("stub mod extra_mod (ty=mod, level module)".into(), g.nodes.get(&extra_mod).is_some_and(|r| r["ty"] == "mod" && r["level"] == "module" && r["file"] == "a/src/lib.rs"));
let on_inside = format!("{extra_mod}on_inside().`cfg(feature = \"on\")`:");
check(
"stub in the never-loaded file a/src/extra_mod.rs, parent = the mod's stub".into(),
g.nodes.get(&on_inside).is_some_and(|r| r["kind"] == "cfg_gated_out" && r["file"] == "a/src/extra_mod.rs" && r["parent"] == extra_mod),
);
check("its own feature `on` is ON, yet it is compiled out: still a stub, GATED_BY on".into(), negated(&on_inside, &f(A, "on")) == "false");
check("an item WITHOUT a cfg inside the compiled-out file is no stub (the mod's stub contains it)".into(), !g.nodes.keys().any(|id| id.contains("in_file")));
let ghost = stub("ghost().", "cfg(feature = \"ghost\")");
check("stub ghost() -> undeclared feature ghost (visibility private)".into(), negated(&ghost, &f(A, "ghost")) == "false" && col(&ghost, "visibility") == "private");
check("stubs never carry CALLS / USES_TYPE (nothing resolved)".into(), g.edges.iter().all(|(k, from, to)| matches!(k.as_str(), "CONTAINS" | "GATED_BY") || !(from.contains("`cfg(") || to.contains("`cfg("))));
check("no stub for a `use` (it is a partial gate of its module)".into(), !g.nodes.keys().any(|id| id.contains("_gated_use")));
// ---- the lower bound is a NUMBER: a's lib unit
let stats: Vec<mimir_driver::CrateStats> = std::fs::read_dir(&out)
.unwrap()
.flatten()
.filter(|e| e.path().to_string_lossy().ends_with(".stats.json"))
.map(|e| serde_json::from_slice(&std::fs::read(e.path()).unwrap()).unwrap())
.collect();
let a_lib = stats.iter().find(|s| s.package == "a" && s.version == "0.1.0" && !s.test_harness).expect("a lib stats");
check(format!("a lib: 3 declared features, 2 compiled in, 1 referenced ({} {} {})", a_lib.feature_nodes, a_lib.features_active, a_lib.feature_refs), (a_lib.feature_nodes, a_lib.features_active, a_lib.feature_refs) == (3, 2, 1));
check(
format!("a lib: 7 own gates of kept items, 8 stubs with 8 own gates, 5 partial ({} {} {} {})", a_lib.gated_by_active, a_lib.gated_out_stubs, a_lib.gated_by_stubs, a_lib.gated_by_partial),
(a_lib.gated_by_active, a_lib.gated_out_stubs, a_lib.gated_by_stubs, a_lib.gated_by_partial) == (7, 8, 8, 5),
);
check(format!("a lib: scanned lib.rs + the never-loaded extra_mod.rs, none failed ({})", a_lib.cfg_scan_files), a_lib.cfg_scan_files == 2 && a_lib.cfg_scan_failed.is_empty() && a_lib.cfg_scan_unresolved_mods.is_empty());
check(
format!(
"a lib: {} feature mentions = {} on an item's own cfg + {} inside an item + 1 that is no edge (the cfg_attr)",
a_lib.feature_cfg_mentions, a_lib.feature_cfg_attributed, a_lib.feature_cfg_partial
),
(a_lib.feature_cfg_mentions, a_lib.feature_cfg_attributed, a_lib.feature_cfg_partial) == (23, 15, 7),
);
check(format!("a lib: api items {} / documented {}", a_lib.api_items, a_lib.api_documented), a_lib.api_items == 30 && a_lib.api_documented == 9);
// a 0.0.9 is a dependency (origin library): features and kept-item gates
// are written, the token scan is not run on it by default
let legacy_stub = format!("{A_OLD}legacy().`cfg(feature = \"legacy\")`:");
check("dependency a 0.0.9: compiled-out legacy() is NOT scanned by default (MIMIR_CFG_SCAN=local)".into(), !g.nodes.contains_key(&legacy_stub));
let old_lib = stats.iter().find(|s| s.package == "a" && s.version == "0.0.9").expect("a-old stats");
check("dependency a 0.0.9: scan did not run (0 files), its feature node is there".into(), old_lib.cfg_scan_files == 0 && old_lib.gated_out_stubs == 0 && old_lib.feature_nodes == 1);
let b_lib = stats.iter().find(|s| s.package == "b" && s.crate_name == "b" && !s.test_harness).expect("b lib stats");
check("b lib: every [features] entry resolved (c from Cargo.lock)".into(), b_lib.feature_enables_unresolved.is_empty() && b_lib.feature_nodes == 4 && b_lib.features_active == 0);
eprintln!("| check | result |\n|---|---|");
for (n, ok) in &results {
eprintln!("| {n} | {} |", if *ok { "PASS" } else { "FAIL" });
}
let failed: Vec<_> = results.iter().filter(|(_, ok)| !ok).map(|(n, _)| n).collect();
assert!(failed.is_empty(), "failed: {failed:#?}");
}
/// `MIMIR_CFG_SCAN=all`: the token scan also covers dependencies (the
/// universe ring) — a 0.0.9's compiled-out `legacy()` becomes a stub.
#[test]
fn fixture_cfg_scan_all_covers_dependencies() {
let src = PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixture"));
let work = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join(format!("fixture-scanall-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&work);
copy_dir(&src, &work);
let out = work.join("out");
let st = Command::new(env!("CARGO_BIN_EXE_cargo-mimir-dump"))
.current_dir(&work)
.env(mimir_driver::ENV_CFG_SCAN, "all")
.args(["--out", out.to_str().unwrap(), "--driver", env!("CARGO_BIN_EXE_mimir-driver")])
.status()
.unwrap();
assert!(st.success());
let g = load(&out);
let stub = format!("{A_OLD}legacy().`cfg(feature = \"legacy\")`:");
let row = g.nodes.get(&stub).unwrap_or_else(|| panic!("no stub {stub}"));
assert_eq!((row["kind"].as_str(), row["origin"].as_str(), row["ty"].as_str()), ("cfg_gated_out", "library", "fn"));
// the same file spelling as the crate's resolved items
assert_eq!(row["file"], g.nodes[&format!("{A_OLD}add().")]["file"]);
assert!(row["file"].ends_with("src/lib.rs"), "{}", row["file"]);
assert!(g.edges.contains(&("GATED_BY".into(), stub, format!("{A_OLD}`feature=legacy`:"))));
}
/// `--workspace-wrapper` (clippy's RUSTC_WORKSPACE_WRAPPER): only members go
/// through the driver, deps are plain rustc → no sidecars for a-old. The name
/// `a` is ambiguous in the metadata map (0.1.0 and 0.0.9); b's direct dep
/// `a_old` must still resolve via `--extern a_old=…` ↔ cargo's resolve graph.
#[test]
fn fixture_workspace_wrapper_resolves_two_versions_via_extern_argv() {
let src = PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixture"));
let work = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join(format!("fixture-ww-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&work);
copy_dir(&src, &work);
let out = work.join("out");
let st = Command::new(env!("CARGO_BIN_EXE_cargo-mimir-dump"))
.current_dir(&work)
.args(["--out", out.to_str().unwrap(), "--workspace-wrapper", "--driver", env!("CARGO_BIN_EXE_mimir-driver")])
.status()
.unwrap();
assert!(st.success());
let g = load(&out);
let run = format!("{B}run().");
assert!(g.edges.contains(&("CALLS".into(), run.clone(), format!("{A_OLD}add()."))), "a_old::add not resolved to a 0.0.9");
assert!(g.edges.contains(&("CALLS".into(), run, format!("{A}add()."))));
assert!(g.nodes.values().all(|r| r["version"] != "?"), "unresolved version in workspace-wrapper mode");
// a-old is not a member → not dumped, only referenced
assert!(g.nodes.get(&format!("{A_OLD}add().")).is_some_and(|r| r["defined_here"] == "false"));
}
/// Symbols in a SCIP index file, read without a protobuf dependency: every
/// length-delimited string that starts with `rust-analyzer cargo `.
fn scip_symbols(bytes: &[u8]) -> HashSet<String> {
let pat = b"rust-analyzer cargo ";
let mut out = HashSet::new();
let mut i = 0;
while i + pat.len() <= bytes.len() {
if &bytes[i..i + pat.len()] == pat {
// 1- or 2-byte varint length prefix right before the string
let mut cands = Vec::new();
if i >= 1 && bytes[i - 1] < 0x80 {
cands.push(bytes[i - 1] as usize);
}
if i >= 2 && bytes[i - 2] >= 0x80 && bytes[i - 1] < 0x80 {
cands.push(((bytes[i - 2] & 0x7f) as usize) | ((bytes[i - 1] as usize) << 7));
}
for len in cands {
if len >= pat.len() && i + len <= bytes.len() {
if let Ok(s) = std::str::from_utf8(&bytes[i..i + len]) {
if !s.chars().any(|c| c.is_control()) {
out.insert(s.to_string());
}
}
}
}
i += pat.len();
} else {
i += 1;
}
}
out
}
/// One package with a lib, a bin, a test, a bench and an example ALL called
/// `twin`, plus a test and a bench called `same` (tests/fixture-targets), each
/// defining `helper()`: no two TARGETS share a node id. The lib has
/// rust-analyzer's ids; a target's `--test` harness has its target's.
#[test]
fn targets_of_one_package_share_no_id() {
let src = PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixture-targets"));
let work = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join(format!("fixture-targets-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&work);
copy_dir(&src, &work);
let out = work.join("out");
let st = Command::new(env!("CARGO_BIN_EXE_cargo-mimir-dump"))
.current_dir(&work)
.args(["--out", out.to_str().unwrap(), "--driver", env!("CARGO_BIN_EXE_mimir-driver")])
.status()
.unwrap();
assert!(st.success(), "cargo mimir-dump failed");
assert!(!out.join(".errors").exists(), "walk errors");
// every unit's dump on its own; a unit's TARGET is told by its crate
// root's file (one root file per target)
const TWIN: &str = "rust-analyzer cargo twin 0.1.0 ";
let mut by_target: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
let mut units = 0;
for f in std::fs::read_dir(&out).unwrap().flatten().map(|e| e.path()) {
if !f.to_string_lossy().ends_with(".nodes.arrow") {
continue;
}
let dir = out.join(".unit").join(f.file_name().unwrap());
std::fs::create_dir_all(&dir).unwrap();
std::fs::copy(&f, dir.join(f.file_name().unwrap())).unwrap();
let t = read_tables(&dir, "nodes").unwrap();
let (id, kind, here, file) = (t.col("id"), t.col("kind"), t.col("defined_here"), t.col("file"));
let own: Vec<&Vec<String>> = t.rows.iter().filter(|r| r[here] == "true" && r[id].starts_with(TWIN)).collect();
let Some(root) = own.iter().find(|r| r[kind] == "crate") else { continue };
units += 1;
by_target.entry(root[file].clone()).or_default().extend(own.iter().map(|r| r[id].clone()));
}
let targets: Vec<&str> = by_target.keys().map(String::as_str).collect();
assert_eq!(
targets,
vec!["twin/benches/same.rs", "twin/benches/twin.rs", "twin/examples/twin.rs", "twin/src/lib.rs", "twin/src/main.rs", "twin/tests/same.rs", "twin/tests/twin.rs"],
"every target was dumped"
);
assert!(units >= 9, "7 targets + the harness of the lib and of the bin: {units} units");
// THE property: an id belongs to one target
let mut owner: BTreeMap<&String, &String> = BTreeMap::new();
let mut shared = Vec::new();
for (target, ids) in &by_target {
for id in ids {
if let Some(other) = owner.insert(id, target) {
shared.push(format!("{id} ({other} and {target})"));
}
}
}
assert!(shared.is_empty(), "ids shared between two targets of one package: {shared:#?}");
// and the ids are these
let has = |target: &str, d: &str| by_target[target].contains(&format!("{TWIN}{d}"));
for d in ["crate/", "helper().", "run().", "Config#", "tests/helps()."] {
assert!(has("twin/src/lib.rs", d), "the lib (and its harness) keeps rust-analyzer's id `{d}`");
}
for (target, prefix) in [
("twin/src/main.rs", "`bin=twin`:"),
("twin/tests/twin.rs", "`test=twin`:"),
("twin/tests/same.rs", "`test=same`:"),
("twin/benches/twin.rs", "`bench=twin`:"),
("twin/benches/same.rs", "`bench=same`:"),
("twin/examples/twin.rs", "`example=twin`:"),
] {
assert!(by_target[target].iter().all(|id| id[TWIN.len()..].starts_with(prefix)), "{target}: every id starts with {prefix}: {:#?}", by_target[target]);
assert!(has(target, &format!("{prefix}crate/")) && has(target, &format!("{prefix}helper().")), "{target}");
}
// the bin's own harness is the bin target
assert!(has("twin/src/main.rs", "`bin=twin`:tests/helps().") && has("twin/src/main.rs", "`bin=twin`:Config#"));
}
/// BYTE-IDENTITY against `rust-analyzer scip` on the same fixture: every
/// symbol rust-analyzer emits for an item of a / b (params and locals aside)
/// must be a node id of ours, byte for byte. Skips (with a message) when the
/// pinned toolchain has no rust-analyzer component.
#[test]
fn matches_rust_analyzer_scip() {
let ra_ok = Command::new("rust-analyzer")
.env("RUSTUP_TOOLCHAIN", mimir_driver::MIMIR_NIGHTLY)
.arg("--version")
.output()
.is_ok_and(|o| o.status.success());
if !ra_ok {
eprintln!("SKIP: no rust-analyzer for {} (rustup component add rust-analyzer)", mimir_driver::MIMIR_NIGHTLY);
return;
}
let src = PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixture"));
let work = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join(format!("fixture-ra-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&work);
copy_dir(&src, &work);
let scip = work.join("index.scip");
let st = Command::new("rust-analyzer")
.env("RUSTUP_TOOLCHAIN", mimir_driver::MIMIR_NIGHTLY)
.current_dir(&work)
.args(["scip", ".", "--output"])
.arg(&scip)
.status()
.unwrap();
assert!(st.success(), "rust-analyzer scip failed");
let ra = scip_symbols(&std::fs::read(&scip).unwrap());
assert!(ra.len() > 10, "too few symbols parsed from index.scip: {ra:?}");
let out = work.join("out");
let st = Command::new(env!("CARGO_BIN_EXE_cargo-mimir-dump"))
.current_dir(&work)
.args(["--out", out.to_str().unwrap(), "--driver", env!("CARGO_BIN_EXE_mimir-driver")])
.status()
.unwrap();
assert!(st.success());
let g = load(&out);
let ours: HashSet<&String> = g.nodes.keys().collect();
let mut compared = Vec::new();
let mut missing = Vec::new();
for s in &ra {
let Some((pkg, _ver, desc)) = mimir_schema::moniker::parse(s) else { continue };
if !matches!(pkg, "a" | "b") || desc.ends_with(')') {
continue; // params / locals: rust-analyzer writes them as `local N`
}
// rust-analyzer gives the bin's main() / crate/ the lib's id (its own
// duplicate-symbol warning); ours are target-prefixed `` `bin=bctl`: ``.
if pkg == "b" && (desc == "main()." || desc == "crate/") && !ours.contains(s) {
continue;
}
compared.push(s.clone());
if !ours.contains(s) {
missing.push(s.clone());
}
}
compared.sort();
eprintln!("rust-analyzer symbols compared ({}):", compared.len());
for c in &compared {
eprintln!(" {} {c}", if missing.contains(c) { "MISSING" } else { "ok " });
}
// also std / core references we resolved: same spelling?
let ra_lang: Vec<&String> = ra.iter().filter(|s| s.contains("github.com/rust-lang/rust/library")).collect();
let both: usize = ra_lang.iter().filter(|s| ours.contains(*s)).count();
eprintln!("lang symbols: rust-analyzer {} / also ours {}", ra_lang.len(), both);
for l in &ra_lang {
eprintln!(" {} {l}", if ours.contains(*l) { "ours " } else { "not-ref" });
}
assert!(compared.len() >= 12, "compared too few: {compared:?}");
assert!(missing.is_empty(), "rust-analyzer symbols we do not produce: {missing:#?}");
}