diff --git a/src/generators.rs b/src/generators.rs index 1456741..12d7888 100644 --- a/src/generators.rs +++ b/src/generators.rs @@ -152,67 +152,342 @@ mod tests { use std::fmt::Debug; use super::*; - use crate::models::{FrozenGraph, Graph}; + use crate::testing::fixtures::MakeTestGraph; - #[test] - fn petersen_append_graph() { - petersen_add::(); + #[derive(Debug, Clone, Copy)] + enum Spec { + Path(usize), + Cycle(usize), + Complete(usize), + Petersen, } - #[test] - fn petersen_graph() { - petersen_add::(); + impl Spec { + fn generate( + &self, + graph: &mut G, + ) -> (Vec, Vec) { + match *self { + Spec::Path(n) => path(graph, n), + Spec::Cycle(n) => cycle(graph, n), + Spec::Complete(n) => complete(graph, n), + Spec::Petersen => petersen(graph), + } + } + + fn assert(&self, built: &Built) + where + G::Vertex: Debug, + G::Edge: Debug, + { + match *self { + Spec::Path(n) => assert_path(built, n), + Spec::Cycle(n) => assert_cycle(built, n), + Spec::Complete(n) => assert_complete(built, n), + Spec::Petersen => assert_petersen(built), + } + } } - #[test] - fn petersen_frozen_graph() { - petersen_generate_as::(); + struct Built { + graph: G, + vertices: Vec, + edges: Vec, + initial_vertex_count: usize, + initial_edge_count: usize, } - fn petersen_generate_as() + /// Generates the test for the `append_graph` harness for the given `Spec` expression. + macro_rules! min_harness_tests { + ($spec:expr) => { + #[test] + fn append_graph() { + run($spec, add_to_empty::<$crate::models::AppendGraph>); + } + }; + } + + /// Generates one test per harness for the given `Spec` expression. + macro_rules! all_harness_tests { + ($spec:expr) => { + min_harness_tests!($spec); + + #[test] + fn append_graph_non_empty() { + run($spec, add_to_standard::<$crate::models::AppendGraph>); + } + + #[test] + fn graph() { + run($spec, add_to_empty::<$crate::models::Graph>); + } + + #[test] + fn graph_non_empty() { + run($spec, add_to_standard::<$crate::models::Graph>); + } + + #[test] + fn frozen_graph() { + run($spec, conv_generate_as::<$crate::models::FrozenGraph>); + } + }; + } + + /// Generates the module tree `::::` for each generator case. + /// + /// The `all_harnesses` cases run on every harness, the `min_harnesses` cases only on a reduced + /// set of harnesses. + macro_rules! generator_tests { + ($($group:ident { + all { $($all_case:ident => $all_generator:expr),+ $(,)? } + min { $($min_case:ident => $min_generator:expr),* $(,)? } + })+) => { + $( + mod $group { + use super::*; + + $( + mod $all_case { + use super::*; + + all_harness_tests!($all_generator); + } + )+ + + $( + mod $min_case { + use super::*; + + min_harness_tests!($min_generator); + } + )* + } + )+ + }; + } + + generator_tests! { + path { + all { + small => Spec::Path(4), + } + min { + empty => Spec::Path(0), + single_vertex => Spec::Path(1), + single_edge => Spec::Path(2), + large => Spec::Path(21), + } + } + cycle { + all { + small_even => Spec::Cycle(4), + } + min { + empty => Spec::Cycle(0), + loop_edge => Spec::Cycle(1), + multi_edge => Spec::Cycle(2), + large_odd => Spec::Cycle(21), + } + } + complete { + all { + small_even => Spec::Complete(4), + } + min { + empty => Spec::Complete(0), + single_vertex => Spec::Complete(1), + single_edge => Spec::Complete(2), + large_odd => Spec::Complete(13), + } + } + } + + mod petersen { + use super::*; + + all_harness_tests!(Spec::Petersen); + } + + /// Builds a graph with `harness` and asserts that it matches what `spec` should produce. + fn run(spec: Spec, harness: impl FnOnce(&Spec) -> Built) where G::Vertex: Debug, G::Edge: Debug, { - let (graph, vertices, edges) = generate_as::(petersen); - assert_petersen(&graph, &vertices, &edges); + let built = harness(&spec); + spec.assert(&built); } - fn petersen_add() - where - G::Vertex: Debug, - G::Edge: Debug, - { + /// Runs `spec` on an empty graph of type `G`. + fn add_to_empty(spec: &Spec) -> Built { let mut graph = G::default(); - let (vertices, edges) = petersen(&mut graph); - assert_petersen(&graph, &vertices, &edges); + let (vertices, edges) = spec.generate(&mut graph); + Built { + graph, + vertices, + edges, + initial_vertex_count: 0, + initial_edge_count: 0, + } } - fn assert_petersen(graph: &G, vertices: &[G::Vertex], edges: &[G::Edge]) + /// Runs `spec` on a non-empty test graph of type `G`. + fn add_to_standard(spec: &Spec) -> Built { + let (mut graph, initial_vertices, initial_edges, _) = G::standard(); + let (vertices, edges) = spec.generate(&mut graph); + Built { + graph, + vertices, + edges, + initial_vertex_count: initial_vertices.len(), + initial_edge_count: initial_edges.len(), + } + } + + /// Runs `spec` within `generate_as` for type `G`. + fn conv_generate_as(spec: &Spec) -> Built { + let (graph, vertices, edges) = generate_as::(|graph| spec.generate(graph)); + Built { + graph, + vertices, + edges, + initial_vertex_count: 0, + initial_edge_count: 0, + } + } + + fn assert_path(built: &Built, n: usize) where G::Vertex: Debug, G::Edge: Debug, { - assert_element_counts(graph, 10, 15); - assert_handles(graph.vertices(), vertices, "vertex"); - assert_handles(graph.edges(), edges, "edge"); - assert_no_loops_no_multi_edges(graph); + assert_element_counts(built, n, n.saturating_sub(1)); + assert_handles(built.graph.vertices(), &built.vertices, "vertex"); + assert_handles(built.graph.edges(), &built.edges, "edge"); + assert_no_loops(built); + assert_no_multi_edges(built); + match n { + 0 => {} + 1 => assert_eq!( + built.graph.degree(built.vertices[0]), + 0, + "path n == 1 should be an isolated vertex" + ), + _ => { + for (i, &v) in built.vertices.iter().enumerate() { + assert_eq!( + built.graph.degree(v), + if i == 0 || i == n - 1 { 1 } else { 2 }, + "unexpected degree of vertex {v:?} in path n == {n}" + ); + } + } + } + assert_path_order(built); + } + + fn assert_cycle(built: &Built, n: usize) + where + G::Vertex: Debug, + G::Edge: Debug, + { + assert_element_counts(built, n, n); + assert_handles(built.graph.vertices(), &built.vertices, "vertex"); + assert_handles(built.graph.edges(), &built.edges, "edge"); + if n != 1 { + assert_no_loops(built); + } + if n != 2 { + assert_no_multi_edges(built); + } assert!( - graph.vertices().all(|v| graph.degree(v) == 3), - "all vertices should have degree 3" + built.vertices.iter().all(|&v| built.graph.degree(v) == 2), + "all new vertices should have degree 2" + ); + assert_path_order(built); + // Asserts the closing edge e(n - 1) == (v(n - 1), v0). + if n > 0 { + // For n == 1, u == v and this validates the loop. For n == 2, the above asserted one of + // the two multi-edges, and this asserts the other. + let e = built.edges[n - 1]; + let u = built.vertices[0]; + let v = built.vertices[n - 1]; + let incident_vertices = built.graph.incident_vertices(e); + assert!( + incident_vertices == (u, v) || incident_vertices == (v, u), + "edge {e:?} should have incident vertices {u:?} and {v:?}", + ); + } + } + + fn assert_complete(built: &Built, n: usize) + where + G::Vertex: Debug, + G::Edge: Debug, + { + assert_element_counts(built, n, n * n.saturating_sub(1) / 2); + assert_handles(built.graph.vertices(), &built.vertices, "vertex"); + assert_handles(built.graph.edges(), &built.edges, "edge"); + // The predicate in `all` is only executed if `vertices` has any elements, i.e. for n > 0. + assert!( + built + .vertices + .iter() + .all(|&v| built.graph.degree(v) == n - 1), + "all new vertices should have degree {}", + n - 1 + ); + // Asserts that all pairs of vertices have exactly one edge between them. This also includes + // checks for loops and multi-edges. If there are no duplicates, then this implies that all + // expected edges were also matched due to the validated counts from above. + let mut seen = vec![false; n * n]; + let position = |v, e| { + built + .vertices + .iter() + .position(|&x| x == v) + .unwrap_or_else(|| panic!("edge {e:?} has an invalid incident vertex {v:?}")) + }; + for &e in &built.edges { + let (u, v) = built.graph.incident_vertices(e); + assert_ne!(u, v, "unexpected loop edge {e:?} at vertex {u:?}"); + let (i, j) = (position(u, e), position(v, e)); + let (i, j) = (i.min(j), i.max(j)); + assert!( + !seen[i * n + j], + "unexpected multi-edge {e:?} between vertices {u:?} and {v:?}" + ); + seen[i * n + j] = true; + } + } + + fn assert_petersen(built: &Built) + where + G::Vertex: Debug, + G::Edge: Debug, + { + assert_element_counts(built, 10, 15); + assert_handles(built.graph.vertices(), &built.vertices, "vertex"); + assert_handles(built.graph.edges(), &built.edges, "edge"); + assert_no_loops(built); + assert_no_multi_edges(built); + assert!( + built.vertices.iter().all(|&v| built.graph.degree(v) == 3), + "all new vertices should have degree 3" ); // Checks that each pair of vertices (u, v) has no common neighbor if they are adjacent, and // exactly one common neighbor otherwise. - for (i, &v) in vertices.iter().enumerate() { - for &u in &vertices[i + 1..] { - if graph.are_adjacent(v, u) { + for (i, &v) in built.vertices.iter().enumerate() { + for &u in &built.vertices[i + 1..] { + if built.graph.are_adjacent(v, u) { assert!( - graph.common_neighbors(u, v).next().is_none(), + built.graph.common_neighbors(u, v).next().is_none(), "adjacent vertices {u:?} and {v:?} should not have a common neighbor" ); } else { assert_eq!( - graph.common_neighbors(u, v).count(), + built.graph.common_neighbors(u, v).count(), 1, "non-adjacent vertices {u:?} and {v:?} should have exactly one common neighbor" ); @@ -222,19 +497,29 @@ mod tests { } fn assert_element_counts( - graph: &G, + built: &Built, expected_vertex_count: usize, expected_edge_count: usize, ) { assert_eq!( - graph.vertex_count(), - expected_vertex_count, - "unexpected number of vertices" + built.graph.vertex_count(), + built.initial_vertex_count + expected_vertex_count, + "unexpected number of added vertices" ); assert_eq!( - graph.edge_count(), + built.vertices.len(), + expected_vertex_count, + "unexpected number of returned vertices" + ); + assert_eq!( + built.graph.edge_count(), + built.initial_edge_count + expected_edge_count, + "unexpected number of added edges" + ); + assert_eq!( + built.edges.len(), expected_edge_count, - "unexpected number of edges" + "unexpected number of returned edges" ); } @@ -253,24 +538,53 @@ mod tests { } } - fn assert_no_loops_no_multi_edges(graph: &G) + fn assert_no_loops(built: &Built) where G::Vertex: Debug, G::Edge: Debug, { - for e in graph.edges() { - let (u, v) = graph.incident_vertices(e); - assert_ne!(u, v, "unexpected loop edge {e:?}"); + for &e in &built.edges { + let (u, v) = built.graph.incident_vertices(e); + assert_ne!(u, v, "unexpected loop edge {e:?} at vertex {u:?}"); } - for v in graph.vertices() { + } + + fn assert_no_multi_edges(built: &Built) + where + G::Vertex: Debug, + { + for &v in &built.vertices { let mut neighbors = Vec::new(); - for u in graph.adjacent_vertices(v) { - assert!( - !neighbors.contains(&u), - "unexpected multi-edge between {v:?} and {u:?}" - ); - neighbors.push(u); + let mut loop_count = 0; + for u in built.graph.adjacent_vertices(v) { + if u == v { + loop_count += 1; + assert!(loop_count < 3, "unexpected multi-edge loop at vertex {v:?}"); + } else { + assert!( + !neighbors.contains(&u), + "unexpected multi-edge between vertices {v:?} and {u:?}" + ); + neighbors.push(u); + } } } } + + /// Asserts that each returned edge *ei* connects the returned vertices *vi* and *v(i + 1)*. + fn assert_path_order(built: &Built) + where + G::Vertex: Debug, + G::Edge: Debug, + { + for (w, &e) in built.vertices.windows(2).zip(&built.edges) { + let incident_vertices = built.graph.incident_vertices(e); + assert!( + incident_vertices == (w[0], w[1]) || incident_vertices == (w[1], w[0]), + "edge {e:?} should have incident vertices {:?} and {:?}", + w[0], + w[1] + ); + } + } }