From a8b6f7591cd46a03a429c9bc5b1482229dde5532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Thu, 3 Sep 2026 09:10:03 +0200 Subject: [PATCH] Move common test helpers to fixtures module --- src/testing/fixtures.rs | 91 +++++++++++++++++++++++++++++++++++++++ tests/bfs.rs | 94 ++++------------------------------------- tests/dfs.rs | 51 +++------------------- tests/dijkstra.rs | 94 +++++++++-------------------------------- 4 files changed, 126 insertions(+), 204 deletions(-) diff --git a/src/testing/fixtures.rs b/src/testing/fixtures.rs index d4c2109..eaa259c 100644 --- a/src/testing/fixtures.rs +++ b/src/testing/fixtures.rs @@ -1,4 +1,6 @@ +use crate::maps::ElementMap; use crate::traits::{GraphTopology, GraphTopologyAddition, Incidence}; +use std::fmt::Debug; pub trait MakeTestGraph: GraphTopology + Sized { fn standard() -> ( @@ -162,3 +164,92 @@ impl MakeTestGraph for G { (graph, vertices, [e0, e1, e2, e3]) } } + +pub fn assert_standard_unweighted_distances_v0(actual_for: F, vertices: &[V]) +where + V: Debug + Copy, + F: Fn(V) -> Option, +{ + let expected = [0, 1, 2, 2, 2, 3, 3, 3, 3, 4]; + for (i, v) in vertices.iter().enumerate() { + assert_eq!( + actual_for(*v), + Some(expected[i]), + "unexpected distance from vertex {:?} to vertex {:?}", + vertices[0], + vertices[i] + ); + } +} + +pub fn assert_standard_unweighted_predecessors_v0( + actual: &ElementMap>, + vertices: &[V], +) { + let expected = [ + 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 0..10 { + assert!( + expected[i].contains(&actual[vertices[i]]), + "unexpected predecessor {:?} of vertex {:?}", + actual[vertices[i]], + vertices[i] + ); + } +} + +/// Asserts that the path given as `path_option` is a valid path in `graph` +/// +/// Walks the path and asserts that it is `Some`, that it is empty if and only if +/// `source == target`, that its edges are pairwise incident in the order given, and that it starts +/// at `source` and ends at `target`. +pub fn assert_valid_path( + graph: &G, + path_option: Option>, + source: G::Vertex, + targets: &[G::Vertex], +) -> Vec +where + G::Vertex: Debug, + G::Edge: Debug, +{ + let path = path_option.expect(&format!( + "path should exist between source vertex {:?} and connected targets {:?}", + source, targets + )); + + if targets.contains(&source) { + assert!( + path.is_empty(), + "path from source to itself should be empty" + ); + } else { + assert!(!path.is_empty(), "path should not be empty"); + // Walks the path: tracks current vertex, confirms 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:?} (index {i}, vertices {v1:?} to {v2:?}) not incident to vertex {current:?}" + ); + current = if v1 == current { v2 } else { v1 }; + } + assert!( + targets.contains(¤t), + "path should end at a target vertex ({targets:?}), but ended at {current:?}" + ); + } + path +} diff --git a/tests/bfs.rs b/tests/bfs.rs index 913234d..89278fd 100644 --- a/tests/bfs.rs +++ b/tests/bfs.rs @@ -1,9 +1,8 @@ use std::fmt::Debug; use grapherity::algorithms; -use grapherity::maps::ElementMap; +use grapherity::testing::fixtures; use grapherity::testing::fixtures::MakeTestGraph; -use grapherity::traits::GraphTopology; macro_rules! bfs_tests { ($T:ty) => { @@ -96,8 +95,8 @@ where { let (graph, vertices, _, _) = G::standard(); let result = algorithms::bfs(&graph, vertices[0]); - assert_bfs_distances::(&result.distances, &vertices); - assert_bfs_predecessors::(&result.predecessors, &vertices); + 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() { @@ -135,7 +134,7 @@ where { let (graph, vertices, _, _) = G::standard(); let distances = algorithms::bfs_distances(&graph, vertices[0]); - assert_bfs_distances::(&distances, &vertices); + fixtures::assert_standard_unweighted_distances_v0(|v| distances[v], &vertices); } fn bfs_find_source() { @@ -161,27 +160,10 @@ 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] - ); - } + fixtures::assert_standard_unweighted_distances_v0( + |v| algorithms::bfs_find(&graph, vertices[0], v), + &vertices, + ); } fn bfs_find_where_source_matches() @@ -245,63 +227,3 @@ where 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] - ); - } -} diff --git a/tests/dfs.rs b/tests/dfs.rs index 4999cc9..6e35afc 100644 --- a/tests/dfs.rs +++ b/tests/dfs.rs @@ -2,6 +2,7 @@ use std::fmt::Debug; use grapherity::algorithms; use grapherity::maps::ElementMap; +use grapherity::testing::fixtures; use grapherity::testing::fixtures::MakeTestGraph; use grapherity::traits::GraphTopology; @@ -257,12 +258,7 @@ where 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"); + assert_eq!(path, [e], "path should contain only the connecting edge"); } fn dfs_find_path() @@ -271,11 +267,8 @@ where 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]); + let path = algorithms::dfs_find_path(&graph, vertices[0], vertices[9]); + fixtures::assert_valid_path(&graph, path, vertices[0], &[vertices[9]]); } fn dfs_find_path_where_source_matches() @@ -320,13 +313,8 @@ where 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]); + let path = algorithms::dfs_find_path_where(&graph, vertices[0], |v| v == vertices[9]); + _ = fixtures::assert_valid_path(&graph, path, vertices[0], &[vertices[9]]); } fn assert_dfs_visited( @@ -369,30 +357,3 @@ fn assert_dfs_predecessors( ); } } - -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:?}" - ); -} diff --git a/tests/dijkstra.rs b/tests/dijkstra.rs index 0945aa9..f7c5423 100644 --- a/tests/dijkstra.rs +++ b/tests/dijkstra.rs @@ -4,6 +4,7 @@ use std::hash::Hash; use grapherity::algorithms; use grapherity::algorithms::DijkstraResult; use grapherity::maps::ElementMap; +use grapherity::testing::fixtures; use grapherity::testing::fixtures::MakeTestGraph; use grapherity::traits::{GraphTopology, Incidence}; @@ -94,18 +95,19 @@ fn dijkstra() where G::Vertex: Debug + Hash, { - let (graph, vertices, _, _, weights) = make_test_graph_weighted::(); + let (graph, vertices, _, _, weights) = make_standard_weighted::(); let result = algorithms::dijkstra(&graph, vertices[0], |e| weights[e]); - assert_test_graph::(&result, &vertices); + assert_standard_weighted_distances_v0::(&result.distances, &vertices); + assert_standard_weighted_predecessors_v0::(&result.predecessors, &vertices); } fn dijkstra_distances() where G::Vertex: Debug + Hash, { - let (graph, vertices, _, _, weights) = make_test_graph_weighted::(); + let (graph, vertices, _, _, weights) = make_standard_weighted::(); let distances = algorithms::dijkstra_distances(&graph, vertices[0], |e| weights[e]); - assert_distances_test_graph::(&distances, &vertices); + assert_standard_weighted_distances_v0::(&distances, &vertices); } fn dijkstra_unweighted_single_vertex() @@ -133,7 +135,8 @@ where { let (graph, vertices, _, _) = G::standard(); let result = algorithms::dijkstra_unweighted(&graph, vertices[0]); - assert_unweighted_test_graph::(&result, &vertices); + fixtures::assert_standard_unweighted_distances_v0(|v| result.distances[v], &vertices); + fixtures::assert_standard_unweighted_predecessors_v0(&result.predecessors, &vertices); } fn dijkstra_distances_unweighted_single_vertex() @@ -161,7 +164,7 @@ where { let (graph, vertices, _, _) = G::standard(); let distances = algorithms::dijkstra_distances_unweighted(&graph, vertices[0]); - assert_distances_unweighted_test_graph::(&distances, &vertices); + fixtures::assert_standard_unweighted_distances_v0(|v| distances[v], &vertices); } fn assert_single_vertex(result: &DijkstraResult, v: G::Vertex) @@ -213,12 +216,13 @@ fn assert_distances_disconnected( } } -fn assert_test_graph(result: &DijkstraResult, vertices: &[G::Vertex]) -where +fn assert_standard_weighted_predecessors_v0( + actual: &ElementMap>, + vertices: &[G::Vertex], +) where G::Vertex: Debug, { - assert_distances_test_graph::(&result.distances, &vertices); - let expected_predecessors_from_v0 = [ + let expected = [ vec![None], vec![Some(vertices[0])], vec![Some(vertices[4])], @@ -232,21 +236,21 @@ where ]; for i in 0..10 { assert!( - expected_predecessors_from_v0[i].contains(&result.predecessors[vertices[i]]), + expected[i].contains(&actual[vertices[i]]), "unexpected predecessor {:?} of {:?}", - result.predecessors[vertices[i]], + actual[vertices[i]], vertices[i] ); } } -fn assert_distances_test_graph( - distances: &ElementMap>, +fn assert_standard_weighted_distances_v0( + actual: &ElementMap>, vertices: &[G::Vertex], ) where G::Vertex: Debug, { - let expected_distances_from_v0 = [ + let expected = [ Some(0), Some(1), Some(65), @@ -260,70 +264,14 @@ fn assert_distances_test_graph( ]; for i in 0..10 { assert_eq!( - distances[vertices[i]], expected_distances_from_v0[i], + actual[vertices[i]], expected[i], "unexpected distance from {:?} to {:?}", vertices[0], vertices[i] ); } } -fn assert_unweighted_test_graph( - result: &DijkstraResult, - vertices: &[G::Vertex], -) where - G::Vertex: Debug, -{ - assert_distances_unweighted_test_graph::(&result.distances, &vertices); - let expected_predecessors_from_v0 = [ - 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 0..10 { - assert!( - expected_predecessors_from_v0[i].contains(&result.predecessors[vertices[i]]), - "unexpected predecessor {:?} of {:?}", - result.predecessors[vertices[i]], - vertices[i] - ); - } -} - -fn assert_distances_unweighted_test_graph( - distances: &ElementMap>, - vertices: &[G::Vertex], -) where - G::Vertex: Debug, -{ - let expected_distances_from_v0 = [ - 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_distances_from_v0[i], - "unexpected distance from {:?} to {:?}", - vertices[0], vertices[i] - ); - } -} - -fn make_test_graph_weighted() -> ( +fn make_standard_weighted() -> ( G, [G::Vertex; 10], [(G::Edge, G::Vertex, G::Vertex); 18],