apsis-rs/crates/network/apsis-topology/tests/graph.rs

217 lines
7.1 KiB
Rust

//! Tests for the topology graph.
use apsis_topology::{Edge, Topology, TopologyErrorKind};
/// A four node ring, with links both ways.
fn ring() -> Topology {
let mut topology = Topology::new(4);
for node in 0..4 {
topology
.add_bidirectional_edge(node, (node + 1) % 4, 1.0)
.expect("the nodes exist");
}
topology
}
#[test]
fn an_empty_topology_has_no_edges() {
let topology = Topology::new(5);
assert_eq!(topology.node_count(), 5);
assert_eq!(topology.edge_count(), 0);
assert!(topology.is_empty());
assert_eq!(topology.total_cost(), 0.0);
assert!(!topology.has_edge(0, 1));
assert_eq!(topology.cost(0, 1), None);
assert!(topology.neighbours(0).is_empty());
}
#[test]
fn an_edge_is_directed() {
let mut topology = Topology::new(2);
topology.add_edge(0, 1, 2.5).expect("valid");
assert!(topology.has_edge(0, 1));
assert!(!topology.has_edge(1, 0));
assert_eq!(topology.cost(0, 1), Some(2.5));
assert_eq!(topology.out_degree(0), 1);
assert_eq!(topology.in_degree(0), 0);
assert_eq!(topology.out_degree(1), 0);
assert_eq!(topology.in_degree(1), 1);
}
#[test]
fn a_bidirectional_edge_adds_both_directions() {
let mut topology = Topology::new(2);
topology.add_bidirectional_edge(0, 1, 3.0).expect("valid");
assert!(topology.has_edge(0, 1));
assert!(topology.has_edge(1, 0));
assert_eq!(topology.edge_count(), 2);
assert_eq!(topology.cost(0, 1), topology.cost(1, 0));
}
#[test]
fn an_out_of_range_node_is_rejected() {
let mut topology = Topology::new(2);
let error = topology
.add_edge(0, 2, 1.0)
.expect_err("node 2 does not exist");
assert_eq!(error.kind(), TopologyErrorKind::NoSuchNode);
assert_eq!(
topology.add_edge(9, 0, 1.0).expect_err("node 9").kind(),
TopologyErrorKind::NoSuchNode
);
// The topology is unchanged.
assert!(topology.is_empty());
}
#[test]
fn an_invalid_cost_is_rejected() {
let mut topology = Topology::new(2);
for cost in [0.0, -1.0, f64::NAN, f64::INFINITY] {
let error = topology
.add_edge(0, 1, cost)
.expect_err("the cost is not usable");
assert_eq!(error.kind(), TopologyErrorKind::InvalidCost, "cost {cost}");
}
assert!(topology.is_empty());
}
#[test]
fn a_duplicate_edge_is_rejected() {
let mut topology = Topology::new(2);
topology.add_edge(0, 1, 1.0).expect("valid");
let error = topology
.add_edge(0, 1, 2.0)
.expect_err("the edge already exists");
assert_eq!(error.kind(), TopologyErrorKind::DuplicateEdge);
// The original cost is retained.
assert_eq!(topology.cost(0, 1), Some(1.0));
assert_eq!(topology.edge_count(), 1);
}
#[test]
fn a_self_loop_is_allowed_and_counted() {
// A self loop is unusual in a satellite network but is not an error:
// a node may have a local interface, and the graph need not forbid it.
let mut topology = Topology::new(1);
topology.add_edge(0, 0, 1.0).expect("valid");
assert!(topology.has_edge(0, 0));
assert_eq!(topology.out_degree(0), 1);
assert_eq!(topology.in_degree(0), 1);
assert_eq!(topology.connected_components(), 1);
}
#[test]
fn an_edge_can_be_removed() {
let mut topology = Topology::new(2);
topology.add_edge(0, 1, 1.0).expect("valid");
topology.remove_edge(0, 1).expect("the edge exists");
assert!(!topology.has_edge(0, 1));
assert!(topology.is_empty());
let error = topology.remove_edge(0, 1).expect_err("the edge is gone");
assert_eq!(error.kind(), TopologyErrorKind::NoSuchEdge);
}
#[test]
fn the_adjacency_lists_every_node() {
let topology = ring();
let adjacency = topology.adjacency();
assert_eq!(adjacency.len(), 4);
// Each node of a ring has two neighbours in each direction; the
// bidirectional construction gives two outgoing edges.
for neighbours in &adjacency {
assert_eq!(neighbours.len(), 2);
}
// The order follows the insertion order: node 0 gains its edge to 1
// when node 0 is processed, and its edge to 3 when node 3 is.
assert_eq!(adjacency[0], vec![1, 3]);
assert_eq!(adjacency[1], vec![0, 2]);
}
#[test]
fn the_ring_is_connected() {
let topology = ring();
assert!(topology.is_connected());
assert_eq!(topology.connected_components(), 1);
}
#[test]
fn removing_a_link_splits_the_ring() {
let mut topology = ring();
// Removing one direction of each of two opposite links breaks the ring
// into two paths.
topology.remove_edge(0, 1).expect("the edge exists");
topology.remove_edge(1, 0).expect("the edge exists");
topology.remove_edge(2, 3).expect("the edge exists");
topology.remove_edge(3, 2).expect("the edge exists");
assert_eq!(topology.connected_components(), 2);
assert!(!topology.is_connected());
}
#[test]
fn a_single_direction_is_enough_to_connect_components() {
// The component count treats edges as undirected, so a link that only
// carries traffic one way still joins two pieces of the graph.
let mut topology = Topology::new(2);
topology.add_edge(0, 1, 1.0).expect("valid");
assert_eq!(topology.connected_components(), 1);
// But the reverse direction does not exist, so the node cannot be
// reached from the other side.
assert!(!topology.has_edge(1, 0));
}
#[test]
fn isolated_nodes_are_each_a_component() {
let topology = Topology::new(4);
assert_eq!(topology.connected_components(), 4);
// A graph with no edges is still connected when it has at most one
// node, and is not when it has more.
assert!(!topology.is_connected());
assert!(Topology::new(1).is_connected());
assert!(Topology::new(0).is_connected());
assert_eq!(Topology::new(0).connected_components(), 0);
}
#[test]
fn the_total_cost_sums_the_edges() {
let mut topology = Topology::new(3);
topology.add_edge(0, 1, 1.5).expect("valid");
topology.add_edge(1, 2, 2.5).expect("valid");
assert!((topology.total_cost() - 4.0).abs() < 1e-12);
}
#[test]
fn an_edge_can_be_reversed() {
let edge = Edge::new(3, 7, 2.0);
let reversed = edge.reversed();
assert_eq!(reversed.from, 7);
assert_eq!(reversed.to, 3);
assert_eq!(reversed.cost, 2.0);
}
#[test]
fn a_failed_bidirectional_add_leaves_the_topology_unchanged() {
let mut topology = Topology::new(2);
// The first direction succeeds, but the second is a duplicate, so the
// whole call must fail and roll back.
topology.add_edge(1, 0, 5.0).expect("valid");
let error = topology
.add_bidirectional_edge(0, 1, 2.0)
.expect_err("the reverse edge exists");
assert_eq!(error.kind(), TopologyErrorKind::DuplicateEdge);
// The forward direction that was added must have been removed.
assert!(!topology.has_edge(0, 1));
assert!(topology.has_edge(1, 0));
assert_eq!(topology.edge_count(), 1);
}
#[test]
fn the_neighbour_order_follows_the_edge_insertion_order() {
let mut topology = Topology::new(4);
topology.add_edge(0, 2, 1.0).expect("valid");
topology.add_edge(0, 1, 1.0).expect("valid");
topology.add_edge(0, 3, 1.0).expect("valid");
assert_eq!(topology.neighbours(0), vec![2, 1, 3]);
}