use std::fmt::Debug; use grapherity::algorithms; use grapherity::maps::ElementMap; use grapherity::testing::fixtures::MakeTestGraph; use grapherity::traits::GraphTopology; macro_rules! bfs_tests { ($T:ty) => { bfs_tests!(@wrap $T, bfs_single_vertex, bfs_disconnected, bfs, bfs_distances_single_vertex, bfs_distances_disconnected, bfs_distances, bfs_find_source, bfs_find_disconnected, bfs_find, bfs_find_where_source_matches, bfs_find_where_disconnected, bfs_find_where_no_match, bfs_find_where_nearest, ); }; (@wrap $T:ty, $($name:ident),* $(,)?) => { $( #[test] fn $name() { super::$name::<$T>(); } )* }; } mod append_graph { use grapherity::models::AppendGraph; bfs_tests!(AppendGraph); } mod graph { use grapherity::models::Graph; bfs_tests!(Graph); } fn bfs_single_vertex() where G::Vertex: Debug, { let (graph, v) = G::single_vertex(); let result = algorithms::bfs(&graph, v); assert_eq!( result.distances[v], Some(0), "unexpected distance of source vertex" ); assert_eq!( result.predecessors[v], None, "unexpected predecessor of source vertex" ); } fn bfs_disconnected() where G::Vertex: Debug, { let (graph, vertices) = G::disconnected(); let result = algorithms::bfs(&graph, vertices[0]); assert_eq!( result.distances[vertices[0]], Some(0), "unexpected distance of source vertex" ); assert_eq!( result.predecessors[vertices[0]], None, "unexpected predecessor of source vertex" ); for &v in &vertices[1..3] { assert_eq!( result.distances[v], None, "disconnected vertex {v:?} should have no distance" ); assert_eq!( result.predecessors[v], None, "disconnected vertex {v:?} should have no predecessor" ); } } fn bfs() where G::Vertex: Debug, { let (graph, vertices, _, _) = G::standard(); let result = algorithms::bfs(&graph, vertices[0]); assert_bfs_distances::(&result.distances, &vertices); assert_bfs_predecessors::(&result.predecessors, &vertices); } fn bfs_distances_single_vertex() { let (graph, v) = G::single_vertex(); let distances = algorithms::bfs_distances(&graph, v); assert_eq!( distances[v], Some(0), "unexpected distance of source vertex" ); } fn bfs_distances_disconnected() where G::Vertex: Debug, { let (graph, vertices) = G::disconnected(); let distances = algorithms::bfs_distances(&graph, vertices[0]); assert_eq!( distances[vertices[0]], Some(0), "unexpected distance of source vertex" ); for &v in &vertices[1..3] { assert_eq!( distances[v], None, "disconnected vertex {v:?} should have no distance" ); } } fn bfs_distances() where G::Vertex: Debug, { let (graph, vertices, _, _) = G::standard(); let distances = algorithms::bfs_distances(&graph, vertices[0]); assert_bfs_distances::(&distances, &vertices); } fn bfs_find_source() { let (graph, v) = G::single_vertex(); assert_eq!( algorithms::bfs_find(&graph, v, v), Some(0), "source should be found at distance 0" ); } fn bfs_find_disconnected() { let (graph, vertices) = G::disconnected(); assert_eq!( algorithms::bfs_find(&graph, vertices[0], vertices[1]), None, "disconnected target should not be found" ); } fn bfs_find() where G::Vertex: Debug, { let (graph, vertices, _, _) = G::standard(); let expected_distances = [ Some(0), Some(1), Some(2), Some(2), Some(2), Some(3), Some(3), Some(3), Some(3), Some(4), ]; for i in 0..10 { assert_eq!( algorithms::bfs_find(&graph, vertices[0], vertices[i]), expected_distances[i], "unexpected distance from {:?} to {:?}", vertices[0], vertices[i] ); } } fn bfs_find_where_source_matches() where G::Vertex: Debug, { let (graph, v) = G::single_vertex(); assert_eq!( algorithms::bfs_find_where(&graph, v, |u| u == v), Some((v, 0)), "source should match with distance 0" ); } fn bfs_find_where_disconnected() where G::Vertex: Debug, { let (graph, vertices) = G::disconnected(); assert_eq!( algorithms::bfs_find_where(&graph, vertices[0], |v| v == vertices[1]), None, "disconnected vertex {:?} should not be found", vertices[1] ); } fn bfs_find_where_no_match() where G::Vertex: Debug, { let (graph, vertices, _, _) = G::standard(); assert_eq!( algorithms::bfs_find_where(&graph, vertices[0], |_| false), None, "no vertex should match an always-false predicate" ); } fn bfs_find_where_nearest() where G::Vertex: Debug, { let (graph, vertices, _, _) = G::standard(); // vertices[5], vertices[6], vertices[7], vertices[8] are all at distance 3 from vertices[0]. // vertices[9] is at distance 4. Predicate matches vertices[7], vertices[8], vertices[9]. // BFS must return one of the distance-3 ones, not vertices[9]. let result = algorithms::bfs_find_where(&graph, vertices[0], |v| { v == vertices[7] || v == vertices[8] || v == vertices[9] }); assert!(result.is_some(), "expected a match"); let (found, distance) = result.unwrap(); assert_eq!( distance, 3, "unexpected distance to nearest matching vertex {found:?}", ); assert!( found == vertices[7] || found == vertices[8], "unexpected nearest match vertex {found:?}, should be {:?} or {:?}", vertices[7], vertices[8] ); } fn assert_bfs_distances( distances: &ElementMap>, vertices: &[G::Vertex], ) where G::Vertex: Debug, { let expected = [ Some(0), Some(1), Some(2), Some(2), Some(2), Some(3), Some(3), Some(3), Some(3), Some(4), ]; for i in 0..10 { assert_eq!( distances[vertices[i]], expected[i], "unexpected distance from {:?} to {:?}", vertices[0], vertices[i] ); } } fn assert_bfs_predecessors( predecessors: &ElementMap>, vertices: &[G::Vertex], ) where G::Vertex: Debug, { assert_eq!( predecessors[vertices[0]], None, "source should have no predecessor" ); // Each non-source vertex's predecessor must be adjacent and at distance one less. let expected_predecessors = [ vec![None], vec![Some(vertices[0])], vec![Some(vertices[1])], vec![Some(vertices[1])], vec![Some(vertices[1])], vec![Some(vertices[2])], vec![Some(vertices[2]), Some(vertices[3])], vec![Some(vertices[4])], vec![Some(vertices[4])], vec![Some(vertices[5]), Some(vertices[6]), Some(vertices[7])], ]; for i in 1..10 { assert!( expected_predecessors[i].contains(&predecessors[vertices[i]]), "unexpected predecessor {:?} of {:?}", predecessors[vertices[i]], vertices[i] ); } }