crates/mimir-binary/tests/fixture_binary.rs
//! Integration: the lane-3 two-crate fixture (crates/mimir-driver/tests/fixture)
//! built `--release` with v0 mangling + full debuginfo, joined onto the
//! driver's source dump.
//!
//! Two shapes:
//! * PRODUCT shape — source dump from `cargo mimir-dump` (pinned nightly,
//! `cargo check`), artifact from the default STABLE toolchain's `cargo build
//! --release`. No id is shared between the two compiles; crates are
//! identified from the artifact's DWARF CU names / deps-dir rlibs.
//! * DRIVER shape — `cargo mimir-dump --build -- --release`: dump and artifact
//! from ONE compile, so the driver's own symbol names join exactly. The v0
//! path is then re-run without them and must land on the same monikers
//! (the oracle).
//!
//! The driver is built from crates/mimir-driver (its own workspace, pinned
//! nightly) on first use.
use std::collections::{BTreeMap, HashMap, HashSet};
use std::path::{Path, PathBuf};
use std::process::Command;
use mimir_binary::{run, BinaryIndex, Options, Output};
use mimir_schema::{kind, role, EdgeKind, Provenance};
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();
}
}
}
fn driver_dir() -> PathBuf {
PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/../mimir-driver"))
}
fn nightly() -> String {
let t = std::fs::read_to_string(driver_dir().join("rust-toolchain.toml")).unwrap();
t.lines()
.find_map(|l| l.trim().strip_prefix("channel = ").map(|v| v.trim_matches('"').to_string()))
.expect("channel in rust-toolchain.toml")
}
/// `program` with the outer `cargo test`'s env scrubbed.
fn scrubbed(program: impl AsRef<std::ffi::OsStr>) -> Command {
let mut c = Command::new(program);
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
}
fn cargo(toolchain: Option<&str>) -> Command {
let mut c = scrubbed("cargo");
if let Some(t) = toolchain {
c.env("RUSTUP_TOOLCHAIN", t);
}
c
}
fn build_driver() -> PathBuf {
let dir = driver_dir();
let st = cargo(Some(&nightly())).current_dir(&dir).args(["build", "--release", "--bins"]).status().unwrap();
assert!(st.success(), "building mimir-driver failed");
dir.join("target/release")
}
fn sysroot(toolchain: Option<&str>) -> PathBuf {
let mut c = Command::new("rustc");
c.env_remove("RUSTUP_TOOLCHAIN");
if let Some(t) = toolchain {
c.env("RUSTUP_TOOLCHAIN", t);
}
let o = c.args(["--print", "sysroot"]).output().unwrap();
PathBuf::from(String::from_utf8_lossy(&o.stdout).trim())
}
const A: &str = "rust-analyzer cargo a 0.1.0 ";
const B: &str = "rust-analyzer cargo b 0.2.0 ";
struct View<'a> {
o: &'a Output,
idx: BinaryIndex,
src: &'a mimir_binary::source::SourceDump,
}
impl<'a> View<'a> {
fn new(o: &'a Output, src: &'a mimir_binary::source::SourceDump) -> View<'a> {
View { o, idx: BinaryIndex::new(o), src }
}
/// DERIVED provenance (the join on monikers), never a row's own field.
fn prov(&self, id: &str) -> Option<Provenance> {
self.idx.provenance(self.src, id)
}
fn edge(&self, k: EdgeKind, from: &str, to: &str) -> bool {
self.o.edges.iter().any(|e| e.kind == k && e.from == from && e.to == to)
}
}
/// The assertions both shapes share. Returns the table rows for the report.
fn assert_fixture(o: &Output, src_dump: &Path, label: &str) -> Vec<(String, String)> {
let src = mimir_binary::source::SourceDump::load(src_dump).unwrap();
let v = View::new(o, &src);
let mut rows: Vec<(String, String)> = Vec::new();
let mut failed: Vec<String> = Vec::new();
let mut check = |name: &str, ok: bool, got: String| {
rows.push((name.to_string(), got.clone()));
if !ok {
failed.push(format!("{name}: {got}"));
}
};
let show = |p: Option<Provenance>| p.map(|p| p.as_str().to_string()).unwrap_or_else(|| "unknown to both streams".into());
let add = format!("{A}add().");
check("a add() = both", v.prov(&add) == Some(Provenance::Both), show(v.prov(&add)));
let run_ = format!("{B}run().");
check("b run() = both", v.prov(&run_) == Some(Provenance::Both), show(v.prov(&run_)));
let main_ = format!("{B}`bin=bctl`:main().");
check("b `bin=bctl`:main() = both", v.prov(&main_) == Some(Provenance::Both), show(v.prov(&main_)));
let ident = format!("{A}ident().");
let monos: Vec<&mimir_schema::NodeRow> = o.nodes.iter().filter(|n| n.kind == kind::MONO_INSTANCE && n.parent.as_deref() == Some(ident.as_str())).collect();
let mono_ids: BTreeMap<&str, bool> = monos.iter().map(|n| (n.id.as_str(), v.edge(EdgeKind::InstanceOf, &n.id, &ident))).collect();
check(
"a ident<T> → 2 mono_instance + INSTANCE_OF",
monos.len() == 2 && mono_ids.values().all(|x| *x) && mono_ids.contains_key(format!("{ident}`a::ident::<u32>`:").as_str()) && mono_ids.contains_key(format!("{ident}`a::ident::<&str>`:").as_str()),
format!("{mono_ids:?}"),
);
check("mono_instance provenance = binary_only (derived)", monos.iter().all(|n| v.prov(&n.id) == Some(Provenance::BinaryOnly)), format!("{:?}", monos.iter().map(|n| v.prov(&n.id)).collect::<Vec<_>>()));
// the binary stream never re-emits a source node: every node it writes is
// one the source half does not define
let reemitted: Vec<&str> = o.nodes.iter().filter(|n| src.nodes.get(&n.id).is_some_and(|s| s.defined_here)).map(|n| n.id.as_str()).collect();
check("binary stream re-emits no source node", reemitted.is_empty(), format!("{reemitted:?}"));
check("generic a ident() itself = both (instances linked)", v.prov(&ident) == Some(Provenance::Both), show(v.prov(&ident)));
for (name, id) in [("#[inline(always)] a tiny()", format!("{A}tiny().")), ("dead a dead()", format!("{A}dead()."))] {
let in_src = src.nodes.get(&id).is_some_and(|n| n.defined_here);
check(&format!("{name} = source_only"), in_src && v.prov(&id) == Some(Provenance::SourceOnly), format!("in source: {in_src}, derived: {}", show(v.prov(&id))));
}
let getpid = format!("{A}getpid().");
let import_row = o.symbols.iter().find(|s| s.id == getpid && s.role == role::IMPORT);
check(
"extern \"C\" a getpid() = both via the undefined symbol",
v.prov(&getpid) == Some(Provenance::Both) && import_row.is_some_and(|s| s.addr.is_none()),
format!("{} / {:?}", show(v.prov(&getpid)), import_row.map(|s| s.symbol.clone())),
);
let abs = format!("{A}abs().");
check("(info) a abs(): LLVM folds libc abs, no import", true, show(v.prov(&abs)));
let ffi_imports = o.nodes.iter().filter(|n| n.kind == kind::FFI_IMPORT).count();
check("undefined symbols with no source decl → ffi_import nodes", ffi_imports > 10, format!("{ffi_imports}"));
let l = &o.ledger;
check("no crate-id conflicts", l.crate_id_conflicts.is_empty(), format!("{:?}", l.crate_id_conflicts));
let ab_ambiguous = l.ambiguous_crates.keys().any(|k| k == "a" || k == "b");
check("a / b crate ids resolved exactly (not by name)", !ab_ambiguous && !l.crate_via.contains_key("ambiguous"), format!("{:?}", l.crate_via));
let a_bytes = l.bytes_by_crate.iter().find(|(k, ..)| k == "a@0.1.0").map(|x| x.1).unwrap_or(0);
check("size attributed to a@0.1.0", a_bytes > 0, format!("{a_bytes} bytes"));
eprintln!("── {label} ──");
for (k, v) in &rows {
eprintln!(" {k:<55} {v}");
}
eprintln!(" ledger: matched {:?} via {:?} roles {:?} imports {:?}", l.matched, l.crate_via, l.roles, l.imports);
eprintln!(" source_only {} of {} ({:?})", l.source_only_items, l.source_items_in_linked_crates, l.source_only_samples);
assert!(failed.is_empty(), "{label} failed: {failed:#?}");
rows
}
#[test]
fn fixture_release_binary_joins_the_source_dump() {
let bin_dir = build_driver();
let src = PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/../mimir-driver/tests/fixture"));
let work = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join(format!("binfixture-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&work);
copy_dir(&src, &work);
let night = nightly();
// ---- PRODUCT shape: nightly check dump + stable release build
let dump = work.join("dump");
let st = scrubbed(bin_dir.join("cargo-mimir-dump"))
.current_dir(&work)
.args(["--out", dump.to_str().unwrap(), "--target-dir", work.join("t-check").to_str().unwrap(), "--driver", bin_dir.join("mimir-driver").to_str().unwrap()])
.status()
.unwrap();
assert!(st.success(), "cargo mimir-dump failed");
let st = cargo(None)
.current_dir(&work)
.env("CARGO_PROFILE_RELEASE_DEBUG", "2")
.env("RUSTFLAGS", "-C symbol-mangling-version=v0")
.args(["build", "--release", "--target-dir", work.join("t-stable").to_str().unwrap()])
.status()
.unwrap();
assert!(st.success(), "stable release build failed");
let o = run(&Options {
binary: work.join("t-stable/release/bctl"),
dump: dump.clone(),
deps_dir: Some(work.join("t-stable/release")),
manifest: Some(work.join("Cargo.toml")),
sysroot: Some(sysroot(None)),
..Default::default()
})
.unwrap();
mimir_binary::write(&work.join("out-stable"), "bctl", &o).unwrap();
assert_fixture(&o, &dump, "PRODUCT shape (stable build, nightly check dump)");
assert_eq!(o.ledger.matched.get("exact_symbol"), None, "a stable artifact cannot share symbol names with a check dump");
// ---- DRIVER shape: one compile for dump + artifact
let ddump = work.join("dump-build");
let st = scrubbed(bin_dir.join("cargo-mimir-dump"))
.current_dir(&work)
.env("CARGO_PROFILE_RELEASE_DEBUG", "2")
.env("RUSTFLAGS", "-C symbol-mangling-version=v0")
.args(["--build", "--out", ddump.to_str().unwrap(), "--target-dir", work.join("t-driver").to_str().unwrap(), "--driver", bin_dir.join("mimir-driver").to_str().unwrap(), "--", "--release"])
.status()
.unwrap();
assert!(st.success(), "cargo mimir-dump --build failed");
let opts = Options {
binary: work.join("t-driver/release/bctl"),
dump: ddump.clone(),
deps_dir: Some(work.join("t-driver/release")),
manifest: Some(work.join("Cargo.toml")),
sysroot: Some(sysroot(Some(&night))),
..Default::default()
};
let exact = run(&opts).unwrap();
assert_fixture(&exact, &ddump, "DRIVER shape (dump + artifact from one compile)");
let n_exact = exact.ledger.matched.get("exact_symbol").copied().unwrap_or(0);
assert!(n_exact >= 3, "driver symbol names should join run/add/main exactly: {:?}", exact.ledger.matched);
// ORACLE: every symbol the driver named, re-derived through the v0 path
let via_v0 = run(&Options { ignore_driver_symbols: true, ..opts }).unwrap();
let by_sym = |o: &Output| -> HashMap<String, String> { o.symbols.iter().map(|s| (s.symbol.clone(), s.id.clone())).collect() };
let (a, b) = (by_sym(&exact), by_sym(&via_v0));
let named: HashSet<String> = mimir_binary::source::SourceDump::load(&ddump).unwrap().symbol_ids.into_keys().collect();
let oracle: Vec<&String> = a.keys().filter(|s| named.contains(*s)).collect();
let mut wrong = Vec::new();
for s in &oracle {
if a.get(*s) != b.get(*s) {
wrong.push(format!("{s}: driver {:?} v0 {:?}", a.get(*s), b.get(*s)));
}
}
eprintln!("oracle: {} driver-named symbols in the artifact, {} re-derived differently", oracle.len(), wrong.len());
assert!(oracle.len() >= 3, "oracle too small");
assert!(wrong.is_empty(), "v0 path disagrees with the driver's symbol names: {wrong:#?}");
}