use super::*;
use crate::tasks::{TaskDependency, TaskNode};
use crate::test_utils::{create_task, create_task_ref, create_task_with_project_ref};
#[test]
fn test_dag_with_task_ref_placeholder() {
let mut graph = TaskGraph::new();
let task1 = create_task("task1", vec![], vec![]);
let task2 = create_task_ref("#other-project:build", vec!["task1"]);
graph.add_task("task1", task1).unwrap();
graph.add_task("task2", task2).unwrap();
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), 2);
assert!(!graph.has_cycles());
let sorted = graph.topological_sort().unwrap();
let names: Vec<&str> = sorted.iter().map(|n| n.name.as_str()).collect();
assert_eq!(names, vec!["task1", "task2"]);
}
#[test]
fn test_dag_with_project_reference_input() {
let mut graph = TaskGraph::new();
let build_task = create_task("build", vec![], vec![]);
let deploy_task = create_task_with_project_ref(
"deploy",
vec!["build"],
"../other-project",
"compile",
vec![("dist/app.js", "vendor/app.js")],
);
graph.add_task("build", build_task).unwrap();
graph.add_task("deploy", deploy_task).unwrap();
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), 2);
assert!(!graph.has_cycles());
let sorted = graph.topological_sort().unwrap();
assert_eq!(sorted[0].name, "build");
assert_eq!(sorted[1].name, "deploy");
}
#[test]
fn test_dag_with_multiple_project_references() {
let mut graph = TaskGraph::new();
let task_a1 = create_task("projectA.build", vec![], vec![]);
let task_a2 = create_task("projectA.test", vec!["projectA.build"], vec![]);
let task_b1 = create_task("projectB.build", vec![], vec![]);
let task_b2 = create_task("projectB.test", vec!["projectB.build"], vec![]);
let integration = create_task(
"integration",
vec!["projectA.test", "projectB.test"],
vec![],
);
graph.add_task("projectA.build", task_a1).unwrap();
graph.add_task("projectA.test", task_a2).unwrap();
graph.add_task("projectB.build", task_b1).unwrap();
graph.add_task("projectB.test", task_b2).unwrap();
graph.add_task("integration", integration).unwrap();
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), 5);
assert!(!graph.has_cycles());
let groups = graph.get_parallel_groups().unwrap();
assert_eq!(groups.len(), 3);
assert_eq!(groups[0].len(), 2);
assert_eq!(groups[1].len(), 2);
assert_eq!(groups[2].len(), 1);
}
#[test]
fn test_synthetic_hook_task_naming() {
let mut graph = TaskGraph::new();
let hook_task1 = create_task("bun.hooks.beforeInstall[0]", vec![], vec![]);
let hook_task2 = create_task(
"bun.hooks.beforeInstall[1]",
vec!["bun.hooks.beforeInstall[0]"],
vec![],
);
let install_task = create_task("bun.install", vec!["bun.hooks.beforeInstall[1]"], vec![]);
let setup_task = create_task("bun.setup", vec!["bun.install"], vec![]);
graph
.add_task("bun.hooks.beforeInstall[0]", hook_task1)
.unwrap();
graph
.add_task("bun.hooks.beforeInstall[1]", hook_task2)
.unwrap();
graph.add_task("bun.install", install_task).unwrap();
graph.add_task("bun.setup", setup_task).unwrap();
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), 4);
assert!(!graph.has_cycles());
let sorted = graph.topological_sort().unwrap();
let names: Vec<&str> = sorted.iter().map(|n| n.name.as_str()).collect();
assert_eq!(
names,
vec![
"bun.hooks.beforeInstall[0]",
"bun.hooks.beforeInstall[1]",
"bun.install",
"bun.setup"
]
);
}
#[test]
fn test_synthetic_hook_with_named_matcher() {
let mut graph = TaskGraph::new();
let hook1 = create_task("bun.hooks.beforeInstall.projen[0]", vec![], vec!["projen"]);
let hook2 = create_task("bun.hooks.beforeInstall.projen[1]", vec![], vec!["projen"]);
let install = create_task(
"bun.install",
vec![
"bun.hooks.beforeInstall.projen[0]",
"bun.hooks.beforeInstall.projen[1]",
],
vec![],
);
graph
.add_task("bun.hooks.beforeInstall.projen[0]", hook1)
.unwrap();
graph
.add_task("bun.hooks.beforeInstall.projen[1]", hook2)
.unwrap();
graph.add_task("bun.install", install).unwrap();
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), 3);
assert!(!graph.has_cycles());
let groups = graph.get_parallel_groups().unwrap();
assert_eq!(groups.len(), 2);
assert_eq!(groups[0].len(), 2); assert_eq!(groups[1].len(), 1); }
#[test]
fn test_synthetic_hook_sequential_matcher() {
let mut graph = TaskGraph::new();
let hook1 = create_task("bun.hooks.beforeInstall.codegen[0]", vec![], vec![]);
let hook2 = create_task(
"bun.hooks.beforeInstall.codegen[1]",
vec!["bun.hooks.beforeInstall.codegen[0]"],
vec![],
);
let hook3 = create_task(
"bun.hooks.beforeInstall.codegen[2]",
vec!["bun.hooks.beforeInstall.codegen[1]"],
vec![],
);
let install = create_task(
"bun.install",
vec!["bun.hooks.beforeInstall.codegen[2]"],
vec![],
);
graph
.add_task("bun.hooks.beforeInstall.codegen[0]", hook1)
.unwrap();
graph
.add_task("bun.hooks.beforeInstall.codegen[1]", hook2)
.unwrap();
graph
.add_task("bun.hooks.beforeInstall.codegen[2]", hook3)
.unwrap();
graph.add_task("bun.install", install).unwrap();
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), 4);
assert!(!graph.has_cycles());
let groups = graph.get_parallel_groups().unwrap();
assert_eq!(groups.len(), 4);
for group in &groups {
assert_eq!(group.len(), 1);
}
}
#[test]
fn test_multiple_hook_steps_in_order() {
let mut graph = TaskGraph::new();
let step0 = create_task_ref("#projen-generator:types", vec![]);
let step1_a = create_task(
"bun.hooks.beforeInstall.projen[0]",
vec!["bun.hooks.beforeInstall[0]"],
vec![],
);
let step1_b = create_task(
"bun.hooks.beforeInstall.projen[1]",
vec!["bun.hooks.beforeInstall[0]"],
vec![],
);
let step2 = create_task(
"bun.hooks.beforeInstall[2]",
vec![
"bun.hooks.beforeInstall.projen[0]",
"bun.hooks.beforeInstall.projen[1]",
],
vec![],
);
let install = create_task(
"bun.install",
vec![
"bun.hooks.beforeInstall[0]",
"bun.hooks.beforeInstall.projen[0]",
"bun.hooks.beforeInstall.projen[1]",
"bun.hooks.beforeInstall[2]",
],
vec![],
);
graph.add_task("bun.hooks.beforeInstall[0]", step0).unwrap();
graph
.add_task("bun.hooks.beforeInstall.projen[0]", step1_a)
.unwrap();
graph
.add_task("bun.hooks.beforeInstall.projen[1]", step1_b)
.unwrap();
graph.add_task("bun.hooks.beforeInstall[2]", step2).unwrap();
graph.add_task("bun.install", install).unwrap();
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), 5);
assert!(!graph.has_cycles());
let sorted = graph.topological_sort().unwrap();
let names: Vec<&str> = sorted.iter().map(|n| n.name.as_str()).collect();
let pos_step0 = names
.iter()
.position(|&n| n == "bun.hooks.beforeInstall[0]")
.unwrap();
let pos_step1a = names
.iter()
.position(|&n| n == "bun.hooks.beforeInstall.projen[0]")
.unwrap();
let pos_step1b = names
.iter()
.position(|&n| n == "bun.hooks.beforeInstall.projen[1]")
.unwrap();
let pos_step2 = names
.iter()
.position(|&n| n == "bun.hooks.beforeInstall[2]")
.unwrap();
let pos_install = names.iter().position(|&n| n == "bun.install").unwrap();
assert!(pos_step0 < pos_step1a);
assert!(pos_step0 < pos_step1b);
assert!(pos_step1a < pos_step2);
assert!(pos_step1b < pos_step2);
assert!(pos_step2 < pos_install);
}
#[test]
fn test_workspace_setup_chain() {
let mut graph = TaskGraph::new();
let hook1 = create_task("bun.hooks.beforeInstall[0]", vec![], vec![]);
let install = create_task("bun.install", vec!["bun.hooks.beforeInstall[0]"], vec![]);
let setup = create_task("bun.setup", vec!["bun.install"], vec![]);
let user_task = create_task("dev", vec!["bun.setup"], vec!["bun"]);
graph.add_task("bun.hooks.beforeInstall[0]", hook1).unwrap();
graph.add_task("bun.install", install).unwrap();
graph.add_task("bun.setup", setup).unwrap();
graph.add_task("dev", user_task).unwrap();
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), 4);
assert!(!graph.has_cycles());
let sorted = graph.topological_sort().unwrap();
let names: Vec<&str> = sorted.iter().map(|n| n.name.as_str()).collect();
assert_eq!(
names,
vec![
"bun.hooks.beforeInstall[0]",
"bun.install",
"bun.setup",
"dev"
]
);
}
#[test]
fn test_multiple_workspaces_setup() {
let mut graph = TaskGraph::new();
let bun_install = create_task("bun.install", vec![], vec![]);
let bun_setup = create_task("bun.setup", vec!["bun.install"], vec![]);
let cargo_install = create_task("cargo.install", vec![], vec![]);
let cargo_setup = create_task("cargo.setup", vec!["cargo.install"], vec![]);
let build = create_task(
"build",
vec!["bun.setup", "cargo.setup"],
vec!["bun", "cargo"],
);
graph.add_task("bun.install", bun_install).unwrap();
graph.add_task("bun.setup", bun_setup).unwrap();
graph.add_task("cargo.install", cargo_install).unwrap();
graph.add_task("cargo.setup", cargo_setup).unwrap();
graph.add_task("build", build).unwrap();
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), 5);
assert!(!graph.has_cycles());
let groups = graph.get_parallel_groups().unwrap();
assert_eq!(groups.len(), 3);
assert_eq!(groups[0].len(), 2);
assert_eq!(groups[1].len(), 2);
assert_eq!(groups[2].len(), 1);
}
#[test]
fn test_hook_with_cross_project_task_ref() {
let mut graph = TaskGraph::new();
let external_install = create_task("projen-generator::install", vec![], vec![]);
let external_types = create_task(
"projen-generator::types",
vec!["projen-generator::install"],
vec![],
);
let hook = create_task(
"website.bun.hooks.beforeInstall[0]",
vec!["projen-generator::types"], vec![],
);
let install = create_task(
"website.bun.install",
vec!["website.bun.hooks.beforeInstall[0]"],
vec![],
);
graph
.add_task("projen-generator::install", external_install)
.unwrap();
graph
.add_task("projen-generator::types", external_types)
.unwrap();
graph
.add_task("website.bun.hooks.beforeInstall[0]", hook)
.unwrap();
graph.add_task("website.bun.install", install).unwrap();
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), 4);
assert!(!graph.has_cycles());
let sorted = graph.topological_sort().unwrap();
let names: Vec<&str> = sorted.iter().map(|n| n.name.as_str()).collect();
let pos_ext_install = names
.iter()
.position(|&n| n == "projen-generator::install")
.unwrap();
let pos_ext_types = names
.iter()
.position(|&n| n == "projen-generator::types")
.unwrap();
let pos_hook = names
.iter()
.position(|&n| n == "website.bun.hooks.beforeInstall[0]")
.unwrap();
let pos_install = names
.iter()
.position(|&n| n == "website.bun.install")
.unwrap();
assert!(pos_ext_install < pos_ext_types);
assert!(pos_ext_types < pos_hook);
assert!(pos_hook < pos_install);
}
#[test]
fn test_transitive_cross_project_dependencies() {
let mut graph = TaskGraph::new();
let c_build = create_task("projectC::build", vec![], vec![]);
let b_build = create_task("projectB::build", vec!["projectC::build"], vec![]);
let a_build = create_task("projectA::build", vec!["projectB::build"], vec![]);
let a_deploy = create_task("projectA::deploy", vec!["projectA::build"], vec![]);
graph.add_task("projectC::build", c_build).unwrap();
graph.add_task("projectB::build", b_build).unwrap();
graph.add_task("projectA::build", a_build).unwrap();
graph.add_task("projectA::deploy", a_deploy).unwrap();
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), 4);
assert!(!graph.has_cycles());
let sorted = graph.topological_sort().unwrap();
let names: Vec<&str> = sorted.iter().map(|n| n.name.as_str()).collect();
assert_eq!(
names,
vec![
"projectC::build",
"projectB::build",
"projectA::build",
"projectA::deploy"
]
);
}
#[test]
fn test_diamond_dependency_with_hooks() {
let mut graph = TaskGraph::new();
let hook = create_task("bun.hooks.beforeInstall[0]", vec![], vec![]);
let install = create_task("bun.install", vec!["bun.hooks.beforeInstall[0]"], vec![]);
let setup = create_task("bun.setup", vec!["bun.install"], vec![]);
let task_a = create_task("taskA", vec!["bun.setup"], vec![]);
let task_b = create_task("taskB", vec!["bun.setup"], vec![]);
let final_task = create_task("final", vec!["taskA", "taskB"], vec![]);
graph.add_task("bun.hooks.beforeInstall[0]", hook).unwrap();
graph.add_task("bun.install", install).unwrap();
graph.add_task("bun.setup", setup).unwrap();
graph.add_task("taskA", task_a).unwrap();
graph.add_task("taskB", task_b).unwrap();
graph.add_task("final", final_task).unwrap();
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), 6);
assert!(!graph.has_cycles());
let groups = graph.get_parallel_groups().unwrap();
assert_eq!(groups.len(), 5);
assert_eq!(groups[3].len(), 2); }
#[test]
fn test_multiple_projects_with_hooks() {
let mut graph = TaskGraph::new();
let a_hook = create_task("projectA::bun.hooks.beforeInstall[0]", vec![], vec![]);
let a_install = create_task(
"projectA::bun.install",
vec!["projectA::bun.hooks.beforeInstall[0]"],
vec![],
);
let b_hook = create_task("projectB::npm.hooks.beforeInstall[0]", vec![], vec![]);
let b_install = create_task(
"projectB::npm.install",
vec!["projectB::npm.hooks.beforeInstall[0]"],
vec![],
);
let integration = create_task(
"integration",
vec!["projectA::bun.install", "projectB::npm.install"],
vec![],
);
graph
.add_task("projectA::bun.hooks.beforeInstall[0]", a_hook)
.unwrap();
graph.add_task("projectA::bun.install", a_install).unwrap();
graph
.add_task("projectB::npm.hooks.beforeInstall[0]", b_hook)
.unwrap();
graph.add_task("projectB::npm.install", b_install).unwrap();
graph.add_task("integration", integration).unwrap();
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), 5);
assert!(!graph.has_cycles());
let groups = graph.get_parallel_groups().unwrap();
assert_eq!(groups.len(), 3);
assert_eq!(groups[0].len(), 2);
assert_eq!(groups[1].len(), 2);
assert_eq!(groups[2].len(), 1);
}
#[test]
fn test_hook_with_afterinstall() {
let mut graph = TaskGraph::new();
let before_hook = create_task("bun.hooks.beforeInstall[0]", vec![], vec![]);
let install = create_task("bun.install", vec!["bun.hooks.beforeInstall[0]"], vec![]);
let after_hook = create_task("bun.hooks.afterInstall[0]", vec!["bun.install"], vec![]);
let setup = create_task("bun.setup", vec!["bun.hooks.afterInstall[0]"], vec![]);
graph
.add_task("bun.hooks.beforeInstall[0]", before_hook)
.unwrap();
graph.add_task("bun.install", install).unwrap();
graph
.add_task("bun.hooks.afterInstall[0]", after_hook)
.unwrap();
graph.add_task("bun.setup", setup).unwrap();
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), 4);
assert!(!graph.has_cycles());
let sorted = graph.topological_sort().unwrap();
let names: Vec<&str> = sorted.iter().map(|n| n.name.as_str()).collect();
assert_eq!(
names,
vec![
"bun.hooks.beforeInstall[0]",
"bun.install",
"bun.hooks.afterInstall[0]",
"bun.setup"
]
);
}
#[test]
fn test_cross_project_cycle_detection() {
let mut graph = TaskGraph::new();
let a_build = create_task("projectA::build", vec!["projectB::build"], vec![]);
let b_build = create_task("projectB::build", vec!["projectA::build"], vec![]);
graph.add_task("projectA::build", a_build).unwrap();
graph.add_task("projectB::build", b_build).unwrap();
graph.add_dependency_edges().unwrap();
assert!(graph.has_cycles());
assert!(graph.topological_sort().is_err());
}
#[test]
fn test_missing_cross_project_dependency() {
let mut graph = TaskGraph::new();
let task = create_task("myTask", vec!["nonexistent-project::build"], vec![]);
graph.add_task("myTask", task).unwrap();
let result = graph.add_dependency_edges();
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("Missing dependencies"));
assert!(err_msg.contains("nonexistent-project::build"));
}
#[test]
fn test_self_referencing_hook() {
let mut graph = TaskGraph::new();
let hook = create_task(
"bun.hooks.beforeInstall[0]",
vec!["bun.hooks.beforeInstall[0]"],
vec![],
);
graph.add_task("bun.hooks.beforeInstall[0]", hook).unwrap();
graph.add_dependency_edges().unwrap();
assert!(graph.has_cycles());
}
#[test]
fn test_empty_hook_list() {
let mut graph = TaskGraph::new();
let install = create_task("bun.install", vec![], vec![]);
let setup = create_task("bun.setup", vec!["bun.install"], vec![]);
let user_task = create_task("dev", vec!["bun.setup"], vec!["bun"]);
graph.add_task("bun.install", install).unwrap();
graph.add_task("bun.setup", setup).unwrap();
graph.add_task("dev", user_task).unwrap();
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), 3);
assert!(!graph.has_cycles());
let sorted = graph.topological_sort().unwrap();
let names: Vec<&str> = sorted.iter().map(|n| n.name.as_str()).collect();
assert_eq!(names, vec!["bun.install", "bun.setup", "dev"]);
}
#[test]
fn test_tasks_with_labels() {
let mut graph = TaskGraph::new();
let codegen1 = create_task("codegen1", vec![], vec!["projen", "codegen"]);
let codegen2 = create_task("codegen2", vec![], vec!["projen", "codegen"]);
let build = create_task("build", vec!["codegen1", "codegen2"], vec![]);
graph.add_task("codegen1", codegen1).unwrap();
graph.add_task("codegen2", codegen2).unwrap();
graph.add_task("build", build).unwrap();
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), 3);
assert!(!graph.has_cycles());
let sorted = graph.topological_sort().unwrap();
let codegen1_node = sorted.iter().find(|n| n.name == "codegen1").unwrap();
assert_eq!(codegen1_node.task.labels, vec!["projen", "codegen"]);
}
#[test]
fn test_parallel_groups_with_labels() {
let mut graph = TaskGraph::new();
let gen1 = create_task("gen.project1", vec![], vec!["codegen"]);
let gen2 = create_task("gen.project2", vec![], vec!["codegen"]);
let gen3 = create_task("gen.project3", vec![], vec!["codegen"]);
let build = create_task(
"build",
vec!["gen.project1", "gen.project2", "gen.project3"],
vec![],
);
graph.add_task("gen.project1", gen1).unwrap();
graph.add_task("gen.project2", gen2).unwrap();
graph.add_task("gen.project3", gen3).unwrap();
graph.add_task("build", build).unwrap();
graph.add_dependency_edges().unwrap();
let groups = graph.get_parallel_groups().unwrap();
assert_eq!(groups.len(), 2);
assert_eq!(groups[0].len(), 3); assert_eq!(groups[1].len(), 1); }
#[test]
fn test_build_for_task_includes_cross_project_deps() {
let mut tasks = Tasks::new();
tasks.tasks.insert(
"external::build".to_string(),
TaskNode::Task(Box::new(create_task("external::build", vec![], vec![]))),
);
tasks.tasks.insert(
"deploy".to_string(),
TaskNode::Task(Box::new(create_task(
"deploy",
vec!["external::build"],
vec![],
))),
);
let mut graph = TaskGraph::new();
graph.build_for_task("deploy", &tasks).unwrap();
assert_eq!(graph.task_count(), 2);
assert!(graph.contains_task("deploy"));
assert!(graph.contains_task("external::build"));
assert!(!graph.has_cycles());
}
#[test]
fn test_build_for_task_with_hook_chain() {
let mut tasks = Tasks::new();
tasks.tasks.insert(
"bun.hooks.beforeInstall[0]".to_string(),
TaskNode::Task(Box::new(create_task("hook", vec![], vec![]))),
);
tasks.tasks.insert(
"bun.install".to_string(),
TaskNode::Task(Box::new(create_task(
"install",
vec!["bun.hooks.beforeInstall[0]"],
vec![],
))),
);
tasks.tasks.insert(
"bun.setup".to_string(),
TaskNode::Task(Box::new(create_task("setup", vec!["bun.install"], vec![]))),
);
tasks.tasks.insert(
"dev".to_string(),
TaskNode::Task(Box::new(create_task("dev", vec!["bun.setup"], vec!["bun"]))),
);
let mut graph = TaskGraph::new();
graph.build_for_task("dev", &tasks).unwrap();
assert_eq!(graph.task_count(), 4);
assert!(graph.contains_task("bun.hooks.beforeInstall[0]"));
assert!(graph.contains_task("bun.install"));
assert!(graph.contains_task("bun.setup"));
assert!(graph.contains_task("dev"));
}
#[test]
fn test_large_dag_with_many_hooks() {
let mut graph = TaskGraph::new();
let num_hooks = 20;
let mut prev_hook = String::new();
for i in 0..num_hooks {
let hook_name = format!("bun.hooks.beforeInstall[{}]", i);
let deps = if prev_hook.is_empty() {
vec![]
} else {
vec![prev_hook.as_str()]
};
let hook = create_task(&hook_name, deps, vec![]);
graph.add_task(&hook_name, hook).unwrap();
prev_hook = hook_name;
}
let install = create_task("bun.install", vec![&prev_hook], vec![]);
graph.add_task("bun.install", install).unwrap();
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), num_hooks + 1);
assert!(!graph.has_cycles());
let groups = graph.get_parallel_groups().unwrap();
assert_eq!(groups.len(), num_hooks + 1);
}
#[test]
fn test_dag_with_many_parallel_projects() {
let mut graph = TaskGraph::new();
let num_projects = 15;
for i in 0..num_projects {
let project_name = format!("project{}::build", i);
let task = create_task(&project_name, vec![], vec![]);
graph.add_task(&project_name, task).unwrap();
}
let integration_deps: Vec<TaskDependency> = (0..num_projects)
.map(|i| TaskDependency::from_name(format!("project{}::build", i)))
.collect();
let integration = Task {
command: "echo integration".to_string(),
depends_on: integration_deps,
..Default::default()
};
graph.add_task("integration", integration).unwrap();
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), num_projects + 1);
assert!(!graph.has_cycles());
let groups = graph.get_parallel_groups().unwrap();
assert_eq!(groups.len(), 2);
assert_eq!(groups[0].len(), num_projects); assert_eq!(groups[1].len(), 1); }
#[test]
fn test_task_with_very_long_name() {
let mut graph = TaskGraph::new();
let long_name = "a".repeat(500);
let task = create_task(&long_name, vec![], vec![]);
graph.add_task(&long_name, task).unwrap();
assert_eq!(graph.task_count(), 1);
assert!(graph.contains_task(&long_name));
}
#[test]
fn test_task_with_unicode_name() {
let mut graph = TaskGraph::new();
let unicode_name = "任务_测试_日本語_émoji🚀";
let task = create_task(unicode_name, vec![], vec![]);
graph.add_task(unicode_name, task).unwrap();
assert_eq!(graph.task_count(), 1);
assert!(graph.contains_task(unicode_name));
}
#[test]
fn test_cross_project_with_unicode_names() {
let mut graph = TaskGraph::new();
let proj_a = "项目A::构建";
let proj_b = "プãƒã‚¸ã‚§ã‚¯ãƒˆB::ビルド";
let task_a = create_task(proj_a, vec![], vec![]);
let task_b = create_task(proj_b, vec![proj_a], vec![]);
graph.add_task(proj_a, task_a).unwrap();
graph.add_task(proj_b, task_b).unwrap();
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), 2);
assert!(!graph.has_cycles());
let sorted = graph.topological_sort().unwrap();
assert_eq!(sorted[0].name, proj_a);
assert_eq!(sorted[1].name, proj_b);
}
#[test]
fn test_task_with_special_characters() {
let mut graph = TaskGraph::new();
let special_name = "task-with.dots_and-dashes";
let task = create_task(special_name, vec![], vec![]);
graph.add_task(special_name, task).unwrap();
assert_eq!(graph.task_count(), 1);
assert!(graph.contains_task(special_name));
}
#[test]
fn test_synthetic_hook_with_special_index() {
let mut graph = TaskGraph::new();
let patterns = vec![
"ws.hooks.beforeInstall[0]",
"ws.hooks.beforeInstall[99]",
"ws.hooks.beforeInstall.matcher[0]",
"ws.hooks.afterInstall[0]",
];
for pattern in &patterns {
let task = create_task(pattern, vec![], vec![]);
graph.add_task(pattern, task).unwrap();
}
assert_eq!(graph.task_count(), 4);
for pattern in &patterns {
assert!(graph.contains_task(pattern));
}
}
#[test]
fn test_empty_depends_on_list() {
let mut graph = TaskGraph::new();
let task1 = Task {
command: "echo task1".to_string(),
depends_on: vec![], ..Default::default()
};
let task2 = Task {
command: "echo task2".to_string(),
depends_on: vec![], ..Default::default()
};
graph.add_task("task1", task1).unwrap();
graph.add_task("task2", task2).unwrap();
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), 2);
assert!(!graph.has_cycles());
let groups = graph.get_parallel_groups().unwrap();
assert_eq!(groups.len(), 1);
assert_eq!(groups[0].len(), 2);
}
#[test]
fn test_project_separator_variations() {
let mut graph = TaskGraph::new();
let formats = [
"project::task",
"project::nested.task",
"project::deeply.nested.task",
"my-project::my-task",
"my_project::my_task",
];
for (i, name) in formats.iter().enumerate() {
let deps: Vec<&str> = if i > 0 { vec![formats[i - 1]] } else { vec![] };
let task = create_task(name, deps, vec![]);
graph.add_task(name, task).unwrap();
}
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), formats.len());
assert!(!graph.has_cycles());
let groups = graph.get_parallel_groups().unwrap();
assert_eq!(groups.len(), formats.len());
}
#[test]
fn test_multiple_tasks_same_project() {
let mut graph = TaskGraph::new();
let tasks = vec![
("myproject::build", vec![]),
("myproject::test", vec!["myproject::build"]),
("myproject::lint", vec!["myproject::build"]),
(
"myproject::deploy",
vec!["myproject::test", "myproject::lint"],
),
];
for (name, deps) in &tasks {
let task = create_task(name, deps.to_vec(), vec![]);
graph.add_task(name, task).unwrap();
}
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), 4);
assert!(!graph.has_cycles());
let groups = graph.get_parallel_groups().unwrap();
assert_eq!(groups.len(), 3);
assert_eq!(groups[0].len(), 1); assert_eq!(groups[1].len(), 2); assert_eq!(groups[2].len(), 1); }
#[test]
fn test_deeply_nested_task_groups() {
let mut graph = TaskGraph::new();
let task_a = create_task("a", vec![], vec![]);
let task_b1 = create_task("b1", vec!["a"], vec![]);
let task_b2 = create_task("b2", vec!["a"], vec![]);
let task_c = create_task("c", vec!["b1", "b2"], vec![]);
let task_d1 = create_task("d1", vec!["c"], vec![]);
let task_d2 = create_task("d2", vec!["c"], vec![]);
let task_d3 = create_task("d3", vec!["c"], vec![]);
let task_e = create_task("e", vec!["d1", "d2", "d3"], vec![]);
graph.add_task("a", task_a).unwrap();
graph.add_task("b1", task_b1).unwrap();
graph.add_task("b2", task_b2).unwrap();
graph.add_task("c", task_c).unwrap();
graph.add_task("d1", task_d1).unwrap();
graph.add_task("d2", task_d2).unwrap();
graph.add_task("d3", task_d3).unwrap();
graph.add_task("e", task_e).unwrap();
graph.add_dependency_edges().unwrap();
assert_eq!(graph.task_count(), 8);
assert!(!graph.has_cycles());
let groups = graph.get_parallel_groups().unwrap();
assert_eq!(groups.len(), 5);
assert_eq!(groups[0].len(), 1); assert_eq!(groups[1].len(), 2); assert_eq!(groups[2].len(), 1); assert_eq!(groups[3].len(), 3); assert_eq!(groups[4].len(), 1); }