use std::collections::BTreeSet;
use pounce_algorithm::application::IpoptApplication;
use pounce_algorithm::unimplemented_options::{UNIMPLEMENTED_FEATURES, UNIMPLEMENTED_VALUES};
fn read_sites() -> Vec<String> {
fn walk(dir: &std::path::Path, out: &mut Vec<String>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for e in entries.flatten() {
let p = e.path();
if p.is_dir() {
walk(&p, out);
} else if p.extension().is_some_and(|x| x == "rs")
&& p.file_name()
.is_some_and(|f| f != "upstream_options.rs" && f != "sens_app.rs")
{
if let Ok(s) = std::fs::read_to_string(&p) {
out.push(s);
}
}
}
}
let crates = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("crates/");
let mut out = Vec::new();
for e in std::fs::read_dir(crates).expect("read crates/").flatten() {
let src = e.path().join("src");
if src.is_dir() {
walk(&src, &mut out);
}
}
assert!(!out.is_empty(), "found no crate sources to scan");
out
}
#[test]
fn every_registered_sipopt_option_is_consumed_or_refused() {
let app = IpoptApplication::new();
let refused: BTreeSet<&str> = UNIMPLEMENTED_FEATURES
.iter()
.flat_map(|g| g.options.iter().copied())
.chain(UNIMPLEMENTED_VALUES.iter().map(|v| v.option))
.collect();
let sources = read_sites();
let mut dangling = Vec::new();
let mut seen = 0usize;
for opt in app.registered_options().registered_options_in_order() {
if opt.category != "sIPOPT" {
continue;
}
seen += 1;
if refused.contains(opt.name.as_str()) {
continue;
}
let quoted = format!("\"{}\"", opt.name);
if sources.iter().any(|s| s.contains("ed)) {
continue;
}
dangling.push(opt.name.clone());
}
assert!(
seen >= 7,
"the sIPOPT category must be non-empty, found {seen}"
);
assert!(
dangling.is_empty(),
"these sIPOPT options are registered but neither read nor \
refused — setting one does nothing, silently: {dangling:?}"
);
}
#[test]
fn n_sens_steps_is_refused_above_its_single_implemented_tier() {
let mut app = IpoptApplication::new();
assert_eq!(app.unimplemented_option_refusal(), None, "unset");
app.options_mut()
.set_integer_value("n_sens_steps", 1, true, false)
.expect("the registered default must still parse");
assert_eq!(
app.unimplemented_option_refusal(),
None,
"`n_sens_steps=1` is the tier pounce computes and asks for nothing"
);
app.options_mut()
.set_integer_value("n_sens_steps", 3, true, false)
.expect("upstream's values must still parse");
let msg = app
.unimplemented_option_refusal()
.expect("`n_sens_steps=3` must be refused");
assert!(msg.contains("n_sens_steps"), "{msg}");
assert!(msg.contains("perturbation tier"), "{msg}");
assert!(
msg.contains("677"),
"the message must name the issue: {msg}"
);
}