use sdivi_graph::dependency_graph::build_dependency_graph;
use sdivi_graph::metrics::compute_metrics;
use sdivi_parsing::feature_record::FeatureRecord;
use std::path::PathBuf;
fn make_record(path: &str, imports: &[&str]) -> FeatureRecord {
FeatureRecord {
path: PathBuf::from(path),
language: "rust".to_string(),
imports: imports.iter().map(|s| s.to_string()).collect(),
exports: vec![],
signatures: vec![],
pattern_hints: vec![],
}
}
#[test]
fn simple_rust_fixture_five_nodes_zero_edges() {
let records = vec![
make_record("src/lib.rs", &["std::collections::BTreeMap", "std::fmt"]),
make_record("src/models.rs", &["serde::{Deserialize, Serialize}"]),
make_record(
"src/utils.rs",
&["std::collections::HashSet", "std::path::Path"],
),
make_record("src/errors.rs", &["std::fmt"]),
make_record(
"src/config.rs",
&["std::collections::BTreeMap", "std::path::PathBuf"],
),
];
let dg = build_dependency_graph(&records);
assert_eq!(dg.node_count(), 5, "simple-rust fixture must have 5 nodes");
assert_eq!(
dg.edge_count(),
0,
"simple-rust fixture must have 0 edges (all imports external)"
);
}
#[test]
fn empty_graph_metrics() {
let dg = build_dependency_graph(&[]);
let m = compute_metrics(&dg);
assert_eq!(m.node_count, 0);
assert_eq!(m.edge_count, 0);
assert_eq!(m.density, 0.0);
assert_eq!(m.cycle_count, 0);
assert_eq!(m.component_count, 0);
}
#[test]
fn single_node_no_self_loop() {
let records = vec![make_record("src/lib.rs", &[])];
let dg = build_dependency_graph(&records);
let m = compute_metrics(&dg);
assert_eq!(m.node_count, 1);
assert_eq!(m.edge_count, 0);
assert_eq!(m.density, 0.0);
assert_eq!(m.cycle_count, 0);
assert_eq!(m.component_count, 1);
}
#[test]
fn two_nodes_one_edge_density() {
let records = vec![
make_record("src/lib.rs", &["crate::models"]),
make_record("src/models.rs", &[]),
];
let dg = build_dependency_graph(&records);
let m = compute_metrics(&dg);
assert_eq!(m.node_count, 2);
assert_eq!(m.edge_count, 1);
assert!((m.density - 0.5).abs() < 1e-9, "density = {}", m.density);
assert_eq!(m.cycle_count, 0);
assert_eq!(m.component_count, 1);
}
#[test]
fn two_nodes_mutual_edge_one_cycle() {
let records = vec![
make_record("src/lib.rs", &["crate::models"]),
make_record("src/models.rs", &["crate::lib"]),
];
let dg = build_dependency_graph(&records);
let m = compute_metrics(&dg);
assert_eq!(m.node_count, 2);
assert_eq!(m.edge_count, 2);
assert_eq!(m.cycle_count, 1, "one back-edge = one cycle");
assert_eq!(m.component_count, 1);
}
#[test]
fn three_disconnected_nodes_three_components() {
let records = vec![
make_record("src/a.rs", &[]),
make_record("src/b.rs", &[]),
make_record("src/c.rs", &[]),
];
let dg = build_dependency_graph(&records);
let m = compute_metrics(&dg);
assert_eq!(m.node_count, 3);
assert_eq!(m.edge_count, 0);
assert_eq!(m.component_count, 3);
}
#[test]
fn chain_a_b_c_one_component_no_cycles() {
let records = vec![
make_record("src/a.rs", &["crate::b"]),
make_record("src/b.rs", &["crate::c"]),
make_record("src/c.rs", &[]),
];
let dg = build_dependency_graph(&records);
let m = compute_metrics(&dg);
assert_eq!(m.node_count, 3);
assert_eq!(m.edge_count, 2);
assert_eq!(m.cycle_count, 0);
assert_eq!(m.component_count, 1);
}
#[test]
fn self_loop_not_counted_as_cycle() {
let records = vec![make_record("src/a.rs", &["crate::a"])];
let dg = build_dependency_graph(&records);
assert_eq!(dg.edge_count(), 0, "self-loop must be dropped");
let m = compute_metrics(&dg);
assert_eq!(m.cycle_count, 0, "self-loop must not count as a cycle");
}
#[test]
fn top_hubs_sorted_by_out_degree() {
let records = vec![
make_record("src/a.rs", &["crate::b", "crate::c"]),
make_record("src/b.rs", &["crate::c"]),
make_record("src/c.rs", &[]),
];
let dg = build_dependency_graph(&records);
let m = compute_metrics(&dg);
assert_eq!(m.top_hubs[0].1, 2, "highest hub has out-degree 2");
assert_eq!(m.top_hubs[1].1, 1, "second hub has out-degree 1");
}
#[test]
fn duplicate_import_does_not_add_duplicate_edge() {
let records = vec![
make_record("src/a.rs", &["crate::b", "crate::b"]),
make_record("src/b.rs", &[]),
];
let dg = build_dependency_graph(&records);
assert_eq!(
dg.edge_count(),
1,
"duplicate import must produce only one edge"
);
}
#[test]
fn triangle_cycle_count_one() {
let records = vec![
make_record("src/a.rs", &["crate::b"]),
make_record("src/b.rs", &["crate::c"]),
make_record("src/c.rs", &["crate::a"]),
];
let dg = build_dependency_graph(&records);
let m = compute_metrics(&dg);
assert_eq!(m.cycle_count, 1);
}
#[test]
fn density_complete_graph_three_nodes() {
let records = vec![
make_record("src/a.rs", &["crate::b", "crate::c"]),
make_record("src/b.rs", &["crate::a", "crate::c"]),
make_record("src/c.rs", &["crate::a", "crate::b"]),
];
let dg = build_dependency_graph(&records);
let m = compute_metrics(&dg);
assert_eq!(m.node_count, 3);
assert_eq!(m.edge_count, 6);
assert!((m.density - 1.0).abs() < 1e-9);
}