169 lines
5.6 KiB
Rust
169 lines
5.6 KiB
Rust
|
|
//! Integration smoke test: spawn the compiled `cstat-extract-rustc`
|
||
|
|
//! binary against the §13 worked-example fixture, parse the resulting
|
||
|
|
//! JSON via `cstat_ir::CstatIrDump`, assert each IR layer is populated.
|
||
|
|
//!
|
||
|
|
//! This test is the executable form of Stage 12's done-when contract:
|
||
|
|
//! "running the binary on the fixture produces a valid cstat-ir JSON
|
||
|
|
//! dump covering HIR, THIR, MIR (pre and post opt), borrowck artifacts,
|
||
|
|
//! and mono-items".
|
||
|
|
//!
|
||
|
|
//! The test requires nightly's `librustc_driver*.so` on the dynamic
|
||
|
|
//! linker path; we derive `LD_LIBRARY_PATH` from `rustc --print
|
||
|
|
//! sysroot` so the test stays self-contained.
|
||
|
|
|
||
|
|
use std::path::PathBuf;
|
||
|
|
use std::process::Command;
|
||
|
|
|
||
|
|
use cstat_ir::CstatIrDump;
|
||
|
|
|
||
|
|
fn fixture_path() -> PathBuf {
|
||
|
|
let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||
|
|
p.pop(); // crates
|
||
|
|
p.pop(); // workspace root
|
||
|
|
p.push("examples");
|
||
|
|
p.push("single-file-lib");
|
||
|
|
p
|
||
|
|
}
|
||
|
|
|
||
|
|
fn rustc_sysroot() -> String {
|
||
|
|
let out = Command::new("rustc")
|
||
|
|
.arg("--print")
|
||
|
|
.arg("sysroot")
|
||
|
|
.output()
|
||
|
|
.expect("rustc --print sysroot");
|
||
|
|
assert!(out.status.success(), "rustc --print sysroot failed");
|
||
|
|
String::from_utf8(out.stdout).unwrap().trim().to_string()
|
||
|
|
}
|
||
|
|
|
||
|
|
fn run_extractor(out_path: &std::path::Path) {
|
||
|
|
let bin = env!("CARGO_BIN_EXE_cstat-extract-rustc");
|
||
|
|
let sysroot = rustc_sysroot();
|
||
|
|
let status = Command::new(bin)
|
||
|
|
.arg("--path")
|
||
|
|
.arg(fixture_path())
|
||
|
|
.arg("--output")
|
||
|
|
.arg(out_path)
|
||
|
|
.env("LD_LIBRARY_PATH", format!("{sysroot}/lib"))
|
||
|
|
.status()
|
||
|
|
.expect("spawn cstat-extract-rustc");
|
||
|
|
assert!(
|
||
|
|
status.success(),
|
||
|
|
"cstat-extract-rustc exited with {status:?}"
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
fn extract_dump() -> CstatIrDump {
|
||
|
|
let tmp = tempdir_path();
|
||
|
|
std::fs::create_dir_all(&tmp).unwrap();
|
||
|
|
let out = tmp.join("dump.json");
|
||
|
|
run_extractor(&out);
|
||
|
|
let txt = std::fs::read_to_string(&out).expect("read dump.json");
|
||
|
|
serde_json::from_str(&txt).expect("parse CstatIrDump")
|
||
|
|
}
|
||
|
|
|
||
|
|
fn tempdir_path() -> PathBuf {
|
||
|
|
std::env::temp_dir().join(format!(
|
||
|
|
"cstat-extract-rustc-it-{}",
|
||
|
|
std::process::id()
|
||
|
|
))
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn fixture_dump_covers_every_ir_layer() {
|
||
|
|
let dump = extract_dump();
|
||
|
|
|
||
|
|
assert_eq!(dump.ir_format_version, cstat_ir::IR_FORMAT_VERSION);
|
||
|
|
assert!(
|
||
|
|
dump.toolchain.version.contains("rustc"),
|
||
|
|
"toolchain version should look like rustc output: {:?}",
|
||
|
|
dump.toolchain.version
|
||
|
|
);
|
||
|
|
|
||
|
|
// §13 contract: every required IR layer is present.
|
||
|
|
let hir = dump.hir.as_ref().expect("hir section");
|
||
|
|
let thir = dump.thir.as_ref().expect("thir section");
|
||
|
|
let mir_preopt = dump.mir_preopt.as_ref().expect("mir_preopt section");
|
||
|
|
let mir_opt = dump.mir_opt.as_ref().expect("mir_opt section");
|
||
|
|
let borrowck = dump.borrowck.as_ref().expect("borrowck section");
|
||
|
|
let mono = dump.mono_items.as_ref().expect("mono_items section");
|
||
|
|
|
||
|
|
// Two pub fns in the fixture → both should appear as HIR items.
|
||
|
|
let hir_fns: Vec<&str> = hir
|
||
|
|
.items
|
||
|
|
.iter()
|
||
|
|
.filter(|i| i.kind == "fn")
|
||
|
|
.map(|i| i.def_path.as_str())
|
||
|
|
.collect();
|
||
|
|
assert!(hir_fns.contains(&"add"), "HIR missing add: {hir_fns:?}");
|
||
|
|
assert!(
|
||
|
|
hir_fns.contains(&"classify"),
|
||
|
|
"HIR missing classify: {hir_fns:?}"
|
||
|
|
);
|
||
|
|
|
||
|
|
// classify has two `if`s → branch_count ≥ 2.
|
||
|
|
let classify_hir = hir
|
||
|
|
.items
|
||
|
|
.iter()
|
||
|
|
.find(|i| i.def_path == "classify")
|
||
|
|
.expect("classify in hir");
|
||
|
|
assert!(
|
||
|
|
classify_hir.branch_count >= 2,
|
||
|
|
"classify branch_count {} should be ≥ 2 (two if exprs)",
|
||
|
|
classify_hir.branch_count
|
||
|
|
);
|
||
|
|
|
||
|
|
// THIR / MIR / borrowck all see both bodies.
|
||
|
|
for layer in [
|
||
|
|
("thir", thir.bodies.iter().map(|b| b.def_path.as_str()).collect::<Vec<_>>()),
|
||
|
|
("mir_preopt", mir_preopt.bodies.iter().map(|b| b.def_path.as_str()).collect()),
|
||
|
|
("mir_opt", mir_opt.bodies.iter().map(|b| b.def_path.as_str()).collect()),
|
||
|
|
("borrowck", borrowck.bodies.iter().map(|b| b.def_path.as_str()).collect()),
|
||
|
|
] {
|
||
|
|
let (name, paths) = layer;
|
||
|
|
assert!(paths.contains(&"add"), "{name} missing add: {paths:?}");
|
||
|
|
assert!(
|
||
|
|
paths.contains(&"classify"),
|
||
|
|
"{name} missing classify: {paths:?}"
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// classify's MIR must have ≥ 4 basic blocks (entry + two if branches
|
||
|
|
// + join). Exact count is rustc-version-sensitive so we lower-bound.
|
||
|
|
let mir_classify = mir_opt
|
||
|
|
.bodies
|
||
|
|
.iter()
|
||
|
|
.find(|b| b.def_path == "classify")
|
||
|
|
.expect("classify in mir_opt");
|
||
|
|
assert!(
|
||
|
|
mir_classify.basic_blocks.len() >= 4,
|
||
|
|
"classify basic_blocks {} should be ≥ 4",
|
||
|
|
mir_classify.basic_blocks.len()
|
||
|
|
);
|
||
|
|
|
||
|
|
// Mono-items: both non-generic fns produce exactly one instantiation.
|
||
|
|
let mono_paths: Vec<&str> = mono.items.iter().map(|m| m.def_path.as_str()).collect();
|
||
|
|
assert!(
|
||
|
|
mono_paths.contains(&"add"),
|
||
|
|
"mono_items missing add: {mono_paths:?}"
|
||
|
|
);
|
||
|
|
assert!(
|
||
|
|
mono_paths.contains(&"classify"),
|
||
|
|
"mono_items missing classify: {mono_paths:?}"
|
||
|
|
);
|
||
|
|
for group in &mono.generic_instantiation_groups {
|
||
|
|
assert_eq!(
|
||
|
|
group.instantiation_count, 1,
|
||
|
|
"non-generic {} should have exactly one instantiation",
|
||
|
|
group.source_def_path
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn fixture_dump_roundtrips_through_json_losslessly() {
|
||
|
|
let dump = extract_dump();
|
||
|
|
let json = serde_json::to_string(&dump).unwrap();
|
||
|
|
let decoded: CstatIrDump = serde_json::from_str(&json).unwrap();
|
||
|
|
assert_eq!(decoded, dump);
|
||
|
|
}
|