use super::*;
#[test]
fn parse_flowcutter_budget_shapes() {
match parse_ok("flowcutter-primal").param {
SpecParam::FcTimed {
timeout_ms,
iters,
patience_ms,
} => {
assert_eq!((timeout_ms, iters, patience_ms), (200, 100_000, 100));
}
_ => panic!("a spec with no budget is timed mode"),
}
match parse_ok("flowcutter-incidence:budget=250ms").param {
SpecParam::FcTimed {
timeout_ms,
iters,
patience_ms,
} => {
assert_eq!((timeout_ms, iters, patience_ms), (250, 100_000, 150));
}
_ => panic!("'budget=<N>ms' is timed mode"),
}
match parse_ok("flowcutter-primal:budget=250ms,iters=50,patience=20").param {
SpecParam::FcTimed {
timeout_ms,
iters,
patience_ms,
} => {
assert_eq!((timeout_ms, iters, patience_ms), (250, 50, 20));
}
_ => panic!("a fully written timed budget is timed mode"),
}
match parse_ok("flowcutter-primal:budget=100000steps").param {
SpecParam::FcSteps { steps, iters } => assert_eq!((steps, iters), (100_000, 900)),
_ => panic!("'budget=<N>steps' is step-budgeted mode"),
}
match parse_ok("flowcutter-primal:budget=100000steps,iters=900").param {
SpecParam::FcSteps { steps, iters } => assert_eq!((steps, iters), (100_000, 900)),
_ => panic!("a step budget with an iteration count is step-budgeted mode"),
}
}
#[test]
fn parse_guided_bisect_budget_shapes() {
let base = "guided-bisect";
match parse_ok(base).param {
SpecParam::FcTimed {
timeout_ms,
iters,
patience_ms,
} => assert_eq!(
(timeout_ms, iters, patience_ms),
(200, 100_000, 100),
"{base} with no budget is the no-budget timed mode",
),
_ => panic!("a bare {base} is timed mode"),
}
let steps = format!("{base}:budget=150000steps,iters=15");
match parse_ok(&steps).param {
SpecParam::FcSteps { steps, iters } => assert_eq!((steps, iters), (150_000, 15)),
_ => panic!("'{steps}' is step-budgeted mode"),
}
}
#[test]
fn parse_seed_and_imbalance_params() {
match parse_ok("goatd-primal:seed=7").param {
SpecParam::Goatd {
seed,
refine,
candidate,
} => {
assert_eq!(seed, 7);
assert!(refine, "the refinement pass is what a goatd spec means");
assert_eq!(candidate, 0, "an absent candidate is the winner");
}
_ => panic!("a goatd param is a seed, a refinement and a candidate"),
}
match parse_ok("goatd-incidence:candidate=3,seed=2").param {
SpecParam::Goatd {
seed, candidate, ..
} => {
assert_eq!(seed, 2);
assert_eq!(candidate, 3);
}
_ => panic!("a goatd param is a seed, a refinement and a candidate"),
}
match parse_ok("goatd-incidence").param {
SpecParam::Goatd { seed, .. } => assert_eq!(seed, 0, "an absent seed is 0"),
_ => panic!("a goatd param is a seed and a refinement"),
}
match parse_ok("hypergraph-bisect:imbalance=0.4").param {
SpecParam::Imbalance(v) => assert!((v - 0.4).abs() < 1e-12),
_ => panic!("a bisect param is an imbalance"),
}
match parse_ok("hypergraph-bisect").param {
SpecParam::Imbalance(v) => {
assert!((v - crate::decompose::IMBALANCE_BALANCED).abs() < 1e-12)
}
_ => panic!("a bisect param is an imbalance"),
}
}
#[test]
fn a_seed_is_accepted_by_every_elimination_order() {
for name in crate::decompose::elimination_spec_names() {
for (view, _) in crate::decompose::VIEW_SUFFIXES {
let spec = format!("{name}{view}:seed=7");
match parse_ok(&spec).param {
SpecParam::Elimination { seed, .. } => {
assert_eq!(seed, 7, "{spec} carries its seed")
}
_ => panic!("{spec} takes a seed"),
}
}
}
}
#[test]
fn a_parameter_written_twice_is_refused_rather_than_last_wins() {
for (spec, key) in [
("flowcutter-primal:budget=200ms,iters=10,iters=20", "iters"),
(
"flowcutter-incidence:budget=200ms,patience=10,patience=20",
"patience",
),
("force:dim=3,dim=4", "dim"),
("goatd-incidence:seed=1,seed=2", "seed"),
("hypergraph-bisect:imbalance=0.1,imbalance=0.2", "imbalance"),
] {
let err = validate_vtree_spec(spec)
.expect_err(&format!("{spec} writes a key twice"))
.to_string();
assert!(
err.contains(key),
"{spec} must be refused naming {key:?}, got: {err}",
);
}
match parse_ok("flowcutter-primal:budget=200ms,iters=10,patience=5").param {
SpecParam::FcTimed {
timeout_ms,
iters,
patience_ms,
} => assert_eq!((timeout_ms, iters, patience_ms), (200, 10, 5)),
_ => panic!("one of each key is the timed mode"),
}
}
#[test]
fn a_parameter_that_is_not_key_equals_value_is_refused() {
for (spec, offender) in [
("flowcutter-primal:200ms", "200ms"),
("goatd-incidence:7", "7"),
("hypergraph-bisect:0.40", "0.40"),
("force:cut", "cut"),
("flowcutter-primal:budget=200ms,best", "best"),
("goatd-incidence:=3", "=3"),
] {
let err = validate_vtree_spec(spec)
.expect_err(&format!("{spec} writes a parameter without its key"))
.to_string();
assert!(
err.contains(offender),
"{spec} must be refused naming {offender:?}, got: {err}",
);
}
}
#[test]
fn goatd_contract_rejects_a_bisect_imbalance_outside_the_legal_range() {
for value in ["2.0", "-1", "1.0", "0.5001", "-0.001", "nan", "inf", "-inf"] {
let spec = format!("hypergraph-bisect:imbalance={value}");
assert!(
validate_vtree_spec(&spec).is_err(),
"{spec} is outside 0.0..=0.5 and must be refused",
);
}
for (value, expected) in [("0", 0.0f64), ("0.5", 0.5), ("0.4", 0.4)] {
let spec = format!("hypergraph-bisect:imbalance={value}");
match parse_ok(&spec).param {
SpecParam::Imbalance(v) => assert!(
(v - expected).abs() < 1e-12,
"{spec} must type to {expected}, got {v}",
),
_ => panic!("{spec} takes an imbalance"),
}
}
}
#[test]
fn a_malformed_number_names_its_own_key() {
for (spec, offender, key) in [
("flowcutter-primal:budget=200ms,iters=abc", "abc", "iters"),
(
"flowcutter-primal:budget=200ms,patience=abc",
"abc",
"patience",
),
("flowcutter-incidence:budget=ms", "ms", "budget"),
("force:dim=abc", "abc", "dim"),
("force:restarts=abc", "abc", "restarts"),
] {
let err = validate_vtree_spec(spec)
.expect_err(&format!("{spec} has an unreadable number"))
.to_string();
assert!(
err.contains(offender) && err.contains(key),
"{spec} must name {offender:?} and the {key} key, got: {err}",
);
}
}