217 lines
7.1 KiB
Rust
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]);
|
|
}
|