#![cfg(feature = "smt")]
use std::fs;
use splitrs::extraction::{
extract_pure_blocks, verify_rewrite_for_test, ExtractionOutcome, GateVerdictForTest,
};
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;
v
}
";
#[test]
fn happy_path_commits_and_round_trips() {
let mut file: syn::File = syn::parse_str(PURE_FN).expect("fixture parses");
let outcomes = extract_pure_blocks(&mut file, 1);
let committed: Vec<_> = outcomes
.iter()
.filter_map(|o| match o {
ExtractionOutcome::Committed {
fn_ident,
helper_ident,
} => Some((fn_ident.clone(), helper_ident.clone())),
_ => None,
})
.collect();
assert!(
!committed.is_empty(),
"expected at least one Committed outcome, got {outcomes:?}"
);
let (fn_ident, helper_ident) = &committed[0];
assert_eq!(fn_ident, "mix");
assert!(
helper_ident.starts_with("mix_extracted"),
"helper should be named after the origin, got {helper_ident}"
);
let rendered = prettyplease::unparse(&file);
assert!(
rendered.contains(helper_ident.as_str()),
"rendered file must contain the helper `{helper_ident}`:\n{rendered}"
);
assert!(
rendered.contains(&format!("{helper_ident}(")),
"rendered file must contain a call to `{helper_ident}`:\n{rendered}"
);
let reparsed = syn::parse_file(&rendered).expect("mutated file must re-parse as valid Rust");
let fn_count = reparsed
.items
.iter()
.filter(|i| matches!(i, syn::Item::Fn(_)))
.count();
assert!(
fn_count >= 2,
"expected helper + caller (>=2 fns), found {fn_count}"
);
}
#[test]
fn happy_path_via_tempfile_round_trips() {
let dir = std::env::temp_dir().join(format!("splitrs_extraction_{}", std::process::id()));
fs::create_dir_all(&dir).expect("create temp dir");
let src_path = dir.join("input.rs");
fs::write(&src_path, PURE_FN).expect("write fixture");
let source = fs::read_to_string(&src_path).expect("read fixture");
let mut file: syn::File = syn::parse_str(&source).expect("parse fixture");
let outcomes = extract_pure_blocks(&mut file, 1);
assert!(
outcomes
.iter()
.any(|o| matches!(o, ExtractionOutcome::Committed { .. })),
"expected a Committed outcome, got {outcomes:?}"
);
let rendered = prettyplease::unparse(&file);
let out_path = dir.join("output.rs");
fs::write(&out_path, &rendered).expect("write output");
let written = fs::read_to_string(&out_path).expect("read output");
syn::parse_file(&written).expect("written output must be valid Rust");
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn impure_call_block_is_not_extracted() {
let src = "\
fn pipeline(a: u32) -> u32 {
let t = stage_one(a);
let u = stage_two(t);
u + 1
}
fn stage_one(x: u32) -> u32 { passthrough(x) }
";
let mut file: syn::File = syn::parse_str(src).expect("fixture parses");
let before = prettyplease::unparse(&file);
let outcomes = extract_pure_blocks(&mut file, 1);
assert!(
!outcomes
.iter()
.any(|o| matches!(o, ExtractionOutcome::Committed { .. })),
"an impure (call-bearing) region must never be committed, got {outcomes:?}"
);
let after = prettyplease::unparse(&file);
assert_eq!(before, after, "file must be unchanged when nothing commits");
}
#[test]
fn rewriter_bug_is_refuted_not_committed() {
let broken_helper = "fn mix_extracted(a: u32, b: u32) -> u32 { a - b }";
let verdict = verify_rewrite_for_test(PURE_FN, 1, Some(broken_helper))
.expect("a candidate must be found in the pure fixture");
match verdict {
GateVerdictForTest::Refuted(cx) => {
assert!(
!cx.inputs.is_empty(),
"a refutation must carry a concrete counterexample"
);
}
other => {
panic!("the SMT gate MUST refute a mis-derived helper (semantic drift), got {other:?}")
}
}
}
#[test]
fn correct_rewrite_would_commit() {
let verdict = verify_rewrite_for_test(PURE_FN, 1, None).expect("a candidate must be found");
assert!(
matches!(verdict, GateVerdictForTest::WouldCommit),
"a faithful rewrite must be proven and committable, got {verdict:?}"
);
}