#![allow(clippy::print_stdout, clippy::unwrap_used, clippy::expect_used)]
use cuenv_task_graph::{TaskGraph, TaskNodeData};
use std::collections::HashSet;
#[derive(Clone, Debug)]
struct StressTask {
deps: Vec<String>,
}
impl TaskNodeData for StressTask {
fn dependency_names(&self) -> impl Iterator<Item = &str> {
self.deps.iter().map(String::as_str)
}
fn add_dependency(&mut self, dep: String) {
if !self.deps.contains(&dep) {
self.deps.push(dep);
}
}
}
impl StressTask {
fn new(deps: &[&str]) -> Self {
Self {
deps: deps.iter().map(|s| (*s).to_string()).collect(),
}
}
fn with_deps(deps: Vec<String>) -> Self {
Self { deps }
}
}
#[test]
#[ignore = "stress test - run explicitly with --ignored"]
fn test_100_parallel_tasks() {
let mut graph = TaskGraph::new();
for i in 0..100 {
let task = StressTask::new(&[]);
graph
.add_task(&format!("parallel_task_{i}"), task)
.expect("Failed to add task");
}
graph
.add_dependency_edges()
.expect("Failed to add dependency edges");
assert!(!graph.has_cycles(), "Graph should not have cycles");
let groups = graph
.get_parallel_groups()
.expect("Failed to get parallel groups");
assert_eq!(groups.len(), 1, "Should have exactly 1 parallel group");
assert_eq!(
groups[0].len(),
100,
"All 100 tasks should be in the first group"
);
let task_names: HashSet<String> = groups[0].iter().map(|n| n.name.clone()).collect();
for i in 0..100 {
assert!(
task_names.contains(&format!("parallel_task_{i}")),
"Missing task parallel_task_{i}"
);
}
let sorted = graph
.topological_sort()
.expect("Topological sort should succeed");
assert_eq!(sorted.len(), 100, "Should have 100 tasks in sorted order");
}
#[test]
#[ignore = "stress test - run explicitly with --ignored"]
fn test_deep_dependency_chain_20_levels() {
const DEPTH: usize = 20;
let mut graph = TaskGraph::new();
graph
.add_task("chain_task_0", StressTask::new(&[]))
.expect("Failed to add first task");
for i in 1..DEPTH {
let dep = format!("chain_task_{}", i - 1);
let task = StressTask::with_deps(vec![dep]);
graph
.add_task(&format!("chain_task_{i}"), task)
.expect("Failed to add task");
}
graph
.add_dependency_edges()
.expect("Failed to add dependency edges");
assert!(!graph.has_cycles(), "Graph should not have cycles");
let groups = graph
.get_parallel_groups()
.expect("Failed to get parallel groups");
assert_eq!(
groups.len(),
DEPTH,
"Should have {DEPTH} levels (one task per level)"
);
for (level, group) in groups.iter().enumerate() {
assert_eq!(group.len(), 1, "Level {level} should have exactly 1 task");
}
let sorted = graph
.topological_sort()
.expect("Topological sort should succeed");
assert_eq!(sorted.len(), DEPTH, "Should have {DEPTH} tasks");
let positions: std::collections::HashMap<String, usize> = sorted
.iter()
.enumerate()
.map(|(i, node)| (node.name.clone(), i))
.collect();
for i in 1..DEPTH {
let current = format!("chain_task_{i}");
let previous = format!("chain_task_{}", i - 1);
assert!(
positions[&previous] < positions[¤t],
"Task {} should come before {}",
previous,
current
);
}
}
#[test]
#[ignore = "stress test - run explicitly with --ignored"]
fn test_large_task_graph_1000_tasks() {
const TOTAL_TASKS: usize = 1000;
const FAN_WIDTH: usize = 10;
let mut graph = TaskGraph::new();
graph
.add_task("root", StressTask::new(&[]))
.expect("Failed to add root task");
let mut last_level_tasks: Vec<String> = vec!["root".to_string()];
let mut task_count = 1;
let num_levels = (TOTAL_TASKS - 2) / FAN_WIDTH;
for level in 0..num_levels {
let mut current_level_tasks = Vec::new();
for i in 0..FAN_WIDTH {
let task_name = format!("level_{level}_task_{i}");
let task = StressTask::with_deps(last_level_tasks.clone());
graph
.add_task(&task_name, task)
.expect("Failed to add task");
current_level_tasks.push(task_name);
task_count += 1;
if task_count >= TOTAL_TASKS - 1 {
break;
}
}
if task_count >= TOTAL_TASKS - 1 {
last_level_tasks = current_level_tasks;
break;
}
last_level_tasks = current_level_tasks;
}
let final_task = StressTask::with_deps(last_level_tasks);
graph
.add_task("final", final_task)
.expect("Failed to add final task");
graph
.add_dependency_edges()
.expect("Failed to add dependency edges");
assert!(!graph.has_cycles(), "Graph should not have cycles");
let actual_task_count = graph.task_count();
assert!(
actual_task_count >= 100,
"Should have at least 100 tasks, got {actual_task_count}"
);
let groups = graph
.get_parallel_groups()
.expect("Failed to get parallel groups");
assert!(
!groups.is_empty(),
"Should have at least one parallel group"
);
assert_eq!(groups[0].len(), 1, "First group should have only root");
assert_eq!(groups[0][0].name, "root", "First task should be root");
let last_group = groups.last().expect("Should have groups");
assert_eq!(
last_group.len(),
1,
"Last group should have only final task"
);
assert_eq!(last_group[0].name, "final", "Last task should be final");
let sorted = graph
.topological_sort()
.expect("Topological sort should succeed");
assert_eq!(
sorted.len(),
actual_task_count,
"Sorted list should contain all tasks"
);
assert_eq!(sorted.first().map(|n| &n.name), Some(&"root".to_string()));
assert_eq!(sorted.last().map(|n| &n.name), Some(&"final".to_string()));
}
#[test]
#[ignore = "stress test - run explicitly with --ignored"]
fn test_wide_graph_500_leaves() {
const LEAF_COUNT: usize = 500;
let mut graph = TaskGraph::new();
graph
.add_task("root", StressTask::new(&[]))
.expect("Failed to add root task");
for i in 0..LEAF_COUNT {
let task = StressTask::new(&["root"]);
graph
.add_task(&format!("leaf_{i}"), task)
.expect("Failed to add leaf task");
}
graph
.add_dependency_edges()
.expect("Failed to add dependency edges");
assert!(!graph.has_cycles());
let groups = graph
.get_parallel_groups()
.expect("Failed to get parallel groups");
assert_eq!(groups.len(), 2, "Should have 2 levels");
assert_eq!(groups[0].len(), 1, "First level should have 1 task (root)");
assert_eq!(
groups[1].len(),
LEAF_COUNT,
"Second level should have all {LEAF_COUNT} leaves"
);
}
#[test]
#[ignore = "stress test - run explicitly with --ignored"]
fn test_diamond_pattern_chain() {
const DIAMONDS: usize = 50;
let mut graph = TaskGraph::new();
let mut prev_bottom: Option<String> = None;
for d in 0..DIAMONDS {
let top_name = if let Some(ref prev) = prev_bottom {
prev.clone()
} else {
let name = format!("diamond_{d}_top");
graph
.add_task(&name, StressTask::new(&[]))
.expect("Failed to add top");
name
};
let left_name = format!("diamond_{d}_left");
let right_name = format!("diamond_{d}_right");
let bottom_name = format!("diamond_{d}_bottom");
graph
.add_task(&left_name, StressTask::new(&[&top_name]))
.expect("Failed to add left");
graph
.add_task(&right_name, StressTask::new(&[&top_name]))
.expect("Failed to add right");
graph
.add_task(&bottom_name, StressTask::new(&[&left_name, &right_name]))
.expect("Failed to add bottom");
prev_bottom = Some(bottom_name);
}
graph
.add_dependency_edges()
.expect("Failed to add dependency edges");
assert!(!graph.has_cycles());
let groups = graph
.get_parallel_groups()
.expect("Failed to get parallel groups");
let expected_levels = 1 + 2 * DIAMONDS;
assert_eq!(
groups.len(),
expected_levels,
"Should have {expected_levels} levels"
);
let sorted = graph
.topological_sort()
.expect("Topological sort should succeed");
let expected_tasks = 1 + 3 * DIAMONDS;
assert_eq!(
sorted.len(),
expected_tasks,
"Should have {expected_tasks} tasks"
);
}