301 lines
8.8 KiB
Rust
301 lines
8.8 KiB
Rust
//! Tests for extremely deep dependency chains
|
|
|
|
use crate::common::task_fn;
|
|
use dagx::{DagResult, DagRunner};
|
|
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
use std::sync::Arc;
|
|
|
|
#[tokio::test]
|
|
async fn test_1000_level_deep_chain() -> DagResult<()> {
|
|
// Test a linear chain of 1000 tasks
|
|
let dag = DagRunner::new();
|
|
|
|
let first = dag.add_task(task_fn::<(), _, _>(|_: ()| 0));
|
|
let mut current = first.into(); // Convert to TaskHandle
|
|
|
|
for _i in 1..1000 {
|
|
current = dag
|
|
.add_task(task_fn::<i32, _, _>(move |&prev: &i32| prev + 1))
|
|
.depends_on(current);
|
|
}
|
|
|
|
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
|
|
|
|
assert_eq!(dag.get(current)?, 999);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_deep_chain_with_branches() -> DagResult<()> {
|
|
// Test a deep chain where each node has a side branch
|
|
let dag = DagRunner::new();
|
|
|
|
let first = dag.add_task(task_fn::<(), _, _>(|_: ()| 0));
|
|
let mut main_chain: dagx::TaskHandle<i32> = first.into();
|
|
let mut branches = Vec::new();
|
|
|
|
for i in 1..500 {
|
|
// Main chain continues
|
|
main_chain = dag
|
|
.add_task(task_fn::<i32, _, _>(move |&prev: &i32| prev + 1))
|
|
.depends_on(main_chain);
|
|
|
|
// Create a branch at every 10th node
|
|
if i % 10 == 0 {
|
|
let branch = dag
|
|
.add_task(task_fn::<i32, _, _>(move |main_val: &i32| main_val * 2))
|
|
.depends_on(main_chain);
|
|
branches.push(branch);
|
|
}
|
|
}
|
|
|
|
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
|
|
|
|
assert_eq!(dag.get(main_chain)?, 499);
|
|
|
|
// Check some branches
|
|
if !branches.is_empty() {
|
|
assert_eq!(dag.get(branches[0])?, 20); // 10 * 2
|
|
assert_eq!(dag.get(branches[4])?, 100); // 50 * 2
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_multiple_parallel_deep_chains() -> DagResult<()> {
|
|
// Test 10 parallel chains of depth 500 each
|
|
let dag = DagRunner::new();
|
|
|
|
let mut chain_ends = Vec::new();
|
|
|
|
for chain_id in 0..8 {
|
|
let first = dag.add_task(task_fn::<(), _, _>(move |_: ()| chain_id * 1000));
|
|
let mut current: dagx::TaskHandle<i32> = first.into();
|
|
|
|
for _ in 0..500 {
|
|
current = dag
|
|
.add_task(task_fn::<i32, _, _>(|&prev: &i32| prev + 1))
|
|
.depends_on(current);
|
|
}
|
|
|
|
chain_ends.push(current);
|
|
}
|
|
|
|
// Add a final task that depends on all chains
|
|
let final_task = dag
|
|
.add_task(task_fn::<(i32, i32, i32, i32, i32, i32, i32, i32), _, _>(
|
|
|inputs: (&i32, &i32, &i32, &i32, &i32, &i32, &i32, &i32)| {
|
|
inputs.0
|
|
+ inputs.1
|
|
+ inputs.2
|
|
+ inputs.3
|
|
+ inputs.4
|
|
+ inputs.5
|
|
+ inputs.6
|
|
+ inputs.7
|
|
},
|
|
))
|
|
.depends_on((
|
|
&chain_ends[0],
|
|
&chain_ends[1],
|
|
&chain_ends[2],
|
|
&chain_ends[3],
|
|
&chain_ends[4],
|
|
&chain_ends[5],
|
|
&chain_ends[6],
|
|
&chain_ends[7],
|
|
));
|
|
|
|
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
|
|
|
|
// Each chain: start + 500
|
|
// Chain 0: 0 + 500 = 500
|
|
// Chain 1: 1000 + 500 = 1500
|
|
// ...
|
|
// Sum = 500*8 + (0+1000+2000+...+7000) = 4000 + 28000 = 32000
|
|
assert_eq!(dag.get(final_task)?, 32000);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_deep_chain_execution_order() -> DagResult<()> {
|
|
// Verify tasks in a deep chain execute in correct order
|
|
let dag = DagRunner::new();
|
|
let order = Arc::new(AtomicUsize::new(0));
|
|
|
|
let first = dag.add_task(task_fn::<(), _, _>({
|
|
let order = order.clone();
|
|
move |_: ()| {
|
|
let order = order.clone();
|
|
assert_eq!(order.fetch_add(1, Ordering::SeqCst), 0);
|
|
0
|
|
}
|
|
}));
|
|
let mut current: dagx::TaskHandle<i32> = first.into();
|
|
|
|
for expected in 1..100 {
|
|
current = dag
|
|
.add_task(task_fn::<i32, _, _>({
|
|
let order = order.clone();
|
|
move |&prev: &i32| {
|
|
let order = order.clone();
|
|
assert_eq!(order.fetch_add(1, Ordering::SeqCst), expected);
|
|
prev + 1
|
|
}
|
|
}))
|
|
.depends_on(current);
|
|
}
|
|
|
|
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
|
|
|
|
assert_eq!(order.load(Ordering::SeqCst), 100);
|
|
assert_eq!(dag.get(current)?, 99);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_deep_binary_tree() -> DagResult<()> {
|
|
// Test a deep binary tree structure (depth 10 = 1023 nodes)
|
|
let dag = DagRunner::new();
|
|
|
|
fn build_tree(dag: &DagRunner, depth: usize, value: i32) -> dagx::TaskHandle<i32> {
|
|
if depth == 0 {
|
|
let task = dag.add_task(task_fn::<(), _, _>(move |_: ()| value));
|
|
task.into() // Convert TaskBuilder to TaskHandle
|
|
} else {
|
|
let left = build_tree(dag, depth - 1, value * 2);
|
|
let right = build_tree(dag, depth - 1, value * 2 + 1);
|
|
|
|
dag.add_task(task_fn::<(i32, i32), _, _>(move |(l, r): (&i32, &i32)| {
|
|
l + r
|
|
}))
|
|
.depends_on((&left, &right))
|
|
}
|
|
}
|
|
|
|
let root = build_tree(&dag, 10, 1);
|
|
|
|
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
|
|
|
|
// Sum of all values in a complete binary tree with values 1024..2047
|
|
// This is sum from 2^10 to 2^11-1 = 1024 * 1023 / 2 + 1024 * 512 = 1047552
|
|
let result = dag.get(root)?;
|
|
assert!(result > 0);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_fibonacci_chain() -> DagResult<()> {
|
|
// Test a Fibonacci-like dependency pattern
|
|
let dag = DagRunner::new();
|
|
|
|
let f0_builder = dag.add_task(task_fn::<(), _, _>(|_: ()| 0i64));
|
|
let f1_builder = dag.add_task(task_fn::<(), _, _>(|_: ()| 1i64));
|
|
|
|
let mut prev2: dagx::TaskHandle<i64> = f0_builder.into();
|
|
let mut prev1: dagx::TaskHandle<i64> = f1_builder.into();
|
|
|
|
for _ in 2..50 {
|
|
let next = dag
|
|
.add_task(task_fn::<(i64, i64), _, _>(|(a, b): (&i64, &i64)| a + b))
|
|
.depends_on((&prev2, &prev1));
|
|
|
|
prev2 = prev1;
|
|
prev1 = next;
|
|
}
|
|
|
|
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
|
|
|
|
// 50th Fibonacci number (0-indexed) = 7778742049
|
|
assert_eq!(dag.get(prev1)?, 7778742049);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_deep_chain_with_accumulator() -> DagResult<()> {
|
|
// Test a deep chain where each node accumulates a value
|
|
let dag = DagRunner::new();
|
|
|
|
// Use a simpler accumulation pattern - just sum
|
|
let first = dag.add_task(task_fn::<(), _, _>(|_: ()| 0i64));
|
|
let mut current: dagx::TaskHandle<i64> = first.into();
|
|
|
|
for i in 1..=200 {
|
|
current = dag
|
|
.add_task(task_fn::<i64, _, _>(move |sum: &i64| sum + i as i64))
|
|
.depends_on(current);
|
|
}
|
|
|
|
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
|
|
|
|
let result = dag.get(current)?;
|
|
assert_eq!(result, 20100); // sum of 1..=200
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_zigzag_dependencies() -> DagResult<()> {
|
|
// Test a zigzag pattern: two chains that cross-reference each other
|
|
let dag = DagRunner::new();
|
|
|
|
let a_first = dag.add_task(task_fn::<(), _, _>(|_: ()| 1));
|
|
let b_first = dag.add_task(task_fn::<(), _, _>(|_: ()| 2));
|
|
|
|
let mut chain_a: dagx::TaskHandle<i32> = a_first.into();
|
|
let mut chain_b: dagx::TaskHandle<i32> = b_first.into();
|
|
|
|
for i in 0..100 {
|
|
if i % 2 == 0 {
|
|
// A depends on B
|
|
chain_a = dag
|
|
.add_task(task_fn::<i32, _, _>(move |b_val: &i32| b_val + 1))
|
|
.depends_on(chain_b);
|
|
} else {
|
|
// B depends on A
|
|
chain_b = dag
|
|
.add_task(task_fn::<i32, _, _>(move |a_val: &i32| a_val + 2))
|
|
.depends_on(chain_a);
|
|
}
|
|
}
|
|
|
|
// Final task depends on both chains
|
|
let final_task = dag
|
|
.add_task(task_fn::<(i32, i32), _, _>(|(a, b): (&i32, &i32)| a + b))
|
|
.depends_on((&chain_a, &chain_b));
|
|
|
|
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
|
|
|
|
let result = dag.get(final_task)?;
|
|
assert!(result > 100);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[cfg_attr(tarpaulin, ignore)] // This test is extremely resource intensive
|
|
async fn test_10000_level_chain_stress() -> DagResult<()> {
|
|
// Ultimate deep chain test
|
|
let dag = DagRunner::new();
|
|
|
|
let first = dag.add_task(task_fn::<(), _, _>(|_: ()| 0u64));
|
|
let mut current: dagx::TaskHandle<u64> = first.into();
|
|
|
|
for _ in 1..10_000 {
|
|
current = dag
|
|
.add_task(task_fn::<u64, _, _>(|prev: &u64| prev + 1))
|
|
.depends_on(current);
|
|
}
|
|
|
|
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() }).await?;
|
|
|
|
assert_eq!(dag.get(current)?, 9_999);
|
|
Ok(())
|
|
}
|