#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::types::{NodeData, Symbol, SymbolKind, Visibility};
use std::path::PathBuf;
pub(super) fn create_test_node(name: &str) -> NodeData {
NodeData {
path: PathBuf::from(format!("{}.rs", name)),
module: name.to_string(),
symbols: vec![Symbol {
name: name.to_string(),
kind: SymbolKind::Function,
visibility: Visibility::Public,
line: 1,
}],
loc: 100,
complexity: 5.0,
ast_hash: 12345,
}
}
fn create_two_community_graph() -> UndirectedGraph {
let mut graph = UndirectedGraph::new();
let nodes: Vec<_> = (0..6)
.map(|i| graph.add_node(create_test_node(&format!("node{}", i))))
.collect();
graph.add_edge(nodes[0], nodes[1], 1.0);
graph.add_edge(nodes[1], nodes[2], 1.0);
graph.add_edge(nodes[0], nodes[2], 1.0);
graph.add_edge(nodes[3], nodes[4], 1.0);
graph.add_edge(nodes[4], nodes[5], 1.0);
graph.add_edge(nodes[3], nodes[5], 1.0);
graph.add_edge(nodes[2], nodes[3], 0.1);
graph
}
fn create_chain_graph(n: usize) -> UndirectedGraph {
let mut graph = UndirectedGraph::new();
let nodes: Vec<_> = (0..n)
.map(|i| graph.add_node(create_test_node(&format!("node{}", i))))
.collect();
for i in 0..n - 1 {
graph.add_edge(nodes[i], nodes[i + 1], 1.0);
}
graph
}
fn create_complete_graph(n: usize) -> UndirectedGraph {
let mut graph = UndirectedGraph::new();
let nodes: Vec<_> = (0..n)
.map(|i| graph.add_node(create_test_node(&format!("node{}", i))))
.collect();
for i in 0..n {
for j in i + 1..n {
graph.add_edge(nodes[i], nodes[j], 1.0);
}
}
graph
}
fn create_star_graph(n: usize) -> UndirectedGraph {
let mut graph = UndirectedGraph::new();
let nodes: Vec<_> = (0..n)
.map(|i| graph.add_node(create_test_node(&format!("node{}", i))))
.collect();
for i in 1..n {
graph.add_edge(nodes[0], nodes[i], 1.0);
}
graph
}
#[test]
fn test_default_parameters() {
let louvain = ParallelLouvain::default();
assert_eq!(louvain.resolution, 1.0);
assert_eq!(louvain.max_iterations, 100);
assert!((louvain.min_improvement - 1e-6).abs() < 1e-10);
assert_eq!(louvain.num_threads, 0);
}
#[test]
fn test_new_equals_default() {
let louvain1 = ParallelLouvain::new();
let louvain2 = ParallelLouvain::default();
assert_eq!(louvain1.resolution, louvain2.resolution);
assert_eq!(louvain1.max_iterations, louvain2.max_iterations);
}
#[test]
fn test_builder_with_resolution() {
let louvain = ParallelLouvain::new().with_resolution(0.5);
assert_eq!(louvain.resolution, 0.5);
}
#[test]
fn test_builder_with_max_iterations() {
let louvain = ParallelLouvain::new().with_max_iterations(50);
assert_eq!(louvain.max_iterations, 50);
}
#[test]
fn test_builder_with_min_improvement() {
let louvain = ParallelLouvain::new().with_min_improvement(1e-8);
assert!((louvain.min_improvement - 1e-8).abs() < 1e-12);
}
#[test]
fn test_builder_with_num_threads() {
let louvain = ParallelLouvain::new().with_num_threads(4);
assert_eq!(louvain.num_threads, 4);
}
#[test]
fn test_builder_chaining() {
let louvain = ParallelLouvain::new()
.with_resolution(0.8)
.with_max_iterations(200)
.with_min_improvement(1e-4)
.with_num_threads(8);
assert_eq!(louvain.resolution, 0.8);
assert_eq!(louvain.max_iterations, 200);
assert!((louvain.min_improvement - 1e-4).abs() < 1e-10);
assert_eq!(louvain.num_threads, 8);
}
#[test]
fn test_detect_empty_graph() {
let graph = UndirectedGraph::new();
let louvain = ParallelLouvain::new();
let communities = louvain.detect(&graph);
assert!(communities.is_empty());
}
#[test]
fn test_detect_single_node() {
let mut graph = UndirectedGraph::new();
graph.add_node(create_test_node("single"));
let louvain = ParallelLouvain::new();
let communities = louvain.detect(&graph);
assert_eq!(communities.len(), 1);
assert_eq!(communities[0], 0);
}
#[test]
fn test_detect_two_disconnected_nodes() {
let mut graph = UndirectedGraph::new();
graph.add_node(create_test_node("node0"));
graph.add_node(create_test_node("node1"));
let louvain = ParallelLouvain::new();
let communities = louvain.detect(&graph);
assert_eq!(communities.len(), 2);
}
#[test]
fn test_detect_two_connected_nodes() {
let mut graph = UndirectedGraph::new();
let n0 = graph.add_node(create_test_node("node0"));
let n1 = graph.add_node(create_test_node("node1"));
graph.add_edge(n0, n1, 1.0);
let louvain = ParallelLouvain::new();
let communities = louvain.detect(&graph);
assert_eq!(communities.len(), 2);
assert_eq!(communities[0], communities[1]);
}
#[test]
fn test_detect_complete_graph() {
let graph = create_complete_graph(5);
let louvain = ParallelLouvain::new();
let communities = louvain.detect(&graph);
assert_eq!(communities.len(), 5);
let num_communities = ParallelLouvain::num_communities(&communities);
assert!(
num_communities <= 2,
"Complete graph should have few communities"
);
}
#[test]
fn test_detect_chain_graph() {
let graph = create_chain_graph(6);
let louvain = ParallelLouvain::new();
let communities = louvain.detect(&graph);
assert_eq!(communities.len(), 6);
}
#[test]
fn test_detect_star_graph() {
let graph = create_star_graph(5);
let louvain = ParallelLouvain::new();
let communities = louvain.detect(&graph);
assert_eq!(communities.len(), 5);
}
#[test]
fn test_high_resolution_more_communities() {
let graph = create_two_community_graph();
let low_res = ParallelLouvain::new().with_resolution(0.5);
let high_res = ParallelLouvain::new().with_resolution(2.0);
let communities_low = low_res.detect(&graph);
let communities_high = high_res.detect(&graph);
let num_low = ParallelLouvain::num_communities(&communities_low);
let num_high = ParallelLouvain::num_communities(&communities_high);
assert!(
num_high >= num_low,
"High resolution should not reduce communities"
);
}
#[test]
fn test_zero_resolution() {
let graph = create_two_community_graph();
let louvain = ParallelLouvain::new().with_resolution(0.0);
let communities = louvain.detect(&graph);
let num_communities = ParallelLouvain::num_communities(&communities);
assert_eq!(num_communities, 1);
}
#[test]
fn test_modularity_empty_graph() {
let graph = UndirectedGraph::new();
let louvain = ParallelLouvain::new();
let communities: Vec<usize> = vec![];
let modularity = louvain.calculate_modularity(&graph, &communities);
assert_eq!(modularity, 0.0);
}
#[test]
fn test_modularity_two_communities() {
let graph = create_two_community_graph();
let louvain = ParallelLouvain::new();
let perfect = vec![0, 0, 0, 1, 1, 1];
let modularity_perfect = louvain.calculate_modularity(&graph, &perfect);
let bad = vec![0, 1, 0, 1, 0, 1];
let modularity_bad = louvain.calculate_modularity(&graph, &bad);
assert!(
modularity_perfect > modularity_bad,
"Perfect assignment should have higher modularity: {} vs {}",
modularity_perfect,
modularity_bad
);
}
#[test]
fn test_modularity_is_bounded() {
let graph = create_two_community_graph();
let louvain = ParallelLouvain::new();
let communities = louvain.detect(&graph);
let modularity = louvain.calculate_modularity(&graph, &communities);
assert!(
(-0.5..=1.0).contains(&modularity),
"Modularity {} should be in [-0.5, 1]",
modularity
);
}
}