#![cfg(feature = "smt")]
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use splitrs::extraction::{extract_pure_blocks, ExtractionOutcome};
use splitrs::file_analyzer::FileAnalyzer;
const PURE_FN: &str = "\
fn mix(a: u32, b: u32) -> u32 {
let s = a + b;
let t = s * 3;
let u = t ^ a;
let v = u + b;
let w = v & a;
let x = w | b;
let y = x << 1;
let z = y + s;
z
}
";
fn unique_tmp_dir(tag: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!(
"splitrs_extract_pure_{tag}_{}_{:?}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0)
));
fs::create_dir_all(&dir).expect("create temp working dir");
dir
}
fn collect_rs_files(dir: &Path, out: &mut Vec<PathBuf>) {
let entries = match fs::read_dir(dir) {
Ok(e) => e,
Err(_) => return,
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
collect_rs_files(&path, out);
} else if path.extension().map(|e| e == "rs").unwrap_or(false) {
out.push(path);
}
}
}
#[test]
fn extract_pure_pipeline_emits_proven_helper_and_round_trips() {
let work = unique_tmp_dir("pipeline");
let src = work.join("calc.rs");
let out = work.join("out");
fs::write(&src, PURE_FN).expect("write source fixture");
let status = Command::new(env!("CARGO_BIN_EXE_splitrs"))
.arg("--input")
.arg(&src)
.arg("--output")
.arg(&out)
.arg("--max-lines")
.arg("1")
.arg("--extract-pure")
.output()
.expect("spawn splitrs binary");
assert!(
status.status.success(),
"splitrs --extract-pure should exit 0.\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&status.stdout),
String::from_utf8_lossy(&status.stderr)
);
let mut rs_files = Vec::new();
collect_rs_files(&out, &mut rs_files);
assert!(
!rs_files.is_empty(),
"expected generated .rs files under {out:?}"
);
let mut helper_seen = false;
for file in &rs_files {
let content = fs::read_to_string(file).expect("read generated module");
syn::parse_file(&content)
.unwrap_or_else(|e| panic!("generated file {file:?} must re-parse as valid Rust: {e}"));
if content.contains("mix_extracted") {
helper_seen = true;
}
}
assert!(
helper_seen,
"the SMT-proven helper `mix_extracted` must appear in the split output \
(files: {rs_files:?})"
);
let _ = fs::remove_dir_all(&work);
}
#[test]
fn extract_pure_helper_survives_split_at_library_level() {
let mut file: syn::File = syn::parse_str(PURE_FN).expect("fixture parses");
let outcomes = extract_pure_blocks(&mut file, 1);
let helper_ident = outcomes
.iter()
.find_map(|o| match o {
ExtractionOutcome::Committed { helper_ident, .. } => Some(helper_ident.clone()),
_ => None,
})
.expect("a Committed extraction must have produced a helper");
assert!(
helper_ident.starts_with("mix_extracted"),
"helper should be named after the origin, got {helper_ident}"
);
let mut analyzer = FileAnalyzer::new(false, 500);
analyzer.analyze(&file);
let modules = analyzer.group_by_module(1);
let helper_in_modules = modules.iter().any(|m| {
m.standalone_items.iter().any(|item| match item {
syn::Item::Fn(f) => f.sig.ident == helper_ident.as_str(),
_ => false,
})
});
assert!(
helper_in_modules,
"the proven helper `{helper_ident}` must survive the split as a real item"
);
}
#[test]
fn verify_report_distinguishes_proven_from_structural() {
let work = unique_tmp_dir("verify");
let src = work.join("calc.rs");
let out = work.join("out");
fs::write(&src, PURE_FN).expect("write source fixture");
let result = Command::new(env!("CARGO_BIN_EXE_splitrs"))
.arg("--input")
.arg(&src)
.arg("--output")
.arg(&out)
.arg("--max-lines")
.arg("1")
.arg("--extract-pure")
.arg("--verify")
.output()
.expect("spawn splitrs binary");
assert!(
result.status.success(),
"splitrs --extract-pure --verify should exit 0.\nstderr:\n{}",
String::from_utf8_lossy(&result.stderr)
);
let stdout = String::from_utf8_lossy(&result.stdout);
assert!(
stdout.contains("Semantic verification report"),
"missing report header.\nstdout:\n{stdout}"
);
assert!(
stdout.contains("SMT-Verified (body rewrite):"),
"missing SMT-Verified section.\nstdout:\n{stdout}"
);
assert!(
stdout.contains("mix: function extraction — SMT-Verified equivalent (QF_BV, all inputs)")
&& stdout.contains("helper mix_extracted"),
"missing the SMT-Verified line for `mix` -> `mix_extracted`.\nstdout:\n{stdout}"
);
assert!(
stdout.contains("Structural identity (NOT SMT-proven):"),
"missing structural-identity section.\nstdout:\n{stdout}"
);
assert!(
stdout.contains("items relocated unchanged (structural identity)")
&& stdout.contains("NOT proven by SMT"),
"missing the honest structural-identity statement.\nstdout:\n{stdout}"
);
let _ = fs::remove_dir_all(&work);
}
#[test]
fn verify_report_without_extract_pure_is_structural_only() {
let work = unique_tmp_dir("verify_only");
let src = work.join("plain.rs");
let out = work.join("out");
fs::write(
&src,
"pub struct Foo { x: u32 }\npub struct Bar { y: u32 }\npub fn helper() -> u32 { 42 }\n",
)
.expect("write source fixture");
let result = Command::new(env!("CARGO_BIN_EXE_splitrs"))
.arg("--input")
.arg(&src)
.arg("--output")
.arg(&out)
.arg("--max-lines")
.arg("1")
.arg("--verify")
.output()
.expect("spawn splitrs binary");
assert!(
result.status.success(),
"splitrs --verify (no extract) should exit 0.\nstderr:\n{}",
String::from_utf8_lossy(&result.stderr)
);
let stdout = String::from_utf8_lossy(&result.stdout);
assert!(
stdout.contains("Semantic verification report"),
"missing report header.\nstdout:\n{stdout}"
);
assert!(
stdout.contains("`--extract-pure` was not requested"),
"report should note that --extract-pure is where SMT proofs apply.\nstdout:\n{stdout}"
);
assert!(
stdout.contains("items relocated unchanged (structural identity)")
&& stdout.contains("NOT proven by SMT"),
"structural-identity statement must always print.\nstdout:\n{stdout}"
);
let _ = fs::remove_dir_all(&work);
}