use super::*;
fn units(count: usize) -> Vec<GroupingUnit> {
(0..count)
.map(|i| GroupingUnit {
key: [u8::try_from(i).unwrap(); 16],
})
.collect()
}
fn edge(a: usize, b: usize, similarity: f64) -> SimilarityEdge {
SimilarityEdge {
a,
b,
similarity,
breakdown: None,
class: CloneClass::Type3,
confidence: Confidence::Medium,
}
}
#[test]
fn a_transitive_chain_does_not_fuse_into_one_group() {
let units = units(5);
let edges = vec![
edge(0, 1, 0.9),
edge(1, 2, 0.9),
edge(2, 3, 0.9),
edge(3, 4, 0.9),
];
let set = group(&units, &edges, &GroupingConfig::default());
assert_eq!(set.stats.components, 1, "the chain is one component");
assert!(
set.groups.iter().all(|g| g.members.len() < 5),
"no group may span the whole chain"
);
for reported in &set.groups {
assert!(reported.min_pairwise >= 0.60);
}
}
#[test]
fn a_clique_is_one_group_with_a_deterministic_medoid() {
let units = units(3);
let edges = vec![edge(0, 1, 0.9), edge(1, 2, 0.9), edge(0, 2, 0.9)];
let set = group(&units, &edges, &GroupingConfig::default());
assert_eq!(set.groups.len(), 1);
let only = &set.groups[0];
assert_eq!(only.members.len(), 3);
assert_eq!(only.canonical, 0);
assert_eq!(only.members[0], 0, "medoid comes first");
}
#[test]
fn medoid_sampling_covers_distinct_content_across_the_component() {
let mut units = vec![GroupingUnit { key: [0; 16] }; 32];
units.extend((32_u8..40).map(|key| GroupingUnit { key: [key; 16] }));
let mut edges = clique(units.len());
for edge in &mut edges {
if edge.a == 39 || edge.b == 39 {
edge.similarity = 0.99;
} else {
edge.similarity = 0.61;
}
}
let config = GroupingConfig {
sampling_threshold: 8,
sample_size: 4,
max_component: 64,
..GroupingConfig::default()
};
let set = group(&units, &edges, &config);
assert_eq!(set.groups[0].canonical, 39);
assert_eq!(set.stats.sampled_medoids, 1);
assert_eq!(set.stats.sampled_medoid_candidates, 4);
}
fn clique(count: usize) -> Vec<SimilarityEdge> {
let mut edges = Vec::new();
for left in 0..count {
for right in (left + 1)..count {
edges.push(edge(left, right, 0.9));
}
}
edges
}
#[test]
fn a_component_over_the_ceiling_is_cut_into_pieces_and_the_cut_is_counted() {
let units = units(10);
let config = GroupingConfig {
max_component: 4,
..GroupingConfig::default()
};
let set = group(&units, &clique(10), &config);
assert_eq!(set.stats.components, 1, "still one component");
assert_eq!(set.stats.oversized_components, 1, "the ceiling is reported");
assert_eq!(set.groups.len(), 3);
assert!(set.groups.iter().all(|g| g.members.len() <= 4));
let grouped: usize = set.groups.iter().map(|g| g.members.len()).sum();
assert_eq!(grouped, 10, "no member is lost to the cut");
assert!(
set.groups
.iter()
.all(|g| g.min_pairwise >= config.min_pairwise_similarity)
);
}
#[test]
fn an_equal_content_class_is_never_split_by_the_component_ceiling() {
let units = vec![GroupingUnit { key: [7; 16] }; 10];
let config = GroupingConfig {
max_component: 4,
..GroupingConfig::default()
};
let set = group(&units, &clique(10), &config);
assert_eq!(set.stats.oversized_components, 1);
assert_eq!(set.groups.len(), 1);
assert_eq!(set.groups[0].members.len(), 10);
assert!(
(0..10).all(|member| !set.severed_by_the_ceiling(0, member)),
"one content class is not severed by the ceiling"
);
}
#[test]
fn equal_similarity_ties_choose_the_pair_by_content_key() {
let units = vec![
GroupingUnit { key: [2; 16] },
GroupingUnit { key: [0; 16] },
GroupingUnit { key: [1; 16] },
];
let sim = SimilarityGraph::build(3, &clique(3));
let config = GroupingConfig {
min_pairwise_similarity: 0.95,
..GroupingConfig::default()
};
let mut forward = vec![0, 1, 2];
let mut backward = vec![2, 1, 0];
let mut forward_rest = Vec::new();
let mut backward_rest = Vec::new();
let mut forward_stats = GroupingStats::default();
let mut backward_stats = GroupingStats::default();
complete_linkage_trim(
1,
&mut forward,
&mut forward_rest,
&units,
&sim,
&config,
&mut forward_stats,
);
complete_linkage_trim(
1,
&mut backward,
&mut backward_rest,
&units,
&sim,
&config,
&mut backward_stats,
);
assert_eq!(forward_rest, vec![2, 0]);
assert_eq!(backward_rest, forward_rest);
assert_eq!(canonical_pair(1, 2, &units), ([0; 16], [1; 16]));
}
#[test]
fn the_cut_follows_the_keys_not_the_order_the_edges_arrived_in() {
let units = units(10);
let config = GroupingConfig {
max_component: 4,
..GroupingConfig::default()
};
let mut reversed = clique(10);
reversed.reverse();
let forward = group(&units, &clique(10), &config);
let backward = group(&units, &reversed, &config);
assert_eq!(forward.groups, backward.groups);
}
#[test]
fn a_component_at_the_ceiling_is_refined_whole() {
let units = units(4);
let config = GroupingConfig {
max_component: 4,
..GroupingConfig::default()
};
let set = group(&units, &clique(4), &config);
assert_eq!(set.stats.oversized_components, 0);
assert_eq!(set.groups.len(), 1);
assert_eq!(set.groups[0].members.len(), 4);
assert!(
!set.severed_by_the_ceiling(0, 3),
"nothing was cut, so nothing was severed"
);
}
#[test]
fn the_cut_says_which_pairs_it_kept_from_ever_meeting() {
let units = units(10);
let config = GroupingConfig {
max_component: 4,
..GroupingConfig::default()
};
let set = group(&units, &clique(10), &config);
let piece_of = |unit: usize| {
set.groups
.iter()
.position(|group| group.members.contains(&unit))
.expect("every member is grouped")
};
for left in 0..10 {
for right in (left + 1)..10 {
assert_eq!(
set.severed_by_the_ceiling(left, right),
piece_of(left) != piece_of(right),
"{left} and {right}"
);
}
}
}
#[test]
fn a_member_far_from_the_medoid_is_ejected() {
let units = units(4);
let edges = vec![
edge(0, 1, 0.95),
edge(0, 2, 0.95),
edge(1, 2, 0.95),
edge(2, 3, 0.62),
];
let set = group(&units, &edges, &GroupingConfig::default());
let big = set
.groups
.iter()
.find(|g| g.members.contains(&0))
.expect("the clique forms a group");
assert!(
!big.members.contains(&3),
"the weakly attached member stays out of the clique"
);
}
#[test]
fn union_find_components_are_not_emitted_verbatim() {
let units = units(6);
let edges = vec![
edge(0, 1, 0.9),
edge(1, 2, 0.9),
edge(0, 2, 0.9),
edge(3, 4, 0.9),
edge(4, 5, 0.9),
edge(3, 5, 0.9),
];
let set = group(&units, &edges, &GroupingConfig::default());
assert_eq!(set.stats.components, 2);
assert_eq!(set.groups.len(), 2);
assert!(set.groups.iter().all(|g| g.members.len() == 3));
}
#[test]
fn a_lone_unit_is_not_a_group() {
let units = units(2);
let set = group(&units, &[], &GroupingConfig::default());
assert!(set.groups.is_empty());
}
#[test]
fn component_collection_omits_isolated_units_before_bucket_allocation() {
let components = connected_components(1_000, &[edge(17, 843, 0.9)]);
assert_eq!(components, vec![vec![17, 843]]);
}
#[test]
fn the_group_takes_the_weakest_class_and_confidence() {
let units = units(3);
let edges = vec![
SimilarityEdge {
a: 0,
b: 1,
similarity: 0.95,
breakdown: None,
class: CloneClass::Type1,
confidence: Confidence::High,
},
SimilarityEdge {
a: 1,
b: 2,
similarity: 0.9,
breakdown: None,
class: CloneClass::Type3,
confidence: Confidence::Low,
},
SimilarityEdge {
a: 0,
b: 2,
similarity: 0.9,
breakdown: None,
class: CloneClass::Type2,
confidence: Confidence::Medium,
},
];
let set = group(&units, &edges, &GroupingConfig::default());
let only = &set.groups[0];
assert_eq!(only.clone_type, CloneClass::Type3);
assert_eq!(only.confidence, Confidence::Low);
}
#[test]
fn grouping_is_deterministic_regardless_of_edge_order() {
let units = units(5);
let forward = vec![
edge(0, 1, 0.9),
edge(1, 2, 0.9),
edge(2, 3, 0.9),
edge(3, 4, 0.9),
];
let mut reversed = forward.clone();
reversed.reverse();
let a = group(&units, &forward, &GroupingConfig::default());
let b = group(&units, &reversed, &GroupingConfig::default());
assert_eq!(a.groups, b.groups);
}