use std::path::PathBuf;
use std::process::Command;
use pounce_cli::solve_report::SolveReport;
fn pounce_exe() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_pounce"))
}
fn fixture_nl() -> PathBuf {
let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
p.push("tests");
p.push("fixtures");
p.push("parametric.nl");
p
}
fn aux_fixture(name: &str) -> PathBuf {
let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
p.push("tests");
p.push("fixtures");
p.push("aux_presolve");
p.push(name);
p
}
fn tmp_json(suffix: &str) -> PathBuf {
let mut p = std::env::temp_dir();
p.push(format!("pounce_aux_{}_{suffix}.json", std::process::id()));
p
}
fn run_for_report(fixture: &PathBuf, extra_args: &[&str]) -> SolveReport {
let json_path = tmp_json(&format!(
"e2e_{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
let mut cmd = Command::new(pounce_exe());
cmd.arg(fixture)
.arg("--json-output")
.arg(&json_path)
.arg("--json-detail")
.arg("summary");
for a in extra_args {
cmd.arg(a);
}
let output = cmd.output().expect("spawn pounce");
assert!(
output.status.success(),
"pounce failed (exit {:?}); stderr:\n{}",
output.status.code(),
String::from_utf8_lossy(&output.stderr)
);
let text = std::fs::read_to_string(&json_path).unwrap();
let _ = std::fs::remove_file(&json_path);
serde_json::from_str(&text).expect("parse SolveReport JSON")
}
#[test]
fn presolve_auxiliary_yes_disabled_by_sensitivity_warning() {
let output = Command::new(pounce_exe())
.arg(fixture_nl())
.arg("presolve=yes")
.arg("presolve_auxiliary=yes")
.output()
.expect("spawn pounce");
let code = output.status.code().unwrap_or(-1);
assert!(
code == 0 || code == 1,
"pounce exited with code {code} (stderr: {})",
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(!stderr.contains("panicked at"), "pounce panicked: {stderr}");
assert!(
stderr.contains("disabling presolve"),
"expected the sensitivity-disable warning in stderr; got:\n{stderr}"
);
}
#[test]
fn presolve_auxiliary_no_matches_baseline_objective() {
let baseline_json = tmp_json("baseline");
let aux_off_json = tmp_json("aux_off");
let baseline_status = Command::new(pounce_exe())
.arg(fixture_nl())
.arg("--json-output")
.arg(&baseline_json)
.arg("--json-detail")
.arg("summary")
.status()
.expect("spawn pounce baseline");
assert!(baseline_status.success());
let aux_off_status = Command::new(pounce_exe())
.arg(fixture_nl())
.arg("--json-output")
.arg(&aux_off_json)
.arg("--json-detail")
.arg("summary")
.arg("presolve=yes")
.arg("presolve_auxiliary=no")
.status()
.expect("spawn pounce aux-off");
assert!(aux_off_status.success());
let baseline_text = std::fs::read_to_string(&baseline_json).unwrap();
let baseline: SolveReport = serde_json::from_str(&baseline_text).unwrap();
let aux_off_text = std::fs::read_to_string(&aux_off_json).unwrap();
let aux_off: SolveReport = serde_json::from_str(&aux_off_text).unwrap();
let bobj = baseline.statistics.final_objective;
let aobj = aux_off.statistics.final_objective;
assert!(
(bobj - aobj).abs() < 1e-6,
"baseline obj {bobj} vs aux-off obj {aobj}"
);
let _ = std::fs::remove_file(&baseline_json);
let _ = std::fs::remove_file(&aux_off_json);
}
#[test]
fn presolve_auxiliary_yes_tutorial_zero_ipm_iters() {
let nl = aux_fixture("tutorial_flow_density.nl");
if !nl.exists() {
eprintln!("skipping: {} not present", nl.display());
return;
}
let baseline = run_for_report(&nl, &["presolve=yes", "presolve_auxiliary=no"]);
let aux_on = run_for_report(&nl, &["presolve=yes", "presolve_auxiliary=yes"]);
assert_eq!(
aux_on.statistics.iteration_count, 0,
"aux on should fix everything pre-IPM; got {} iters",
aux_on.statistics.iteration_count
);
let bobj = baseline.statistics.final_objective;
let aobj = aux_on.statistics.final_objective;
assert!(
(bobj - aobj).abs() < 1e-6,
"aux objective {aobj} disagrees with baseline {bobj}"
);
}
#[test]
fn presolve_auxiliary_yes_tutorial_perturbed_zero_ipm_iters() {
let nl = aux_fixture("tutorial_flow_density_perturbed.nl");
if !nl.exists() {
eprintln!("skipping: {} not present", nl.display());
return;
}
let baseline = run_for_report(&nl, &["presolve=yes", "presolve_auxiliary=no"]);
let aux_on = run_for_report(&nl, &["presolve=yes", "presolve_auxiliary=yes"]);
assert_eq!(
aux_on.statistics.iteration_count, 0,
"aux on should fix everything pre-IPM; got {} iters",
aux_on.statistics.iteration_count
);
let bobj = baseline.statistics.final_objective;
let aobj = aux_on.statistics.final_objective;
assert!(
(bobj - aobj).abs() < 1e-6,
"aux objective {aobj} disagrees with baseline {bobj}"
);
}
#[test]
fn presolve_auxiliary_yes_gaslib11_solves_with_defaults() {
let nl = aux_fixture("gaslib11_steady.nl");
if !nl.exists() {
eprintln!("skipping: {} not present", nl.display());
return;
}
let report = run_for_report(&nl, &["presolve=yes", "presolve_auxiliary=yes"]);
assert!(
report.statistics.final_objective.is_finite(),
"expected a finite objective; got {}",
report.statistics.final_objective
);
}