use super::helpers::{generate_2d_square_adjacency_list, setup_2d_square};
use crate::{
graph::{self, AdjacencyList, index::DegreeStats, test::provider as test_provider},
provider::{Delete, NeighborAccessor},
test::cmp::assert_eq_verbose,
};
#[tokio::test(flavor = "current_thread")]
async fn test_count_reachable_nodes() {
let adjacency_list = generate_2d_square_adjacency_list();
let index = setup_2d_square(adjacency_list, 4);
let mut accessor = index.provider().neighbors();
let starting_point = [4];
let initial_result = index
.count_reachable_nodes(&starting_point, &mut accessor)
.await
.unwrap();
assert_eq!(initial_result, 5);
let ctx = test_provider::Context::default();
let strat = test_provider::Strategy::new();
index
.inplace_delete(
strat.clone(),
&ctx,
&3,
3,
graph::InplaceDeleteMethod::OneHop,
)
.await
.unwrap();
let post_delete_result = index
.count_reachable_nodes(&starting_point, &mut accessor)
.await
.unwrap();
assert_eq!(post_delete_result, 4);
}
#[tokio::test(flavor = "current_thread")]
async fn test_count_unreachable_isolated_nodes() {
let adjacency_list = vec![
AdjacencyList::from_iter_untrusted([]),
AdjacencyList::from_iter_untrusted([]),
AdjacencyList::from_iter_untrusted([]),
AdjacencyList::from_iter_untrusted([]),
AdjacencyList::from_iter_untrusted([]),
];
let index = setup_2d_square(adjacency_list, 1);
let mut accessor = index.provider().neighbors();
let starting_point = [4];
let count = index
.count_reachable_nodes(&starting_point, &mut accessor)
.await
.unwrap();
assert_eq!(count, 1);
}
#[tokio::test(flavor = "current_thread")]
async fn test_get_degree_stats() {
let adjacency_list = generate_2d_square_adjacency_list();
let index = setup_2d_square(adjacency_list, 4);
let mut accessor = index.provider().neighbors();
let stats = index
.get_degree_stats(&mut accessor, index.provider().non_start_points_ids())
.await
.unwrap();
let expected = DegreeStats {
max_degree: 2,
avg_degree: 2.0,
min_degree: 2,
cnt_less_than_two: 0,
};
assert_eq_verbose!(expected, stats);
}
#[tokio::test(flavor = "current_thread")]
async fn test_prune_range() {
let adjacency_list = generate_2d_square_adjacency_list();
let index = setup_2d_square(adjacency_list, 1);
let ctx = test_provider::Context::default();
let strat = test_provider::Strategy::new();
index.prune_range(&strat, &ctx, 0..4).await.unwrap();
let mut accessor = index.provider().neighbors();
let mut list = AdjacencyList::new();
for node in 0u32..4 {
accessor.get_neighbors(node, &mut list).await.unwrap();
assert!(
list.len() <= 1,
"node {node} should have degree <= 1 after prune, got {}",
list.len()
);
}
accessor.get_neighbors(4, &mut list).await.unwrap();
assert_eq!(
list.len(),
4,
"start node should be untouched by prune_range(0..4)"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_count_reachable_nodes_multiple_starts() {
let adjacency_list = vec![
AdjacencyList::from_iter_untrusted([1, 4]),
AdjacencyList::from_iter_untrusted([0, 4]),
AdjacencyList::from_iter_untrusted([3]),
AdjacencyList::from_iter_untrusted([2]),
AdjacencyList::from_iter_untrusted([0, 1]),
];
let index = setup_2d_square(adjacency_list, 4);
let mut accessor = index.provider().neighbors();
let from_start = index
.count_reachable_nodes(&[4], &mut accessor)
.await
.unwrap();
assert_eq!(from_start, 3);
let from_two = index
.count_reachable_nodes(&[2], &mut accessor)
.await
.unwrap();
assert_eq!(from_two, 2);
let from_both = index
.count_reachable_nodes(&[4, 2], &mut accessor)
.await
.unwrap();
assert_eq!(from_both, 5);
}
#[tokio::test(flavor = "current_thread")]
async fn test_get_degree_stats_mixed() {
let adjacency_list = vec![
AdjacencyList::from_iter_untrusted([]),
AdjacencyList::from_iter_untrusted([4]),
AdjacencyList::from_iter_untrusted([3, 4]),
AdjacencyList::from_iter_untrusted([2, 4]),
AdjacencyList::from_iter_untrusted([1, 2, 3]),
];
let index = setup_2d_square(adjacency_list, 4);
let mut accessor = index.provider().neighbors();
let stats = index
.get_degree_stats(&mut accessor, index.provider().non_start_points_ids())
.await
.unwrap();
let expected = DegreeStats {
max_degree: 2,
avg_degree: 1.25,
min_degree: 0,
cnt_less_than_two: 2,
};
assert_eq_verbose!(expected, stats);
}
#[tokio::test(flavor = "current_thread")]
async fn test_is_any_neighbor_deleted() {
let adjacency_list = generate_2d_square_adjacency_list();
let index = setup_2d_square(adjacency_list, 4);
let ctx = test_provider::Context::new();
let mut accessor = index.provider().neighbors();
let result = index
.is_any_neighbor_deleted(&ctx, &mut accessor, 2)
.await
.unwrap();
assert!(!result, "no neighbors should be deleted yet");
index.provider().delete(&ctx, &3).await.unwrap();
let result = index
.is_any_neighbor_deleted(&ctx, &mut accessor, 2)
.await
.unwrap();
assert!(result, "node 2 should detect deleted neighbor 3");
let result = index
.is_any_neighbor_deleted(&ctx, &mut accessor, 0)
.await
.unwrap();
assert!(!result, "node 0 has no deleted neighbors");
}
#[tokio::test(flavor = "current_thread")]
async fn test_drop_deleted_neighbors() {
let adjacency_list = generate_2d_square_adjacency_list();
let index = setup_2d_square(adjacency_list, 4);
let ctx = test_provider::Context::new();
let mut accessor = index.provider().neighbors();
index.provider().delete(&ctx, &3).await.unwrap();
let result = index
.drop_deleted_neighbors(&ctx, &mut accessor, 2, false)
.await
.unwrap();
assert_eq!(result, graph::ConsolidateKind::Complete);
let mut list = AdjacencyList::new();
accessor.get_neighbors(2, &mut list).await.unwrap();
assert!(
!list.contains(3),
"node 2 should not reference deleted node 3"
);
assert!(
list.contains(4),
"node 2 should still reference start node 4"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_drop_deleted_neighbors_only_orphans() {
let adjacency_list = generate_2d_square_adjacency_list();
let index = setup_2d_square(adjacency_list, 4);
let ctx = test_provider::Context::new();
let mut accessor = index.provider().neighbors();
index.provider().delete(&ctx, &3).await.unwrap();
let result = index
.drop_deleted_neighbors(&ctx, &mut accessor, 2, true)
.await
.unwrap();
assert_eq!(result, graph::ConsolidateKind::Complete);
let mut list = AdjacencyList::new();
accessor.get_neighbors(2, &mut list).await.unwrap();
assert!(
list.contains(3),
"non-orphan deleted neighbor should be retained"
);
assert!(
list.contains(4),
"node 2 should still reference start node 4"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_drop_deleted_neighbors_noop() {
let adjacency_list = generate_2d_square_adjacency_list();
let index = setup_2d_square(adjacency_list, 4);
let ctx = test_provider::Context::new();
let mut accessor = index.provider().neighbors();
let result = index
.drop_deleted_neighbors(&ctx, &mut accessor, 0, false)
.await
.unwrap();
assert_eq!(result, graph::ConsolidateKind::Complete);
}