1use super::{ParallelGroup, Task, TaskDefinition, TaskGroup, Tasks};
7use crate::Result;
8use petgraph::algo::{is_cyclic_directed, toposort};
9use petgraph::graph::{DiGraph, NodeIndex};
10use petgraph::visit::IntoNodeReferences;
11use std::collections::{HashMap, HashSet};
12use tracing::debug;
13
14#[derive(Debug, Clone)]
16pub struct TaskNode {
17 pub name: String,
19 pub task: Task,
21}
22
23pub struct TaskGraph {
25 graph: DiGraph<TaskNode, ()>,
27 name_to_node: HashMap<String, NodeIndex>,
29 group_children: HashMap<String, Vec<String>>,
31}
32
33impl TaskGraph {
34 pub fn new() -> Self {
36 Self {
37 graph: DiGraph::new(),
38 name_to_node: HashMap::new(),
39 group_children: HashMap::new(),
40 }
41 }
42
43 pub fn build_from_definition(
45 &mut self,
46 name: &str,
47 definition: &TaskDefinition,
48 all_tasks: &Tasks,
49 ) -> Result<Vec<NodeIndex>> {
50 match definition {
51 TaskDefinition::Single(task) => {
52 let node = self.add_task(name, task.as_ref().clone())?;
53 Ok(vec![node])
54 }
55 TaskDefinition::Group(group) => self.build_from_group(name, group, all_tasks),
56 }
57 }
58
59 fn build_from_group(
61 &mut self,
62 prefix: &str,
63 group: &TaskGroup,
64 all_tasks: &Tasks,
65 ) -> Result<Vec<NodeIndex>> {
66 match group {
67 TaskGroup::Sequential(tasks) => self.build_sequential_group(prefix, tasks, all_tasks),
68 TaskGroup::Parallel(group) => self.build_parallel_group(prefix, group, all_tasks),
69 }
70 }
71
72 fn build_sequential_group(
74 &mut self,
75 prefix: &str,
76 tasks: &[TaskDefinition],
77 all_tasks: &Tasks,
78 ) -> Result<Vec<NodeIndex>> {
79 let mut nodes = Vec::new();
80 let mut previous: Option<NodeIndex> = None;
81
82 let child_names: Vec<String> = (0..tasks.len())
84 .map(|i| format!("{}[{}]", prefix, i))
85 .collect();
86
87 for (i, task_def) in tasks.iter().enumerate() {
88 let task_name = format!("{}[{}]", prefix, i);
89 let task_nodes = self.build_from_definition(&task_name, task_def, all_tasks)?;
90
91 if let Some(prev) = previous
93 && let Some(first) = task_nodes.first()
94 {
95 self.graph.add_edge(prev, *first, ());
96 }
97
98 if let Some(last) = task_nodes.last() {
99 previous = Some(*last);
100 }
101
102 nodes.extend(task_nodes);
103 }
104
105 if !child_names.is_empty() {
107 self.group_children.insert(prefix.to_string(), child_names);
108 }
109
110 Ok(nodes)
111 }
112
113 fn build_parallel_group(
115 &mut self,
116 prefix: &str,
117 group: &ParallelGroup,
118 all_tasks: &Tasks,
119 ) -> Result<Vec<NodeIndex>> {
120 let mut nodes = Vec::new();
121
122 let child_names: Vec<String> = group
124 .tasks
125 .keys()
126 .map(|name| format!("{}.{}", prefix, name))
127 .collect();
128
129 for (name, task_def) in &group.tasks {
130 let task_name = format!("{}.{}", prefix, name);
131 let task_nodes = self.build_from_definition(&task_name, task_def, all_tasks)?;
132
133 if !group.depends_on.is_empty() {
135 for node_idx in &task_nodes {
136 let node = &mut self.graph[*node_idx];
137 for dep in &group.depends_on {
138 if !node.task.depends_on.contains(dep) {
139 node.task.depends_on.push(dep.clone());
140 }
141 }
142 }
143 }
144
145 nodes.extend(task_nodes);
146 }
147
148 if !child_names.is_empty() {
150 self.group_children.insert(prefix.to_string(), child_names);
151 }
152
153 Ok(nodes)
154 }
155
156 pub fn add_task(&mut self, name: &str, task: Task) -> Result<NodeIndex> {
158 if let Some(&node) = self.name_to_node.get(name) {
160 return Ok(node);
161 }
162
163 let node = TaskNode {
164 name: name.to_string(),
165 task,
166 };
167
168 let node_index = self.graph.add_node(node);
169 self.name_to_node.insert(name.to_string(), node_index);
170 debug!("Added task node '{}'", name);
171
172 Ok(node_index)
173 }
174
175 fn expand_dep_to_leaf_tasks(&self, dep_name: &str) -> Vec<String> {
180 if self.name_to_node.contains_key(dep_name) {
181 vec![dep_name.to_string()]
183 } else if let Some(children) = self.group_children.get(dep_name) {
184 children
186 .iter()
187 .flat_map(|child| self.expand_dep_to_leaf_tasks(child))
188 .collect()
189 } else {
190 vec![dep_name.to_string()]
192 }
193 }
194
195 fn add_dependency_edges(&mut self) -> Result<()> {
198 let mut missing_deps = Vec::new();
199 let mut edges_to_add = Vec::new();
200
201 for (node_index, node) in self.graph.node_references() {
203 for dep_name in &node.task.depends_on {
204 let expanded_deps = self.expand_dep_to_leaf_tasks(dep_name);
206
207 for expanded_dep in expanded_deps {
208 if let Some(&dep_node_index) = self.name_to_node.get(&expanded_dep) {
209 edges_to_add.push((dep_node_index, node_index));
211 } else {
212 missing_deps.push((node.name.clone(), expanded_dep));
213 }
214 }
215 }
216 }
217
218 if !missing_deps.is_empty() {
220 let missing_list = missing_deps
221 .iter()
222 .map(|(task, dep)| format!("Task '{}' depends on missing task '{}'", task, dep))
223 .collect::<Vec<_>>()
224 .join(", ");
225 return Err(crate::Error::configuration(format!(
226 "Missing dependencies: {}",
227 missing_list
228 )));
229 }
230
231 for (from, to) in edges_to_add {
233 self.graph.add_edge(from, to, ());
234 }
235
236 Ok(())
237 }
238
239 pub fn has_cycles(&self) -> bool {
241 is_cyclic_directed(&self.graph)
242 }
243
244 pub fn topological_sort(&self) -> Result<Vec<TaskNode>> {
246 if self.has_cycles() {
247 return Err(crate::Error::configuration(
248 "Task dependency graph contains cycles".to_string(),
249 ));
250 }
251
252 match toposort(&self.graph, None) {
253 Ok(sorted_indices) => Ok(sorted_indices
254 .into_iter()
255 .map(|idx| self.graph[idx].clone())
256 .collect()),
257 Err(_) => Err(crate::Error::configuration(
258 "Failed to sort tasks topologically".to_string(),
259 )),
260 }
261 }
262
263 pub fn get_parallel_groups(&self) -> Result<Vec<Vec<TaskNode>>> {
265 let sorted = self.topological_sort()?;
266
267 if sorted.is_empty() {
268 return Ok(vec![]);
269 }
270
271 let mut groups: Vec<Vec<TaskNode>> = vec![];
273 let mut processed: HashMap<String, usize> = HashMap::new();
274
275 for task in sorted {
276 let mut level = 0;
278 for dep in &task.task.depends_on {
279 if let Some(&dep_level) = processed.get(dep) {
280 level = level.max(dep_level + 1);
281 }
282 }
283
284 if level >= groups.len() {
286 groups.resize(level + 1, vec![]);
287 }
288 groups[level].push(task.clone());
289 processed.insert(task.name.clone(), level);
290 }
291
292 Ok(groups)
293 }
294
295 pub fn task_count(&self) -> usize {
297 self.graph.node_count()
298 }
299
300 pub fn contains_task(&self, name: &str) -> bool {
302 self.name_to_node.contains_key(name)
303 }
304
305 pub fn build_complete_graph(&mut self, tasks: &Tasks) -> Result<()> {
308 for (name, definition) in tasks.tasks.iter() {
310 match definition {
311 TaskDefinition::Single(task) => {
312 self.add_task(name, task.as_ref().clone())?;
313 }
314 TaskDefinition::Group(_) => {
315 }
319 }
320 }
321
322 self.add_dependency_edges()?;
324
325 Ok(())
326 }
327
328 pub fn build_for_task(&mut self, task_name: &str, all_tasks: &Tasks) -> Result<()> {
330 let mut to_process = vec![task_name.to_string()];
331 let mut processed = HashSet::new();
332
333 debug!(
334 "Building graph for '{}' with tasks {:?}",
335 task_name,
336 all_tasks.list_tasks()
337 );
338
339 while let Some(current_name) = to_process.pop() {
341 if processed.contains(¤t_name) {
342 continue;
343 }
344 processed.insert(current_name.clone());
345
346 if let Some(definition) = all_tasks.get(¤t_name) {
347 match definition {
348 TaskDefinition::Single(task) => {
349 self.add_task(¤t_name, task.as_ref().clone())?;
350 for dep in &task.depends_on {
352 if !processed.contains(dep) {
353 to_process.push(dep.clone());
354 }
355 }
356 }
357 TaskDefinition::Group(_) => {
358 let added_nodes =
360 self.build_from_definition(¤t_name, definition, all_tasks)?;
361 for node_idx in added_nodes {
363 let node = &self.graph[node_idx];
364 for dep in &node.task.depends_on {
365 if !processed.contains(dep) {
366 to_process.push(dep.clone());
367 }
368 }
369 }
370 }
371 }
372 } else {
373 debug!("Task '{}' not found while building graph", current_name);
374 }
375 }
376
377 self.add_dependency_edges()?;
379
380 Ok(())
381 }
382}
383
384impl Default for TaskGraph {
385 fn default() -> Self {
386 Self::new()
387 }
388}
389
390#[cfg(test)]
391#[path = "graph_advanced_tests.rs"]
392mod graph_advanced_tests;
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397 use crate::test_utils::create_task;
398
399 #[test]
400 fn test_task_graph_new() {
401 let graph = TaskGraph::new();
402 assert_eq!(graph.task_count(), 0);
403 }
404
405 #[test]
406 fn test_add_single_task() {
407 let mut graph = TaskGraph::new();
408 let task = create_task("test", vec![], vec![]);
409
410 let node = graph.add_task("test", task).unwrap();
411 assert!(graph.contains_task("test"));
412 assert_eq!(graph.task_count(), 1);
413
414 let task2 = create_task("test", vec![], vec![]);
416 let node2 = graph.add_task("test", task2).unwrap();
417 assert_eq!(node, node2);
418 assert_eq!(graph.task_count(), 1);
419 }
420
421 #[test]
422 fn test_task_dependencies() {
423 let mut graph = TaskGraph::new();
424
425 let task1 = create_task("task1", vec![], vec![]);
427 let task2 = create_task("task2", vec!["task1"], vec![]);
428 let task3 = create_task("task3", vec!["task1", "task2"], vec![]);
429
430 graph.add_task("task1", task1).unwrap();
431 graph.add_task("task2", task2).unwrap();
432 graph.add_task("task3", task3).unwrap();
433 graph.add_dependency_edges().unwrap(); assert_eq!(graph.task_count(), 3);
436 assert!(!graph.has_cycles());
437
438 let sorted = graph.topological_sort().unwrap();
439 assert_eq!(sorted.len(), 3);
440
441 let positions: HashMap<String, usize> = sorted
443 .iter()
444 .enumerate()
445 .map(|(i, node)| (node.name.clone(), i))
446 .collect();
447
448 assert!(positions["task1"] < positions["task2"]);
449 assert!(positions["task1"] < positions["task3"]);
450 assert!(positions["task2"] < positions["task3"]);
451 }
452
453 #[test]
454 fn test_cycle_detection() {
455 let mut graph = TaskGraph::new();
456
457 let task1 = create_task("task1", vec!["task3"], vec![]);
459 let task2 = create_task("task2", vec!["task1"], vec![]);
460 let task3 = create_task("task3", vec!["task2"], vec![]);
461
462 graph.add_task("task1", task1).unwrap();
463 graph.add_task("task2", task2).unwrap();
464 graph.add_task("task3", task3).unwrap();
465 graph.add_dependency_edges().unwrap(); assert!(graph.has_cycles());
468 assert!(graph.topological_sort().is_err());
469 }
470
471 #[test]
472 fn test_parallel_groups() {
473 let mut graph = TaskGraph::new();
474
475 let task1 = create_task("task1", vec![], vec![]);
481 let task2 = create_task("task2", vec![], vec![]);
482 let task3 = create_task("task3", vec!["task1"], vec![]);
483 let task4 = create_task("task4", vec!["task2"], vec![]);
484 let task5 = create_task("task5", vec!["task3", "task4"], vec![]);
485
486 graph.add_task("task1", task1).unwrap();
487 graph.add_task("task2", task2).unwrap();
488 graph.add_task("task3", task3).unwrap();
489 graph.add_task("task4", task4).unwrap();
490 graph.add_task("task5", task5).unwrap();
491 graph.add_dependency_edges().unwrap(); let groups = graph.get_parallel_groups().unwrap();
494
495 assert_eq!(groups.len(), 3);
497
498 assert_eq!(groups[0].len(), 2);
500
501 assert_eq!(groups[1].len(), 2);
503
504 assert_eq!(groups[2].len(), 1);
506 assert_eq!(groups[2][0].name, "task5");
507 }
508
509 #[test]
510 fn test_build_from_sequential_group() {
511 let mut graph = TaskGraph::new();
512 let tasks = Tasks::new();
513
514 let task1 = create_task("t1", vec![], vec![]);
515 let task2 = create_task("t2", vec![], vec![]);
516
517 let group = TaskGroup::Sequential(vec![
518 TaskDefinition::Single(Box::new(task1)),
519 TaskDefinition::Single(Box::new(task2)),
520 ]);
521
522 let nodes = graph.build_from_group("seq", &group, &tasks).unwrap();
523 assert_eq!(nodes.len(), 2);
524
525 let sorted = graph.topological_sort().unwrap();
527 assert_eq!(sorted.len(), 2);
528 assert_eq!(sorted[0].name, "seq[0]");
529 assert_eq!(sorted[1].name, "seq[1]");
530 }
531
532 #[test]
533 fn test_build_from_parallel_group() {
534 let mut graph = TaskGraph::new();
535 let tasks = Tasks::new();
536
537 let task1 = create_task("t1", vec![], vec![]);
538 let task2 = create_task("t2", vec![], vec![]);
539
540 let mut parallel_tasks = HashMap::new();
541 parallel_tasks.insert("first".to_string(), TaskDefinition::Single(Box::new(task1)));
542 parallel_tasks.insert(
543 "second".to_string(),
544 TaskDefinition::Single(Box::new(task2)),
545 );
546
547 let group = TaskGroup::Parallel(ParallelGroup {
548 tasks: parallel_tasks,
549 depends_on: vec![],
550 });
551
552 let nodes = graph.build_from_group("par", &group, &tasks).unwrap();
553 assert_eq!(nodes.len(), 2);
554
555 assert!(!graph.has_cycles());
557
558 let groups = graph.get_parallel_groups().unwrap();
559 assert_eq!(groups.len(), 1); assert_eq!(groups[0].len(), 2); }
562
563 #[test]
564 fn test_three_way_cycle_detection() {
565 let mut graph = TaskGraph::new();
566
567 let task_a = create_task("task_a", vec!["task_c"], vec![]);
569 let task_b = create_task("task_b", vec!["task_a"], vec![]);
570 let task_c = create_task("task_c", vec!["task_b"], vec![]);
571
572 graph.add_task("task_a", task_a).unwrap();
573 graph.add_task("task_b", task_b).unwrap();
574 graph.add_task("task_c", task_c).unwrap();
575 graph.add_dependency_edges().unwrap(); assert!(graph.has_cycles());
579
580 assert!(graph.get_parallel_groups().is_err());
582 }
583
584 #[test]
585 fn test_self_dependency_cycle() {
586 let mut graph = TaskGraph::new();
587
588 let task = create_task("self_ref", vec!["self_ref"], vec![]);
590 graph.add_task("self_ref", task).unwrap();
591 graph.add_dependency_edges().unwrap(); assert!(graph.has_cycles());
594 assert!(graph.get_parallel_groups().is_err());
595 }
596
597 #[test]
598 fn test_complex_dependency_graph() {
599 let mut graph = TaskGraph::new();
600
601 let task_a = create_task("a", vec![], vec![]);
608 let task_b = create_task("b", vec!["a"], vec![]);
609 let task_c = create_task("c", vec!["a"], vec![]);
610 let task_d = create_task("d", vec!["b", "c"], vec![]);
611
612 graph.add_task("a", task_a).unwrap();
613 graph.add_task("b", task_b).unwrap();
614 graph.add_task("c", task_c).unwrap();
615 graph.add_task("d", task_d).unwrap();
616 graph.add_dependency_edges().unwrap(); assert!(!graph.has_cycles());
619 assert_eq!(graph.task_count(), 4);
620
621 let groups = graph.get_parallel_groups().unwrap();
622
623 assert_eq!(groups.len(), 3);
625 assert_eq!(groups[0].len(), 1); assert_eq!(groups[1].len(), 2); assert_eq!(groups[2].len(), 1); }
629
630 #[test]
631 fn test_missing_dependency() {
632 let mut graph = TaskGraph::new();
633
634 let task = create_task("dependent", vec!["missing"], vec![]);
636 graph.add_task("dependent", task).unwrap();
637
638 assert!(graph.add_dependency_edges().is_err());
640 }
641
642 #[test]
643 fn test_empty_graph() {
644 let graph = TaskGraph::new();
645
646 assert_eq!(graph.task_count(), 0);
647 assert!(!graph.has_cycles());
648
649 let groups = graph.get_parallel_groups().unwrap();
650 assert!(groups.is_empty());
651 }
652
653 #[test]
654 fn test_single_task_no_deps() {
655 let mut graph = TaskGraph::new();
656
657 let task = create_task("solo", vec![], vec![]);
658 graph.add_task("solo", task).unwrap();
659
660 assert_eq!(graph.task_count(), 1);
661 assert!(!graph.has_cycles());
662
663 let groups = graph.get_parallel_groups().unwrap();
664 assert_eq!(groups.len(), 1);
665 assert_eq!(groups[0].len(), 1);
666 }
667
668 #[test]
669 fn test_linear_chain() {
670 let mut graph = TaskGraph::new();
671
672 let task_a = create_task("a", vec![], vec![]);
674 let task_b = create_task("b", vec!["a"], vec![]);
675 let task_c = create_task("c", vec!["b"], vec![]);
676 let task_d = create_task("d", vec!["c"], vec![]);
677
678 graph.add_task("a", task_a).unwrap();
679 graph.add_task("b", task_b).unwrap();
680 graph.add_task("c", task_c).unwrap();
681 graph.add_task("d", task_d).unwrap();
682 graph.add_dependency_edges().unwrap(); assert!(!graph.has_cycles());
685 assert_eq!(graph.task_count(), 4);
686
687 let groups = graph.get_parallel_groups().unwrap();
688
689 assert_eq!(groups.len(), 4);
691 for group in &groups {
692 assert_eq!(group.len(), 1);
693 }
694 }
695
696 #[test]
697 fn test_group_as_dependency_parallel() {
698 let mut graph = TaskGraph::new();
699 let tasks = Tasks::new();
700
701 let deps_task = create_task("deps", vec![], vec![]);
703 let compile_task = create_task("compile", vec![], vec![]);
704
705 let mut parallel_tasks = HashMap::new();
706 parallel_tasks.insert(
707 "deps".to_string(),
708 TaskDefinition::Single(Box::new(deps_task)),
709 );
710 parallel_tasks.insert(
711 "compile".to_string(),
712 TaskDefinition::Single(Box::new(compile_task)),
713 );
714
715 let build_group = TaskGroup::Parallel(ParallelGroup {
716 tasks: parallel_tasks,
717 depends_on: vec![],
718 });
719
720 graph
722 .build_from_group("build", &build_group, &tasks)
723 .unwrap();
724
725 let test_task = create_task("test", vec!["build"], vec![]);
727 graph.add_task("test", test_task).unwrap();
728
729 graph.add_dependency_edges().unwrap();
731
732 assert!(!graph.has_cycles());
733 assert_eq!(graph.task_count(), 3);
734
735 let sorted = graph.topological_sort().unwrap();
737 let positions: HashMap<String, usize> = sorted
738 .iter()
739 .enumerate()
740 .map(|(i, node)| (node.name.clone(), i))
741 .collect();
742
743 assert!(positions["build.deps"] < positions["test"]);
744 assert!(positions["build.compile"] < positions["test"]);
745 }
746
747 #[test]
748 fn test_group_as_dependency_sequential() {
749 let mut graph = TaskGraph::new();
750 let tasks = Tasks::new();
751
752 let task1 = create_task("s1", vec![], vec![]);
754 let task2 = create_task("s2", vec![], vec![]);
755
756 let setup_group = TaskGroup::Sequential(vec![
757 TaskDefinition::Single(Box::new(task1)),
758 TaskDefinition::Single(Box::new(task2)),
759 ]);
760
761 graph
763 .build_from_group("setup", &setup_group, &tasks)
764 .unwrap();
765
766 let run_task = create_task("run", vec!["setup"], vec![]);
768 graph.add_task("run", run_task).unwrap();
769
770 graph.add_dependency_edges().unwrap();
772
773 assert!(!graph.has_cycles());
774 assert_eq!(graph.task_count(), 3);
775
776 let sorted = graph.topological_sort().unwrap();
778 let positions: HashMap<String, usize> = sorted
779 .iter()
780 .enumerate()
781 .map(|(i, node)| (node.name.clone(), i))
782 .collect();
783
784 assert!(positions["setup[0]"] < positions["run"]);
785 assert!(positions["setup[1]"] < positions["run"]);
786 }
787
788 #[test]
789 fn test_nested_group_as_dependency() {
790 let mut graph = TaskGraph::new();
791 let tasks = Tasks::new();
792
793 let frontend_t1 = create_task("fe1", vec![], vec![]);
801 let frontend_t2 = create_task("fe2", vec![], vec![]);
802 let frontend_group = TaskGroup::Sequential(vec![
803 TaskDefinition::Single(Box::new(frontend_t1)),
804 TaskDefinition::Single(Box::new(frontend_t2)),
805 ]);
806
807 let backend_task = create_task("be", vec![], vec![]);
808
809 let mut parallel_tasks = HashMap::new();
810 parallel_tasks.insert(
811 "frontend".to_string(),
812 TaskDefinition::Group(frontend_group),
813 );
814 parallel_tasks.insert(
815 "backend".to_string(),
816 TaskDefinition::Single(Box::new(backend_task)),
817 );
818
819 let build_group = TaskGroup::Parallel(ParallelGroup {
820 tasks: parallel_tasks,
821 depends_on: vec![],
822 });
823
824 graph
826 .build_from_group("build", &build_group, &tasks)
827 .unwrap();
828
829 let deploy_task = create_task("deploy", vec!["build"], vec![]);
831 graph.add_task("deploy", deploy_task).unwrap();
832
833 graph.add_dependency_edges().unwrap();
836
837 assert!(!graph.has_cycles());
838 assert_eq!(graph.task_count(), 4); let sorted = graph.topological_sort().unwrap();
842 let positions: HashMap<String, usize> = sorted
843 .iter()
844 .enumerate()
845 .map(|(i, node)| (node.name.clone(), i))
846 .collect();
847
848 assert!(positions["build.frontend[0]"] < positions["deploy"]);
849 assert!(positions["build.frontend[1]"] < positions["deploy"]);
850 assert!(positions["build.backend"] < positions["deploy"]);
851 }
852
853 #[test]
854 fn test_mixed_exact_and_group_dependencies() {
855 let mut graph = TaskGraph::new();
856 let tasks = Tasks::new();
857
858 let lint_task = create_task("lint", vec![], vec![]);
860 graph.add_task("lint", lint_task).unwrap();
861
862 let deps_task = create_task("deps", vec![], vec![]);
864 let compile_task = create_task("compile", vec![], vec![]);
865
866 let mut parallel_tasks = HashMap::new();
867 parallel_tasks.insert(
868 "deps".to_string(),
869 TaskDefinition::Single(Box::new(deps_task)),
870 );
871 parallel_tasks.insert(
872 "compile".to_string(),
873 TaskDefinition::Single(Box::new(compile_task)),
874 );
875
876 let build_group = TaskGroup::Parallel(ParallelGroup {
877 tasks: parallel_tasks,
878 depends_on: vec![],
879 });
880
881 graph
882 .build_from_group("build", &build_group, &tasks)
883 .unwrap();
884
885 let test_task = create_task("test", vec!["lint", "build"], vec![]);
887 graph.add_task("test", test_task).unwrap();
888
889 graph.add_dependency_edges().unwrap();
890
891 assert!(!graph.has_cycles());
892 assert_eq!(graph.task_count(), 4);
893
894 let sorted = graph.topological_sort().unwrap();
896 let positions: HashMap<String, usize> = sorted
897 .iter()
898 .enumerate()
899 .map(|(i, node)| (node.name.clone(), i))
900 .collect();
901
902 assert!(positions["lint"] < positions["test"]);
903 assert!(positions["build.deps"] < positions["test"]);
904 assert!(positions["build.compile"] < positions["test"]);
905 }
906
907 #[test]
908 fn test_cycle_with_group_expansion() {
909 let mut graph = TaskGraph::new();
910 let tasks = Tasks::new();
911
912 let test_task = create_task("test", vec!["setup"], vec![]);
917 graph.add_task("test", test_task).unwrap();
918
919 let task1 = create_task("s1", vec!["test"], vec![]);
921 let task2 = create_task("s2", vec![], vec![]);
922
923 let setup_group = TaskGroup::Sequential(vec![
924 TaskDefinition::Single(Box::new(task1)),
925 TaskDefinition::Single(Box::new(task2)),
926 ]);
927
928 graph
929 .build_from_group("setup", &setup_group, &tasks)
930 .unwrap();
931
932 graph.add_dependency_edges().unwrap();
933
934 assert!(graph.has_cycles());
936 assert!(graph.topological_sort().is_err());
937 }
938}