1use std::collections::BTreeSet;
16
17use haz_domain::task_id::TaskId;
18
19use crate::edge::Edge;
20
21#[derive(Debug, Clone, Default, PartialEq, Eq)]
54pub struct TaskGraph {
55 pub nodes: BTreeSet<TaskId>,
57 pub edges: BTreeSet<Edge>,
59}
60
61#[cfg(test)]
62mod tests {
63 use std::collections::BTreeSet;
64 use std::str::FromStr;
65
66 use haz_domain::name::{ProjectName, TaskName};
67 use haz_domain::task_id::TaskId;
68
69 use crate::edge::{Edge, EdgeKind};
70 use crate::graph::TaskGraph;
71
72 fn id(project: &str, task: &str) -> TaskId {
73 TaskId {
74 project: ProjectName::from_str(project).unwrap(),
75 task: TaskName::from_str(task).unwrap(),
76 }
77 }
78
79 fn edge(from: TaskId, to: TaskId, kind: EdgeKind) -> Edge {
80 Edge { from, to, kind }
81 }
82
83 #[test]
84 fn default_graph_is_empty() {
85 let graph = TaskGraph::default();
86 assert!(graph.nodes.is_empty());
87 assert!(graph.edges.is_empty());
88 }
89
90 #[test]
91 fn graph_with_isolated_nodes_no_edges() {
92 let graph = TaskGraph {
93 nodes: BTreeSet::from([id("p", "a"), id("p", "b"), id("q", "a")]),
94 edges: BTreeSet::new(),
95 };
96 assert_eq!(graph.nodes.len(), 3);
97 assert!(graph.edges.is_empty());
98 }
99
100 #[test]
101 fn graph_with_hard_chain_a_to_b_to_c() {
102 let a = id("p", "a");
103 let b = id("p", "b");
104 let c = id("p", "c");
105 let graph = TaskGraph {
106 nodes: BTreeSet::from([a.clone(), b.clone(), c.clone()]),
107 edges: BTreeSet::from([
108 edge(a.clone(), b.clone(), EdgeKind::Hard),
109 edge(b.clone(), c.clone(), EdgeKind::Hard),
110 ]),
111 };
112
113 assert_eq!(graph.nodes.len(), 3);
114 assert_eq!(graph.edges.len(), 2);
115
116 let hard: Vec<_> = graph
117 .edges
118 .iter()
119 .filter(|e| e.kind == EdgeKind::Hard)
120 .cloned()
121 .collect();
122 assert_eq!(hard.len(), 2);
123 assert!(hard.iter().any(|e| e.from == a && e.to == b));
124 assert!(hard.iter().any(|e| e.from == b && e.to == c));
125 }
126
127 #[test]
128 fn graph_admits_producer_matching_self_edge() {
129 let node = id("formatter", "format");
132 let graph = TaskGraph {
133 nodes: BTreeSet::from([node.clone()]),
134 edges: BTreeSet::from([edge(node.clone(), node.clone(), EdgeKind::ProducerMatching)]),
135 };
136 assert_eq!(graph.nodes.len(), 1);
137 assert_eq!(graph.edges.len(), 1);
138 let only = graph.edges.iter().next().unwrap();
139 assert_eq!(only.from, only.to);
140 assert_eq!(only.kind, EdgeKind::ProducerMatching);
141 }
142
143 #[test]
144 fn graph_clones_preserve_value() {
145 let a = id("p", "a");
146 let b = id("p", "b");
147 let original = TaskGraph {
148 nodes: BTreeSet::from([a.clone(), b.clone()]),
149 edges: BTreeSet::from([edge(a, b, EdgeKind::Soft)]),
150 };
151 let cloned = original.clone();
152 assert_eq!(original, cloned);
153 }
154}