The tests only declare a list of tests in the macro, no longer the test functions themselves. This allows better tooling support (e.g. IDE and rustfmt).
399 lines
10 KiB
Rust
399 lines
10 KiB
Rust
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<G: MakeTestGraph>()
|
|
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<G: MakeTestGraph>()
|
|
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<G: MakeTestGraph>()
|
|
where
|
|
G::Vertex: Debug,
|
|
{
|
|
let (graph, vertices, _, _) = G::standard();
|
|
let result = algorithms::dfs(&graph, vertices[0]);
|
|
assert_dfs_visited::<G>(&result.visited, &vertices);
|
|
assert_dfs_predecessors(&graph, &result.visited, &result.predecessors, &vertices);
|
|
}
|
|
|
|
fn dfs_visited_single_vertex<G: MakeTestGraph>() {
|
|
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<G: MakeTestGraph>()
|
|
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<G: MakeTestGraph>()
|
|
where
|
|
G::Vertex: Debug,
|
|
{
|
|
let (graph, vertices, _, _) = G::standard();
|
|
let visited = algorithms::dfs_visited(&graph, vertices[0]);
|
|
assert_dfs_visited::<G>(&visited, &vertices);
|
|
}
|
|
|
|
fn dfs_find_source<G: MakeTestGraph>() {
|
|
let (graph, v) = G::single_vertex();
|
|
assert!(
|
|
algorithms::dfs_find(&graph, v, v),
|
|
"source should find itself"
|
|
);
|
|
}
|
|
|
|
fn dfs_find_disconnected<G: MakeTestGraph>() {
|
|
let (graph, vertices) = G::disconnected();
|
|
assert!(
|
|
!algorithms::dfs_find(&graph, vertices[0], vertices[1]),
|
|
"disconnected target should not be found"
|
|
);
|
|
}
|
|
|
|
fn dfs_find<G: MakeTestGraph>()
|
|
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<G: MakeTestGraph>()
|
|
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<G: MakeTestGraph>()
|
|
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<G: MakeTestGraph>()
|
|
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<G: MakeTestGraph>()
|
|
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<G: MakeTestGraph>()
|
|
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<G: MakeTestGraph>()
|
|
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<G: MakeTestGraph>()
|
|
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<G: MakeTestGraph>()
|
|
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<G: MakeTestGraph>()
|
|
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<G: MakeTestGraph>()
|
|
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<G: MakeTestGraph>()
|
|
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<G: MakeTestGraph>()
|
|
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<G: MakeTestGraph>()
|
|
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<G: GraphTopology>(
|
|
visited: &ElementMap<G::Vertex, bool>,
|
|
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<G: GraphTopology>(
|
|
graph: &G,
|
|
visited: &ElementMap<G::Vertex, bool>,
|
|
predecessors: &ElementMap<G::Vertex, Option<G::Vertex>>,
|
|
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<G: GraphTopology>(
|
|
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:?}"
|
|
);
|
|
}
|