use std::path::PathBuf;
use std::process::Command;
use std::sync::atomic::{AtomicU64, Ordering};
use pounce_cli::solve_report::SolveReport;
fn pounce_exe() -> 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
}
fn tmp_path(suffix: &str) -> PathBuf {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
let mut p = std::env::temp_dir();
p.push(format!(
"pounce_issue534_{}_{}_{suffix}",
std::process::id(),
n
));
p
}
fn solve(extra: &[&str]) -> SolveReport {
let json_path = tmp_path("csfi2.json");
let sol_path = tmp_path("csfi2.sol");
let mut cmd = Command::new(pounce_exe());
cmd.arg(fixture("csfi2.nl"))
.arg(&sol_path)
.arg("--json-output")
.arg(&json_path);
for o in extra {
cmd.arg(o);
}
let _ = cmd.status().expect("spawn pounce");
let text = std::fs::read_to_string(&json_path).expect("read json report");
let _ = std::fs::remove_file(&json_path);
let _ = std::fs::remove_file(&sol_path);
serde_json::from_str(&text).expect("deserialize SolveReport")
}
const FORCE_DEFERRAL: [&str; 1] = ["resto_decline_progress_ratio=1e20"];
const NO_DEFERRAL: [&str; 1] = ["resto_decline_deferrals=0"];
#[test]
fn a_stalled_solve_is_declined_exactly_as_before() {
let baseline = solve(&NO_DEFERRAL);
let default = solve(&[]);
assert_eq!(
default.solution.status, baseline.solution.status,
"the progress test changed the status on a stalled solve",
);
assert_eq!(
default.solution.objective, baseline.solution.objective,
"the progress test moved the reported point on a stalled solve",
);
assert_eq!(
default.statistics.iteration_count, baseline.statistics.iteration_count,
"the progress test cost iterations on a solve it should not have \
deferred at all",
);
}
#[test]
fn a_lost_deferral_returns_the_same_point() {
let baseline = solve(&NO_DEFERRAL);
let deferred = solve(&FORCE_DEFERRAL);
assert_eq!(
deferred.solution.status, baseline.solution.status,
"a lost deferral changed the reported status",
);
assert_eq!(
deferred.solution.objective, baseline.solution.objective,
"a lost deferral returned a different point than declining would have \
({} vs {})",
deferred.solution.objective, baseline.solution.objective,
);
assert_eq!(
deferred.solution.x, baseline.solution.x,
"a lost deferral returned a different primal iterate than declining \
would have",
);
}
#[test]
fn a_lost_deferral_costs_a_bounded_number_of_iterations() {
let baseline = solve(&NO_DEFERRAL).statistics.iteration_count;
let deferred = solve(&FORCE_DEFERRAL).statistics.iteration_count;
assert!(
deferred > baseline,
"the deferral was not actually taken (both runs stopped at iteration \
{baseline}); this test would then pass vacuously",
);
assert!(
deferred <= baseline + 11,
"a lost deferral ran {} iterations past the decline; the continuation \
budget is 10",
deferred - baseline,
);
}