refactor: consolidate some tests/examples/benchmarks

This commit is contained in:
TechnoPorg committed 2026-02-12 23:31:19 +01:00
1 parent 49be497964
commit b8204c4ed9
83 files changed
+569 -10068

No files matched your search

-317
View File
@@ -1,317 +0,0 @@
// Basic execution tests for DagRunner
use crate::common::task_fn;
use dagx::*;
#[tokio::test]
async fn test_empty_dag() {
let dag = DagRunner::new();
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap(); // Should complete without error
}
#[tokio::test]
async fn test_single_task() {
let dag = DagRunner::new();
let task = dag.add_task(task_fn::<(), _, _>(|_: ()| 42));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
assert_eq!(dag.get(task).unwrap(), 42);
}
#[tokio::test]
async fn test_deep_chain() {
let dag = DagRunner::new();
let a = dag.add_task(task_fn::<(), _, _>(|_: ()| 1));
let b = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 1))
.depends_on(a);
let c = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 1))
.depends_on(b);
let d = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 1))
.depends_on(c);
let e = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 1))
.depends_on(d);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
// Note: a, b, c, d are not sinks (they have dependents in the chain)
// Only e is a sink and can be retrieved
assert_eq!(dag.get(e).unwrap(), 5);
}
#[tokio::test]
async fn test_wide_parallel() {
let dag = DagRunner::new();
let tasks: Vec<_> = (0..10)
.map(|i| dag.add_task(task_fn::<(), _, _>(move |_: ()| i * 2)))
.collect();
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
for (i, task) in tasks.into_iter().enumerate() {
assert_eq!(dag.get(task).unwrap(), i * 2);
}
}
#[tokio::test]
async fn test_diamond_dependency() {
let dag = DagRunner::new();
let a: TaskHandle<_> = dag.add_task(task_fn::<(), _, _>(|_: ()| 10)).into();
let b = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 5))
.depends_on(a);
let c = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x * 2))
.depends_on(a);
let d = dag
.add_task(task_fn::<(i32, i32), _, _>(|(x, y): (&i32, &i32)| x + y))
.depends_on((&b, &c));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
// Note: a, b, and c are not sinks (they have dependents)
// Only d is a sink and can be retrieved
assert_eq!(dag.get(d).unwrap(), 35); // 15 + 20
}
#[derive(Clone)]
struct User {
name: String,
age: u32,
}
#[tokio::test]
async fn test_different_output_types() {
let dag = DagRunner::new();
let string_task = dag.add_task(task_fn::<(), _, _>(|_: ()| "hello".to_string()));
let vec_task = dag.add_task(task_fn::<(), _, _>(|_: ()| vec![1, 2, 3]));
let struct_task = dag.add_task(task_fn::<(), _, _>(|_: ()| User {
name: "Alice".to_string(),
age: 30,
}));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
assert_eq!(&dag.get(string_task).unwrap(), "hello");
assert_eq!(dag.get(vec_task).unwrap(), vec![1, 2, 3]);
let user = dag.get(struct_task).unwrap();
assert_eq!(user.name, "Alice");
assert_eq!(user.age, 30);
}
#[tokio::test]
async fn test_multiple_sinks() {
let dag = DagRunner::new();
let source: TaskHandle<_> = dag.add_task(task_fn::<(), _, _>(|_: ()| 10)).into();
// Branch 1
let branch1 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x * 2))
.depends_on(source);
let sink1 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 1))
.depends_on(branch1);
// Branch 2
let branch2 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 5))
.depends_on(source);
let sink2 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x * 3))
.depends_on(branch2);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap(); // Should wait for both sinks
assert_eq!(dag.get(sink1).unwrap(), 21); // (10 * 2) + 1
assert_eq!(dag.get(sink2).unwrap(), 45); // (10 + 5) * 3
}
#[tokio::test]
async fn test_single_task_inline_path() {
// Test single-task layer inline execution path (line 367-410 in runner.rs)
let dag = DagRunner::new();
// Single task in its own layer
let t1: TaskHandle<_> = dag.add_task(task_fn::<(), _, _>(|_: ()| 10)).into();
let t2 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x * 2))
.depends_on(t1);
let t3 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 5))
.depends_on(t2);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
assert_eq!(dag.get(t3).unwrap(), 25); // (10 * 2) + 5
}
#[tokio::test]
async fn test_multi_task_spawn_path() {
// Test multi-task layer spawn path (line 411-456 in runner.rs)
let dag = DagRunner::new();
let source: TaskHandle<_> = dag.add_task(task_fn::<(), _, _>(|_: ()| 100)).into();
// Create multiple tasks in the same layer (all depend on source)
let t1 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 1))
.depends_on(source);
let t2 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 2))
.depends_on(source);
let t3 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 3))
.depends_on(source);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
assert_eq!(dag.get(t1).unwrap(), 101);
assert_eq!(dag.get(t2).unwrap(), 102);
assert_eq!(dag.get(t3).unwrap(), 103);
}
#[tokio::test]
async fn test_producer_consumer_channels() {
// Test channel creation and management (lines 301-337 in runner.rs)
let dag = DagRunner::new();
// Producer
let producer: TaskHandle<_> = dag
.add_task(task_fn::<(), _, _>(|_: ()| vec![1, 2, 3, 4, 5]))
.into();
// Consumer
let consumer = dag
.add_task(task_fn::<Vec<_>, _, _>(|data: &Vec<i32>| {
data.iter().sum::<i32>()
}))
.depends_on(producer);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
assert_eq!(dag.get(consumer).unwrap(), 15);
}
#[tokio::test]
async fn test_multi_consumer_fanout() {
// Test fanout with multiple consumers from one producer
let dag = DagRunner::new();
let producer: TaskHandle<_> = dag.add_task(task_fn::<(), _, _>(|_: ()| 42)).into();
// Multiple consumers
let c1 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x * 2))
.depends_on(producer);
let c2 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 10))
.depends_on(producer);
let c3 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x - 5))
.depends_on(producer);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
assert_eq!(dag.get(c1).unwrap(), 84);
assert_eq!(dag.get(c2).unwrap(), 52);
assert_eq!(dag.get(c3).unwrap(), 37);
}
#[tokio::test]
async fn test_compute_layers_with_dependents_tracking() {
// Test layer computation with dependent tracking (lines 590-600 in runner.rs)
let dag = DagRunner::new();
// Create diamond pattern to ensure dependents are tracked correctly
let source: TaskHandle<_> = dag.add_task(task_fn::<(), _, _>(|_: ()| 1)).into();
let left = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 1))
.depends_on(source);
let right = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 2))
.depends_on(source);
let sink = dag
.add_task(task_fn::<(i32, i32), _, _>(|(l, r): (&i32, &i32)| l + r))
.depends_on((&left, &right));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
assert_eq!(dag.get(sink).unwrap(), 5); // (1+1) + (1+2) = 5
}
#[tokio::test]
async fn test_layer_execution_order() {
// Test that layers execute in correct topological order
use parking_lot::Mutex;
use std::sync::Arc;
let execution_order = Arc::new(Mutex::new(Vec::new()));
let dag = DagRunner::new();
let order1 = execution_order.clone();
let t1 = dag.add_task(task_fn::<(), _, _>(move |_: ()| {
let order = order1.clone();
order.lock().push(1);
1
}));
let order2 = execution_order.clone();
let t2 = dag
.add_task(task_fn::<i32, _, _>(move |&x: &i32| {
let order = order2.clone();
order.lock().push(2);
x + 1
}))
.depends_on(t1);
let order3 = execution_order.clone();
let t3 = dag
.add_task(task_fn::<i32, _, _>(move |&x: &i32| {
let order = order3.clone();
order.lock().push(3);
x + 1
}))
.depends_on(t2);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
let order = execution_order.lock().clone();
assert_eq!(order, vec![1, 2, 3]);
assert_eq!(dag.get(t3).unwrap(), 3);
}
-381
View File
@@ -1,381 +0,0 @@
//! Integration tests for end-to-end functionality
use crate::common::task_fn;
use dagx::{DagResult, DagRunner, Task};
use std::time::Instant;
use tokio::time::{sleep, Duration};
// === Shared Test Tasks ===
struct LoadValue {
value: i32,
}
impl LoadValue {
fn new(value: i32) -> Self {
Self { value }
}
}
#[dagx::task]
impl LoadValue {
async fn run(&self) -> i32 {
self.value // Read-only state: use &self
}
}
struct Multiply {
factor: i32,
}
impl Multiply {
fn new(factor: i32) -> Self {
Self { factor }
}
}
#[dagx::task]
impl Multiply {
async fn run(&self, input: &i32) -> i32 {
input * self.factor // Read-only state: use &self
}
}
struct Add;
#[dagx::task]
impl Add {
async fn run(a: &i32, b: &i32) -> i32 {
a + b // Stateless: no self needed
}
}
// === Integration Tests ===
#[tokio::test]
async fn integration_test_etl_pipeline() -> DagResult<()> {
// Simulate an Extract-Transform-Load pipeline
let dag = DagRunner::new();
// Extract: Load data from sources
let source1 = dag.add_task(LoadValue::new(10));
let source2 = dag.add_task(LoadValue::new(20));
let source3 = dag.add_task(LoadValue::new(30));
// Transform: Process each source
let transform1 = dag.add_task(Multiply::new(2)).depends_on(source1);
let transform2 = dag.add_task(Multiply::new(3)).depends_on(source2);
let transform3 = dag.add_task(Multiply::new(4)).depends_on(source3);
// Load: Aggregate results
let combine12 = dag.add_task(Add).depends_on((&transform1, &transform2));
let final_result = dag.add_task(Add).depends_on((&combine12, &transform3));
// Execute pipeline
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
// Verify
let result = dag.get(final_result)?;
assert_eq!(result, (10 * 2) + (20 * 3) + (30 * 4)); // 20 + 60 + 120 = 200
Ok(())
}
#[tokio::test]
async fn integration_test_multi_layer_dag() -> DagResult<()> {
// Complex multi-layer DAG
let dag = DagRunner::new();
// Layer 1: Sources
let a = dag.add_task(LoadValue::new(1));
let b = dag.add_task(LoadValue::new(2));
let c = dag.add_task(LoadValue::new(3));
// Layer 2: First transformations
let ab = dag.add_task(Add).depends_on((&a, &b));
let bc = dag.add_task(Add).depends_on((&b, &c));
// Layer 3: Second transformations
let double_ab = dag.add_task(Multiply::new(2)).depends_on(ab);
let double_bc = dag.add_task(Multiply::new(2)).depends_on(bc);
// Layer 4: Final result
let final_result = dag.add_task(Add).depends_on((&double_ab, &double_bc));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
let result = dag.get(final_result)?;
// (1+2)*2 + (2+3)*2 = 6 + 10 = 16
assert_eq!(result, 16);
Ok(())
}
#[tokio::test]
async fn integration_test_multiple_sinks() -> DagResult<()> {
// DAG with multiple output sinks
let dag = DagRunner::new();
let source = dag.add_task(LoadValue::new(10));
// Multiple independent branches
let branch1 = dag.add_task(Multiply::new(2)).depends_on(source);
let branch2 = dag.add_task(Multiply::new(3)).depends_on(source);
let branch3 = dag.add_task(Multiply::new(4)).depends_on(source);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
// All branches should complete
assert_eq!(dag.get(branch1)?, 20);
assert_eq!(dag.get(branch2)?, 30);
assert_eq!(dag.get(branch3)?, 40);
Ok(())
}
#[tokio::test]
async fn integration_test_diamond_cascade() -> DagResult<()> {
// Multiple diamond patterns in sequence
let dag = DagRunner::new();
// Diamond 1
let source1 = dag.add_task(LoadValue::new(10));
let left1 = dag.add_task(Multiply::new(2)).depends_on(source1);
let right1 = dag.add_task(Multiply::new(3)).depends_on(source1);
let sink1 = dag.add_task(Add).depends_on((&left1, &right1));
// Diamond 2 (depends on Diamond 1)
let left2 = dag.add_task(Multiply::new(2)).depends_on(sink1);
let right2 = dag.add_task(Multiply::new(3)).depends_on(sink1);
let sink2 = dag.add_task(Add).depends_on((&left2, &right2));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
let result = dag.get(sink2)?;
// Diamond 1: (10*2) + (10*3) = 50
// Diamond 2: (50*2) + (50*3) = 250
assert_eq!(result, 250);
Ok(())
}
#[tokio::test]
async fn integration_test_wide_and_deep() -> DagResult<()> {
// Combination of wide (parallel) and deep (sequential) patterns
let dag = DagRunner::new();
// Wide: 5 parallel sources
let sources: Vec<_> = (0..5)
.map(|i| dag.add_task(LoadValue::new(i * 10)))
.collect();
// Deep: Chain each source through 3 transformations
let mut final_nodes = Vec::new();
for source in &sources {
let step1 = dag.add_task(Multiply::new(2)).depends_on(source);
let step2 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 5))
.depends_on(step1);
let step3 = dag.add_task(Multiply::new(3)).depends_on(step2);
final_nodes.push(step3);
}
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
// Verify each chain
for (i, node) in final_nodes.iter().enumerate() {
let expected = ((i as i32 * 10) * 2 + 5) * 3;
assert_eq!(dag.get(node)?, expected);
}
Ok(())
}
#[tokio::test]
async fn integration_test_error_recovery() -> DagResult<()> {
// Test that DAG handles normal execution gracefully
let dag = DagRunner::new();
let source = dag.add_task(LoadValue::new(42));
let transform = dag.add_task(Multiply::new(2)).depends_on(source);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
// Should be able to access results
assert_eq!(dag.get(transform)?, 84);
// Getting valid task should return ok
let result = dag.get(transform);
assert!(result.is_ok());
Ok(())
}
#[tokio::test]
async fn integration_test_large_scale() -> DagResult<()> {
// Large-scale integration test
let dag = DagRunner::new();
// Create 100 sources
let sources: Vec<_> = (0..100).map(|i| dag.add_task(LoadValue::new(i))).collect();
// Create 50 aggregation tasks (each combines 2 sources)
let aggregations: Vec<_> = (0..50)
.map(|i| {
dag.add_task(Add)
.depends_on((&sources[i * 2], &sources[i * 2 + 1]))
})
.collect();
// Create 25 second-level aggregations
let second_level: Vec<_> = (0..25)
.map(|i| {
dag.add_task(Add)
.depends_on((&aggregations[i * 2], &aggregations[i * 2 + 1]))
})
.collect();
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
// Verify some results
assert_eq!(dag.get(aggregations[0])?, 1); // First two
assert_eq!(dag.get(second_level[0])?, 1 + (2 + 3)); // First four
Ok(())
}
#[tokio::test]
async fn integration_test_reused_values() -> DagResult<()> {
// Test that a single source can be used by multiple consumers
let dag = DagRunner::new();
let source = dag.add_task(LoadValue::new(100));
// Use source in multiple different ways
let double = dag.add_task(Multiply::new(2)).depends_on(source);
let triple = dag.add_task(Multiply::new(3)).depends_on(source);
let quadruple = dag.add_task(Multiply::new(4)).depends_on(source);
// Combine some of the results
let combined = dag.add_task(Add).depends_on((&double, &triple));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(double)?, 200);
assert_eq!(dag.get(triple)?, 300);
assert_eq!(dag.get(quadruple)?, 400);
assert_eq!(dag.get(combined)?, 500);
Ok(())
}
#[tokio::test]
async fn integration_test_parallel_execution_timing() -> DagResult<()> {
// Test that independent tasks actually run in parallel by measuring execution time
let dag = DagRunner::new();
let sleep_duration = Duration::from_millis(100);
let num_parallel_tasks = 5;
// Create 5 independent tasks that each sleep for 100ms
let tasks: Vec<_> = (0..num_parallel_tasks)
.map(|i| {
dag.add_task(task_fn::<(), _, _>(move |_: ()| {
sleep(sleep_duration).await;
i
}))
})
.collect();
let start = Instant::now();
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
let elapsed = start.elapsed();
// Verify all tasks completed
for (i, task) in tasks.iter().enumerate() {
assert_eq!(dag.get(task)?, i);
}
// If tasks ran sequentially, it would take 5 * 100ms = 500ms
// With parallelism, it should take ~100ms (plus overhead)
// We'll check it's less than 300ms (generous margin for CI environments)
assert!(
elapsed < Duration::from_millis(300),
"Parallel execution took {:?}, expected < 300ms (sequential would be 500ms)",
elapsed
);
Ok(())
}
#[tokio::test]
async fn integration_test_parallel_vs_sequential_timing() -> DagResult<()> {
// Test mixed parallel and sequential execution patterns
let dag = DagRunner::new();
let sleep_duration = Duration::from_millis(50);
// Layer 1: 4 parallel tasks (should take ~50ms)
let parallel_tasks: Vec<_> = (0..4)
.map(|i| {
dag.add_task(task_fn::<(), _, _>(move |_: ()| {
sleep(sleep_duration).await;
i * 10
}))
})
.collect();
// Layer 2: Sequential chain of 3 tasks depending on the first parallel task
// (should add ~150ms)
let seq1 = dag
.add_task(task_fn::<i32, _, _>(move |&x: &i32| {
sleep(sleep_duration).await;
x + 1
}))
.depends_on(parallel_tasks[0]);
let seq2 = dag
.add_task(task_fn::<i32, _, _>(move |&x: &i32| {
sleep(sleep_duration).await;
x + 1
}))
.depends_on(seq1);
let seq3 = dag
.add_task(task_fn::<i32, _, _>(move |&x: &i32| {
sleep(sleep_duration).await;
x + 1
}))
.depends_on(seq2);
let start = Instant::now();
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
let elapsed = start.elapsed();
// Verify results
assert_eq!(dag.get(seq3)?, 3); // 0 + 1 + 1 + 1
// Expected time: ~200ms (4 layers of 50ms each: parallel layer + 3 sequential)
// If everything ran sequentially: 7 * 50ms = 350ms
// We'll check it's less than 300ms
assert!(
elapsed < Duration::from_millis(300),
"Execution took {:?}, expected < 300ms (fully sequential would be 350ms)",
elapsed
);
// Also verify it took more than just the parallel layer
assert!(
elapsed >= Duration::from_millis(150),
"Execution took {:?}, expected >= 150ms (should include sequential chain)",
elapsed
);
Ok(())
}
+168 -3
View File
@@ -1,4 +1,169 @@
// Test module for execution tests
// Basic execution tests for DagRunner
use dagx::task_fn;
mod basic;
mod results;
use dagx::*;
#[tokio::test]
async fn test_empty_dag() {
let dag = DagRunner::new();
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap(); // Should complete without error
}
#[tokio::test]
async fn test_single_task() {
let dag = DagRunner::new();
let task = dag.add_task(task_fn::<(), _, _>(|_: ()| 42));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
assert_eq!(dag.get(task).unwrap(), 42);
}
#[tokio::test]
async fn test_wide_parallel() {
let dag = DagRunner::new();
let tasks: Vec<_> = (0..10)
.map(|i| dag.add_task(task_fn::<(), _, _>(move |_: ()| i * 2)))
.collect();
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
for (i, task) in tasks.into_iter().enumerate() {
assert_eq!(dag.get(task).unwrap(), i * 2);
}
}
#[tokio::test]
async fn test_diamond_dependency() {
let dag = DagRunner::new();
let a: TaskHandle<_> = dag.add_task(task_fn::<(), _, _>(|_: ()| 10)).into();
let b = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 5))
.depends_on(a);
let c = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x * 2))
.depends_on(a);
let d = dag
.add_task(task_fn::<(i32, i32), _, _>(|(x, y): (&i32, &i32)| x + y))
.depends_on((&b, &c));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
// Note: a, b, and c are not sinks (they have dependents)
// Only d is a sink and can be retrieved
assert_eq!(dag.get(d).unwrap(), 35); // 15 + 20
}
#[derive(Clone)]
struct User {
name: String,
age: u32,
}
#[tokio::test]
async fn test_different_output_types() {
let dag = DagRunner::new();
let string_task = dag.add_task(task_fn::<(), _, _>(|_: ()| "hello".to_string()));
let vec_task = dag.add_task(task_fn::<(), _, _>(|_: ()| vec![1, 2, 3]));
let struct_task = dag.add_task(task_fn::<(), _, _>(|_: ()| User {
name: "Alice".to_string(),
age: 30,
}));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
assert_eq!(&dag.get(string_task).unwrap(), "hello");
assert_eq!(dag.get(vec_task).unwrap(), vec![1, 2, 3]);
let user = dag.get(struct_task).unwrap();
assert_eq!(user.name, "Alice");
assert_eq!(user.age, 30);
}
#[tokio::test]
async fn test_multiple_sinks() {
let dag = DagRunner::new();
let source: TaskHandle<_> = dag.add_task(task_fn::<(), _, _>(|_: ()| 10)).into();
// Branch 1
let branch1 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x * 2))
.depends_on(source);
let sink1 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 1))
.depends_on(branch1);
// Branch 2
let branch2 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 5))
.depends_on(source);
let sink2 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x * 3))
.depends_on(branch2);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap(); // Should wait for both sinks
assert_eq!(dag.get(sink1).unwrap(), 21); // (10 * 2) + 1
assert_eq!(dag.get(sink2).unwrap(), 45); // (10 + 5) * 3
}
#[tokio::test]
async fn test_single_task_inline_path() {
// Test single-task layer inline execution path (line 367-410 in runner.rs)
let dag = DagRunner::new();
// Single task in its own layer
let t1: TaskHandle<_> = dag.add_task(task_fn::<(), _, _>(|_: ()| 10)).into();
let t2 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x * 2))
.depends_on(t1);
let t3 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 5))
.depends_on(t2);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
assert_eq!(dag.get(t3).unwrap(), 25); // (10 * 2) + 5
}
#[tokio::test]
async fn test_multi_consumer_fanout() {
// Test fanout with multiple consumers from one producer
let dag = DagRunner::new();
let producer: TaskHandle<_> = dag.add_task(task_fn::<(), _, _>(|_: ()| 42)).into();
// Multiple consumers
let c1 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x * 2))
.depends_on(producer);
let c2 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 10))
.depends_on(producer);
let c3 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x - 5))
.depends_on(producer);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
assert_eq!(dag.get(c1).unwrap(), 84);
assert_eq!(dag.get(c2).unwrap(), 52);
assert_eq!(dag.get(c3).unwrap(), 37);
}
-117
View File
@@ -1,117 +0,0 @@
// Result and error handling tests for DagRunner
use crate::common::task_fn;
use crate::common::tasks::Counter;
use dagx::*;
#[tokio::test]
async fn test_result_type() {
let dag = DagRunner::new();
let success_task = dag.add_task(task_fn::<(), _, _>(|_: ()| Ok::<i32, String>(42)));
let error_task = dag.add_task(task_fn::<(), _, _>(|_: ()| {
Err::<i32, String>("error".to_string())
}));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
assert_eq!(dag.get(success_task).unwrap(), Ok(42));
assert_eq!(dag.get(error_task).unwrap(), Err("error".to_string()));
}
#[tokio::test]
async fn test_dag_result_type_works() {
let dag = DagRunner::new();
let task = dag.add_task(task_fn::<(), _, _>(|_: ()| 100));
// Test that DagResult<()> works with ? operator pattern
async fn run_dag(dag: &DagRunner) -> DagResult<()> {
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
Ok(())
}
let result = run_dag(&dag).await;
assert!(result.is_ok());
assert_eq!(dag.get(task).unwrap(), 100);
}
#[tokio::test]
async fn test_task_not_executed_error() {
let dag = DagRunner::new();
let task = dag.add_task(task_fn::<(), _, _>(|_: ()| 42));
// Try to get result before running - should error
let result = dag.get(task);
assert!(result.is_err());
match result {
Err(DagError::ResultNotFound { task_id }) => {
assert_eq!(task_id, 0); // First task has ID 0
}
_ => panic!("Expected ResultNotFound error"),
}
}
#[tokio::test]
async fn test_error_messages_are_helpful() {
// Test ResultNotFound error message
let dag = DagRunner::new();
let task = dag.add_task(task_fn::<(), _, _>(|_: ()| 42));
let result = dag.get(task);
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(err_msg.contains("Result not found"));
assert!(err_msg.contains("task #0")); // Task ID
assert!(err_msg.contains("Call dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })"));
}
#[tokio::test]
async fn test_successful_execution_returns_ok() {
let dag = DagRunner::new();
let task = dag.add_task(task_fn::<(), _, _>(|_: ()| 42));
// Should return Ok(())
let result = dag
.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await;
assert!(result.is_ok());
// Should be able to get value
assert_eq!(dag.get(task).unwrap(), 42);
}
#[tokio::test]
async fn test_stateful_tasks() {
let dag = DagRunner::new();
let input = dag.add_task(task_fn::<(), _, _>(|_: ()| 5));
let counter = dag.add_task(Counter::new(10)).depends_on(input);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
assert_eq!(dag.get(counter).unwrap(), 15); // 10 + 5
}
// Cycle detection test removed - it required access to private fields
// The public API correctly prevents creating cycles, so this is tested implicitly
#[tokio::test]
async fn test_task_fn_with_captured_state() {
let dag = DagRunner::new();
let multiplier = 7;
let base = dag.add_task(task_fn::<(), _, _>(|_: ()| 5));
let scaled = dag
.add_task(task_fn::<i32, _, _>(move |&x: &i32| x * multiplier))
.depends_on(base);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
assert_eq!(dag.get(scaled).unwrap(), 35);
}