#![cfg(feature = "ma57")]
use std::path::PathBuf;
use std::process::Command;
fn pounce_bin() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_pounce"))
}
fn fixture(name: &str) -> PathBuf {
let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
p.push("tests");
p.push("fixtures");
p.push(name);
p
}
#[test]
fn the_ma57_binary_starts() {
let out = Command::new(pounce_bin())
.arg("--version")
.output()
.expect("spawn pounce");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
!stderr.contains("Library not loaded") && !stderr.contains("LC_RPATH"),
"the ma57 build of `pounce` cannot start — the CoinHSL rpath did not reach the \
binary (gh#811). Check that crates/pounce-hsl/build.rs still emits \
`cargo:rpath=` and that crates/pounce-cli/build.rs still re-emits it from \
DEP_COINHSL_RPATH.\nstderr: {stderr}"
);
assert!(out.status.success(), "`pounce --version` failed: {stderr}");
}
#[test]
fn the_binary_reports_ma57_enabled() {
let out = Command::new(pounce_bin())
.arg("--about")
.output()
.expect("spawn pounce");
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("ma57: enabled"),
"built with --features ma57 but `--about` does not report it:\n{stdout}"
);
}
fn run(args: &[&str]) -> (Vec<u32>, String) {
let out = Command::new(pounce_bin())
.arg(fixture("deb7.nl"))
.arg("linear_solver=ma57")
.args(args)
.output()
.expect("spawn pounce");
let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
let stderr = String::from_utf8_lossy(&out.stderr);
let status = stdout
.lines()
.find_map(|l| l.trim().strip_prefix("Status:"))
.unwrap_or_else(|| {
panic!("{args:?} produced no status line\nstdout: {stdout}\nstderr: {stderr}")
})
.trim()
.to_string();
let iters = stdout
.lines()
.filter_map(|l| l.trim().strip_prefix("Number of Iterations....:"))
.filter_map(|v| v.trim().parse::<u32>().ok())
.collect::<Vec<_>>();
assert!(
!iters.is_empty(),
"{args:?} produced no iteration counts:\n{stdout}"
);
(iters, status)
}
fn solved(status: &str) -> bool {
matches!(status, "Solve_Succeeded" | "Solved_To_Acceptable_Level")
}
#[test]
fn ma57_solves_a_model() {
let (_, status) = run(&[]);
assert!(
solved(&status),
"linear_solver=ma57 did not solve deb7: {status}"
);
}
#[test]
fn an_ma57_option_changes_the_solve() {
let (baseline, base_status) = run(&[]);
let (stabilized, stab_status) = run(&["ma57_pivtol=0.5"]);
assert!(solved(&base_status) && solved(&stab_status));
assert_ne!(
baseline, stabilized,
"ma57_pivtol=0.5 left the solve identical to the 1e-8 default. Either the option is \
being discarded again (gh#825), or it is reaching MA57 and genuinely changing \
nothing — the first is a bug and the second is worth understanding before this \
assertion is relaxed."
);
}
#[test]
fn the_resto_prefix_reaches_only_the_restoration_subsolve() {
let (baseline, base_status) = run(&[]);
let (unprefixed, _) = run(&["ma57_pivtol=0.5"]);
let (prefixed, prefixed_status) = run(&["resto.ma57_pivtol=0.5"]);
assert!(solved(&base_status) && solved(&prefixed_status));
assert_ne!(
baseline, prefixed,
"`resto.ma57_pivtol=0.5` did not move the run. This fixture is only evidence while \
its solve actually enters restoration — if deb7's trajectory has changed so that it \
no longer does, this test needs a model that does, not a relaxed assertion."
);
assert_ne!(
unprefixed, prefixed,
"`resto.ma57_pivtol=0.5` reproduced the un-prefixed arm exactly, which is what an \
ignored prefix looks like: the value reached the main IPM instead of the \
restoration sub-IPM."
);
}