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

251 lines
8.1 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 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.
// 菱形:0 在 1 与 2 之前,二者又都在 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.
// 无论输入次序如何,约束强制 0 在 1 之前、1 在 2 之前。
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.
// 只有 0 受约束(在 1 之前)。任务 2 与 3 是自由的。
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.
// 偏好次序是 3、2、0、1,故在自由任务中 3 最先。
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.
// 任务 9 完全没有约束。
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)));
}