use std::fmt::Debug; use grapherity::algorithms; use grapherity::maps::ElementMap; use grapherity::testing::fixtures::MakeTestGraph; use grapherity::traits::GraphTopology; macro_rules! dfs_tests { ($T:ty) => { dfs_tests!(@wrap $T, dfs_single_vertex, dfs_disconnected, dfs, dfs_visited_single_vertex, dfs_visited_disconnected, dfs_visited, dfs_find_source, dfs_find_disconnected, dfs_find, dfs_find_where_source_matches, dfs_find_where_disconnected, dfs_find_where_no_match, dfs_find_where_adjacent, dfs_find_where, dfs_find_path_source_equals_target, dfs_find_path_disconnected, dfs_find_path_adjacent, dfs_find_path, dfs_find_path_where_source_matches, dfs_find_path_where_disconnected, dfs_find_path_where_no_match, dfs_find_path_where, ); }; (@wrap $T:ty, $($name:ident),* $(,)?) => { $( #[test] fn $name() { super::$name::<$T>(); } )* }; } mod append_graph { use grapherity::models::AppendGraph; dfs_tests!(AppendGraph); } mod graph { use grapherity::models::Graph; dfs_tests!(Graph); } fn dfs_single_vertex() where G::Vertex: Debug, { let (graph, v) = G::single_vertex(); let result = algorithms::dfs(&graph, v); assert!(result.visited[v], "source vertex should be visited"); assert_eq!( result.predecessors[v], None, "unexpected predecessor of source vertex" ); } fn dfs_disconnected() where G::Vertex: Debug, { let (graph, vertices) = G::disconnected(); let result = algorithms::dfs(&graph, vertices[0]); assert!( result.visited[vertices[0]], "source vertex should be visited" ); assert_eq!( result.predecessors[vertices[0]], None, "unexpected predecessor of source vertex" ); for &v in &vertices[1..3] { assert!( !result.visited[v], "disconnected vertex {v:?} should not be visited" ); assert_eq!( result.predecessors[v], None, "disconnected vertex {v:?} should have no predecessor" ); } } fn dfs() where G::Vertex: Debug, { let (graph, vertices, _, _) = G::standard(); let result = algorithms::dfs(&graph, vertices[0]); assert_dfs_visited::(&result.visited, &vertices); assert_dfs_predecessors(&graph, &result.visited, &result.predecessors, &vertices); } fn dfs_visited_single_vertex() { let (graph, v) = G::single_vertex(); let visited = algorithms::dfs_visited(&graph, v); assert!(visited[v], "source vertex should be visited"); } fn dfs_visited_disconnected() where G::Vertex: Debug, { let (graph, vertices) = G::disconnected(); let visited = algorithms::dfs_visited(&graph, vertices[0]); assert!(visited[vertices[0]], "source vertex should be visited"); for &v in &vertices[1..3] { assert!( !visited[v], "disconnected vertex {v:?} should not be visited" ); } } fn dfs_visited() where G::Vertex: Debug, { let (graph, vertices, _, _) = G::standard(); let visited = algorithms::dfs_visited(&graph, vertices[0]); assert_dfs_visited::(&visited, &vertices); } fn dfs_find_source() { let (graph, v) = G::single_vertex(); assert!( algorithms::dfs_find(&graph, v, v), "source should find itself" ); } fn dfs_find_disconnected() { let (graph, vertices) = G::disconnected(); assert!( !algorithms::dfs_find(&graph, vertices[0], vertices[1]), "disconnected target should not be found" ); } fn dfs_find() where G::Vertex: Debug, { let (graph, vertices, _, _) = G::standard(); for i in 0..10 { assert!( algorithms::dfs_find(&graph, vertices[0], vertices[i]), "vertex {:?} should be reachable from {:?}", vertices[i], vertices[0] ); } } fn dfs_find_where_source_matches() where G::Vertex: Debug, { let (graph, v) = G::single_vertex(); assert_eq!( algorithms::dfs_find_where(&graph, v, |u| u == v), Some(v), "source should find itself" ); } fn dfs_find_where_disconnected() where G::Vertex: Debug, { let (graph, vertices) = G::disconnected(); assert_eq!( algorithms::dfs_find_where(&graph, vertices[0], |v| v == vertices[1]), None, "disconnected vertex {:?} should not be found", vertices[1] ); } fn dfs_find_where_no_match() where G::Vertex: Debug, { let (graph, vertices, _, _) = G::standard(); assert_eq!( algorithms::dfs_find_where(&graph, vertices[0], |_| false), None, "no vertex should match an always-false predicate" ); } fn dfs_find_where_adjacent() where G::Vertex: Debug, { let (graph, vertices, _, _) = G::standard(); assert_eq!( algorithms::dfs_find_where(&graph, vertices[0], |v| v == vertices[1]), Some(vertices[1]), "expected to find adjacent vertex" ); } fn dfs_find_where() where G::Vertex: Debug, { let (graph, vertices, _, _) = G::standard(); assert_eq!( algorithms::dfs_find_where(&graph, vertices[0], |v| v == vertices[9]), Some(vertices[9]), "expected to find connected vertex" ); } fn dfs_find_path_source_equals_target() where G::Edge: Debug, { let (graph, v) = G::single_vertex(); assert_eq!( algorithms::dfs_find_path(&graph, v, v), Some(vec![]), "path from source to itself should be empty" ); } fn dfs_find_path_disconnected() where G::Edge: Debug, { let (graph, vertices) = G::disconnected(); assert_eq!( algorithms::dfs_find_path(&graph, vertices[0], vertices[1]), None, "no path should exist to disconnected vertex" ); } fn dfs_find_path_adjacent() where G::Edge: Debug, { let (graph, vertices, e) = G::single_edge(); let path = algorithms::dfs_find_path(&graph, vertices[0], vertices[1]) .expect("path should exist between adjacent vertices"); assert_eq!( path.len(), 1, "unexpected path length between adjacent vertices" ); assert_eq!(path[0], e, "path should use the connecting edge"); } fn dfs_find_path() where G::Vertex: Debug, G::Edge: Debug, { let (graph, vertices, _, _) = G::standard(); let path = algorithms::dfs_find_path(&graph, vertices[0], vertices[9]).expect(&format!( "path should exist between connected vertices {:?} and {:?}", vertices[0], vertices[9] )); assert_valid_path(&graph, &path, vertices[0], vertices[9]); } fn dfs_find_path_where_source_matches() where G::Edge: Debug, { let (graph, v) = G::single_vertex(); assert_eq!( algorithms::dfs_find_path_where(&graph, v, |u| u == v), Some(vec![]), "path from source to itself should be empty" ); } fn dfs_find_path_where_disconnected() where G::Edge: Debug, { let (graph, vertices) = G::disconnected(); assert_eq!( algorithms::dfs_find_path_where(&graph, vertices[0], |v| v == vertices[1]), None, "no path should exist to disconnected vertex" ); } fn dfs_find_path_where_no_match() where G::Edge: Debug, { let (graph, vertices, _, _) = G::standard(); assert_eq!( algorithms::dfs_find_path_where(&graph, vertices[0], |_| false), None, "no path should exist when predicate never matches" ); } fn dfs_find_path_where() where G::Vertex: Debug, G::Edge: Debug, { let (graph, vertices, _, _) = G::standard(); let path = algorithms::dfs_find_path_where(&graph, vertices[0], |v| v == vertices[9]).expect( &format!( "path should exist between connected vertices {:?} and {:?}", vertices[0], vertices[9] ), ); assert_valid_path(&graph, &path, vertices[0], vertices[9]); } fn assert_dfs_visited( visited: &ElementMap, vertices: &[G::Vertex], ) where G::Vertex: Debug, { for i in 0..10 { assert!( visited[vertices[i]], "vertex {:?} should be visited", vertices[i] ); } } fn assert_dfs_predecessors( graph: &G, visited: &ElementMap, predecessors: &ElementMap>, vertices: &[G::Vertex], ) where G::Vertex: Debug, { assert_eq!( predecessors[vertices[0]], None, "source should have no predecessor" ); for i in 1..10 { let v = vertices[i]; let p = predecessors[v].expect(&format!("vertex {v:?} should have a predecessor")); assert!( visited[p], "predecessor {p:?} of vertex {v:?} should be visited" ); assert!( graph.are_adjacent(v, p), "predecessor {p:?} of vertex {v:?} should be adjacent" ); } } fn assert_valid_path( graph: &G, path: &[G::Edge], source: G::Vertex, target: G::Vertex, ) where G::Vertex: Debug, G::Edge: Debug, { assert!(!path.is_empty(), "path should be non-empty"); // Walks the path: tracks current vertex, confirm each edge is incident to it. let mut current = source; for (i, &e) in path.iter().enumerate() { let (v1, v2) = graph.incident_vertices(e); assert_ne!(v1, v2, "path should not contain loop edge {e:?}"); assert!( v1 == current || v2 == current, "path edge {e:?} (idx {i}, {v1:?} to {v2:?}) not incident to vertex {current:?}" ); current = if v1 == current { v2 } else { v1 }; } assert_eq!( current, target, "path should end at target {target:?}, but ended at {current:?}" ); }