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

nordisk/nornir

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:#?}");
}