use std::fmt::Debug; use grapherity::algorithms; use grapherity::testing::fixtures; use grapherity::testing::fixtures::MakeTestGraph; 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, bfs_find_path_source_equals_target, bfs_find_path_disconnected, bfs_find_path_adjacent, bfs_find_path, bfs_find_path_where_source_matches, bfs_find_path_where_disconnected, bfs_find_path_where_no_match, bfs_find_path_where, ); }; (@wrap $T:ty, $($name:ident),* $(,)?) => { $( #[test] fn $name() { super::$name::<$T>(); } )* }; } mod frozen_graph { use grapherity::models::FrozenGraph; bfs_tests!(FrozenGraph); } 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]); fixtures::assert_standard_unweighted_distances_v0(|v| result.distances[v], &vertices); fixtures::assert_standard_unweighted_predecessors_v0(&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]); fixtures::assert_standard_unweighted_distances_v0(|v| distances[v], &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(); fixtures::assert_standard_unweighted_distances_v0( |v| algorithms::bfs_find(&graph, vertices[0], v), &vertices, ); } 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 bfs_find_path_source_equals_target() where G::Edge: Debug, { let (graph, v) = G::single_vertex(); assert_eq!( algorithms::bfs_find_path(&graph, v, v), Some(vec![]), "path from source to itself should be empty" ); } fn bfs_find_path_disconnected() where G::Edge: Debug, { let (graph, vertices) = G::disconnected(); assert_eq!( algorithms::bfs_find_path(&graph, vertices[0], vertices[1]), None, "no path should exist to disconnected vertex" ); } fn bfs_find_path_adjacent() where G::Edge: Debug, { let (graph, vertices, e) = G::single_edge(); let path = algorithms::bfs_find_path(&graph, vertices[0], vertices[1]) .expect("path should exist between adjacent vertices"); assert_eq!(path, [e], "path should contain only the connecting edge"); } fn bfs_find_path() where G::Vertex: Debug, G::Edge: Debug, { let (graph, vertices, _, _) = G::standard(); fixtures::assert_standard_unweighted_distances_v0( |v| { let result = algorithms::bfs_find_path(&graph, vertices[0], v); let path = fixtures::assert_valid_path(&graph, result, vertices[0], &[v]); Some(u32::try_from(path.len()).unwrap()) }, &vertices, ); } fn bfs_find_path_where_source_matches() where G::Edge: Debug, { let (graph, v) = G::single_vertex(); assert_eq!( algorithms::bfs_find_path_where(&graph, v, |u| u == v), Some(vec![]), "path from source to itself should be empty" ); } fn bfs_find_path_where_disconnected() where G::Edge: Debug, { let (graph, vertices) = G::disconnected(); assert_eq!( algorithms::bfs_find_path_where(&graph, vertices[0], |v| v == vertices[1]), None, "no path should exist to disconnected vertex" ); } fn bfs_find_path_where_no_match() where G::Edge: Debug, { let (graph, vertices, _, _) = G::standard(); assert_eq!( algorithms::bfs_find_path_where(&graph, vertices[0], |_| false), None, "no path should exist when predicate never matches" ); } fn bfs_find_path_where() where G::Vertex: Debug, G::Edge: Debug, { let (graph, vertices, _, _) = G::standard(); // vertices[7] and vertices[8] are at distance 3 from vertices[0]; vertices[9] is at distance 4. // BFS must return one shortest path, so it must lead to one of the distance-3 matches. let result = algorithms::bfs_find_path_where(&graph, vertices[0], |v| { v == vertices[7] || v == vertices[8] || v == vertices[9] }); let path = fixtures::assert_valid_path(&graph, result, vertices[0], &[vertices[7], vertices[8]]); assert_eq!( path.len(), 3, "BFS should return shortest path to a matching vertex" ); }