use proptest::prelude::*;
use trueno_graph::{CsrGraph, NodeId};
proptest! {
#[test]
fn prop_from_edge_list_valid_csr(edges in prop_edge_list(0usize..100usize, 0u32..50u32)) {
let graph = CsrGraph::from_edge_list(&edges).unwrap();
for i in 0..graph.csr_components().0.len() - 1 {
let (row_offsets, _, _) = graph.csr_components();
prop_assert!(row_offsets[i] <= row_offsets[i + 1]);
}
let (row_offsets, col_indices, _) = graph.csr_components();
prop_assert_eq!(*row_offsets.last().unwrap() as usize, col_indices.len());
let (_, col_indices, edge_weights) = graph.csr_components();
prop_assert_eq!(col_indices.len(), edge_weights.len());
prop_assert_eq!(graph.num_edges(), edges.len());
}
}
proptest! {
#[test]
fn prop_outgoing_neighbors_correct(edges in prop_edge_list(0usize..100usize, 0u32..20u32)) {
let graph = CsrGraph::from_edge_list(&edges).unwrap();
for node_id in 0..graph.num_nodes() {
let neighbors = graph.outgoing_neighbors(NodeId(node_id as u32)).unwrap();
let expected: Vec<_> = edges.iter()
.filter(|(src, _, _)| src.0 == node_id as u32)
.map(|(_, dst, _)| dst.0)
.collect();
prop_assert_eq!(neighbors.len(), expected.len());
for neighbor in neighbors {
prop_assert!(expected.contains(neighbor));
}
}
}
}
proptest! {
#[test]
fn prop_incoming_neighbors_correct(edges in prop_edge_list(0usize..100usize, 0u32..20u32)) {
let graph = CsrGraph::from_edge_list(&edges).unwrap();
for node_id in 0..graph.num_nodes() {
let callers = graph.incoming_neighbors(NodeId(node_id as u32)).unwrap();
let mut expected: Vec<_> = edges.iter()
.filter(|(_, dst, _)| dst.0 == node_id as u32)
.map(|(src, _, _)| src.0)
.collect();
expected.sort_unstable();
prop_assert_eq!(callers.len(), expected.len(),
"Node {}: callers.len()={}, expected.len()={}",
node_id, callers.len(), expected.len());
let mut callers_sorted = callers.to_vec();
callers_sorted.sort_unstable();
prop_assert_eq!(callers_sorted, expected);
}
}
}
proptest! {
#[test]
fn prop_parquet_roundtrip(edges in prop_edge_list(0usize..100usize, 0u32..20u32)) {
let runtime = tokio::runtime::Runtime::new().unwrap();
runtime.block_on(async {
let graph = CsrGraph::from_edge_list(&edges).unwrap();
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("prop_test_graph");
graph.write_parquet(&path).await.unwrap();
let loaded = CsrGraph::read_parquet(&path).await.unwrap();
prop_assert_eq!(loaded.num_nodes(), graph.num_nodes());
prop_assert_eq!(loaded.num_edges(), graph.num_edges());
let (row1, col1, weights1) = graph.csr_components();
let (row2, col2, weights2) = loaded.csr_components();
prop_assert_eq!(row1, row2);
prop_assert_eq!(col1, col2);
prop_assert_eq!(weights1, weights2);
Ok(())
})?;
}
}
proptest! {
#[test]
fn prop_add_edge_increases_count(
src in 0u32..50,
dst in 0u32..50,
weight in 0.0f32..100.0
) {
let mut graph = CsrGraph::new();
let initial_count = graph.num_edges();
graph.add_edge(NodeId(src), NodeId(dst), weight).unwrap();
prop_assert_eq!(graph.num_edges(), initial_count + 1);
}
}
proptest! {
#[test]
fn prop_node_count_grows(edges in prop_edge_list(0usize..100usize, 0u32..50u32)) {
let graph = CsrGraph::from_edge_list(&edges).unwrap();
if let Some(max_node) = edges.iter()
.flat_map(|(src, dst, _)| [src.0, dst.0])
.max()
{
prop_assert!(graph.num_nodes() >= (max_node + 1) as usize);
}
let _ = graph.num_nodes(); }
}
fn prop_edge_list(
num_edges: impl Strategy<Value = usize>,
max_node: impl Strategy<Value = u32>,
) -> impl Strategy<Value = Vec<(NodeId, NodeId, f32)>> {
(num_edges, max_node).prop_flat_map(|(n, max_node)| {
let max_node = max_node.max(1);
prop::collection::vec(
(0..max_node, 0..max_node, 0.0..100.0f32)
.prop_map(|(src, dst, weight)| (NodeId(src), NodeId(dst), weight)),
0..=n,
)
})
}
#[cfg(test)]
mod unit_tests {
use super::*;
#[test]
fn test_empty_graph_invariants() {
let graph = CsrGraph::new();
let (row_offsets, col_indices, edge_weights) = graph.csr_components();
assert_eq!(row_offsets, &[0]); assert_eq!(col_indices.len(), 0);
assert_eq!(edge_weights.len(), 0);
assert_eq!(graph.num_nodes(), 0);
assert_eq!(graph.num_edges(), 0);
}
#[test]
fn test_single_edge_invariants() {
let edges = vec![(NodeId(0), NodeId(1), 1.0)];
let graph = CsrGraph::from_edge_list(&edges).unwrap();
let (row_offsets, col_indices, edge_weights) = graph.csr_components();
assert_eq!(graph.num_nodes(), 2);
assert_eq!(row_offsets, &[0, 1, 1]);
assert_eq!(col_indices, &[1]);
assert_eq!(edge_weights, &[1.0]);
}
}