apsis-rs/crates/ops/apsis-task/tests/precedence.rs

234 lines
7.3 KiB
Rust

//! Tests for the precedence graph.
use apsis_task::{PrecedenceGraph, TaskErrorKind, TaskId};
/// The identifiers used by the tests.
fn id(index: usize) -> TaskId {
TaskId::new(index)
}
#[test]
fn an_empty_graph_has_no_edges() {
let graph = PrecedenceGraph::new();
assert!(graph.is_empty());
assert_eq!(graph.edge_count(), 0);
assert_eq!(graph.task_count(), 0);
assert!(!graph.has_constraint(id(0), id(1)));
assert!(graph.predecessors_of(id(0)).is_empty());
assert!(graph.successors_of(id(0)).is_empty());
}
#[test]
fn a_constraint_is_recorded_both_ways() {
let mut graph = PrecedenceGraph::new();
graph.add(id(0), id(1)).expect("no cycle");
assert!(graph.has_constraint(id(0), id(1)));
// The constraint is directed, so the reverse does not exist.
assert!(!graph.has_constraint(id(1), id(0)));
assert_eq!(graph.successors_of(id(0)), vec![id(1)]);
assert_eq!(graph.predecessors_of(id(1)), vec![id(0)]);
assert_eq!(graph.edge_count(), 1);
assert_eq!(graph.task_count(), 2);
}
#[test]
fn a_self_constraint_is_rejected() {
let mut graph = PrecedenceGraph::new();
let error = graph
.add(id(0), id(0))
.expect_err("a task cannot precede itself");
assert_eq!(error.kind(), TaskErrorKind::PrecedenceCycle);
assert!(graph.is_empty());
}
#[test]
fn a_duplicate_constraint_is_accepted_and_ignored() {
let mut graph = PrecedenceGraph::new();
graph.add(id(0), id(1)).expect("no cycle");
graph.add(id(0), id(1)).expect("already present");
// The edge is recorded once.
assert_eq!(graph.edge_count(), 1);
assert_eq!(graph.successors_of(id(0)), vec![id(1)]);
}
#[test]
fn a_cycle_is_rejected() {
let mut graph = PrecedenceGraph::new();
graph.add(id(0), id(1)).expect("no cycle");
graph.add(id(1), id(2)).expect("no cycle");
// Closing the cycle is refused.
let error = graph.add(id(2), id(0)).expect_err("this closes a cycle");
assert_eq!(error.kind(), TaskErrorKind::PrecedenceCycle);
// The graph is unchanged.
assert!(!graph.has_constraint(id(2), id(0)));
assert_eq!(graph.edge_count(), 2);
}
#[test]
fn a_longer_cycle_is_rejected() {
let mut graph = PrecedenceGraph::new();
for edge in [(0, 1), (1, 2), (2, 3), (3, 4)] {
graph.add(id(edge.0), id(edge.1)).expect("no cycle");
}
assert_eq!(
graph
.add(id(4), id(1))
.expect_err("this closes a cycle")
.kind(),
TaskErrorKind::PrecedenceCycle
);
}
#[test]
fn reachability_follows_the_direction() {
let mut graph = PrecedenceGraph::new();
graph.add(id(0), id(1)).expect("no cycle");
graph.add(id(1), id(2)).expect("no cycle");
// The transitive relation holds forward but not backward.
assert!(graph.is_reachable(id(0), id(1)));
assert!(graph.is_reachable(id(0), id(2)));
assert!(graph.is_reachable(id(1), id(2)));
assert!(!graph.is_reachable(id(2), id(0)));
// A task reaches itself by a path of no edges.
assert!(graph.is_reachable(id(0), id(0)));
}
#[test]
fn roots_and_leaves_are_reported() {
let mut graph = PrecedenceGraph::new();
// A diamond: 0 before 1 and 2, and both before 3.
graph.add(id(0), id(1)).expect("no cycle");
graph.add(id(0), id(2)).expect("no cycle");
graph.add(id(1), id(3)).expect("no cycle");
graph.add(id(2), id(3)).expect("no cycle");
assert_eq!(graph.roots(), vec![id(0)]);
assert_eq!(graph.leaves(), vec![id(3)]);
}
#[test]
fn a_chain_has_one_root_and_one_leaf() {
let mut graph = PrecedenceGraph::new();
graph.add(id(0), id(1)).expect("no cycle");
graph.add(id(1), id(2)).expect("no cycle");
assert_eq!(graph.roots(), vec![id(0)]);
assert_eq!(graph.leaves(), vec![id(2)]);
}
#[test]
fn the_topological_order_respects_the_constraints() {
let mut graph = PrecedenceGraph::new();
graph.add(id(0), id(1)).expect("no cycle");
graph.add(id(1), id(2)).expect("no cycle");
let tasks = [id(2), id(1), id(0)];
let order = graph
.topological_order(&tasks)
.expect("the graph is acyclic");
// The constraints force 0 before 1 before 2, whatever the input order.
assert_eq!(order, vec![id(0), id(1), id(2)]);
}
#[test]
fn the_topological_order_breaks_ties_by_position() {
let mut graph = PrecedenceGraph::new();
// Only 0 is constrained, before 1. Tasks 2 and 3 are free.
graph.add(id(0), id(1)).expect("no cycle");
// The preference order is 3, 2, 0, 1, so among the free tasks 3 comes
// first.
let tasks = [id(3), id(2), id(0), id(1)];
let order = graph
.topological_order(&tasks)
.expect("the graph is acyclic");
assert_eq!(order, vec![id(3), id(2), id(0), id(1)]);
}
#[test]
fn the_topological_order_honours_the_diamond() {
let mut graph = PrecedenceGraph::new();
graph.add(id(0), id(1)).expect("no cycle");
graph.add(id(0), id(2)).expect("no cycle");
graph.add(id(1), id(3)).expect("no cycle");
graph.add(id(2), id(3)).expect("no cycle");
let tasks = [id(0), id(1), id(2), id(3)];
let order = graph
.topological_order(&tasks)
.expect("the graph is acyclic");
assert_eq!(order, vec![id(0), id(1), id(2), id(3)]);
// The position of each task in the result relative to its constraints.
let position_of = |task: TaskId| {
order
.iter()
.position(|candidate| *candidate == task)
.expect("present")
};
assert!(position_of(id(0)) < position_of(id(1)));
assert!(position_of(id(0)) < position_of(id(2)));
assert!(position_of(id(1)) < position_of(id(3)));
assert!(position_of(id(2)) < position_of(id(3)));
}
#[test]
fn the_topological_order_includes_unconstrained_tasks() {
let mut graph = PrecedenceGraph::new();
graph.add(id(0), id(1)).expect("no cycle");
// Task 9 has no constraints at all.
let tasks = [id(0), id(1), id(9)];
let order = graph
.topological_order(&tasks)
.expect("the graph is acyclic");
assert_eq!(order.len(), 3);
assert!(order.contains(&id(9)));
}
#[test]
fn the_topological_order_ignores_predecessors_outside_the_list() {
// A constraint whose predecessor is not in the list cannot be waited
// for, so it is ignored rather than blocking the successor.
let mut graph = PrecedenceGraph::new();
graph.add(id(5), id(1)).expect("no cycle");
let tasks = [id(1)];
assert_eq!(
graph
.topological_order(&tasks)
.expect("the graph is acyclic"),
vec![id(1)]
);
}
#[test]
fn the_topological_order_of_an_empty_list_is_empty() {
let graph = PrecedenceGraph::new();
assert!(
graph
.topological_order(&[])
.expect("the graph is acyclic")
.is_empty()
);
}
#[test]
fn a_graph_with_two_independent_chains_orders_both() {
let mut graph = PrecedenceGraph::new();
graph.add(id(0), id(1)).expect("no cycle");
graph.add(id(2), id(3)).expect("no cycle");
let tasks = [id(0), id(1), id(2), id(3)];
let order = graph
.topological_order(&tasks)
.expect("the graph is acyclic");
let position_of = |task: TaskId| {
order
.iter()
.position(|candidate| *candidate == task)
.expect("present")
};
assert!(position_of(id(0)) < position_of(id(1)));
assert!(position_of(id(2)) < position_of(id(3)));
}