use std::collections::HashSet;
use crate::dep_graph::DepGraph;
#[must_use]
pub fn compute_rebuild_set(graph: &DepGraph, changed_modules: &[String]) -> HashSet<String> {
let mut rebuild_set = HashSet::new();
let mut worklist: Vec<String> = changed_modules.to_vec();
while let Some(module_id) = worklist.pop() {
if !rebuild_set.insert(module_id.clone()) {
continue; }
if let Some(dependents) = graph.dependents(&module_id) {
for dep in dependents {
if !rebuild_set.contains(dep) {
worklist.push(dep.clone());
}
}
}
}
rebuild_set
}
#[must_use]
pub fn ordered_rebuild_set(graph: &DepGraph, changed_modules: &[String]) -> Option<Vec<String>> {
let rebuild_set = compute_rebuild_set(graph, changed_modules);
let topo_order = graph.topological_order()?;
Some(
topo_order
.into_iter()
.filter(|m| rebuild_set.contains(m))
.collect(),
)
}
#[cfg(test)]
mod tests {
use super::*;
fn make_chain_graph() -> DepGraph {
let mut graph = DepGraph::new();
graph.add_module("Core".to_string());
graph.add_module_with_deps("Lib".to_string(), HashSet::from(["Core".to_string()]));
graph.add_module_with_deps("App".to_string(), HashSet::from(["Lib".to_string()]));
graph
}
#[test]
fn rebuild_leaf_only() {
let graph = make_chain_graph();
let rebuild = compute_rebuild_set(&graph, &["App".to_string()]);
assert_eq!(rebuild, HashSet::from(["App".to_string()]));
}
#[test]
fn rebuild_root_cascades() {
let graph = make_chain_graph();
let rebuild = compute_rebuild_set(&graph, &["Core".to_string()]);
assert_eq!(
rebuild,
HashSet::from(["Core".to_string(), "Lib".to_string(), "App".to_string()])
);
}
#[test]
fn rebuild_middle_cascades_partially() {
let graph = make_chain_graph();
let rebuild = compute_rebuild_set(&graph, &["Lib".to_string()]);
assert_eq!(
rebuild,
HashSet::from(["Lib".to_string(), "App".to_string()])
);
}
#[test]
fn rebuild_nothing_changed() {
let graph = make_chain_graph();
let rebuild = compute_rebuild_set(&graph, &[]);
assert!(rebuild.is_empty());
}
#[test]
fn rebuild_unknown_module() {
let graph = make_chain_graph();
let rebuild = compute_rebuild_set(&graph, &["Unknown".to_string()]);
assert_eq!(rebuild, HashSet::from(["Unknown".to_string()]));
}
#[test]
fn ordered_rebuild_respects_topo_order() {
let graph = make_chain_graph();
let order = ordered_rebuild_set(&graph, &["Core".to_string()]).unwrap();
let core_pos = order.iter().position(|m| m == "Core").unwrap();
let lib_pos = order.iter().position(|m| m == "Lib").unwrap();
let app_pos = order.iter().position(|m| m == "App").unwrap();
assert!(core_pos < lib_pos);
assert!(lib_pos < app_pos);
}
#[test]
fn diamond_rebuild() {
let mut graph = DepGraph::new();
graph.add_module("Base".to_string());
graph.add_module_with_deps("Left".to_string(), HashSet::from(["Base".to_string()]));
graph.add_module_with_deps("Right".to_string(), HashSet::from(["Base".to_string()]));
graph.add_module_with_deps(
"Top".to_string(),
HashSet::from(["Left".to_string(), "Right".to_string()]),
);
let rebuild = compute_rebuild_set(&graph, &["Base".to_string()]);
assert_eq!(
rebuild,
HashSet::from([
"Base".to_string(),
"Left".to_string(),
"Right".to_string(),
"Top".to_string()
])
);
}
#[test]
fn diamond_partial_rebuild() {
let mut graph = DepGraph::new();
graph.add_module("Base".to_string());
graph.add_module_with_deps("Left".to_string(), HashSet::from(["Base".to_string()]));
graph.add_module_with_deps("Right".to_string(), HashSet::from(["Base".to_string()]));
graph.add_module_with_deps(
"Top".to_string(),
HashSet::from(["Left".to_string(), "Right".to_string()]),
);
let rebuild = compute_rebuild_set(&graph, &["Left".to_string()]);
assert_eq!(
rebuild,
HashSet::from(["Left".to_string(), "Top".to_string()])
);
}
}