use crate::component::{VtreeBuild, build_vtree};
use crate::config::RunConfig;
use crate::decompose::{CandidatePreference, PortfolioKnobs, SelectionCtx};
use crate::error::VitriError;
use crate::tests::common::wide_component;
fn build_preferring(prefer: Option<CandidatePreference>) -> Result<VtreeBuild, VitriError> {
let ctx = SelectionCtx {
portfolio: PortfolioKnobs {
prefer,
..PortfolioKnobs::default()
},
..SelectionCtx::plain()
};
build_vtree(&wide_component(), &RunConfig::default(), &ctx)
}
fn winner(build: &VtreeBuild) -> String {
build.selections[0]
.winning_spec
.clone()
.expect("a portfolio build names the candidate that won")
}
#[test]
fn a_preference_selects_the_candidate_it_names_when_that_candidate_built() {
let on_score = winner(&build_preferring(None).expect("the fixture builds"));
let mut honored = Vec::new();
let mut did_not_build = Vec::new();
for name in PortfolioKnobs::candidate_names() {
let build = build_preferring(Some(CandidatePreference::Preferred(name.clone())))
.expect("a preference never fails a build that would have succeeded");
if winner(&build) == name {
honored.push(name);
} else {
assert_eq!(
winner(&build),
on_score,
"a preference that could not be met leaves selection exactly as it was",
);
did_not_build.push(name);
}
}
assert!(
honored.iter().any(|n| *n != on_score),
"the preference has to be able to overturn the score, or it decides \
nothing: honored {honored:?}, score picked {on_score}",
);
assert!(
!did_not_build.is_empty(),
"this fixture must keep at least one candidate that does not build over \
it, or the fallback half of the contract is untested",
);
}
#[test]
fn a_required_candidate_that_did_not_build_is_an_error_rather_than_a_substitute() {
let on_score = winner(&build_preferring(None).expect("the fixture builds"));
let unbuildable = PortfolioKnobs::candidate_names()
.into_iter()
.find(|name| {
let build = build_preferring(Some(CandidatePreference::Preferred(name.clone())))
.expect("a soft preference never fails the build");
winner(&build) != *name
})
.expect("this fixture keeps a candidate that does not build over it");
let err = build_preferring(Some(CandidatePreference::Required(unbuildable.clone())))
.expect_err("a required candidate that did not build must fail the build");
assert!(
matches!(err, VitriError::Construction { .. }),
"the build ran and could not deliver, which is a construction failure: {err:?}",
);
assert!(
err.to_string().contains(&unbuildable),
"the error names the candidate that was required: {err}",
);
assert_ne!(
on_score, unbuildable,
"a candidate that did not build cannot also be the one the scores chose",
);
}
#[test]
fn an_unknown_candidate_name_is_refused_before_any_build_runs() {
let err = build_preferring(Some(CandidatePreference::Preferred(
"minfill-primal".into(),
)))
.expect_err("an unknown candidate name must be refused");
assert!(
matches!(err, VitriError::Config { .. }),
"the request is unanswerable, not the formula: {err:?}",
);
let text = err.to_string();
for name in PortfolioKnobs::candidate_names() {
assert!(
text.contains(&name),
"the refusal lists what the catalog does build, and is missing {name}: {text}",
);
}
}
#[test]
fn every_name_a_build_can_report_is_a_name_a_preference_accepts() {
let names = PortfolioKnobs::candidate_names();
let reported = winner(&build_preferring(None).expect("the fixture builds"));
assert!(
names.contains(&reported),
"a build reported {reported}, which a preference would refuse: {names:?}",
);
}