use super::types::{CallGraph, FunctionId, FunctionNode};
use im::{HashMap, HashSet};
impl CallGraph {
pub fn find_test_functions(&self) -> Vec<FunctionId> {
let mut funcs: Vec<FunctionId> = self
.nodes
.iter()
.filter(|(_, node)| node.is_test)
.map(|(id, _)| id.clone())
.collect();
funcs.sort();
funcs
}
pub fn is_test_helper(&self, func_id: &FunctionId) -> bool {
let callers = self.get_callers(func_id);
if callers.is_empty() {
return false;
}
callers.iter().all(|caller| self.is_test_function(caller))
}
fn collect_test_functions(nodes: &HashMap<FunctionId, FunctionNode>) -> HashSet<FunctionId> {
let mut test_funcs: Vec<FunctionId> = nodes
.iter()
.filter(|(_, node)| node.is_test)
.map(|(id, _)| id.clone())
.collect();
test_funcs.sort();
test_funcs.into_iter().collect()
}
pub fn is_production_entry_point(node: &FunctionNode, callers: &[FunctionId]) -> bool {
!node.is_test && (node.is_entry_point || callers.is_empty())
}
fn filter_test_only_functions(
reachable_from_tests: HashSet<FunctionId>,
reachable_from_production: &HashSet<FunctionId>,
nodes: &HashMap<FunctionId, FunctionNode>,
) -> HashSet<FunctionId> {
reachable_from_tests
.into_iter()
.filter(|id| {
!reachable_from_production.contains(id)
&& nodes.get(id).is_some_and(|node| !node.is_test)
})
.collect()
}
pub fn find_test_only_functions(&self) -> HashSet<FunctionId> {
let test_functions = Self::collect_test_functions(&self.nodes);
let reachable_from_tests = self.find_functions_reachable_from_tests(&test_functions);
let reachable_from_production = self.find_functions_reachable_from_production();
Self::filter_test_only_functions(
reachable_from_tests,
&reachable_from_production,
&self.nodes,
)
}
fn find_functions_reachable_from_tests(
&self,
test_functions: &HashSet<FunctionId>,
) -> HashSet<FunctionId> {
let mut reachable_from_tests = test_functions.clone();
let mut sorted_tests: Vec<_> = test_functions.iter().collect();
sorted_tests.sort();
for test_fn in sorted_tests {
let callees = self.get_transitive_callees(test_fn, usize::MAX);
reachable_from_tests.extend(callees);
}
reachable_from_tests
}
fn find_functions_reachable_from_production(&self) -> HashSet<FunctionId> {
let mut reachable_from_production = HashSet::new();
let mut sorted_nodes: Vec<_> = self.nodes.iter().collect();
sorted_nodes.sort_by(|a, b| a.0.cmp(b.0));
for (id, node) in sorted_nodes {
let callers = self.get_callers(id);
if Self::is_production_entry_point(node, &callers) {
reachable_from_production.insert(id.clone());
let callees = self.get_transitive_callees(id, usize::MAX);
reachable_from_production.extend(callees);
}
}
reachable_from_production
}
}