use std::fmt;
use haz_domain::task_id::TaskId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum EdgeKind {
Hard,
Soft,
ProducerMatching,
}
impl fmt::Display for EdgeKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
EdgeKind::Hard => "hard",
EdgeKind::Soft => "soft",
EdgeKind::ProducerMatching => "producer-matching",
};
f.write_str(s)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Edge {
pub from: TaskId,
pub to: TaskId,
pub kind: EdgeKind,
}
impl fmt::Display for Edge {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} -> {} ({})", self.from, self.to, self.kind)
}
}
#[cfg(test)]
mod tests {
use std::cmp::Ordering;
use std::collections::BTreeSet;
use std::str::FromStr;
use haz_domain::name::{ProjectName, TaskName};
use haz_domain::task_id::TaskId;
use crate::edge::{Edge, EdgeKind};
fn id(project: &str, task: &str) -> TaskId {
TaskId {
project: ProjectName::from_str(project).unwrap(),
task: TaskName::from_str(task).unwrap(),
}
}
fn edge(from: TaskId, to: TaskId, kind: EdgeKind) -> Edge {
Edge { from, to, kind }
}
#[test]
fn edge_kind_display() {
assert_eq!(EdgeKind::Hard.to_string(), "hard");
assert_eq!(EdgeKind::Soft.to_string(), "soft");
assert_eq!(EdgeKind::ProducerMatching.to_string(), "producer-matching");
}
#[test]
fn edge_display_renders_from_arrow_to_paren_kind() {
let e = edge(id("a", "b"), id("c", "d"), EdgeKind::Hard);
assert_eq!(e.to_string(), "a:b -> c:d (hard)");
}
#[test]
fn edge_equality_is_structural() {
let e1 = edge(id("a", "x"), id("b", "y"), EdgeKind::Hard);
let e2 = edge(id("a", "x"), id("b", "y"), EdgeKind::Hard);
let e3 = edge(id("a", "x"), id("b", "y"), EdgeKind::Soft);
let e4 = edge(id("a", "x"), id("b", "z"), EdgeKind::Hard);
assert_eq!(e1, e2);
assert_ne!(e1, e3, "different kind => different edge");
assert_ne!(e1, e4, "different endpoints => different edge");
}
#[test]
fn edge_ord_is_from_then_to_then_kind() {
let lower_from = edge(id("a", "z"), id("z", "z"), EdgeKind::Hard);
let higher_from = edge(id("b", "a"), id("a", "a"), EdgeKind::Hard);
assert_eq!(
lower_from.cmp(&higher_from),
Ordering::Less,
"from decides first"
);
let lower_to = edge(id("a", "a"), id("a", "x"), EdgeKind::Hard);
let higher_to = edge(id("a", "a"), id("a", "y"), EdgeKind::Hard);
assert_eq!(lower_to.cmp(&higher_to), Ordering::Less, "to decides next");
let hard = edge(id("a", "a"), id("b", "b"), EdgeKind::Hard);
let soft = edge(id("a", "a"), id("b", "b"), EdgeKind::Soft);
let producer = edge(id("a", "a"), id("b", "b"), EdgeKind::ProducerMatching);
assert_eq!(hard.cmp(&soft), Ordering::Less);
assert_eq!(soft.cmp(&producer), Ordering::Less);
}
#[test]
fn self_edge_is_representable_for_producer_matching() {
let node = id("formatter", "format");
let self_edge = edge(node.clone(), node, EdgeKind::ProducerMatching);
assert_eq!(self_edge.from, self_edge.to);
}
#[test]
fn self_edge_is_representable_for_hard_and_soft_too() {
let node = id("p", "t");
let _ = edge(node.clone(), node.clone(), EdgeKind::Hard);
let _ = edge(node.clone(), node, EdgeKind::Soft);
}
#[test]
fn btreeset_iterates_in_canonical_order() {
let mut set = BTreeSet::new();
set.insert(edge(id("b", "y"), id("c", "z"), EdgeKind::Hard));
set.insert(edge(id("a", "x"), id("b", "y"), EdgeKind::Hard));
set.insert(edge(id("a", "x"), id("b", "y"), EdgeKind::Soft));
set.insert(edge(id("a", "x"), id("b", "y"), EdgeKind::ProducerMatching));
let collected: Vec<_> = set.iter().map(ToString::to_string).collect();
assert_eq!(
collected,
vec![
"a:x -> b:y (hard)",
"a:x -> b:y (soft)",
"a:x -> b:y (producer-matching)",
"b:y -> c:z (hard)",
],
);
}
#[test]
fn clone_preserves_value() {
let original = edge(id("a", "b"), id("c", "d"), EdgeKind::Soft);
let cloned = original.clone();
assert_eq!(original, cloned);
}
}