use std::collections::HashSet;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct CapturePlan {
pub to_capture: Vec<String>,
pub deferred: usize,
}
#[must_use]
pub fn plan_capture(
valid: &[String],
cached: &HashSet<String>,
baseline: &HashSet<String>,
max_per_pass: usize,
) -> CapturePlan {
let mut candidates: Vec<String> = valid
.iter()
.filter(|h| !cached.contains(*h) && !baseline.contains(*h))
.cloned()
.collect();
candidates.sort_unstable();
candidates.dedup();
let deferred = candidates.len().saturating_sub(max_per_pass);
candidates.truncate(max_per_pass);
CapturePlan {
to_capture: candidates,
deferred,
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct WatchReport {
pub scanned: usize,
pub captured: usize,
pub failed: usize,
pub deferred: usize,
}
#[cfg(test)]
mod tests {
use super::*;
fn set(items: &[&str]) -> HashSet<String> {
items.iter().map(|s| (*s).to_string()).collect()
}
fn v(items: &[&str]) -> Vec<String> {
items.iter().map(|s| (*s).to_string()).collect()
}
#[test]
fn baseline_paths_are_not_captured() {
let plan = plan_capture(&v(&["a", "b"]), &set(&[]), &set(&["a", "b"]), 100);
assert!(plan.to_capture.is_empty());
assert_eq!(plan.deferred, 0);
}
#[test]
fn a_newly_realized_path_is_captured() {
let plan = plan_capture(&v(&["a", "b", "new"]), &set(&[]), &set(&["a", "b"]), 100);
assert_eq!(plan.to_capture, v(&["new"]));
}
#[test]
fn cached_paths_are_skipped() {
let plan = plan_capture(&v(&["a", "new"]), &set(&["new"]), &set(&["a"]), 100);
assert!(plan.to_capture.is_empty());
}
#[test]
fn a_failed_capture_is_retried_next_pass() {
let baseline = set(&["a"]);
let first = plan_capture(&v(&["a", "new"]), &set(&[]), &baseline, 100);
assert_eq!(first.to_capture, v(&["new"]));
let second = plan_capture(&v(&["a", "new"]), &set(&[]), &baseline, 100);
assert_eq!(second.to_capture, v(&["new"]), "must retry, not drop");
}
#[test]
fn max_per_pass_bounds_the_work_and_reports_the_remainder() {
let plan = plan_capture(&v(&["a", "b", "c", "d", "e"]), &set(&[]), &set(&[]), 2);
assert_eq!(plan.to_capture.len(), 2);
assert_eq!(plan.deferred, 3);
}
#[test]
fn empty_baseline_backfills() {
let plan = plan_capture(&v(&["a", "b"]), &set(&["a"]), &set(&[]), 100);
assert_eq!(plan.to_capture, v(&["b"]));
}
#[test]
fn selection_is_deterministic() {
let valid = v(&["z", "m", "a", "q"]);
let a = plan_capture(&valid, &set(&[]), &set(&[]), 2);
let b = plan_capture(&valid, &set(&[]), &set(&[]), 2);
assert_eq!(a, b);
assert_eq!(a.to_capture, v(&["a", "m"]));
}
}