use std::collections::BTreeSet;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TestCandidate {
pub id: String,
pub cost: u64,
pub flake_penalty: u64,
pub covers: BTreeSet<String>,
pub explanation: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ObligationNeed {
pub id: String,
pub high_risk: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SelectionInput {
pub candidates: Vec<TestCandidate>,
pub obligations: Vec<ObligationNeed>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SelectedTest {
pub id: String,
pub covers: Vec<String>,
pub cost: u64,
pub explanation: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SelectionPlan {
pub selected: Vec<SelectedTest>,
pub uncovered_mandatory: Vec<String>,
pub algorithm: &'static str,
}
const MANDATORY_WEIGHT: u64 = 1_000;
const ALGORITHM: &str = "greedy-weighted-set-cover";
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CandidateSources {
pub base_protectors: Vec<TestCandidate>,
pub head_static: Vec<TestCandidate>,
pub head_dynamic: Vec<TestCandidate>,
pub obligation_tests: Vec<TestCandidate>,
pub risk_required: Vec<TestCandidate>,
}
#[must_use]
pub fn flow_aware_candidates(sources: CandidateSources) -> Vec<TestCandidate> {
let mut merged: Vec<TestCandidate> = Vec::new();
let labelled = [
("base historical protector", sources.base_protectors),
("head static selection", sources.head_static),
("head dynamic coverage", sources.head_dynamic),
("proves a changed obligation", sources.obligation_tests),
("risk required", sources.risk_required),
];
for (origin, candidates) in labelled {
for candidate in candidates {
if let Some(existing) = merged.iter_mut().find(|item| item.id == candidate.id) {
existing.covers.extend(candidate.covers);
existing.cost = existing.cost.min(candidate.cost);
existing.flake_penalty = existing.flake_penalty.max(candidate.flake_penalty);
for line in candidate.explanation {
if !existing.explanation.contains(&line) {
existing.explanation.push(line);
}
}
existing
.explanation
.push(format!("also selected by: {origin}"));
} else {
let mut candidate = candidate;
candidate.explanation.push(format!("selected by: {origin}"));
merged.push(candidate);
}
}
}
merged.sort_by(|left, right| left.id.cmp(&right.id));
merged
}
#[must_use]
pub fn select_flow_aware_plan(
sources: CandidateSources,
obligations: Vec<ObligationNeed>,
) -> SelectionPlan {
select_minimal_plan(SelectionInput {
candidates: flow_aware_candidates(sources),
obligations,
})
}
#[must_use]
pub fn select_minimal_plan(input: SelectionInput) -> SelectionPlan {
let mandatory: BTreeSet<String> = input
.obligations
.iter()
.filter(|item| item.high_risk)
.map(|item| item.id.clone())
.collect();
let mut remaining: BTreeSet<String> = input
.obligations
.iter()
.map(|item| item.id.clone())
.collect();
let mut unused = input.candidates;
unused.sort_by(|left, right| left.id.cmp(&right.id));
let mut selected = Vec::new();
loop {
let Some((index, gain_mandatory, gain_optional)) =
best_index(&unused, &remaining, &mandatory)
else {
break;
};
let candidate = unused.remove(index);
let mut newly: Vec<String> = candidate
.covers
.iter()
.filter(|id| remaining.contains(*id))
.cloned()
.collect();
newly.sort();
for id in &newly {
remaining.remove(id);
}
let cost = effective_cost(&candidate);
let mut explanation = candidate.explanation;
explanation.push(format!("covers obligations: {}", newly.join(", ")));
explanation.push(format!(
"greedy gain: {gain_mandatory} mandatory + {gain_optional} optional / cost {cost}"
));
selected.push(SelectedTest {
id: candidate.id,
covers: newly,
cost,
explanation,
});
if remaining.is_empty() {
break;
}
}
let uncovered_mandatory = mandatory
.into_iter()
.filter(|id| remaining.contains(id))
.collect();
SelectionPlan {
selected,
uncovered_mandatory,
algorithm: ALGORITHM,
}
}
fn best_index(
unused: &[TestCandidate],
remaining: &BTreeSet<String>,
mandatory: &BTreeSet<String>,
) -> Option<(usize, u64, u64)> {
let mut best: Option<(usize, u64, u64, u64, &str)> = None;
for (index, candidate) in unused.iter().enumerate() {
let mut gain_mandatory = 0_u64;
let mut gain_optional = 0_u64;
for id in &candidate.covers {
if !remaining.contains(id) {
continue;
}
if mandatory.contains(id) {
gain_mandatory = gain_mandatory.saturating_add(1);
} else {
gain_optional = gain_optional.saturating_add(1);
}
}
if gain_mandatory == 0 && gain_optional == 0 {
continue;
}
let cost = effective_cost(candidate).max(1);
let score = gain_mandatory
.saturating_mul(MANDATORY_WEIGHT)
.saturating_add(gain_optional);
match best {
None => best = Some((index, score, cost, gain_mandatory, candidate.id.as_str())),
Some((_, best_score, best_cost, _, best_id)) => {
if better(
score,
cost,
candidate.id.as_str(),
best_score,
best_cost,
best_id,
) {
best = Some((index, score, cost, gain_mandatory, candidate.id.as_str()));
}
}
}
}
best.map(|(index, _, _, gain_m, _)| {
let candidate = &unused[index];
let gain_o = u64::try_from(
candidate
.covers
.iter()
.filter(|id| remaining.contains(*id) && !mandatory.contains(*id))
.count(),
)
.unwrap_or(u64::MAX);
(index, gain_m, gain_o)
})
}
fn better(score: u64, cost: u64, id: &str, best_score: u64, best_cost: u64, best_id: &str) -> bool {
let left = score.saturating_mul(best_cost);
let right = best_score.saturating_mul(cost);
if left != right {
return left > right;
}
if cost != best_cost {
return cost < best_cost;
}
id < best_id
}
fn effective_cost(candidate: &TestCandidate) -> u64 {
candidate.cost.saturating_add(candidate.flake_penalty)
}