Address soundness hole in cycle prevention
This commit is contained in:
1 parent
4ad88bcd68
commit
f82205baf4
80 files changed
+730
-717
No files matched your search
@@ -84,10 +84,11 @@ async fn main() {
|
|||||||
|
|
||||||
// Add source tasks with no dependencies
|
// Add source tasks with no dependencies
|
||||||
let x = dag.add_task(Value(2));
|
let x = dag.add_task(Value(2));
|
||||||
let y = dag.add_task(Value(3));
|
let y: TaskHandle<_> = dag.add_task(Value(3)).into();
|
||||||
|
|
||||||
// Add task that depends on both x and y
|
// Add task that depends on both x and y.
|
||||||
let sum = dag.add_task(Add).depends_on((&x, &y));
|
// Here, y could be reused as a dependency for other tasks, while x could not.
|
||||||
|
let sum = dag.add_task(Add).depends_on((x, &y));
|
||||||
|
|
||||||
// Execute with true parallelism
|
// Execute with true parallelism
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ pub fn bench_dag_execution(c: &mut Criterion) {
|
|||||||
let t0 = dag.add_task(Value(0));
|
let t0 = dag.add_task(Value(0));
|
||||||
let t1 = dag
|
let t1 = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
||||||
.depends_on(&t0);
|
.depends_on(t0);
|
||||||
let t2 = dag
|
let t2 = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
||||||
.depends_on(t1);
|
.depends_on(t1);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
//! Long sequential chain: 100 tasks in sequence
|
//! Long sequential chain: 100 tasks in sequence
|
||||||
|
|
||||||
use criterion::Criterion;
|
use criterion::Criterion;
|
||||||
|
use dagx::TaskHandle;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
|
||||||
pub fn bench_deep_chain(c: &mut Criterion) {
|
pub fn bench_deep_chain(c: &mut Criterion) {
|
||||||
@@ -17,10 +18,10 @@ pub fn bench_deep_chain(c: &mut Criterion) {
|
|||||||
|
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let first = dag.add_task(task_fn(|_: ()| async { 0 }));
|
let first: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 0 })).into();
|
||||||
let mut prev = dag
|
let mut prev = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
||||||
.depends_on(&first);
|
.depends_on(first);
|
||||||
|
|
||||||
for i in 2..100 {
|
for i in 2..100 {
|
||||||
prev = dag
|
prev = dag
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
//! Classic diamond: A → B,C → D
|
//! Classic diamond: A → B,C → D
|
||||||
|
|
||||||
use criterion::Criterion;
|
use criterion::Criterion;
|
||||||
|
use dagx::TaskHandle;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
|
||||||
pub fn bench_diamond_pattern(c: &mut Criterion) {
|
pub fn bench_diamond_pattern(c: &mut Criterion) {
|
||||||
@@ -17,13 +18,13 @@ pub fn bench_diamond_pattern(c: &mut Criterion) {
|
|||||||
|
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let a = dag.add_task(task_fn(|_: ()| async { 10 }));
|
let a: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 10 })).into();
|
||||||
let b = dag
|
let b = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
||||||
.depends_on(&a);
|
.depends_on(a);
|
||||||
let c = dag
|
let c = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 5 }))
|
.add_task(task_fn(|x: i32| async move { x + 5 }))
|
||||||
.depends_on(&a);
|
.depends_on(a);
|
||||||
let d = dag
|
let d = dag
|
||||||
.add_task(task_fn(|(x, y): (i32, i32)| async move { x + y }))
|
.add_task(task_fn(|(x, y): (i32, i32)| async move { x + y }))
|
||||||
.depends_on((&b, &c));
|
.depends_on((&b, &c));
|
||||||
|
|||||||
@@ -27,19 +27,19 @@ pub fn bench_etl_pipeline(c: &mut Criterion) {
|
|||||||
.add_task(task_fn(|data: Vec<i32>| async move {
|
.add_task(task_fn(|data: Vec<i32>| async move {
|
||||||
data.into_iter().map(|x| x * 2).collect::<Vec<_>>()
|
data.into_iter().map(|x| x * 2).collect::<Vec<_>>()
|
||||||
}))
|
}))
|
||||||
.depends_on(&source1);
|
.depends_on(source1);
|
||||||
|
|
||||||
let transform2 = dag
|
let transform2 = dag
|
||||||
.add_task(task_fn(|data: Vec<i32>| async move {
|
.add_task(task_fn(|data: Vec<i32>| async move {
|
||||||
data.into_iter().map(|x| x * 2).collect::<Vec<_>>()
|
data.into_iter().map(|x| x * 2).collect::<Vec<_>>()
|
||||||
}))
|
}))
|
||||||
.depends_on(&source2);
|
.depends_on(source2);
|
||||||
|
|
||||||
let transform3 = dag
|
let transform3 = dag
|
||||||
.add_task(task_fn(|data: Vec<i32>| async move {
|
.add_task(task_fn(|data: Vec<i32>| async move {
|
||||||
data.into_iter().map(|x| x * 2).collect::<Vec<_>>()
|
data.into_iter().map(|x| x * 2).collect::<Vec<_>>()
|
||||||
}))
|
}))
|
||||||
.depends_on(&source3);
|
.depends_on(source3);
|
||||||
|
|
||||||
// Merge all transformed data
|
// Merge all transformed data
|
||||||
let merge = dag
|
let merge = dag
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ pub fn bench_linear_pipeline(c: &mut Criterion) {
|
|||||||
let a = dag.add_task(task_fn(|_: ()| async { 1 }));
|
let a = dag.add_task(task_fn(|_: ()| async { 1 }));
|
||||||
let b = dag
|
let b = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
||||||
.depends_on(&a);
|
.depends_on(a);
|
||||||
let c = dag
|
let c = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
||||||
.depends_on(b);
|
.depends_on(b);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
//! One source task feeding 100 dependent tasks
|
//! One source task feeding 100 dependent tasks
|
||||||
|
|
||||||
use criterion::Criterion;
|
use criterion::Criterion;
|
||||||
|
use dagx::TaskHandle;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
|
||||||
pub fn bench_wide_fanout(c: &mut Criterion) {
|
pub fn bench_wide_fanout(c: &mut Criterion) {
|
||||||
@@ -16,12 +17,12 @@ pub fn bench_wide_fanout(c: &mut Criterion) {
|
|||||||
use dagx::{task_fn, DagRunner};
|
use dagx::{task_fn, DagRunner};
|
||||||
|
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
let source = dag.add_task(task_fn(|_: ()| async { 42 }));
|
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 42 })).into();
|
||||||
|
|
||||||
// Create 100 tasks that all depend on source
|
// Create 100 tasks that all depend on source
|
||||||
for i in 0..100 {
|
for i in 0..100 {
|
||||||
dag.add_task(task_fn(move |x: i32| async move { x + i }))
|
dag.add_task(task_fn(move |x: i32| async move { x + i }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Diamond pattern benchmarks (fan-out then fan-in)
|
//! Diamond pattern benchmarks (fan-out then fan-in)
|
||||||
|
|
||||||
use criterion::Criterion;
|
use criterion::Criterion;
|
||||||
use dagx::{task_fn, DagRunner};
|
use dagx::{task_fn, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
|
||||||
pub fn bench_diamond(c: &mut Criterion) {
|
pub fn bench_diamond(c: &mut Criterion) {
|
||||||
@@ -13,14 +13,15 @@ pub fn bench_diamond(c: &mut Criterion) {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
for i in 0..50 {
|
for i in 0..50 {
|
||||||
let source = dag.add_task(task_fn(move |_: ()| async move { i }));
|
let source: TaskHandle<_> =
|
||||||
|
dag.add_task(task_fn(move |_: ()| async move { i })).into();
|
||||||
|
|
||||||
let left = dag
|
let left = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
let right = dag
|
let right = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 3 }))
|
.add_task(task_fn(|x: i32| async move { x * 3 }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
|
|
||||||
dag.add_task(task_fn(|(l, r): (i32, i32)| async move { l + r }))
|
dag.add_task(task_fn(|(l, r): (i32, i32)| async move { l + r }))
|
||||||
.depends_on((&left, &right));
|
.depends_on((&left, &right));
|
||||||
|
|||||||
+12
-10
@@ -1,7 +1,7 @@
|
|||||||
//! Fan-out pattern benchmarks (1 → N dependencies)
|
//! Fan-out pattern benchmarks (1 → N dependencies)
|
||||||
|
|
||||||
use criterion::Criterion;
|
use criterion::Criterion;
|
||||||
use dagx::{task_fn, DagRunner};
|
use dagx::{task_fn, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
|
||||||
pub fn bench_fanout(c: &mut Criterion) {
|
pub fn bench_fanout(c: &mut Criterion) {
|
||||||
@@ -12,11 +12,11 @@ pub fn bench_fanout(c: &mut Criterion) {
|
|||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
let source = dag.add_task(task_fn(|_: ()| async { 42 }));
|
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 42 })).into();
|
||||||
|
|
||||||
for i in 0..100 {
|
for i in 0..100 {
|
||||||
dag.add_task(task_fn(move |x: i32| async move { x + i }))
|
dag.add_task(task_fn(move |x: i32| async move { x + i }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
@@ -31,17 +31,19 @@ pub fn bench_fanout(c: &mut Criterion) {
|
|||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
let source = dag.add_task(task_fn(|_: ()| async {
|
let source: TaskHandle<_> = dag
|
||||||
// Create 1000 strings - framework wraps in Arc automatically
|
.add_task(task_fn(|_: ()| async {
|
||||||
(0..1000).map(|i| format!("Item {}", i)).collect::<Vec<_>>()
|
// Create 1000 strings - framework wraps in Arc automatically
|
||||||
}));
|
(0..1000).map(|i| format!("Item {}", i)).collect::<Vec<_>>()
|
||||||
|
}))
|
||||||
|
.into();
|
||||||
|
|
||||||
for i in 0..100 {
|
for i in 0..100 {
|
||||||
dag.add_task(task_fn(move |data: Vec<String>| async move {
|
dag.add_task(task_fn(move |data: Vec<String>| async move {
|
||||||
// Each consumer gets data extracted from Arc
|
// Each consumer gets data extracted from Arc
|
||||||
data.iter().filter(|s| s.contains(&i.to_string())).count()
|
data.iter().filter(|s| s.contains(&i.to_string())).count()
|
||||||
}))
|
}))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
@@ -61,12 +63,12 @@ pub fn bench_fanout(c: &mut Criterion) {
|
|||||||
|
|
||||||
// Source produces a simple Copy type (usize)
|
// Source produces a simple Copy type (usize)
|
||||||
// Framework wraps in Arc<usize> internally
|
// Framework wraps in Arc<usize> internally
|
||||||
let source = dag.add_task(task_fn(|_: ()| async { 42usize }));
|
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 42usize })).into();
|
||||||
|
|
||||||
// 100 dependents - Arc overhead visible on Copy types
|
// 100 dependents - Arc overhead visible on Copy types
|
||||||
for i in 0..100 {
|
for i in 0..100 {
|
||||||
dag.add_task(task_fn(move |x: usize| async move { x + i }))
|
dag.add_task(task_fn(move |x: usize| async move { x + i }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Configuration broadcast pattern benchmark - one config shared across many workers
|
//! Configuration broadcast pattern benchmark - one config shared across many workers
|
||||||
|
|
||||||
use criterion::{BenchmarkId, Criterion};
|
use criterion::{BenchmarkId, Criterion};
|
||||||
use dagx::{task_fn, DagRunner};
|
use dagx::{task_fn, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::hint::black_box;
|
use std::hint::black_box;
|
||||||
@@ -20,18 +20,20 @@ pub fn bench_config_broadcast(c: &mut Criterion) {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
// Configuration source - framework wraps HashMap in Arc automatically
|
// Configuration source - framework wraps HashMap in Arc automatically
|
||||||
let config = dag.add_task(task_fn(|_: ()| async {
|
let config: TaskHandle<_> = dag
|
||||||
HashMap::from([
|
.add_task(task_fn(|_: ()| async {
|
||||||
("max_connections".to_string(), "100".to_string()),
|
HashMap::from([
|
||||||
("timeout_ms".to_string(), "5000".to_string()),
|
("max_connections".to_string(), "100".to_string()),
|
||||||
("batch_size".to_string(), "50".to_string()),
|
("timeout_ms".to_string(), "5000".to_string()),
|
||||||
("retry_count".to_string(), "3".to_string()),
|
("batch_size".to_string(), "50".to_string()),
|
||||||
("worker_threads".to_string(), "4".to_string()),
|
("retry_count".to_string(), "3".to_string()),
|
||||||
("buffer_size".to_string(), "8192".to_string()),
|
("worker_threads".to_string(), "4".to_string()),
|
||||||
("compression".to_string(), "true".to_string()),
|
("buffer_size".to_string(), "8192".to_string()),
|
||||||
("log_level".to_string(), "info".to_string()),
|
("compression".to_string(), "true".to_string()),
|
||||||
])
|
("log_level".to_string(), "info".to_string()),
|
||||||
}));
|
])
|
||||||
|
}))
|
||||||
|
.into();
|
||||||
|
|
||||||
// Workers receive and process based on shared config
|
// Workers receive and process based on shared config
|
||||||
for worker_id in 0..workers {
|
for worker_id in 0..workers {
|
||||||
@@ -49,7 +51,7 @@ pub fn bench_config_broadcast(c: &mut Criterion) {
|
|||||||
// Simulate some computation
|
// Simulate some computation
|
||||||
black_box(worker_id * batch_size + timeout / 100)
|
black_box(worker_id * batch_size + timeout / 100)
|
||||||
}))
|
}))
|
||||||
.depends_on(&config);
|
.depends_on(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! ETL (Extract, Transform, Load) pipeline benchmark
|
//! ETL (Extract, Transform, Load) pipeline benchmark
|
||||||
|
|
||||||
use criterion::{BenchmarkId, Criterion};
|
use criterion::{BenchmarkId, Criterion};
|
||||||
use dagx::{task_fn, DagRunner};
|
use dagx::{task_fn, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
|
||||||
pub fn bench_etl_pipeline(c: &mut Criterion) {
|
pub fn bench_etl_pipeline(c: &mut Criterion) {
|
||||||
@@ -21,7 +21,7 @@ pub fn bench_etl_pipeline(c: &mut Criterion) {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
// Extract: Load JSON-like data (framework wraps in Arc automatically)
|
// Extract: Load JSON-like data (framework wraps in Arc automatically)
|
||||||
let extract = dag.add_task(task_fn(move |_: ()| async move {
|
let extract: TaskHandle<_> = dag.add_task(task_fn(move |_: ()| async move {
|
||||||
(0..count)
|
(0..count)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
format!(
|
format!(
|
||||||
@@ -32,7 +32,7 @@ pub fn bench_etl_pipeline(c: &mut Criterion) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}));
|
})).into();
|
||||||
|
|
||||||
// Transform: Three parallel transformations
|
// Transform: Three parallel transformations
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ pub fn bench_etl_pipeline(c: &mut Criterion) {
|
|||||||
})
|
})
|
||||||
.count()
|
.count()
|
||||||
}))
|
}))
|
||||||
.depends_on(&extract);
|
.depends_on(extract);
|
||||||
|
|
||||||
// T2: Extract scores
|
// T2: Extract scores
|
||||||
let scores = dag
|
let scores = dag
|
||||||
@@ -60,7 +60,7 @@ pub fn bench_etl_pipeline(c: &mut Criterion) {
|
|||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}))
|
}))
|
||||||
.depends_on(&extract);
|
.depends_on(extract);
|
||||||
|
|
||||||
// T3: Extract names
|
// T3: Extract names
|
||||||
let names = dag
|
let names = dag
|
||||||
@@ -74,7 +74,7 @@ pub fn bench_etl_pipeline(c: &mut Criterion) {
|
|||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}))
|
}))
|
||||||
.depends_on(&extract);
|
.depends_on(extract);
|
||||||
|
|
||||||
// Load: Aggregate all transformations
|
// Load: Aggregate all transformations
|
||||||
dag.add_task(task_fn(
|
dag.add_task(task_fn(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Memory efficiency benchmark with large data structures
|
//! Memory efficiency benchmark with large data structures
|
||||||
|
|
||||||
use criterion::{BenchmarkId, Criterion};
|
use criterion::{BenchmarkId, Criterion};
|
||||||
use dagx::{task_fn, DagRunner};
|
use dagx::{task_fn, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use std::hint::black_box;
|
use std::hint::black_box;
|
||||||
|
|
||||||
@@ -19,10 +19,12 @@ pub fn bench_memory_efficiency(c: &mut Criterion) {
|
|||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let source = dag.add_task(task_fn(move |_: ()| async move {
|
let source: TaskHandle<_> = dag
|
||||||
// Create MB of data - framework wraps in Arc automatically
|
.add_task(task_fn(move |_: ()| async move {
|
||||||
vec![0u8; mb * 1024 * 1024]
|
// Create MB of data - framework wraps in Arc automatically
|
||||||
}));
|
vec![0u8; mb * 1024 * 1024]
|
||||||
|
}))
|
||||||
|
.into();
|
||||||
|
|
||||||
// 20 consumers share the data via automatic Arc wrapping
|
// 20 consumers share the data via automatic Arc wrapping
|
||||||
for i in 0..20 {
|
for i in 0..20 {
|
||||||
@@ -36,7 +38,7 @@ pub fn bench_memory_efficiency(c: &mut Criterion) {
|
|||||||
.sum();
|
.sum();
|
||||||
black_box(sample_sum)
|
black_box(sample_sum)
|
||||||
}))
|
}))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Mixed realistic patterns - fan-out, fan-in, and processing
|
//! Mixed realistic patterns - fan-out, fan-in, and processing
|
||||||
|
|
||||||
use criterion::Criterion;
|
use criterion::Criterion;
|
||||||
use dagx::{task_fn, DagRunner};
|
use dagx::{task_fn, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
|
||||||
pub fn bench_mixed_patterns(c: &mut Criterion) {
|
pub fn bench_mixed_patterns(c: &mut Criterion) {
|
||||||
@@ -13,28 +13,30 @@ pub fn bench_mixed_patterns(c: &mut Criterion) {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
// Stage 1: Load data (framework wraps in Arc automatically)
|
// Stage 1: Load data (framework wraps in Arc automatically)
|
||||||
let data = dag.add_task(task_fn(|_: ()| async {
|
let data: TaskHandle<_> = dag
|
||||||
(0..1000)
|
.add_task(task_fn(|_: ()| async {
|
||||||
.map(|i| format!("Record-{:04}", i))
|
(0..1000)
|
||||||
.collect::<Vec<_>>()
|
.map(|i| format!("Record-{:04}", i))
|
||||||
}));
|
.collect::<Vec<_>>()
|
||||||
|
}))
|
||||||
|
.into();
|
||||||
|
|
||||||
// Stage 2: Fan-out for parallel analysis
|
// Stage 2: Fan-out for parallel analysis
|
||||||
let analysis1 = dag
|
let analysis1 = dag
|
||||||
.add_task(task_fn(|d: Vec<String>| async move {
|
.add_task(task_fn(|d: Vec<String>| async move {
|
||||||
d.iter().filter(|s| s.contains("00")).count()
|
d.iter().filter(|s| s.contains("00")).count()
|
||||||
}))
|
}))
|
||||||
.depends_on(&data);
|
.depends_on(data);
|
||||||
|
|
||||||
let analysis2 = dag
|
let analysis2 = dag
|
||||||
.add_task(task_fn(|d: Vec<String>| async move {
|
.add_task(task_fn(|d: Vec<String>| async move {
|
||||||
d.iter().map(|s| s.len()).sum::<usize>()
|
d.iter().map(|s| s.len()).sum::<usize>()
|
||||||
}))
|
}))
|
||||||
.depends_on(&data);
|
.depends_on(data);
|
||||||
|
|
||||||
let analysis3 = dag
|
let analysis3 = dag
|
||||||
.add_task(task_fn(|d: Vec<String>| async move { d.len() }))
|
.add_task(task_fn(|d: Vec<String>| async move { d.len() }))
|
||||||
.depends_on(&data);
|
.depends_on(data);
|
||||||
|
|
||||||
// Stage 3: Fan-in to aggregate
|
// Stage 3: Fan-in to aggregate
|
||||||
let summary = dag
|
let summary = dag
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ impl Add { async fn run(a: &i32, b: &i32) -> i32 { a + b } }
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
let x = dag.add_task(Value(2));
|
let x = dag.add_task(Value(2));
|
||||||
let y = dag.add_task(Value(3));
|
let y = dag.add_task(Value(3));
|
||||||
let sum = dag.add_task(Add).depends_on((&x, &y));
|
let sum = dag.add_task(Add).depends_on((x, y));
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ async fn main() {
|
|||||||
// Step 4: Add source tasks (tasks with no dependencies)
|
// Step 4: Add source tasks (tasks with no dependencies)
|
||||||
//
|
//
|
||||||
// add_task() returns a TaskHandle<T> where T is the task's output type
|
// add_task() returns a TaskHandle<T> where T is the task's output type
|
||||||
let x = dag.add_task(Value::new(2)); // TaskHandle<i32>
|
let x = dag.add_task(Value::new(2)).into(); // TaskHandle<i32>
|
||||||
let y = dag.add_task(Value::new(3)); // TaskHandle<i32>
|
let y = dag.add_task(Value::new(3)).into(); // TaskHandle<i32>
|
||||||
|
|
||||||
// Step 5: Add a task with dependencies
|
// Step 5: Add a task with dependencies
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
//! Squared: 100
|
//! Squared: 100
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use dagx::{task, DagRunner};
|
use dagx::{task, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
|
||||||
// Source task with a value field
|
// Source task with a value field
|
||||||
@@ -131,13 +131,13 @@ async fn main() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
// Single source task - constructed with ::new()
|
// Single source task - constructed with ::new()
|
||||||
let source = dag.add_task(Source::new(10));
|
let source: TaskHandle<_> = dag.add_task(Source::new(10)).into();
|
||||||
|
|
||||||
// Multiple downstream tasks consuming the same value
|
// Multiple downstream tasks consuming the same value
|
||||||
// Each constructed differently to show flexibility
|
// Each constructed differently to show flexibility
|
||||||
let plus_one = dag.add_task(AddOffset::new(1)).depends_on(&source);
|
let plus_one = dag.add_task(AddOffset::new(1)).depends_on(source);
|
||||||
let times_two = dag.add_task(Multiply::new(2)).depends_on(&source);
|
let times_two = dag.add_task(Multiply::new(2)).depends_on(source);
|
||||||
let squared = dag.add_task(Power { exponent: 2 }).depends_on(&source);
|
let squared = dag.add_task(Power { exponent: 2 }).depends_on(source);
|
||||||
|
|
||||||
// Run the DAG
|
// Run the DAG
|
||||||
println!("Running fan-out DAG...\n");
|
println!("Running fan-out DAG...\n");
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ async fn main() {
|
|||||||
// Single downstream task consuming all values
|
// Single downstream task consuming all values
|
||||||
let profile = dag
|
let profile = dag
|
||||||
.add_task(BuildProfile)
|
.add_task(BuildProfile)
|
||||||
.depends_on((&name, &age, &city, &active));
|
.depends_on((name, age, city, active));
|
||||||
|
|
||||||
// Run the DAG
|
// Run the DAG
|
||||||
println!("Running fan-in DAG...\n");
|
println!("Running fan-in DAG...\n");
|
||||||
|
|||||||
@@ -123,9 +123,7 @@ async fn main() -> DagResult<()> {
|
|||||||
let sum3 = dag.add_task(ComputeSum::new(501, 751, WORK_DELAY));
|
let sum3 = dag.add_task(ComputeSum::new(501, 751, WORK_DELAY));
|
||||||
let sum4 = dag.add_task(ComputeSum::new(751, 1001, WORK_DELAY));
|
let sum4 = dag.add_task(ComputeSum::new(751, 1001, WORK_DELAY));
|
||||||
|
|
||||||
let total = dag
|
let total = dag.add_task(Aggregate).depends_on((sum1, sum2, sum3, sum4));
|
||||||
.add_task(Aggregate)
|
|
||||||
.depends_on((&sum1, &sum2, &sum3, &sum4));
|
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
|
|||||||
@@ -448,7 +448,7 @@ async fn main() {
|
|||||||
|
|
||||||
let fallback5 = dag
|
let fallback5 = dag
|
||||||
.add_task(FallbackService::new("ServiceCall-5"))
|
.add_task(FallbackService::new("ServiceCall-5"))
|
||||||
.depends_on(&call5);
|
.depends_on(call5);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -184,8 +184,8 @@ async fn main() -> DagResult<()> {
|
|||||||
let source_b = dag.add_task(LoadData::new("B"));
|
let source_b = dag.add_task(LoadData::new("B"));
|
||||||
|
|
||||||
// Transform: Process each source (parallel)
|
// Transform: Process each source (parallel)
|
||||||
let transform_a = dag.add_task(Transform::new(2)).depends_on(&source_a);
|
let transform_a = dag.add_task(Transform::new(2)).depends_on(source_a);
|
||||||
let transform_b = dag.add_task(Transform::new(3)).depends_on(&source_b);
|
let transform_b = dag.add_task(Transform::new(3)).depends_on(source_b);
|
||||||
|
|
||||||
// Combine: Merge the transformed datasets
|
// Combine: Merge the transformed datasets
|
||||||
let combined = dag
|
let combined = dag
|
||||||
|
|||||||
@@ -128,9 +128,9 @@ async fn main() -> DagResult<()> {
|
|||||||
let processed3 = dag.add_task(ValidateAndDouble::new(20)); // Valid: 40
|
let processed3 = dag.add_task(ValidateAndDouble::new(20)); // Valid: 40
|
||||||
|
|
||||||
// Categorize each result (also parallel, threshold = 25)
|
// Categorize each result (also parallel, threshold = 25)
|
||||||
let cat1 = dag.add_task(Categorize::new(25)).depends_on(&processed1); // 20 < 25 = "low"
|
let cat1 = dag.add_task(Categorize::new(25)).depends_on(processed1); // 20 < 25 = "low"
|
||||||
let cat2 = dag.add_task(Categorize::new(25)).depends_on(&processed2); // 0 = "invalid"
|
let cat2 = dag.add_task(Categorize::new(25)).depends_on(processed2); // 0 = "invalid"
|
||||||
let cat3 = dag.add_task(Categorize::new(25)).depends_on(&processed3); // 40 >= 25 = "high"
|
let cat3 = dag.add_task(Categorize::new(25)).depends_on(processed3); // 40 >= 25 = "high"
|
||||||
|
|
||||||
// Combine results
|
// Combine results
|
||||||
let summary = dag
|
let summary = dag
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
//! Validation: Alice is valid (age 30 >= 18)
|
//! Validation: Alice is valid (age 30 >= 18)
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use dagx::{task, DagRunner};
|
use dagx::{task, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
|
||||||
// Custom type - just derive Clone!
|
// Custom type - just derive Clone!
|
||||||
@@ -97,15 +97,17 @@ async fn main() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
// Create a task that produces a custom User type
|
// Create a task that produces a custom User type
|
||||||
let user_task = dag.add_task(CreateUser {
|
let user_task: TaskHandle<_> = dag
|
||||||
name: "Alice".to_string(),
|
.add_task(CreateUser {
|
||||||
age: 30,
|
name: "Alice".to_string(),
|
||||||
});
|
age: 30,
|
||||||
|
})
|
||||||
|
.into();
|
||||||
|
|
||||||
// Two different tasks consume the same custom User type
|
// Two different tasks consume the same custom User type
|
||||||
// dagx automatically handles Arc wrapping, so both tasks get &User
|
// dagx automatically handles Arc wrapping, so both tasks get &User
|
||||||
let greeting = dag.add_task(GreetUser).depends_on(&user_task);
|
let greeting = dag.add_task(GreetUser).depends_on(user_task);
|
||||||
let validation = dag.add_task(ValidateUser).depends_on(&user_task);
|
let validation = dag.add_task(ValidateUser).depends_on(user_task);
|
||||||
|
|
||||||
// Execute the DAG
|
// Execute the DAG
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
|
|||||||
@@ -191,15 +191,15 @@ async fn main() {
|
|||||||
// Stage 2: Data transformation (with stateful tasks)
|
// Stage 2: Data transformation (with stateful tasks)
|
||||||
let transformed_1 = dag
|
let transformed_1 = dag
|
||||||
.add_task(DataTransformer::new(1.5))
|
.add_task(DataTransformer::new(1.5))
|
||||||
.depends_on(&raw_value_1);
|
.depends_on(raw_value_1);
|
||||||
|
|
||||||
let transformed_2 = dag
|
let transformed_2 = dag
|
||||||
.add_task(DataTransformer::new(2.0))
|
.add_task(DataTransformer::new(2.0))
|
||||||
.depends_on(&raw_value_2);
|
.depends_on(raw_value_2);
|
||||||
|
|
||||||
let transformed_3 = dag
|
let transformed_3 = dag
|
||||||
.add_task(DataTransformer::new(0.5))
|
.add_task(DataTransformer::new(0.5))
|
||||||
.depends_on(&raw_value_3);
|
.depends_on(raw_value_3);
|
||||||
|
|
||||||
// Stage 3: Statistical analysis (parallel operations on transformed data)
|
// Stage 3: Statistical analysis (parallel operations on transformed data)
|
||||||
let sum = dag
|
let sum = dag
|
||||||
|
|||||||
@@ -398,7 +398,7 @@ async fn main() {
|
|||||||
// Aggregate traces
|
// Aggregate traces
|
||||||
let summary = dag
|
let summary = dag
|
||||||
.add_task(TraceAggregator::new(correlation_id))
|
.add_task(TraceAggregator::new(correlation_id))
|
||||||
.depends_on((&inventory, &payment, &shipping));
|
.depends_on((inventory, payment, shipping));
|
||||||
|
|
||||||
// Run the DAG
|
// Run the DAG
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ async fn main() {
|
|||||||
|
|
||||||
let input = dag.add_task(StringSource("42"));
|
let input = dag.add_task(StringSource("42"));
|
||||||
|
|
||||||
let parsed = dag.add_task(ParseInt).depends_on(&input);
|
let parsed = dag.add_task(ParseInt).depends_on(input);
|
||||||
|
|
||||||
let validated = dag
|
let validated = dag
|
||||||
.add_task(ValidateRange { min: 0, max: 100 })
|
.add_task(ValidateRange { min: 0, max: 100 })
|
||||||
@@ -240,7 +240,7 @@ async fn main() {
|
|||||||
|
|
||||||
let input = dag.add_task(StringSource("not a number"));
|
let input = dag.add_task(StringSource("not a number"));
|
||||||
|
|
||||||
let parsed = dag.add_task(ParseInt).depends_on(&input);
|
let parsed = dag.add_task(ParseInt).depends_on(input);
|
||||||
|
|
||||||
let validated = dag
|
let validated = dag
|
||||||
.add_task(ValidateRange { min: 0, max: 100 })
|
.add_task(ValidateRange { min: 0, max: 100 })
|
||||||
@@ -266,7 +266,7 @@ async fn main() {
|
|||||||
|
|
||||||
let input = dag.add_task(StringSource("150"));
|
let input = dag.add_task(StringSource("150"));
|
||||||
|
|
||||||
let parsed = dag.add_task(ParseInt).depends_on(&input);
|
let parsed = dag.add_task(ParseInt).depends_on(input);
|
||||||
|
|
||||||
let validated = dag
|
let validated = dag
|
||||||
.add_task(ValidateRange { min: 0, max: 100 })
|
.add_task(ValidateRange { min: 0, max: 100 })
|
||||||
@@ -292,7 +292,7 @@ async fn main() {
|
|||||||
|
|
||||||
let input = dag.add_task(StringSource("invalid"));
|
let input = dag.add_task(StringSource("invalid"));
|
||||||
|
|
||||||
let parsed = dag.add_task(ParseInt).depends_on(&input);
|
let parsed = dag.add_task(ParseInt).depends_on(input);
|
||||||
|
|
||||||
let with_fallback = dag
|
let with_fallback = dag
|
||||||
.add_task(WithFallback { fallback: 0 })
|
.add_task(WithFallback { fallback: 0 })
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ async fn main() {
|
|||||||
|
|
||||||
// Verify a few results
|
// Verify a few results
|
||||||
let sum: i32 = tasks
|
let sum: i32 = tasks
|
||||||
.iter()
|
.into_iter()
|
||||||
.take(10)
|
.take(10)
|
||||||
.map(|t| dag.get(t).unwrap())
|
.map(|t| dag.get(t).unwrap())
|
||||||
.fold(0i32, |acc, x| acc.wrapping_add(x));
|
.fold(0i32, |acc, x| acc.wrapping_add(x));
|
||||||
@@ -208,7 +208,7 @@ async fn main() {
|
|||||||
|
|
||||||
// Create a deep chain by combining adjacent task pairs
|
// Create a deep chain by combining adjacent task pairs
|
||||||
let tasks: Vec<_> = (0..100)
|
let tasks: Vec<_> = (0..100)
|
||||||
.map(|i| dag.add_task(Compute { value: i }))
|
.map(|i| dag.add_task(Compute { value: i }).into())
|
||||||
.collect();
|
.collect();
|
||||||
let mut chain_tasks = vec![];
|
let mut chain_tasks = vec![];
|
||||||
for i in 0..99 {
|
for i in 0..99 {
|
||||||
@@ -246,7 +246,7 @@ async fn main() {
|
|||||||
// Process all levels reducing by combining pairs
|
// Process all levels reducing by combining pairs
|
||||||
let mut level = 0;
|
let mut level = 0;
|
||||||
let mut layer: Vec<dagx::TaskHandle<i32>> =
|
let mut layer: Vec<dagx::TaskHandle<i32>> =
|
||||||
current_level.iter().map(|b| b.into()).collect();
|
current_level.into_iter().map(|b| b.into()).collect();
|
||||||
|
|
||||||
while layer.len() > 1 {
|
while layer.len() > 1 {
|
||||||
level += 1;
|
level += 1;
|
||||||
@@ -291,7 +291,7 @@ async fn main() {
|
|||||||
|
|
||||||
// Layer 1: 50 source tasks
|
// Layer 1: 50 source tasks
|
||||||
let sources: Vec<_> = (0..50)
|
let sources: Vec<_> = (0..50)
|
||||||
.map(|i| dag.add_task(Compute { value: i }))
|
.map(|i| dag.add_task(Compute { value: i }).into())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Layer 2: 50 tasks, each depends on all sources
|
// Layer 2: 50 tasks, each depends on all sources
|
||||||
@@ -361,11 +361,11 @@ async fn main() {
|
|||||||
|
|
||||||
// Verify results are accessible
|
// Verify results are accessible
|
||||||
let i32_sum: i32 = small_tasks
|
let i32_sum: i32 = small_tasks
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(|t| dag.get(t).unwrap())
|
.map(|t| dag.get(t).unwrap())
|
||||||
.fold(0i32, |acc, x| acc.wrapping_add(x));
|
.fold(0i32, |acc, x| acc.wrapping_add(x));
|
||||||
let string_count = string_tasks
|
let string_count = string_tasks
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(|t| dag.get(t).unwrap().len())
|
.map(|t| dag.get(t).unwrap().len())
|
||||||
.sum::<usize>();
|
.sum::<usize>();
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ async fn main() {
|
|||||||
let elapsed = start.elapsed();
|
let elapsed = start.elapsed();
|
||||||
|
|
||||||
// Verify all tasks completed correctly
|
// Verify all tasks completed correctly
|
||||||
for (i, task) in tasks.iter().enumerate() {
|
for (i, task) in tasks.into_iter().enumerate() {
|
||||||
assert_eq!(dag.get(task).unwrap(), i as u32);
|
assert_eq!(dag.get(task).unwrap(), i as u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-8
@@ -98,7 +98,7 @@
|
|||||||
//! === Timeout Example Complete ===
|
//! === Timeout Example Complete ===
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use dagx::{task, DagRunner};
|
use dagx::{task, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use tokio::time::{sleep, timeout, Duration};
|
use tokio::time::{sleep, timeout, Duration};
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ async fn main() {
|
|||||||
timeout_ms: 200,
|
timeout_ms: 200,
|
||||||
});
|
});
|
||||||
|
|
||||||
let processed = dag.add_task(ProcessResult).depends_on(&timed);
|
let processed = dag.add_task(ProcessResult).depends_on(timed);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -241,13 +241,15 @@ async fn main() {
|
|||||||
{
|
{
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let source = dag.add_task(TaskWithTimeout {
|
let source: TaskHandle<_> = dag
|
||||||
work_duration_ms: 500,
|
.add_task(TaskWithTimeout {
|
||||||
timeout_ms: 100,
|
work_duration_ms: 500,
|
||||||
});
|
timeout_ms: 100,
|
||||||
|
})
|
||||||
|
.into();
|
||||||
|
|
||||||
let processed1 = dag.add_task(ProcessResult).depends_on(&source);
|
let processed1 = dag.add_task(ProcessResult).depends_on(source);
|
||||||
let processed2 = dag.add_task(ProcessResult).depends_on(&source);
|
let processed2 = dag.add_task(ProcessResult).depends_on(source);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ async fn main() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
// Layer 0: Source
|
// Layer 0: Source
|
||||||
let source = dag.add_task(Value(10));
|
let source = dag.add_task(Value(10)).into();
|
||||||
|
|
||||||
// Layer 1: Two parallel computations
|
// Layer 1: Two parallel computations
|
||||||
let left = dag.add_task(Add).depends_on((&source, &source));
|
let left = dag.add_task(Add).depends_on((&source, &source));
|
||||||
@@ -112,7 +112,7 @@ async fn main() {
|
|||||||
|
|
||||||
// Create a linear chain - each layer has only one task
|
// Create a linear chain - each layer has only one task
|
||||||
// This should trigger inline execution optimization
|
// This should trigger inline execution optimization
|
||||||
let a = dag.add_task(Value(1));
|
let a = dag.add_task(Value(1)).into();
|
||||||
let b = dag.add_task(Add).depends_on((&a, &a));
|
let b = dag.add_task(Add).depends_on((&a, &a));
|
||||||
let c = dag.add_task(Multiply).depends_on((&b, &b));
|
let c = dag.add_task(Multiply).depends_on((&b, &b));
|
||||||
let d = dag.add_task(Add).depends_on((&c, &c));
|
let d = dag.add_task(Add).depends_on((&c, &c));
|
||||||
@@ -132,10 +132,10 @@ async fn main() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
// Layer 0: Multiple sources
|
// Layer 0: Multiple sources
|
||||||
let v1 = dag.add_task(Value(1));
|
let v1 = dag.add_task(Value(1)).into();
|
||||||
let v2 = dag.add_task(Value(2));
|
let v2 = dag.add_task(Value(2)).into();
|
||||||
let v3 = dag.add_task(Value(3));
|
let v3 = dag.add_task(Value(3)).into();
|
||||||
let v4 = dag.add_task(Value(4));
|
let v4 = dag.add_task(Value(4)).into();
|
||||||
|
|
||||||
// Layer 1: Multiple parallel computations
|
// Layer 1: Multiple parallel computations
|
||||||
let sum12 = dag.add_task(Add).depends_on((&v1, &v2));
|
let sum12 = dag.add_task(Add).depends_on((&v1, &v2));
|
||||||
|
|||||||
+4
-8
@@ -63,11 +63,11 @@ impl IsUnitType for () {}
|
|||||||
/// let b = dag.add_task(Multiplier::new(2));
|
/// let b = dag.add_task(Multiplier::new(2));
|
||||||
/// // b is TaskBuilder<_, Pending> until we call depends_on()
|
/// // b is TaskBuilder<_, Pending> until we call depends_on()
|
||||||
///
|
///
|
||||||
/// let b = b.depends_on(&a);
|
/// let b = b.depends_on(a);
|
||||||
/// // Now b is a TaskHandle<i32>
|
/// // Now b is a TaskHandle<i32>
|
||||||
///
|
///
|
||||||
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
||||||
/// assert_eq!(dag.get(&b).unwrap(), 20);
|
/// assert_eq!(dag.get(b).unwrap(), 20);
|
||||||
/// # };
|
/// # };
|
||||||
/// ```
|
/// ```
|
||||||
pub struct TaskBuilder<'a, Tk: Task, Deps> {
|
pub struct TaskBuilder<'a, Tk: Task, Deps> {
|
||||||
@@ -118,14 +118,14 @@ impl<'a, Tk: Task, Deps> TaskBuilder<'a, Tk, Deps> {
|
|||||||
/// # async {
|
/// # async {
|
||||||
/// let dag = DagRunner::new();
|
/// let dag = DagRunner::new();
|
||||||
///
|
///
|
||||||
/// let x = dag.add_task(Value(2));
|
/// let x = dag.add_task(Value(2)).into();
|
||||||
/// let y = dag.add_task(Value(3));
|
/// let y = dag.add_task(Value(3));
|
||||||
///
|
///
|
||||||
/// // Single dependency
|
/// // Single dependency
|
||||||
/// let double = dag.add_task(Scale(2)).depends_on(&x);
|
/// let double = dag.add_task(Scale(2)).depends_on(&x);
|
||||||
///
|
///
|
||||||
/// // Multiple dependencies: tuple form
|
/// // Multiple dependencies: tuple form
|
||||||
/// let sum = dag.add_task(Add).depends_on((&x, &y));
|
/// let sum = dag.add_task(Add).depends_on((&x, y));
|
||||||
///
|
///
|
||||||
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
||||||
/// # };
|
/// # };
|
||||||
@@ -169,7 +169,3 @@ impl<'a, Tk: Task, Deps> TaskBuilder<'a, Tk, Deps> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
// Note: We cannot safely implement AsRef<TaskHandle<Tk::Output>> for TaskBuilder<Tk, Deps>
|
|
||||||
// because Handle is Copy and returning a reference to a temporary would be unsound.
|
|
||||||
// Instead, users should use From/Into conversions or call .into() explicitly.
|
|
||||||
@@ -76,7 +76,7 @@ fn test_task_builder_depends_on_returns_handle() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let source = dag.add_task(TestTask { value: 10 });
|
let source = dag.add_task(TestTask { value: 10 });
|
||||||
let dependent = dag.add_task(TestTaskWithInput).depends_on(&source);
|
let dependent = dag.add_task(TestTaskWithInput).depends_on(source);
|
||||||
|
|
||||||
// depends_on should return a TaskHandle
|
// depends_on should return a TaskHandle
|
||||||
let _handle: TaskHandle<i32> = dependent;
|
let _handle: TaskHandle<i32> = dependent;
|
||||||
@@ -87,12 +87,12 @@ fn test_task_builder_chain() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
// Test chaining multiple tasks
|
// Test chaining multiple tasks
|
||||||
let t1 = dag.add_task(TestTask { value: 1 });
|
let t1 = dag.add_task(TestTask { value: 1 }).into();
|
||||||
let t2 = dag.add_task(TestTaskWithInput).depends_on(&t1);
|
let t2 = dag.add_task(TestTaskWithInput).depends_on(t1);
|
||||||
let t3 = dag.add_task(TestTaskWithInput).depends_on(t2);
|
let t3 = dag.add_task(TestTaskWithInput).depends_on(t2);
|
||||||
|
|
||||||
// All should return TaskHandles
|
// All should return TaskHandles
|
||||||
let _: TaskHandle<i32> = t1.into();
|
let _: TaskHandle<i32> = t1;
|
||||||
let _: TaskHandle<i32> = t2;
|
let _: TaskHandle<i32> = t2;
|
||||||
let _: TaskHandle<i32> = t3;
|
let _: TaskHandle<i32> = t3;
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,7 @@ fn test_multiple_dependencies() {
|
|||||||
let b = dag.add_task(TestTask { value: 20 });
|
let b = dag.add_task(TestTask { value: 20 });
|
||||||
|
|
||||||
// Test multiple dependencies
|
// Test multiple dependencies
|
||||||
let sum = dag.add_task(AddTask).depends_on((&a, &b));
|
let sum = dag.add_task(AddTask).depends_on((a, b));
|
||||||
|
|
||||||
// Should return a TaskHandle
|
// Should return a TaskHandle
|
||||||
let _: TaskHandle<i32> = sum;
|
let _: TaskHandle<i32> = sum;
|
||||||
|
|||||||
+53
-59
@@ -11,52 +11,60 @@ use crate::builder::TaskBuilder;
|
|||||||
use crate::task::Task;
|
use crate::task::Task;
|
||||||
use crate::types::{NodeId, TaskHandle};
|
use crate::types::{NodeId, TaskHandle};
|
||||||
|
|
||||||
|
trait IntoNodeRef {
|
||||||
|
type Output;
|
||||||
|
fn into_node_ref(self) -> NodeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IntoNodeRef for &TaskHandle<T> {
|
||||||
|
type Output = T;
|
||||||
|
fn into_node_ref(self) -> NodeId {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IntoNodeRef for TaskHandle<T> {
|
||||||
|
type Output = T;
|
||||||
|
fn into_node_ref(self) -> NodeId {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'runner, Tk: Task, Deps> IntoNodeRef for TaskBuilder<'runner, Tk, Deps> {
|
||||||
|
type Output = Tk::Output;
|
||||||
|
|
||||||
|
fn into_node_ref(self) -> NodeId {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) trait DepsTuple<Input> {
|
pub(crate) trait DepsTuple<Input> {
|
||||||
fn to_node_ids(&self) -> Vec<NodeId>;
|
fn to_node_ids(self) -> Vec<NodeId>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implementation for unit (no dependencies)
|
// Implementation for unit (no dependencies)
|
||||||
impl DepsTuple<()> for () {
|
impl DepsTuple<()> for () {
|
||||||
fn to_node_ids(&self) -> Vec<NodeId> {
|
fn to_node_ids(self) -> Vec<NodeId> {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implementation for single dependency (all forms)
|
// Implementation for single dependency (all forms)
|
||||||
impl<T> DepsTuple<T> for &TaskHandle<T> {
|
impl<T> DepsTuple<T::Output> for T
|
||||||
fn to_node_ids(&self) -> Vec<NodeId> {
|
where
|
||||||
vec![self.id]
|
T: IntoNodeRef,
|
||||||
|
{
|
||||||
|
fn to_node_ids(self) -> Vec<NodeId> {
|
||||||
|
vec![self.into_node_ref()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> DepsTuple<T> for TaskHandle<T> {
|
impl<T> DepsTuple<T::Output> for (T,)
|
||||||
fn to_node_ids(&self) -> Vec<NodeId> {
|
where
|
||||||
vec![self.id]
|
T: IntoNodeRef,
|
||||||
}
|
{
|
||||||
}
|
fn to_node_ids(self) -> Vec<NodeId> {
|
||||||
|
vec![self.0.into_node_ref()]
|
||||||
impl<T> DepsTuple<T> for (&TaskHandle<T>,) {
|
|
||||||
fn to_node_ids(&self) -> Vec<NodeId> {
|
|
||||||
vec![self.0.id]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> DepsTuple<T> for (TaskHandle<T>,) {
|
|
||||||
fn to_node_ids(&self) -> Vec<NodeId> {
|
|
||||||
vec![self.0.id]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Support for &TaskBuilder
|
|
||||||
impl<'a, Tk: Task, Deps> DepsTuple<Tk::Output> for &TaskBuilder<'a, Tk, Deps> {
|
|
||||||
fn to_node_ids(&self) -> Vec<NodeId> {
|
|
||||||
vec![self.id]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Tk: Task, Deps> DepsTuple<Tk::Output> for (&TaskBuilder<'a, Tk, Deps>,) {
|
|
||||||
fn to_node_ids(&self) -> Vec<NodeId> {
|
|
||||||
vec![self.0.id]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,10 +73,6 @@ impl<'a, Tk: Task, Deps> DepsTuple<Tk::Output> for (&TaskBuilder<'a, Tk, Deps>,)
|
|||||||
/// This macro exists because Rust lacks variadic generics - we need separate implementations
|
/// This macro exists because Rust lacks variadic generics - we need separate implementations
|
||||||
/// for each tuple size.
|
/// for each tuple size.
|
||||||
///
|
///
|
||||||
/// For each tuple size, generates two implementations:
|
|
||||||
/// - `(&TaskHandle<A>, &TaskHandle<B>, ...)` → `DepsTuple<(A, B, ...)>`
|
|
||||||
/// - `(&TaskBuilder<TkA, _>, &TaskBuilder<TkB, _>, ...)` → `DepsTuple<(TkA::Output, TkB::Output, ...)>`
|
|
||||||
///
|
|
||||||
/// This allows flexible dependency specification:
|
/// This allows flexible dependency specification:
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// // Using handles
|
/// // Using handles
|
||||||
@@ -78,37 +82,27 @@ impl<'a, Tk: Task, Deps> DepsTuple<Tk::Output> for (&TaskBuilder<'a, Tk, Deps>,)
|
|||||||
/// task.depends_on((&dag.add_task(TaskA), &dag.add_task(TaskB)))
|
/// task.depends_on((&dag.add_task(TaskA), &dag.add_task(TaskB)))
|
||||||
/// ```
|
/// ```
|
||||||
macro_rules! impl_deps_tuple {
|
macro_rules! impl_deps_tuple {
|
||||||
($($T:ident : $Tk:ident : $D:ident),+) => {
|
($($T:ident),+) => {
|
||||||
// For &TaskHandle tuples
|
// For tuples of any combination of &TaskHandle, TaskHandle, and TaskBuilder
|
||||||
impl<$($T),+> DepsTuple<($($T,)+)> for ($(&TaskHandle<$T>,)+) {
|
impl<$($T: IntoNodeRef),+> DepsTuple<($($T::Output,)+)> for ($($T,)+) {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn to_node_ids(&self) -> Vec<NodeId> {
|
fn to_node_ids(self) -> Vec<NodeId> {
|
||||||
let ($($T,)+) = self;
|
let ($($T,)+) = self;
|
||||||
vec![$($T.id,)+]
|
vec![$($T.into_node_ref(),)+]
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For &TaskBuilder tuples
|
|
||||||
impl<'a, $($Tk: Task, $D),+> DepsTuple<($($Tk::Output,)+)> for ($(&TaskBuilder<'a, $Tk, $D>,)+) {
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn to_node_ids(&self) -> Vec<NodeId> {
|
|
||||||
let ($($T,)+) = self;
|
|
||||||
vec![$($T.id,)+]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate DepsTuple implementations for tuples of size 2-8.
|
// Generate DepsTuple implementations for tuples of size 2-8.
|
||||||
// Each invocation generates 2 implementations (TaskHandle refs and TaskBuilder refs).
|
|
||||||
// Supporting up to 8 elements covers the vast majority of use cases.
|
// Supporting up to 8 elements covers the vast majority of use cases.
|
||||||
impl_deps_tuple!(A:TkA:DA, B:TkB:DB);
|
impl_deps_tuple!(T1, T2);
|
||||||
impl_deps_tuple!(A:TkA:DA, B:TkB:DB, C:TkC:DC);
|
impl_deps_tuple!(T1, T2, T3);
|
||||||
impl_deps_tuple!(A:TkA:DA, B:TkB:DB, C:TkC:DC, D:TkD:DD);
|
impl_deps_tuple!(T1, T2, T3, T4);
|
||||||
impl_deps_tuple!(A:TkA:DA, B:TkB:DB, C:TkC:DC, D:TkD:DD, E:TkE:DE);
|
impl_deps_tuple!(T1, T2, T3, T4, T5);
|
||||||
impl_deps_tuple!(A:TkA:DA, B:TkB:DB, C:TkC:DC, D:TkD:DD, E:TkE:DE, F:TkF:DF);
|
impl_deps_tuple!(T1, T2, T3, T4, T5, T6);
|
||||||
impl_deps_tuple!(A:TkA:DA, B:TkB:DB, C:TkC:DC, D:TkD:DD, E:TkE:DE, F:TkF:DF, G:TkG:DG);
|
impl_deps_tuple!(T1, T2, T3, T4, T5, T6, T7);
|
||||||
impl_deps_tuple!(A:TkA:DA, B:TkB:DB, C:TkC:DC, D:TkD:DD, E:TkE:DE, F:TkF:DF, G:TkG:DG, H:TkH:DH);
|
impl_deps_tuple!(T1, T2, T3, T4, T5, T6, T7, T8);
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
+13
-14
@@ -91,12 +91,13 @@ fn test_deps_tuple_task_builder_ref() {
|
|||||||
// Test lines 51-55 in deps.rs - &TaskBuilder
|
// Test lines 51-55 in deps.rs - &TaskBuilder
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
let builder = dag.add_task(TestTask { value: 42 });
|
let builder = dag.add_task(TestTask { value: 42 });
|
||||||
|
let builder_id = builder.id;
|
||||||
|
|
||||||
// We need to test the DepsTuple implementation directly
|
// We need to test the DepsTuple implementation directly
|
||||||
// The builder has an id field we can check
|
// The builder has an id field we can check
|
||||||
let node_ids = (&builder).to_node_ids();
|
let node_ids = builder.to_node_ids();
|
||||||
assert_eq!(node_ids.len(), 1);
|
assert_eq!(node_ids.len(), 1);
|
||||||
assert_eq!(node_ids[0], builder.id);
|
assert_eq!(node_ids[0], builder_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -104,10 +105,11 @@ fn test_deps_tuple_task_builder_tuple() {
|
|||||||
// Test lines 57-61 in deps.rs - (&TaskBuilder,)
|
// Test lines 57-61 in deps.rs - (&TaskBuilder,)
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
let builder = dag.add_task(TestTask { value: 100 });
|
let builder = dag.add_task(TestTask { value: 100 });
|
||||||
|
let builder_id = builder.id;
|
||||||
|
|
||||||
let node_ids = (&builder,).to_node_ids();
|
let node_ids = (builder,).to_node_ids();
|
||||||
assert_eq!(node_ids.len(), 1);
|
assert_eq!(node_ids.len(), 1);
|
||||||
assert_eq!(node_ids[0], builder.id);
|
assert_eq!(node_ids[0], builder_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -145,21 +147,18 @@ fn test_deps_tuple_multiple_builders() {
|
|||||||
// Test macro-generated implementations for multiple builders
|
// Test macro-generated implementations for multiple builders
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
let builder1 = dag.add_task(TestTask { value: 10 });
|
let builder1 = dag.add_task(TestTask { value: 10 });
|
||||||
|
let builder1_id = builder1.id;
|
||||||
let builder2 = dag.add_task(TestTask { value: 20 });
|
let builder2 = dag.add_task(TestTask { value: 20 });
|
||||||
|
let builder2_id = builder2.id;
|
||||||
let builder3 = dag.add_task(TestTask { value: 30 });
|
let builder3 = dag.add_task(TestTask { value: 30 });
|
||||||
|
let builder3_id = builder3.id;
|
||||||
// Test 2-tuple of builders
|
|
||||||
let node_ids = (&builder1, &builder2).to_node_ids();
|
|
||||||
assert_eq!(node_ids.len(), 2);
|
|
||||||
assert_eq!(node_ids[0], builder1.id);
|
|
||||||
assert_eq!(node_ids[1], builder2.id);
|
|
||||||
|
|
||||||
// Test 3-tuple of builders
|
// Test 3-tuple of builders
|
||||||
let node_ids = (&builder1, &builder2, &builder3).to_node_ids();
|
let node_ids = (builder1, builder2, builder3).to_node_ids();
|
||||||
assert_eq!(node_ids.len(), 3);
|
assert_eq!(node_ids.len(), 3);
|
||||||
assert_eq!(node_ids[0], builder1.id);
|
assert_eq!(node_ids[0], builder1_id);
|
||||||
assert_eq!(node_ids[1], builder2.id);
|
assert_eq!(node_ids[1], builder2_id);
|
||||||
assert_eq!(node_ids[2], builder3.id);
|
assert_eq!(node_ids[2], builder3_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+12
-12
@@ -23,7 +23,7 @@
|
|||||||
//! # Quick Start
|
//! # Quick Start
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! use dagx::{task, DagRunner, Task};
|
//! use dagx::{task, DagRunner, Task, TaskHandle};
|
||||||
//! use futures::FutureExt; // for FutureExt::map
|
//! use futures::FutureExt; // for FutureExt::map
|
||||||
//!
|
//!
|
||||||
//! // Source task with read-only state (tuple struct)
|
//! // Source task with read-only state (tuple struct)
|
||||||
@@ -47,10 +47,10 @@
|
|||||||
//!
|
//!
|
||||||
//! // Add source tasks
|
//! // Add source tasks
|
||||||
//! let x = dag.add_task(Value(2));
|
//! let x = dag.add_task(Value(2));
|
||||||
//! let y = dag.add_task(Value(3));
|
//! let y: TaskHandle<_> = dag.add_task(Value(3)).into(); // Allows y to be reused as a dependency
|
||||||
//!
|
//!
|
||||||
//! // Add task with dependencies
|
//! // Add task with dependencies
|
||||||
//! let sum = dag.add_task(Add).depends_on((&x, &y));
|
//! let sum = dag.add_task(Add).depends_on((x, &y));
|
||||||
//!
|
//!
|
||||||
//! // Execute and retrieve results
|
//! // Execute and retrieve results
|
||||||
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
||||||
@@ -255,7 +255,7 @@
|
|||||||
//! # async {
|
//! # async {
|
||||||
//! # let dag = DagRunner::new();
|
//! # let dag = DagRunner::new();
|
||||||
//! # let source = dag.add_task(Value(42));
|
//! # let source = dag.add_task(Value(42));
|
||||||
//! let doubled = dag.add_task(Double).depends_on(&source);
|
//! let doubled = dag.add_task(Double).depends_on(source);
|
||||||
//! # };
|
//! # };
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
@@ -279,7 +279,7 @@
|
|||||||
//! # let dag = DagRunner::new();
|
//! # let dag = DagRunner::new();
|
||||||
//! # let x = dag.add_task(Value(2));
|
//! # let x = dag.add_task(Value(2));
|
||||||
//! # let y = dag.add_task(Value(3));
|
//! # let y = dag.add_task(Value(3));
|
||||||
//! let sum = dag.add_task(Add).depends_on((&x, &y));
|
//! let sum = dag.add_task(Add).depends_on((x, y));
|
||||||
//! # };
|
//! # };
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
@@ -320,7 +320,7 @@
|
|||||||
//! # async {
|
//! # async {
|
||||||
//! let dag = DagRunner::new();
|
//! let dag = DagRunner::new();
|
||||||
//!
|
//!
|
||||||
//! let base = dag.add_task(Value(10));
|
//! let base = dag.add_task(Value(10)).into();
|
||||||
//! let plus1 = dag.add_task(Add(1)).depends_on(&base);
|
//! let plus1 = dag.add_task(Add(1)).depends_on(&base);
|
||||||
//! let times2 = dag.add_task(Scale(2)).depends_on(&base);
|
//! let times2 = dag.add_task(Scale(2)).depends_on(&base);
|
||||||
//!
|
//!
|
||||||
@@ -379,7 +379,7 @@
|
|||||||
//! let name = dag.add_task(Name("Alice".to_string()));
|
//! let name = dag.add_task(Name("Alice".to_string()));
|
||||||
//! let age = dag.add_task(Age(30));
|
//! let age = dag.add_task(Age(30));
|
||||||
//! let active = dag.add_task(Active(true));
|
//! let active = dag.add_task(Active(true));
|
||||||
//! let result = dag.add_task(FormatUser).depends_on((&name, &age, &active));
|
//! let result = dag.add_task(FormatUser).depends_on((name, age, active));
|
||||||
//!
|
//!
|
||||||
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
||||||
//!
|
//!
|
||||||
@@ -424,12 +424,12 @@
|
|||||||
//!
|
//!
|
||||||
//! // Layer 1: Sources
|
//! // Layer 1: Sources
|
||||||
//! let x = dag.add_task(Value(2));
|
//! let x = dag.add_task(Value(2));
|
||||||
//! let y = dag.add_task(Value(3));
|
//! let y = dag.add_task(Value(3)).into();
|
||||||
//! let z = dag.add_task(Value(5));
|
//! let z = dag.add_task(Value(5));
|
||||||
//!
|
//!
|
||||||
//! // Layer 2: Intermediate computations
|
//! // Layer 2: Intermediate computations
|
||||||
//! let sum_xy = dag.add_task(Add).depends_on((&x, &y)); // 2 + 3 = 5
|
//! let sum_xy = dag.add_task(Add).depends_on((x, &y)); // 2 + 3 = 5
|
||||||
//! let prod_yz = dag.add_task(Multiply).depends_on((&y, &z)); // 3 * 5 = 15
|
//! let prod_yz = dag.add_task(Multiply).depends_on((&y, z)); // 3 * 5 = 15
|
||||||
//!
|
//!
|
||||||
//! // Layer 3: Final result
|
//! // Layer 3: Final result
|
||||||
//! let total = dag.add_task(Add).depends_on((&sum_xy, &prod_yz)); // 5 + 15 = 20
|
//! let total = dag.add_task(Add).depends_on((&sum_xy, &prod_yz)); // 5 + 15 = 20
|
||||||
@@ -476,7 +476,7 @@
|
|||||||
//! let dag = DagRunner::new();
|
//! let dag = DagRunner::new();
|
||||||
//!
|
//!
|
||||||
//! let user = dag.add_task(CreateUser);
|
//! let user = dag.add_task(CreateUser);
|
||||||
//! let formatted = dag.add_task(FormatUser).depends_on(&user);
|
//! let formatted = dag.add_task(FormatUser).depends_on(user);
|
||||||
//!
|
//!
|
||||||
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
||||||
//!
|
//!
|
||||||
@@ -656,7 +656,7 @@
|
|||||||
//!
|
//!
|
||||||
//! # async {
|
//! # async {
|
||||||
//! let dag = DagRunner::new();
|
//! let dag = DagRunner::new();
|
||||||
//! let data = dag.add_task(FetchData);
|
//! let data = dag.add_task(FetchData).into();
|
||||||
//!
|
//!
|
||||||
//! // All three tasks get efficient Arc-wrapped sharing automatically
|
//! // All three tasks get efficient Arc-wrapped sharing automatically
|
||||||
//! let task1 = dag.add_task(ProcessData).depends_on(&data);
|
//! let task1 = dag.add_task(ProcessData).depends_on(&data);
|
||||||
|
|||||||
+4
-4
@@ -83,7 +83,7 @@ impl<'a> Drop for RunGuard<'a> {
|
|||||||
/// // Construct instances using ::new() pattern
|
/// // Construct instances using ::new() pattern
|
||||||
/// let x = dag.add_task(LoadValue::new(2));
|
/// let x = dag.add_task(LoadValue::new(2));
|
||||||
/// let y = dag.add_task(LoadValue::new(3));
|
/// let y = dag.add_task(LoadValue::new(3));
|
||||||
/// let sum = dag.add_task(Add).depends_on((&x, &y));
|
/// let sum = dag.add_task(Add).depends_on((x, y));
|
||||||
///
|
///
|
||||||
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
||||||
///
|
///
|
||||||
@@ -190,10 +190,10 @@ impl DagRunner {
|
|||||||
/// let base = dag.add_task(LoadValue::new(10));
|
/// let base = dag.add_task(LoadValue::new(10));
|
||||||
///
|
///
|
||||||
/// // Construct task with offset of 1
|
/// // Construct task with offset of 1
|
||||||
/// let inc = dag.add_task(AddOffset::new(1)).depends_on(&base);
|
/// let inc = dag.add_task(AddOffset::new(1)).depends_on(base);
|
||||||
///
|
///
|
||||||
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
|
||||||
/// assert_eq!(dag.get(&inc).unwrap(), 11);
|
/// assert_eq!(dag.get(inc).unwrap(), 11);
|
||||||
/// # };
|
/// # };
|
||||||
/// ```
|
/// ```
|
||||||
pub fn add_task<Tk>(&self, task: Tk) -> TaskBuilder<'_, Tk, Pending>
|
pub fn add_task<Tk>(&self, task: Tk) -> TaskBuilder<'_, Tk, Pending>
|
||||||
@@ -278,7 +278,7 @@ impl DagRunner {
|
|||||||
///
|
///
|
||||||
/// let a = dag.add_task(Value(1));
|
/// let a = dag.add_task(Value(1));
|
||||||
/// let b = dag.add_task(Value(2));
|
/// let b = dag.add_task(Value(2));
|
||||||
/// let sum = dag.add_task(Add).depends_on((&a, &b));
|
/// let sum = dag.add_task(Add).depends_on((a, b));
|
||||||
///
|
///
|
||||||
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap(); // Executes all tasks
|
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap(); // Executes all tasks
|
||||||
/// # };
|
/// # };
|
||||||
|
|||||||
+7
-7
@@ -190,16 +190,16 @@ async fn test_multiple_get_calls() {
|
|||||||
init_tracing();
|
init_tracing();
|
||||||
// Test that get() can be called multiple times for the same handle
|
// Test that get() can be called multiple times for the same handle
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
let handle = dag.add_task(TestTask { value: 100 });
|
let handle: TaskHandle<_> = dag.add_task(TestTask { value: 100 }).into();
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Multiple get calls should all return the same value
|
// Multiple get calls should all return the same value
|
||||||
let result1 = dag.get(&handle).unwrap();
|
let result1 = dag.get(handle).unwrap();
|
||||||
let result2 = dag.get(&handle).unwrap();
|
let result2 = dag.get(handle).unwrap();
|
||||||
let result3 = dag.get(&handle).unwrap();
|
let result3 = dag.get(handle).unwrap();
|
||||||
|
|
||||||
assert_eq!(result1, 100);
|
assert_eq!(result1, 100);
|
||||||
assert_eq!(result2, 100);
|
assert_eq!(result2, 100);
|
||||||
@@ -264,11 +264,11 @@ async fn test_task_panic_in_multi_task_layer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
let source = dag.add_task(Source);
|
let source: TaskHandle<_> = dag.add_task(Source).into();
|
||||||
|
|
||||||
// Create two tasks in the same layer - one panics, one doesn't
|
// Create two tasks in the same layer - one panics, one doesn't
|
||||||
let _panic_task = dag.add_task(PanicTask).depends_on(&source);
|
let _panic_task = dag.add_task(PanicTask).depends_on(source);
|
||||||
let _good_task = dag.add_task(GoodTask).depends_on(&source);
|
let _good_task = dag.add_task(GoodTask).depends_on(source);
|
||||||
|
|
||||||
// Run the DAG - the panic should be caught
|
// Run the DAG - the panic should be caught
|
||||||
let result = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
let result = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
||||||
|
|||||||
@@ -75,18 +75,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Tk: Task, Deps> From<&TaskBuilder<'a, Tk, Deps>> for TaskHandle<Tk::Output>
|
|
||||||
where
|
|
||||||
Tk::Input: IsUnitType,
|
|
||||||
{
|
|
||||||
fn from(node: &TaskBuilder<'a, Tk, Deps>) -> Self {
|
|
||||||
TaskHandle {
|
|
||||||
id: node.id,
|
|
||||||
_phantom: std::marker::PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TaskHandle can be converted from &TaskHandle (for .get() calls)
|
// TaskHandle can be converted from &TaskHandle (for .get() calls)
|
||||||
impl<T> From<&TaskHandle<T>> for TaskHandle<T> {
|
impl<T> From<&TaskHandle<T>> for TaskHandle<T> {
|
||||||
fn from(handle: &TaskHandle<T>) -> Self {
|
fn from(handle: &TaskHandle<T>) -> Self {
|
||||||
|
|||||||
@@ -147,25 +147,3 @@ fn test_task_handle_ref_from_conversion() {
|
|||||||
assert_eq!(converted.id.0, 123);
|
assert_eq!(converted.id.0, 123);
|
||||||
assert_eq!(handle.id, converted.id);
|
assert_eq!(handle.id, converted.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_task_builder_ref_from_conversion() {
|
|
||||||
use crate::runner::DagRunner;
|
|
||||||
|
|
||||||
// Test From<&TaskBuilder> for TaskHandle (line 77-87, specifically line 83)
|
|
||||||
struct SimpleTask2;
|
|
||||||
#[crate::task]
|
|
||||||
impl SimpleTask2 {
|
|
||||||
async fn run(&self) -> String {
|
|
||||||
"test".to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let dag = DagRunner::new();
|
|
||||||
let builder = dag.add_task(SimpleTask2);
|
|
||||||
let builder_id = builder.id;
|
|
||||||
|
|
||||||
// This conversion uses the From<&TaskBuilder> implementation
|
|
||||||
let handle: TaskHandle<String> = (&builder).into();
|
|
||||||
assert_eq!(handle.id, builder_id);
|
|
||||||
}
|
|
||||||
+63
-59
@@ -9,7 +9,7 @@
|
|||||||
//!
|
//!
|
||||||
//! These tests serve as both validation and documentation for proper Arc usage.
|
//! These tests serve as both validation and documentation for proper Arc usage.
|
||||||
|
|
||||||
use dagx::{task, DagRunner};
|
use dagx::{task, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -42,7 +42,7 @@ async fn test_arc_basic_pipeline() {
|
|||||||
|
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
let producer = dag.add_task(Producer);
|
let producer = dag.add_task(Producer);
|
||||||
let consumer = dag.add_task(Consumer).depends_on(&producer);
|
let consumer = dag.add_task(Consumer).depends_on(producer);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -98,7 +98,7 @@ async fn test_arc_with_primitives() {
|
|||||||
let bool_prod = dag.add_task(BoolProducer);
|
let bool_prod = dag.add_task(BoolProducer);
|
||||||
let consumer = dag
|
let consumer = dag
|
||||||
.add_task(AllConsumer)
|
.add_task(AllConsumer)
|
||||||
.depends_on((&i32_prod, &f64_prod, &bool_prod));
|
.depends_on((i32_prod, f64_prod, bool_prod));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -172,7 +172,7 @@ async fn test_arc_with_collections() {
|
|||||||
let set_prod = dag.add_task(SetProducer);
|
let set_prod = dag.add_task(SetProducer);
|
||||||
let consumer = dag
|
let consumer = dag
|
||||||
.add_task(CollectionConsumer)
|
.add_task(CollectionConsumer)
|
||||||
.depends_on((&vec_prod, &map_prod, &set_prod));
|
.depends_on((vec_prod, map_prod, set_prod));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -240,10 +240,10 @@ async fn test_arc_with_custom_structs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
let person_prod = dag.add_task(PersonProducer);
|
let person_prod: TaskHandle<_> = dag.add_task(PersonProducer).into();
|
||||||
let name_ext = dag.add_task(NameExtractor).depends_on(&person_prod);
|
let name_ext = dag.add_task(NameExtractor).depends_on(person_prod);
|
||||||
let age_ext = dag.add_task(AgeExtractor).depends_on(&person_prod);
|
let age_ext = dag.add_task(AgeExtractor).depends_on(person_prod);
|
||||||
let email_ext = dag.add_task(EmailCountExtractor).depends_on(&person_prod);
|
let email_ext = dag.add_task(EmailCountExtractor).depends_on(person_prod);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -306,10 +306,10 @@ async fn test_arc_fan_out_three_consumers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
let producer = dag.add_task(DataProducer);
|
let producer: TaskHandle<_> = dag.add_task(DataProducer).into();
|
||||||
let c1 = dag.add_task(CountConsumer).depends_on(&producer);
|
let c1 = dag.add_task(CountConsumer).depends_on(producer);
|
||||||
let c2 = dag.add_task(FirstConsumer).depends_on(&producer);
|
let c2 = dag.add_task(FirstConsumer).depends_on(producer);
|
||||||
let c3 = dag.add_task(JoinConsumer).depends_on(&producer);
|
let c3 = dag.add_task(JoinConsumer).depends_on(producer);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -378,12 +378,12 @@ async fn test_arc_fan_out_five_consumers_large_data() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
let producer = dag.add_task(LargeDataProducer);
|
let producer: TaskHandle<_> = dag.add_task(LargeDataProducer).into();
|
||||||
let c1 = dag.add_task(First100Analyzer).depends_on(&producer);
|
let c1 = dag.add_task(First100Analyzer).depends_on(producer);
|
||||||
let c2 = dag.add_task(Last100Analyzer).depends_on(&producer);
|
let c2 = dag.add_task(Last100Analyzer).depends_on(producer);
|
||||||
let c3 = dag.add_task(LongestFinder).depends_on(&producer);
|
let c3 = dag.add_task(LongestFinder).depends_on(producer);
|
||||||
let c4 = dag.add_task(PatternCounter).depends_on(&producer);
|
let c4 = dag.add_task(PatternCounter).depends_on(producer);
|
||||||
let c5 = dag.add_task(TotalBytesComputer).depends_on(&producer);
|
let c5 = dag.add_task(TotalBytesComputer).depends_on(producer);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -467,9 +467,9 @@ async fn test_arc_nested_fan_out_tree() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
let root = dag.add_task(RootProducer);
|
let root: TaskHandle<_> = dag.add_task(RootProducer).into();
|
||||||
let l1_sum = dag.add_task(Level1Sum).depends_on(&root);
|
let l1_sum = dag.add_task(Level1Sum).depends_on(root);
|
||||||
let l1_prod = dag.add_task(Level1Product).depends_on(&root);
|
let l1_prod = dag.add_task(Level1Product).depends_on(root);
|
||||||
let l2_double = dag.add_task(Level2Double).depends_on(l1_sum);
|
let l2_double = dag.add_task(Level2Double).depends_on(l1_sum);
|
||||||
let l2_square = dag.add_task(Level2Square).depends_on(l1_sum);
|
let l2_square = dag.add_task(Level2Square).depends_on(l1_sum);
|
||||||
let l2_negate = dag.add_task(Level2Negate).depends_on(l1_prod);
|
let l2_negate = dag.add_task(Level2Negate).depends_on(l1_prod);
|
||||||
@@ -536,10 +536,10 @@ async fn test_arc_performance_vs_clone() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
let producer = dag.add_task(LargeProducer);
|
let producer: TaskHandle<_> = dag.add_task(LargeProducer).into();
|
||||||
let p1 = dag.add_task(Process1).depends_on(&producer);
|
let p1 = dag.add_task(Process1).depends_on(producer);
|
||||||
let p2 = dag.add_task(Process2).depends_on(&producer);
|
let p2 = dag.add_task(Process2).depends_on(producer);
|
||||||
let p3 = dag.add_task(Process3).depends_on(&producer);
|
let p3 = dag.add_task(Process3).depends_on(producer);
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
@@ -625,8 +625,8 @@ async fn test_arc_with_result() {
|
|||||||
let result_arc_ok = dag.add_task(ResultArcOk);
|
let result_arc_ok = dag.add_task(ResultArcOk);
|
||||||
let arc_result_err = dag.add_task(ArcResultErr);
|
let arc_result_err = dag.add_task(ArcResultErr);
|
||||||
|
|
||||||
let consumer1 = dag.add_task(ArcResultConsumer).depends_on(&arc_result_ok);
|
let consumer1 = dag.add_task(ArcResultConsumer).depends_on(arc_result_ok);
|
||||||
let consumer2 = dag.add_task(ResultArcConsumer).depends_on(&result_arc_ok);
|
let consumer2 = dag.add_task(ResultArcConsumer).depends_on(result_arc_ok);
|
||||||
|
|
||||||
// Create a new instance for the error case
|
// Create a new instance for the error case
|
||||||
struct ArcResultConsumer2;
|
struct ArcResultConsumer2;
|
||||||
@@ -641,7 +641,7 @@ async fn test_arc_with_result() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let consumer3 = dag.add_task(ArcResultConsumer2).depends_on(&arc_result_err);
|
let consumer3 = dag.add_task(ArcResultConsumer2).depends_on(arc_result_err);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -745,10 +745,10 @@ async fn test_arc_with_option() {
|
|||||||
let opt_arc_some = dag.add_task(OptionArcSome);
|
let opt_arc_some = dag.add_task(OptionArcSome);
|
||||||
let opt_arc_none = dag.add_task(OptionArcNone);
|
let opt_arc_none = dag.add_task(OptionArcNone);
|
||||||
|
|
||||||
let c1 = dag.add_task(ArcOptionConsumer1).depends_on(&arc_opt_some);
|
let c1 = dag.add_task(ArcOptionConsumer1).depends_on(arc_opt_some);
|
||||||
let c2 = dag.add_task(ArcOptionConsumer2).depends_on(&arc_opt_none);
|
let c2 = dag.add_task(ArcOptionConsumer2).depends_on(arc_opt_none);
|
||||||
let c3 = dag.add_task(OptionArcConsumer1).depends_on(&opt_arc_some);
|
let c3 = dag.add_task(OptionArcConsumer1).depends_on(opt_arc_some);
|
||||||
let c4 = dag.add_task(OptionArcConsumer2).depends_on(&opt_arc_none);
|
let c4 = dag.add_task(OptionArcConsumer2).depends_on(opt_arc_none);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -800,9 +800,9 @@ async fn test_arc_diamond_dependency() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
let top = dag.add_task(Top);
|
let top: TaskHandle<_> = dag.add_task(Top).into();
|
||||||
let left = dag.add_task(LeftBranch).depends_on(&top);
|
let left = dag.add_task(LeftBranch).depends_on(top);
|
||||||
let right = dag.add_task(RightBranch).depends_on(&top);
|
let right = dag.add_task(RightBranch).depends_on(top);
|
||||||
let bottom = dag.add_task(Bottom).depends_on((&left, &right));
|
let bottom = dag.add_task(Bottom).depends_on((&left, &right));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
@@ -871,7 +871,7 @@ async fn test_arc_empty_collections() {
|
|||||||
let map_task = dag.add_task(EmptyHashMap);
|
let map_task = dag.add_task(EmptyHashMap);
|
||||||
let consumer = dag
|
let consumer = dag
|
||||||
.add_task(EmptyConsumer)
|
.add_task(EmptyConsumer)
|
||||||
.depends_on((&vec_task, &string_task, &map_task));
|
.depends_on((vec_task, string_task, map_task));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -938,11 +938,11 @@ async fn test_arc_very_large_data() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
let producer = dag.add_task(HugeProducer);
|
let producer: TaskHandle<_> = dag.add_task(HugeProducer).into();
|
||||||
let counter = dag.add_task(ItemCounter).depends_on(&producer);
|
let counter = dag.add_task(ItemCounter).depends_on(producer);
|
||||||
let first = dag.add_task(FirstSampler).depends_on(&producer);
|
let first = dag.add_task(FirstSampler).depends_on(producer);
|
||||||
let last = dag.add_task(LastSampler).depends_on(&producer);
|
let last = dag.add_task(LastSampler).depends_on(producer);
|
||||||
let middle = dag.add_task(MiddleSampler).depends_on(&producer);
|
let middle = dag.add_task(MiddleSampler).depends_on(producer);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -979,7 +979,7 @@ async fn test_arc_of_arc() {
|
|||||||
|
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
let producer = dag.add_task(NestedProducer);
|
let producer = dag.add_task(NestedProducer);
|
||||||
let consumer = dag.add_task(NestedConsumer).depends_on(&producer);
|
let consumer = dag.add_task(NestedConsumer).depends_on(producer);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -1037,13 +1037,13 @@ async fn test_arc_mixed_with_non_arc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
let arc_prod = dag.add_task(ArcProducer);
|
let arc_prod = dag.add_task(ArcProducer).into();
|
||||||
let non_arc_prod = dag.add_task(NonArcProducer);
|
let non_arc_prod = dag.add_task(NonArcProducer).into();
|
||||||
let mixed = dag
|
let mixed = dag
|
||||||
.add_task(MixedConsumer)
|
.add_task(MixedConsumer)
|
||||||
.depends_on((&arc_prod, &non_arc_prod));
|
.depends_on((&arc_prod, &non_arc_prod));
|
||||||
let unwrap = dag.add_task(Unwrapper).depends_on(&arc_prod);
|
let unwrap = dag.add_task(Unwrapper).depends_on(arc_prod);
|
||||||
let wrap = dag.add_task(Wrapper).depends_on(&non_arc_prod);
|
let wrap = dag.add_task(Wrapper).depends_on(non_arc_prod);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -1106,10 +1106,10 @@ async fn test_arc_parallel_execution() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
let producer = dag.add_task(SharedProducer);
|
let producer: TaskHandle<_> = dag.add_task(SharedProducer).into();
|
||||||
let t1 = dag.add_task(ParallelTask1).depends_on(&producer);
|
let t1 = dag.add_task(ParallelTask1).depends_on(producer);
|
||||||
let t2 = dag.add_task(ParallelTask2).depends_on(&producer);
|
let t2 = dag.add_task(ParallelTask2).depends_on(producer);
|
||||||
let t3 = dag.add_task(ParallelTask3).depends_on(&producer);
|
let t3 = dag.add_task(ParallelTask3).depends_on(producer);
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
@@ -1160,7 +1160,7 @@ async fn test_arc_reference_counting() {
|
|||||||
|
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
let producer = dag.add_task(RefCountProducer);
|
let producer = dag.add_task(RefCountProducer);
|
||||||
let checker = dag.add_task(RefCountChecker).depends_on(&producer);
|
let checker = dag.add_task(RefCountChecker).depends_on(producer);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -1223,16 +1223,18 @@ async fn test_arc_massive_fanout_performance_comparison() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let dag_without_arc = DagRunner::new();
|
let dag_without_arc = DagRunner::new();
|
||||||
let producer = dag_without_arc.add_task(ProducerWithoutArc {
|
let producer: TaskHandle<_> = dag_without_arc
|
||||||
data: random_data.clone(),
|
.add_task(ProducerWithoutArc {
|
||||||
});
|
data: random_data.clone(),
|
||||||
|
})
|
||||||
|
.into();
|
||||||
|
|
||||||
// Create consumer tasks for fan-out pattern
|
// Create consumer tasks for fan-out pattern
|
||||||
let consumers_without_arc: Vec<_> = (0..FAN_OUT_COUNT)
|
let consumers_without_arc: Vec<_> = (0..FAN_OUT_COUNT)
|
||||||
.map(|_| {
|
.map(|_| {
|
||||||
dag_without_arc
|
dag_without_arc
|
||||||
.add_task(ConsumerWithoutArc)
|
.add_task(ConsumerWithoutArc)
|
||||||
.depends_on(&producer)
|
.depends_on(producer)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -1275,16 +1277,18 @@ async fn test_arc_massive_fanout_performance_comparison() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let dag_with_arc = DagRunner::new();
|
let dag_with_arc = DagRunner::new();
|
||||||
let producer_arc = dag_with_arc.add_task(ProducerWithArc {
|
let producer_arc: TaskHandle<_> = dag_with_arc
|
||||||
data: random_data.clone(),
|
.add_task(ProducerWithArc {
|
||||||
});
|
data: random_data.clone(),
|
||||||
|
})
|
||||||
|
.into();
|
||||||
|
|
||||||
// Create consumer tasks for fan-out pattern
|
// Create consumer tasks for fan-out pattern
|
||||||
let consumers_with_arc: Vec<_> = (0..FAN_OUT_COUNT)
|
let consumers_with_arc: Vec<_> = (0..FAN_OUT_COUNT)
|
||||||
.map(|_| {
|
.map(|_| {
|
||||||
dag_with_arc
|
dag_with_arc
|
||||||
.add_task(ConsumerWithArc)
|
.add_task(ConsumerWithArc)
|
||||||
.depends_on(&producer_arc)
|
.depends_on(producer_arc)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ async fn test_1000_level_deep_chain() -> DagResult<()> {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let first = dag.add_task(task_fn(|_: ()| async { 0 }));
|
let first = dag.add_task(task_fn(|_: ()| async { 0 }));
|
||||||
let mut current = (&first).into(); // Convert to TaskHandle
|
let mut current = first.into(); // Convert to TaskHandle
|
||||||
|
|
||||||
for _i in 1..1000 {
|
for _i in 1..1000 {
|
||||||
current = dag
|
current = dag
|
||||||
@@ -32,7 +32,7 @@ async fn test_deep_chain_with_branches() -> DagResult<()> {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let first = dag.add_task(task_fn(|_: ()| async { 0 }));
|
let first = dag.add_task(task_fn(|_: ()| async { 0 }));
|
||||||
let mut main_chain: dagx::TaskHandle<i32> = (&first).into();
|
let mut main_chain: dagx::TaskHandle<i32> = first.into();
|
||||||
let mut branches = Vec::new();
|
let mut branches = Vec::new();
|
||||||
|
|
||||||
for i in 1..500 {
|
for i in 1..500 {
|
||||||
@@ -72,7 +72,7 @@ async fn test_multiple_parallel_deep_chains() -> DagResult<()> {
|
|||||||
|
|
||||||
for chain_id in 0..8 {
|
for chain_id in 0..8 {
|
||||||
let first = dag.add_task(task_fn(move |_: ()| async move { chain_id * 1000 }));
|
let first = dag.add_task(task_fn(move |_: ()| async move { chain_id * 1000 }));
|
||||||
let mut current: dagx::TaskHandle<i32> = (&first).into();
|
let mut current: dagx::TaskHandle<i32> = first.into();
|
||||||
|
|
||||||
for _ in 0..500 {
|
for _ in 0..500 {
|
||||||
current = dag
|
current = dag
|
||||||
@@ -136,7 +136,7 @@ async fn test_deep_chain_execution_order() -> DagResult<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
let mut current: dagx::TaskHandle<i32> = (&first).into();
|
let mut current: dagx::TaskHandle<i32> = first.into();
|
||||||
|
|
||||||
for expected in 1..100 {
|
for expected in 1..100 {
|
||||||
current = dag
|
current = dag
|
||||||
@@ -169,7 +169,7 @@ async fn test_deep_binary_tree() -> DagResult<()> {
|
|||||||
fn build_tree(dag: &DagRunner, depth: usize, value: i32) -> dagx::TaskHandle<i32> {
|
fn build_tree(dag: &DagRunner, depth: usize, value: i32) -> dagx::TaskHandle<i32> {
|
||||||
if depth == 0 {
|
if depth == 0 {
|
||||||
let task = dag.add_task(task_fn(move |_: ()| async move { value }));
|
let task = dag.add_task(task_fn(move |_: ()| async move { value }));
|
||||||
(&task).into() // Convert TaskBuilder to TaskHandle
|
task.into() // Convert TaskBuilder to TaskHandle
|
||||||
} else {
|
} else {
|
||||||
let left = build_tree(dag, depth - 1, value * 2);
|
let left = build_tree(dag, depth - 1, value * 2);
|
||||||
let right = build_tree(dag, depth - 1, value * 2 + 1);
|
let right = build_tree(dag, depth - 1, value * 2 + 1);
|
||||||
@@ -199,8 +199,8 @@ async fn test_fibonacci_chain() -> DagResult<()> {
|
|||||||
let f0_builder = dag.add_task(task_fn(|_: ()| async { 0i64 }));
|
let f0_builder = dag.add_task(task_fn(|_: ()| async { 0i64 }));
|
||||||
let f1_builder = dag.add_task(task_fn(|_: ()| async { 1i64 }));
|
let f1_builder = dag.add_task(task_fn(|_: ()| async { 1i64 }));
|
||||||
|
|
||||||
let mut prev2: dagx::TaskHandle<i64> = (&f0_builder).into();
|
let mut prev2: dagx::TaskHandle<i64> = f0_builder.into();
|
||||||
let mut prev1: dagx::TaskHandle<i64> = (&f1_builder).into();
|
let mut prev1: dagx::TaskHandle<i64> = f1_builder.into();
|
||||||
|
|
||||||
for _ in 2..50 {
|
for _ in 2..50 {
|
||||||
let next = dag
|
let next = dag
|
||||||
@@ -226,7 +226,7 @@ async fn test_deep_chain_with_accumulator() -> DagResult<()> {
|
|||||||
|
|
||||||
// Use a simpler accumulation pattern - just sum
|
// Use a simpler accumulation pattern - just sum
|
||||||
let first = dag.add_task(task_fn(|_: ()| async { 0i64 }));
|
let first = dag.add_task(task_fn(|_: ()| async { 0i64 }));
|
||||||
let mut current: dagx::TaskHandle<i64> = (&first).into();
|
let mut current: dagx::TaskHandle<i64> = first.into();
|
||||||
|
|
||||||
for i in 1..=200 {
|
for i in 1..=200 {
|
||||||
current = dag
|
current = dag
|
||||||
@@ -250,8 +250,8 @@ async fn test_zigzag_dependencies() -> DagResult<()> {
|
|||||||
let a_first = dag.add_task(task_fn(|_: ()| async { 1 }));
|
let a_first = dag.add_task(task_fn(|_: ()| async { 1 }));
|
||||||
let b_first = dag.add_task(task_fn(|_: ()| async { 2 }));
|
let b_first = dag.add_task(task_fn(|_: ()| async { 2 }));
|
||||||
|
|
||||||
let mut chain_a: dagx::TaskHandle<i32> = (&a_first).into();
|
let mut chain_a: dagx::TaskHandle<i32> = a_first.into();
|
||||||
let mut chain_b: dagx::TaskHandle<i32> = (&b_first).into();
|
let mut chain_b: dagx::TaskHandle<i32> = b_first.into();
|
||||||
|
|
||||||
for i in 0..100 {
|
for i in 0..100 {
|
||||||
if i % 2 == 0 {
|
if i % 2 == 0 {
|
||||||
@@ -287,7 +287,7 @@ async fn test_10000_level_chain_stress() -> DagResult<()> {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let first = dag.add_task(task_fn(|_: ()| async { 0u64 }));
|
let first = dag.add_task(task_fn(|_: ()| async { 0u64 }));
|
||||||
let mut current: dagx::TaskHandle<u64> = (&first).into();
|
let mut current: dagx::TaskHandle<u64> = first.into();
|
||||||
|
|
||||||
for _ in 1..10_000 {
|
for _ in 1..10_000 {
|
||||||
current = dag
|
current = dag
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ async fn test_10000_independent_tasks() -> DagResult<()> {
|
|||||||
|
|
||||||
let counter = Arc::new(AtomicUsize::new(0));
|
let counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
let tasks: Vec<_> = (0..10_000)
|
let mut tasks: Vec<_> = (0..10_000)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let counter = counter.clone();
|
let counter = counter.clone();
|
||||||
dag.add_task(task_fn(move |_: ()| {
|
dag.add_task(task_fn(move |_: ()| {
|
||||||
@@ -32,9 +32,9 @@ async fn test_10000_independent_tasks() -> DagResult<()> {
|
|||||||
assert_eq!(counter.load(Ordering::Relaxed), 10_000);
|
assert_eq!(counter.load(Ordering::Relaxed), 10_000);
|
||||||
|
|
||||||
// Spot check some results
|
// Spot check some results
|
||||||
assert_eq!(dag.get(&tasks[0])?, 0);
|
assert_eq!(dag.get(tasks.swap_remove(9_999))?, 9_999);
|
||||||
assert_eq!(dag.get(&tasks[9_999])?, 9_999);
|
assert_eq!(dag.get(tasks.swap_remove(5_000))?, 5_000);
|
||||||
assert_eq!(dag.get(&tasks[5_000])?, 5_000);
|
assert_eq!(dag.get(tasks.swap_remove(0))?, 0);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ async fn test_5000_node_pyramid() -> DagResult<()> {
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
(&task).into()
|
task.into()
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ async fn test_5000_node_pyramid_single_threaded() -> DagResult<()> {
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
(&task).into()
|
task.into()
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -188,7 +188,10 @@ async fn test_wide_dag_1000_sources_1000_sinks() -> DagResult<()> {
|
|||||||
|
|
||||||
// Create 1000 source tasks
|
// Create 1000 source tasks
|
||||||
let sources: Vec<_> = (0..1000)
|
let sources: Vec<_> = (0..1000)
|
||||||
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i % 10 })))
|
.map(|i| {
|
||||||
|
dag.add_task(task_fn(move |_: ()| async move { i % 10 }))
|
||||||
|
.into()
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Create 1000 sink tasks, each depending on 3 random sources
|
// Create 1000 sink tasks, each depending on 3 random sources
|
||||||
@@ -238,7 +241,7 @@ async fn test_10000_node_linear_chain_segments() -> DagResult<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
let mut current: dagx::TaskHandle<i32> = (&first).into();
|
let mut current: dagx::TaskHandle<i32> = first.into();
|
||||||
|
|
||||||
for _i in 1..100 {
|
for _i in 1..100 {
|
||||||
current = dag
|
current = dag
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Tests for type system edge cases and limits
|
//! Tests for type system edge cases and limits
|
||||||
|
|
||||||
use crate::common::task_fn;
|
use crate::common::task_fn;
|
||||||
use dagx::{DagResult, DagRunner};
|
use dagx::{DagResult, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ async fn test_zero_sized_types_throughout() -> DagResult<()> {
|
|||||||
let t1 = dag.add_task(task_fn(|_: ()| async { Empty }));
|
let t1 = dag.add_task(task_fn(|_: ()| async { Empty }));
|
||||||
|
|
||||||
// Task that also produces ZST (independent since we can't extract custom types)
|
// Task that also produces ZST (independent since we can't extract custom types)
|
||||||
let t2 = dag.add_task(task_fn(|_: ()| async { 42 }));
|
let t2: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 42 })).into();
|
||||||
|
|
||||||
// Task with PhantomData
|
// Task with PhantomData
|
||||||
let _t3 = dag.add_task(task_fn(|_: ()| async {
|
let _t3 = dag.add_task(task_fn(|_: ()| async {
|
||||||
@@ -37,7 +37,7 @@ async fn test_zero_sized_types_throughout() -> DagResult<()> {
|
|||||||
assert_eq!(val, 42);
|
assert_eq!(val, 42);
|
||||||
"success"
|
"success"
|
||||||
}))
|
}))
|
||||||
.depends_on(&t2);
|
.depends_on(t2);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ async fn test_large_value_types() -> DagResult<()> {
|
|||||||
.add_task(task_fn(|val: usize| async move {
|
.add_task(task_fn(|val: usize| async move {
|
||||||
val // Just pass through to verify
|
val // Just pass through to verify
|
||||||
}))
|
}))
|
||||||
.depends_on(&t1);
|
.depends_on(t1);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
@@ -86,12 +86,12 @@ async fn test_unit_type_chains() -> DagResult<()> {
|
|||||||
// Test chains of unit type operations
|
// Test chains of unit type operations
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let t1 = dag.add_task(task_fn(|_: ()| async {}));
|
let t1: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async {})).into();
|
||||||
let t2 = dag.add_task(task_fn(|_: ()| async {})).depends_on(&t1);
|
let t2 = dag.add_task(task_fn(|_: ()| async {})).depends_on(t1);
|
||||||
let t3 = dag.add_task(task_fn(|_: ()| async {})).depends_on(t2);
|
let t3 = dag.add_task(task_fn(|_: ()| async {})).depends_on(t2);
|
||||||
let t4 = dag
|
let t4 = dag
|
||||||
.add_task(task_fn(|_: ()| async { "done" }))
|
.add_task(task_fn(|_: ()| async { "done" }))
|
||||||
.depends_on(&t3);
|
.depends_on(t3);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ async fn test_nested_option_result_types() -> DagResult<()> {
|
|||||||
_ => 0,
|
_ => 0,
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.depends_on(&t1);
|
.depends_on(t1);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ async fn test_tuple_of_tuples() -> DagResult<()> {
|
|||||||
a + b + c + d + e + f + g + h
|
a + b + c + d + e + f + g + h
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.depends_on((&t1, &t2));
|
.depends_on((t1, t2));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ async fn test_string_types_variety() -> DagResult<()> {
|
|||||||
std::sync::Arc<&str>,
|
std::sync::Arc<&str>,
|
||||||
)| async move { format!("{} {} {} {} {} {}", s1, s2, s3, s4, s5, s6) },
|
)| async move { format!("{} {} {} {} {} {}", s1, s2, s3, s4, s5, s6) },
|
||||||
))
|
))
|
||||||
.depends_on((&t1, &t2, &t3, &t4, &t5, &t6));
|
.depends_on((t1, t2, t3, t4, t5, t6));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
@@ -209,7 +209,7 @@ async fn test_generic_type_constraints() -> DagResult<()> {
|
|||||||
format!("{:?} {:?} {:?}", w1, w2, w3)
|
format!("{:?} {:?} {:?}", w1, w2, w3)
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.depends_on((&t1, &t2, &t3));
|
.depends_on((t1, t2, t3));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
@@ -235,7 +235,7 @@ async fn test_reference_wrapper_types() -> DagResult<()> {
|
|||||||
|
|
||||||
let t2 = dag
|
let t2 = dag
|
||||||
.add_task(task_fn(|val: i32| async move { val * 2 }))
|
.add_task(task_fn(|val: i32| async move { val * 2 }))
|
||||||
.depends_on(&t1);
|
.depends_on(t1);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
@@ -260,7 +260,7 @@ async fn test_array_types() -> DagResult<()> {
|
|||||||
}
|
}
|
||||||
sum
|
sum
|
||||||
}))
|
}))
|
||||||
.depends_on((&t1, &t2));
|
.depends_on((t1, t2));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
@@ -299,7 +299,7 @@ async fn test_enum_variants() -> DagResult<()> {
|
|||||||
&& matches!(e4, MyEnum::LargeVariant(_))
|
&& matches!(e4, MyEnum::LargeVariant(_))
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.depends_on((&t1, &t2, &t3, &t4));
|
.depends_on((t1, t2, t3, t4));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ async fn test_zero_sized_type_task() -> DagResult<()> {
|
|||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
assert_eq!(dag.get(&task)?, ZeroSized);
|
assert_eq!(dag.get(task)?, ZeroSized);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ async fn test_dag_with_only_source_tasks() -> DagResult<()> {
|
|||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
for (i, task) in tasks.iter().enumerate() {
|
for (i, task) in tasks.into_iter().enumerate() {
|
||||||
assert_eq!(dag.get(task)?, i);
|
assert_eq!(dag.get(task)?, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ async fn test_dag_with_never_type_simulation() -> DagResult<()> {
|
|||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
assert_eq!(dag.get(&task)?, Ok(42));
|
assert_eq!(dag.get(task)?, Ok(42));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -1,5 +1,5 @@
|
|||||||
// Test that custom types work without any trait implementations
|
// Test that custom types work without any trait implementations
|
||||||
use dagx::{task, DagRunner};
|
use dagx::{task, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
@@ -55,7 +55,7 @@ async fn test_custom_type_single_dependency() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let fetch = dag.add_task(FetchPerson);
|
let fetch = dag.add_task(FetchPerson);
|
||||||
let process = dag.add_task(ProcessPerson).depends_on(&fetch);
|
let process = dag.add_task(ProcessPerson).depends_on(fetch);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -69,9 +69,9 @@ async fn test_custom_type_single_dependency() {
|
|||||||
async fn test_custom_type_passthrough() {
|
async fn test_custom_type_passthrough() {
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let fetch = dag.add_task(FetchPerson);
|
let fetch: TaskHandle<_> = dag.add_task(FetchPerson).into();
|
||||||
let transform = dag.add_task(PersonToCompany).depends_on(&fetch);
|
let transform = dag.add_task(PersonToCompany).depends_on(fetch);
|
||||||
let process = dag.add_task(ProcessPerson).depends_on(&fetch);
|
let process = dag.add_task(ProcessPerson).depends_on(fetch);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -90,11 +90,11 @@ async fn test_custom_type_diamond_pattern() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
// Source
|
// Source
|
||||||
let fetch = dag.add_task(FetchPerson);
|
let fetch: TaskHandle<_> = dag.add_task(FetchPerson).into();
|
||||||
|
|
||||||
// Two paths from source
|
// Two paths from source
|
||||||
let path_a = dag.add_task(ProcessPerson).depends_on(&fetch);
|
let path_a = dag.add_task(ProcessPerson).depends_on(fetch);
|
||||||
let path_b = dag.add_task(PersonToCompany).depends_on(&fetch);
|
let path_b = dag.add_task(PersonToCompany).depends_on(fetch);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -111,10 +111,10 @@ async fn test_custom_type_diamond_pattern() {
|
|||||||
async fn test_custom_type_fan_out() {
|
async fn test_custom_type_fan_out() {
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let fetch = dag.add_task(FetchPerson);
|
let fetch: TaskHandle<_> = dag.add_task(FetchPerson).into();
|
||||||
let process1 = dag.add_task(ProcessPerson).depends_on(&fetch);
|
let process1 = dag.add_task(ProcessPerson).depends_on(fetch);
|
||||||
let process2 = dag.add_task(ProcessPerson).depends_on(&fetch);
|
let process2 = dag.add_task(ProcessPerson).depends_on(fetch);
|
||||||
let process3 = dag.add_task(ProcessPerson).depends_on(&fetch);
|
let process3 = dag.add_task(ProcessPerson).depends_on(fetch);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
|
|||||||
+12
-12
@@ -31,11 +31,11 @@ fn test_builder_consumed_by_depends_on() {
|
|||||||
let consumer_builder = dag.add_task(Consumer);
|
let consumer_builder = dag.add_task(Consumer);
|
||||||
|
|
||||||
// This consumes consumer_builder and returns a TaskHandle
|
// This consumes consumer_builder and returns a TaskHandle
|
||||||
let _consumer_handle = consumer_builder.depends_on(&source);
|
let _consumer_handle = consumer_builder.depends_on(source);
|
||||||
|
|
||||||
// We CANNOT use consumer_builder again because it was moved
|
// We CANNOT use consumer_builder again because it was moved
|
||||||
// This would fail to compile:
|
// This would fail to compile:
|
||||||
// let _consumer2 = consumer_builder.depends_on(&source); // ERROR: use of moved value
|
// let _consumer2 = consumer_builder.depends_on(source); // ERROR: use of moved value
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Demonstrates that you can only get a TaskHandle by finalizing a builder,
|
/// Demonstrates that you can only get a TaskHandle by finalizing a builder,
|
||||||
@@ -64,7 +64,7 @@ fn test_handle_is_immutable_reference() {
|
|||||||
// But TaskHandle has NO methods to modify the DAG
|
// But TaskHandle has NO methods to modify the DAG
|
||||||
// There's no way to add dependencies to it
|
// There's no way to add dependencies to it
|
||||||
// This would fail to compile:
|
// This would fail to compile:
|
||||||
// handle.depends_on(&something); // ERROR: no method named `depends_on`
|
// handle.depends_on(something); // ERROR: no method named `depends_on`
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Documents the ordering constraint that prevents cycles
|
/// Documents the ordering constraint that prevents cycles
|
||||||
@@ -110,7 +110,7 @@ fn test_ordering_prevents_cycles() {
|
|||||||
// Let's create a valid DAG instead: A→B (B depends on A)
|
// Let's create a valid DAG instead: A→B (B depends on A)
|
||||||
let a_handle: TaskHandle<i32> = a_builder.into(); // A has no deps (unit input)
|
let a_handle: TaskHandle<i32> = a_builder.into(); // A has no deps (unit input)
|
||||||
let _b_handle = b_builder.depends_on(a_handle);
|
let _b_handle = b_builder.depends_on(a_handle);
|
||||||
// Now we could try: a_handle.depends_on(&b_handle)?
|
// Now we could try: a_handle.depends_on(b_handle)?
|
||||||
// But TaskHandle has no such method!
|
// But TaskHandle has no such method!
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ fn test_unit_tasks_cannot_create_cycles() {
|
|||||||
// And once you've converted to TaskHandle, you can't go back
|
// And once you've converted to TaskHandle, you can't go back
|
||||||
|
|
||||||
// This would fail to compile:
|
// This would fail to compile:
|
||||||
// _handle1.depends_on(&_handle2); // ERROR: no method named `depends_on`
|
// _handle1.depends_on(_handle2); // ERROR: no method named `depends_on`
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Demonstrates the catch-22 of three-way cycles
|
/// Demonstrates the catch-22 of three-way cycles
|
||||||
@@ -183,9 +183,9 @@ fn test_three_way_cycle_impossible() {
|
|||||||
let _c_builder = dag.add_task(C);
|
let _c_builder = dag.add_task(C);
|
||||||
|
|
||||||
// To create A→C→B→A:
|
// To create A→C→B→A:
|
||||||
// 1. a_builder.depends_on(&c_handle) // Need c_handle
|
// 1. a_builder.depends_on(c_handle) // Need c_handle
|
||||||
// 2. c_builder.depends_on(&b_handle) // Need b_handle
|
// 2. c_builder.depends_on(b_handle) // Need b_handle
|
||||||
// 3. b_builder.depends_on(&a_handle) // Need a_handle (from step 1!)
|
// 3. b_builder.depends_on(a_handle) // Need a_handle (from step 1!)
|
||||||
//
|
//
|
||||||
// But step 3 needs a_handle which doesn't exist until step 1 completes.
|
// But step 3 needs a_handle which doesn't exist until step 1 completes.
|
||||||
// And step 1 needs c_handle which doesn't exist until step 2 completes.
|
// And step 1 needs c_handle which doesn't exist until step 2 completes.
|
||||||
@@ -210,7 +210,7 @@ fn test_self_loop_prevented() {
|
|||||||
let _builder = dag.add_task(SelfReferential);
|
let _builder = dag.add_task(SelfReferential);
|
||||||
|
|
||||||
// To create A→A (self-loop):
|
// To create A→A (self-loop):
|
||||||
// builder.depends_on(&builder)
|
// builder.depends_on(builder)
|
||||||
//
|
//
|
||||||
// But this would fail because:
|
// But this would fail because:
|
||||||
// 1. depends_on() takes `self` by value (moves builder)
|
// 1. depends_on() takes `self` by value (moves builder)
|
||||||
@@ -218,7 +218,7 @@ fn test_self_loop_prevented() {
|
|||||||
// 3. We can't also use &builder as an argument because it's being moved
|
// 3. We can't also use &builder as an argument because it's being moved
|
||||||
//
|
//
|
||||||
// This would fail to compile:
|
// This would fail to compile:
|
||||||
// let _handle = builder.depends_on(&builder); // ERROR: use of moved value
|
// let _handle = builder.depends_on(builder); // ERROR: use of moved value
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Comprehensive test showing all the guardrails
|
/// Comprehensive test showing all the guardrails
|
||||||
@@ -264,8 +264,8 @@ async fn test_type_safety_prevents_invalid_graphs() {
|
|||||||
let s2 = dag.add_task(Source);
|
let s2 = dag.add_task(Source);
|
||||||
|
|
||||||
// Transform depends on sources
|
// Transform depends on sources
|
||||||
let t1 = dag.add_task(Transform).depends_on(&s1);
|
let t1 = dag.add_task(Transform).depends_on(s1);
|
||||||
let t2 = dag.add_task(Transform).depends_on(&s2);
|
let t2 = dag.add_task(Transform).depends_on(s2);
|
||||||
|
|
||||||
// Aggregate depends on transforms
|
// Aggregate depends on transforms
|
||||||
let result = dag.add_task(Aggregate).depends_on((&t1, &t2));
|
let result = dag.add_task(Aggregate).depends_on((&t1, &t2));
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Tests for dependency resolution order and transitive dependencies
|
//! Tests for dependency resolution order and transitive dependencies
|
||||||
|
|
||||||
use crate::common::task_fn;
|
use crate::common::task_fn;
|
||||||
use dagx::{DagResult, DagRunner};
|
use dagx::{DagResult, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ async fn test_dependency_resolution_order() -> DagResult<()> {
|
|||||||
x + 1
|
x + 1
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.depends_on(&a)
|
.depends_on(a)
|
||||||
};
|
};
|
||||||
|
|
||||||
let c = {
|
let c = {
|
||||||
@@ -108,17 +108,17 @@ async fn test_transitive_dependencies() -> DagResult<()> {
|
|||||||
// A -> B -> C (creates transitive A -> C relationship)
|
// A -> B -> C (creates transitive A -> C relationship)
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let a = dag.add_task(task_fn(|_: ()| async { 10 }));
|
let a: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 10 })).into();
|
||||||
let b = dag
|
let b = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
||||||
.depends_on(&a);
|
.depends_on(a);
|
||||||
let c = dag
|
let c = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 5 }))
|
.add_task(task_fn(|x: i32| async move { x + 5 }))
|
||||||
.depends_on(b);
|
.depends_on(b);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
assert_eq!(dag.get(&a)?, 10);
|
assert_eq!(dag.get(a)?, 10);
|
||||||
assert_eq!(dag.get(b)?, 20);
|
assert_eq!(dag.get(b)?, 20);
|
||||||
assert_eq!(dag.get(c)?, 25); // 20 + 5
|
assert_eq!(dag.get(c)?, 25); // 20 + 5
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ async fn test_deep_chain_50_levels() -> DagResult<()> {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let first = dag.add_task(task_fn(|_: ()| async { 0 }));
|
let first = dag.add_task(task_fn(|_: ()| async { 0 }));
|
||||||
let mut handles = vec![(&first).into()];
|
let mut handles = vec![first.into()];
|
||||||
|
|
||||||
for _i in 1..=50 {
|
for _i in 1..=50 {
|
||||||
let next = dag
|
let next = dag
|
||||||
@@ -150,7 +150,7 @@ async fn test_deep_chain_200_levels() -> DagResult<()> {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let first = dag.add_task(task_fn(|_: ()| async { 1 }));
|
let first = dag.add_task(task_fn(|_: ()| async { 1 }));
|
||||||
let mut current = (&first).into();
|
let mut current = first.into();
|
||||||
|
|
||||||
for _ in 0..200 {
|
for _ in 0..200 {
|
||||||
current = dag
|
current = dag
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Tests for single dependencies and basic dependency data flow
|
//! Tests for single dependencies and basic dependency data flow
|
||||||
|
|
||||||
use crate::common::task_fn;
|
use crate::common::task_fn;
|
||||||
use dagx::{DagResult, DagRunner};
|
use dagx::{DagResult, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -11,7 +11,7 @@ async fn test_single_dependency() -> DagResult<()> {
|
|||||||
let source = dag.add_task(task_fn(|_: ()| async { 42 }));
|
let source = dag.add_task(task_fn(|_: ()| async { 42 }));
|
||||||
let dependent = dag
|
let dependent = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
@@ -24,17 +24,19 @@ async fn test_dependency_data_flow() -> DagResult<()> {
|
|||||||
// Test that data flows correctly through dependencies
|
// Test that data flows correctly through dependencies
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let source = dag.add_task(task_fn(|_: ()| async { vec![1, 2, 3, 4, 5] }));
|
let source: TaskHandle<_> = dag
|
||||||
|
.add_task(task_fn(|_: ()| async { vec![1, 2, 3, 4, 5] }))
|
||||||
|
.into();
|
||||||
|
|
||||||
let sum = dag
|
let sum = dag
|
||||||
.add_task(task_fn(|v: Vec<i32>| async move { v.iter().sum::<i32>() }))
|
.add_task(task_fn(|v: Vec<i32>| async move { v.iter().sum::<i32>() }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
|
|
||||||
let product = dag
|
let product = dag
|
||||||
.add_task(task_fn(
|
.add_task(task_fn(
|
||||||
|v: Vec<i32>| async move { v.iter().product::<i32>() },
|
|v: Vec<i32>| async move { v.iter().product::<i32>() },
|
||||||
))
|
))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
|
|
||||||
let final_result = dag
|
let final_result = dag
|
||||||
.add_task(task_fn(|(s, p): (i32, i32)| async move { s + p }))
|
.add_task(task_fn(|(s, p): (i32, i32)| async move { s + p }))
|
||||||
@@ -62,7 +64,7 @@ async fn test_dependencies_with_different_types() -> DagResult<()> {
|
|||||||
.add_task(task_fn(|(i, s, b): (i32, String, bool)| async move {
|
.add_task(task_fn(|(i, s, b): (i32, String, bool)| async move {
|
||||||
format!("{}: {} ({})", s, i, b)
|
format!("{}: {} ({})", s, i, b)
|
||||||
}))
|
}))
|
||||||
.depends_on((&int_source, &string_source, &bool_source));
|
.depends_on((int_source, string_source, bool_source));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
@@ -75,13 +77,13 @@ async fn test_shared_dependencies() -> DagResult<()> {
|
|||||||
// Test that a single node can be a dependency for multiple downstream nodes
|
// Test that a single node can be a dependency for multiple downstream nodes
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let shared = dag.add_task(task_fn(|_: ()| async { 42 }));
|
let shared: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 42 })).into();
|
||||||
|
|
||||||
// Create 10 tasks that all depend on the shared node
|
// Create 10 tasks that all depend on the shared node
|
||||||
let dependents: Vec<_> = (0..10)
|
let dependents: Vec<_> = (0..10)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
dag.add_task(task_fn(move |x: i32| async move { x * (i + 1) }))
|
dag.add_task(task_fn(move |x: i32| async move { x * (i + 1) }))
|
||||||
.depends_on(&shared)
|
.depends_on(shared)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -106,7 +108,7 @@ async fn test_multiple_source_nodes() -> DagResult<()> {
|
|||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
for (i, source) in sources.iter().enumerate() {
|
for (i, source) in sources.into_iter().enumerate() {
|
||||||
assert_eq!(dag.get(source)?, i as i32 * 10);
|
assert_eq!(dag.get(source)?, i as i32 * 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ async fn test_two_dependencies() -> DagResult<()> {
|
|||||||
let b = dag.add_task(task_fn(|_: ()| async { 20 }));
|
let b = dag.add_task(task_fn(|_: ()| async { 20 }));
|
||||||
let sum = dag
|
let sum = dag
|
||||||
.add_task(task_fn(|(x, y): (i32, i32)| async move { x + y }))
|
.add_task(task_fn(|(x, y): (i32, i32)| async move { x + y }))
|
||||||
.depends_on((&a, &b));
|
.depends_on((a, b));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ async fn test_three_dependencies() -> DagResult<()> {
|
|||||||
.add_task(task_fn(
|
.add_task(task_fn(
|
||||||
|(x, y, z): (i32, i32, i32)| async move { x + y + z },
|
|(x, y, z): (i32, i32, i32)| async move { x + y + z },
|
||||||
))
|
))
|
||||||
.depends_on((&a, &b, &c));
|
.depends_on((a, b, c));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ async fn test_four_dependencies() -> DagResult<()> {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let deps: Vec<_> = (1..=4)
|
let deps: Vec<_> = (1..=4)
|
||||||
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i })))
|
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i })).into())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let sum = dag
|
let sum = dag
|
||||||
@@ -64,7 +64,7 @@ async fn test_five_dependencies() -> DagResult<()> {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let deps: Vec<_> = (1..=5)
|
let deps: Vec<_> = (1..=5)
|
||||||
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i })))
|
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i })).into())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let sum = dag
|
let sum = dag
|
||||||
@@ -84,7 +84,7 @@ async fn test_six_dependencies() -> DagResult<()> {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let deps: Vec<_> = (1..=6)
|
let deps: Vec<_> = (1..=6)
|
||||||
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i })))
|
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i })).into())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let sum = dag.add_task(task_fn(
|
let sum = dag.add_task(task_fn(
|
||||||
@@ -106,7 +106,7 @@ async fn test_seven_dependencies() -> DagResult<()> {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let deps: Vec<_> = (1..=7)
|
let deps: Vec<_> = (1..=7)
|
||||||
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i })))
|
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i })).into())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let sum = dag
|
let sum = dag
|
||||||
@@ -130,7 +130,7 @@ async fn test_eight_dependencies() -> DagResult<()> {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let deps: Vec<_> = (1..=8)
|
let deps: Vec<_> = (1..=8)
|
||||||
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i })))
|
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i })).into())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let sum = dag
|
let sum = dag
|
||||||
|
|||||||
+13
-13
@@ -1,17 +1,17 @@
|
|||||||
//! Tests for different error types and error handling
|
//! Tests for different error types and error handling
|
||||||
|
|
||||||
use crate::common::task_fn;
|
use crate::common::task_fn;
|
||||||
use dagx::{DagError, DagRunner};
|
use dagx::{DagError, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_result_not_found_error() {
|
async fn test_result_not_found_error() {
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let t1 = dag.add_task(task_fn(|_: ()| async { 42 }));
|
let t1: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 42 })).into();
|
||||||
|
|
||||||
// Try to get result before running
|
// Try to get result before running
|
||||||
let result = dag.get(&t1);
|
let result = dag.get(t1);
|
||||||
assert!(matches!(result, Err(DagError::ResultNotFound { .. })));
|
assert!(matches!(result, Err(DagError::ResultNotFound { .. })));
|
||||||
|
|
||||||
// Run and verify it works after
|
// Run and verify it works after
|
||||||
@@ -19,7 +19,7 @@ async fn test_result_not_found_error() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(dag.get(&t1).unwrap(), 42);
|
assert_eq!(dag.get(t1).unwrap(), 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -38,8 +38,8 @@ async fn test_nested_result_error_handling() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Both tasks complete successfully (they return Results)
|
// Both tasks complete successfully (they return Results)
|
||||||
assert_eq!(dag.get(&task1).unwrap(), Err("Internal error".to_string()));
|
assert_eq!(dag.get(task1).unwrap(), Err("Internal error".to_string()));
|
||||||
assert_eq!(dag.get(&task2).unwrap(), Ok(42));
|
assert_eq!(dag.get(task2).unwrap(), Ok(42));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -53,7 +53,7 @@ async fn test_option_none_not_error() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// None is a valid result, not an error
|
// None is a valid result, not an error
|
||||||
assert_eq!(dag.get(&task).unwrap(), None);
|
assert_eq!(dag.get(task).unwrap(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -82,12 +82,12 @@ async fn test_custom_error_types() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Check custom errors are preserved
|
// Check custom errors are preserved
|
||||||
match dag.get(&task1).unwrap() {
|
match dag.get(task1).unwrap() {
|
||||||
Err(CustomError::ValidationError(msg)) => assert_eq!(msg, "Invalid input"),
|
Err(CustomError::ValidationError(msg)) => assert_eq!(msg, "Invalid input"),
|
||||||
_ => panic!("Wrong error type"),
|
_ => panic!("Wrong error type"),
|
||||||
}
|
}
|
||||||
|
|
||||||
match dag.get(&task2).unwrap() {
|
match dag.get(task2).unwrap() {
|
||||||
Err(CustomError::ProcessingError { code, .. }) => assert_eq!(code, 500),
|
Err(CustomError::ProcessingError { code, .. }) => assert_eq!(code, 500),
|
||||||
_ => panic!("Wrong error type"),
|
_ => panic!("Wrong error type"),
|
||||||
}
|
}
|
||||||
@@ -104,7 +104,7 @@ async fn test_error_in_tuple_dependency() {
|
|||||||
|
|
||||||
let dependent = dag
|
let dependent = dag
|
||||||
.add_task(task_fn(|(a, b): (i32, i32)| async move { a + b }))
|
.add_task(task_fn(|(a, b): (i32, i32)| async move { a + b }))
|
||||||
.depends_on((&ok_task, &err_task));
|
.depends_on((ok_task, err_task));
|
||||||
|
|
||||||
let result = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
let result = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
||||||
|
|
||||||
@@ -127,8 +127,8 @@ async fn test_string_panic_vs_structured_panic() {
|
|||||||
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
||||||
|
|
||||||
// Both should result in errors
|
// Both should result in errors
|
||||||
assert!(dag.get(&string_panic).is_err());
|
assert!(dag.get(string_panic).is_err());
|
||||||
assert!(dag.get(&formatted_panic).is_err());
|
assert!(dag.get(formatted_panic).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -149,7 +149,7 @@ async fn test_anyhow_like_error_chain() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Get the chained error
|
// Get the chained error
|
||||||
let result = dag.get(&task).unwrap();
|
let result = dag.get(task).unwrap();
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
|
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
|
|||||||
+58
-48
@@ -1,7 +1,7 @@
|
|||||||
//! Tests for error propagation through the DAG
|
//! Tests for error propagation through the DAG
|
||||||
|
|
||||||
use crate::common::task_fn;
|
use crate::common::task_fn;
|
||||||
use dagx::DagRunner;
|
use dagx::{DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -10,12 +10,12 @@ use std::sync::Arc;
|
|||||||
async fn test_linear_error_propagation() {
|
async fn test_linear_error_propagation() {
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let t1 = dag.add_task(task_fn(|_: ()| async { 1 }));
|
let t1: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 1 })).into();
|
||||||
let t2 = dag
|
let t2 = dag
|
||||||
.add_task(task_fn(|x: i32| async move {
|
.add_task(task_fn(|x: i32| async move {
|
||||||
panic!("Error at t2 with input {}", x);
|
panic!("Error at t2 with input {}", x);
|
||||||
}))
|
}))
|
||||||
.depends_on(&t1);
|
.depends_on(t1);
|
||||||
let t3 = dag
|
let t3 = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
||||||
.depends_on(t2);
|
.depends_on(t2);
|
||||||
@@ -26,7 +26,7 @@ async fn test_linear_error_propagation() {
|
|||||||
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
||||||
|
|
||||||
// t1 succeeds
|
// t1 succeeds
|
||||||
assert_eq!(dag.get(&t1).unwrap(), 1);
|
assert_eq!(dag.get(t1).unwrap(), 1);
|
||||||
|
|
||||||
// t2 fails
|
// t2 fails
|
||||||
assert!(dag.get(t2).is_err());
|
assert!(dag.get(t2).is_err());
|
||||||
@@ -46,17 +46,17 @@ async fn test_diamond_error_propagation() {
|
|||||||
// \ /
|
// \ /
|
||||||
// sink
|
// sink
|
||||||
|
|
||||||
let source = dag.add_task(task_fn(|_: ()| async { 100 }));
|
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 100 })).into();
|
||||||
|
|
||||||
let left = dag
|
let left = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x / 2 }))
|
.add_task(task_fn(|x: i32| async move { x / 2 }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
|
|
||||||
let right = dag
|
let right = dag
|
||||||
.add_task(task_fn(|x: i32| async move {
|
.add_task(task_fn(|x: i32| async move {
|
||||||
panic!("Right path fails with {}", x);
|
panic!("Right path fails with {}", x);
|
||||||
}))
|
}))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
|
|
||||||
let sink = dag
|
let sink = dag
|
||||||
.add_task(task_fn(|(l, r): (i32, i32)| async move { l + r }))
|
.add_task(task_fn(|(l, r): (i32, i32)| async move { l + r }))
|
||||||
@@ -64,7 +64,7 @@ async fn test_diamond_error_propagation() {
|
|||||||
|
|
||||||
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
||||||
|
|
||||||
assert_eq!(dag.get(&source).unwrap(), 100);
|
assert_eq!(dag.get(source).unwrap(), 100);
|
||||||
assert_eq!(dag.get(left).unwrap(), 50);
|
assert_eq!(dag.get(left).unwrap(), 50);
|
||||||
assert!(dag.get(right).is_err());
|
assert!(dag.get(right).is_err());
|
||||||
assert!(dag.get(sink).is_err()); // Fails due to right dependency
|
assert!(dag.get(sink).is_err()); // Fails due to right dependency
|
||||||
@@ -76,16 +76,18 @@ async fn test_error_stops_at_boundary() {
|
|||||||
let execution_tracker = Arc::new(AtomicUsize::new(0));
|
let execution_tracker = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
// Branch with error
|
// Branch with error
|
||||||
let error_source = dag.add_task(task_fn({
|
let error_source: TaskHandle<_> = dag
|
||||||
let tracker = execution_tracker.clone();
|
.add_task(task_fn({
|
||||||
move |_: ()| {
|
let tracker = execution_tracker.clone();
|
||||||
let tracker = tracker.clone();
|
move |_: ()| {
|
||||||
async move {
|
let tracker = tracker.clone();
|
||||||
tracker.fetch_add(1, Ordering::SeqCst);
|
async move {
|
||||||
panic!("Error source");
|
tracker.fetch_add(1, Ordering::SeqCst);
|
||||||
|
panic!("Error source");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}))
|
||||||
}));
|
.into();
|
||||||
|
|
||||||
let error_dependent = dag
|
let error_dependent = dag
|
||||||
.add_task(task_fn({
|
.add_task(task_fn({
|
||||||
@@ -98,7 +100,7 @@ async fn test_error_stops_at_boundary() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.depends_on(&error_source);
|
.depends_on(error_source);
|
||||||
|
|
||||||
// Independent branch
|
// Independent branch
|
||||||
let independent = dag.add_task(task_fn({
|
let independent = dag.add_task(task_fn({
|
||||||
@@ -115,11 +117,11 @@ async fn test_error_stops_at_boundary() {
|
|||||||
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
||||||
|
|
||||||
// Error branch: source runs but dependent doesn't
|
// Error branch: source runs but dependent doesn't
|
||||||
assert!(dag.get(&error_source).is_err());
|
assert!(dag.get(error_source).is_err());
|
||||||
assert!(dag.get(error_dependent).is_err());
|
assert!(dag.get(error_dependent).is_err());
|
||||||
|
|
||||||
// Independent branch runs
|
// Independent branch runs
|
||||||
assert_eq!(dag.get(&independent).unwrap(), 10);
|
assert_eq!(dag.get(independent).unwrap(), 10);
|
||||||
|
|
||||||
// Check execution: 1 (error_source) + 10 (independent) = 11
|
// Check execution: 1 (error_source) + 10 (independent) = 11
|
||||||
assert_eq!(execution_tracker.load(Ordering::SeqCst), 11);
|
assert_eq!(execution_tracker.load(Ordering::SeqCst), 11);
|
||||||
@@ -130,15 +132,19 @@ async fn test_multiple_error_sources_convergence() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
// Multiple error sources
|
// Multiple error sources
|
||||||
let error1 = dag.add_task(task_fn(|_: ()| async {
|
let error1 = dag
|
||||||
panic!("Error source 1");
|
.add_task(task_fn(|_: ()| async {
|
||||||
}));
|
panic!("Error source 1");
|
||||||
|
}))
|
||||||
|
.into();
|
||||||
|
|
||||||
let error2 = dag.add_task(task_fn(|_: ()| async {
|
let error2 = dag
|
||||||
panic!("Error source 2");
|
.add_task(task_fn(|_: ()| async {
|
||||||
}));
|
panic!("Error source 2");
|
||||||
|
}))
|
||||||
|
.into();
|
||||||
|
|
||||||
let success = dag.add_task(task_fn(|_: ()| async { 42 }));
|
let success = dag.add_task(task_fn(|_: ()| async { 42 })).into();
|
||||||
|
|
||||||
// Convergence point depends on all three
|
// Convergence point depends on all three
|
||||||
let convergence = dag
|
let convergence = dag
|
||||||
@@ -148,11 +154,11 @@ async fn test_multiple_error_sources_convergence() {
|
|||||||
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
||||||
|
|
||||||
// All error sources fail
|
// All error sources fail
|
||||||
assert!(dag.get(&error1).is_err());
|
assert!(dag.get(error1).is_err());
|
||||||
assert!(dag.get(&error2).is_err());
|
assert!(dag.get(error2).is_err());
|
||||||
|
|
||||||
// Success task works
|
// Success task works
|
||||||
assert_eq!(dag.get(&success).unwrap(), 42);
|
assert_eq!(dag.get(success).unwrap(), 42);
|
||||||
|
|
||||||
// Convergence fails due to any error dependency
|
// Convergence fails due to any error dependency
|
||||||
assert!(dag.get(convergence).is_err());
|
assert!(dag.get(convergence).is_err());
|
||||||
@@ -162,22 +168,24 @@ async fn test_multiple_error_sources_convergence() {
|
|||||||
async fn test_error_in_wide_fanout() {
|
async fn test_error_in_wide_fanout() {
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let source = dag.add_task(task_fn(|_: ()| async {
|
let source: TaskHandle<_> = dag
|
||||||
panic!("Source fails");
|
.add_task(task_fn(|_: ()| async {
|
||||||
}));
|
panic!("Source fails");
|
||||||
|
}))
|
||||||
|
.into();
|
||||||
|
|
||||||
// Create wide fanout from failing source
|
// Create wide fanout from failing source
|
||||||
let dependents: Vec<_> = (0..20)
|
let dependents: Vec<_> = (0..20)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
dag.add_task(task_fn(move |x: i32| async move { x + i }))
|
dag.add_task(task_fn(move |x: i32| async move { x + i }))
|
||||||
.depends_on(&source)
|
.depends_on(source)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
||||||
|
|
||||||
// Source fails
|
// Source fails
|
||||||
assert!(dag.get(&source).is_err());
|
assert!(dag.get(source).is_err());
|
||||||
|
|
||||||
// All dependents fail
|
// All dependents fail
|
||||||
for dependent in &dependents {
|
for dependent in &dependents {
|
||||||
@@ -190,9 +198,11 @@ async fn test_selective_error_propagation() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
// Source that produces a Result
|
// Source that produces a Result
|
||||||
let source = dag.add_task(task_fn(|_: ()| async {
|
let source: TaskHandle<_> = dag
|
||||||
Result::<i32, String>::Err("Source error".to_string())
|
.add_task(task_fn(|_: ()| async {
|
||||||
}));
|
Result::<i32, String>::Err("Source error".to_string())
|
||||||
|
}))
|
||||||
|
.into();
|
||||||
|
|
||||||
// Handler that processes the Result
|
// Handler that processes the Result
|
||||||
let handler = dag
|
let handler = dag
|
||||||
@@ -202,7 +212,7 @@ async fn test_selective_error_propagation() {
|
|||||||
Err(_) => -1, // Default value on error
|
Err(_) => -1, // Default value on error
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
|
|
||||||
// Further processing
|
// Further processing
|
||||||
let final_task = dag
|
let final_task = dag
|
||||||
@@ -220,7 +230,7 @@ async fn test_selective_error_propagation() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Source returns an Err variant (not a panic)
|
// Source returns an Err variant (not a panic)
|
||||||
assert_eq!(dag.get(&source).unwrap(), Err("Source error".to_string()));
|
assert_eq!(dag.get(source).unwrap(), Err("Source error".to_string()));
|
||||||
|
|
||||||
// Handler processes it
|
// Handler processes it
|
||||||
assert_eq!(dag.get(handler).unwrap(), -1);
|
assert_eq!(dag.get(handler).unwrap(), -1);
|
||||||
@@ -258,7 +268,7 @@ async fn test_error_propagation_timing() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.depends_on(&t1);
|
.depends_on(t1);
|
||||||
|
|
||||||
let _t3 = dag
|
let _t3 = dag
|
||||||
.add_task(task_fn({
|
.add_task(task_fn({
|
||||||
@@ -271,7 +281,7 @@ async fn test_error_propagation_timing() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.depends_on(&t2);
|
.depends_on(t2);
|
||||||
|
|
||||||
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
||||||
|
|
||||||
@@ -297,12 +307,12 @@ async fn test_partial_branch_failure_propagation() {
|
|||||||
// \ | /
|
// \ | /
|
||||||
// sink
|
// sink
|
||||||
|
|
||||||
let root = dag.add_task(task_fn(|_: ()| async { 100 }));
|
let root: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 100 })).into();
|
||||||
|
|
||||||
// Branch A
|
// Branch A
|
||||||
let a1 = dag
|
let a1 = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
||||||
.depends_on(&root);
|
.depends_on(root);
|
||||||
let a2 = dag
|
let a2 = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 2 }))
|
.add_task(task_fn(|x: i32| async move { x + 2 }))
|
||||||
.depends_on(a1);
|
.depends_on(a1);
|
||||||
@@ -313,22 +323,22 @@ async fn test_partial_branch_failure_propagation() {
|
|||||||
// Branch B (with error) - use Result instead of panic
|
// Branch B (with error) - use Result instead of panic
|
||||||
let b1 = dag
|
let b1 = dag
|
||||||
.add_task(task_fn(|x: i32| async move { Ok(x * 2) }))
|
.add_task(task_fn(|x: i32| async move { Ok(x * 2) }))
|
||||||
.depends_on(&root);
|
.depends_on(root);
|
||||||
let b2 = dag
|
let b2 = dag
|
||||||
.add_task(task_fn(|_x: Result<i32, &str>| async move {
|
.add_task(task_fn(|_x: Result<i32, &str>| async move {
|
||||||
Err("Branch B fails at b2")
|
Err("Branch B fails at b2")
|
||||||
}))
|
}))
|
||||||
.depends_on(&b1);
|
.depends_on(b1);
|
||||||
let b3 = dag
|
let b3 = dag
|
||||||
.add_task(task_fn(
|
.add_task(task_fn(
|
||||||
|x: Result<i32, &str>| async move { x.map(|v| v * 3) },
|
|x: Result<i32, &str>| async move { x.map(|v| v * 3) },
|
||||||
))
|
))
|
||||||
.depends_on(&b2);
|
.depends_on(b2);
|
||||||
|
|
||||||
// Branch C
|
// Branch C
|
||||||
let c1 = dag
|
let c1 = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x - 1 }))
|
.add_task(task_fn(|x: i32| async move { x - 1 }))
|
||||||
.depends_on(&root);
|
.depends_on(root);
|
||||||
let c2 = dag
|
let c2 = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x - 2 }))
|
.add_task(task_fn(|x: i32| async move { x - 2 }))
|
||||||
.depends_on(c1);
|
.depends_on(c1);
|
||||||
|
|||||||
+17
-13
@@ -27,10 +27,10 @@ async fn test_fallback_on_error() -> DagResult<()> {
|
|||||||
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
||||||
|
|
||||||
// Primary should fail
|
// Primary should fail
|
||||||
assert!(dag.get(&primary).is_err());
|
assert!(dag.get(primary).is_err());
|
||||||
|
|
||||||
// Fallback should succeed
|
// Fallback should succeed
|
||||||
assert_eq!(dag.get(&fallback)?, 100);
|
assert_eq!(dag.get(fallback)?, 100);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,7 @@ async fn test_retry_pattern_simulation() -> DagResult<()> {
|
|||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
// Task completes with error on first "attempt"
|
// Task completes with error on first "attempt"
|
||||||
assert_eq!(dag.get(&task)?, Err("Simulated failure"));
|
assert_eq!(dag.get(task)?, Err("Simulated failure"));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@ async fn test_circuit_breaker_pattern() -> DagResult<()> {
|
|||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
// First few tasks fail and open circuit
|
// First few tasks fail and open circuit
|
||||||
for task in &tasks[0..3] {
|
for task in tasks.into_iter().take(4) {
|
||||||
assert!(dag.get(task)?.is_err());
|
assert!(dag.get(task)?.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ async fn test_partial_recovery_with_defaults() -> DagResult<()> {
|
|||||||
|
|
||||||
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
||||||
|
|
||||||
let result = dag.get(&aggregator)?;
|
let result = dag.get(aggregator)?;
|
||||||
assert_eq!(result.len(), 10);
|
assert_eq!(result.len(), 10);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -149,7 +149,7 @@ async fn test_error_accumulation_pattern() -> DagResult<()> {
|
|||||||
let errors = Arc::new(parking_lot::Mutex::new(Vec::new()));
|
let errors = Arc::new(parking_lot::Mutex::new(Vec::new()));
|
||||||
|
|
||||||
// Tasks that may produce errors
|
// Tasks that may produce errors
|
||||||
let tasks: Vec<_> = (0..10)
|
let mut tasks: Vec<_> = (0..10)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let errors = errors.clone();
|
let errors = errors.clone();
|
||||||
dag.add_task(task_fn(move |_: ()| {
|
dag.add_task(task_fn(move |_: ()| {
|
||||||
@@ -171,9 +171,13 @@ async fn test_error_accumulation_pattern() -> DagResult<()> {
|
|||||||
let error_list = errors.lock().clone();
|
let error_list = errors.lock().clone();
|
||||||
assert_eq!(error_list.len(), 5); // Even numbered tasks
|
assert_eq!(error_list.len(), 5); // Even numbered tasks
|
||||||
|
|
||||||
|
tasks.truncate(2);
|
||||||
|
|
||||||
|
let [first, second] = <[_; 2]>::try_from(tasks).map_err(|_e| ()).unwrap();
|
||||||
|
|
||||||
// Verify specific results
|
// Verify specific results
|
||||||
assert!(dag.get(&tasks[0])?.is_err());
|
assert!(dag.get(first)?.is_err());
|
||||||
assert_eq!(dag.get(&tasks[1])?, Ok(1));
|
assert_eq!(dag.get(second)?, Ok(1));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -197,7 +201,7 @@ async fn test_graceful_degradation() -> DagResult<()> {
|
|||||||
// Here just uses base
|
// Here just uses base
|
||||||
base.iter().sum::<i32>()
|
base.iter().sum::<i32>()
|
||||||
}))
|
}))
|
||||||
.depends_on(&critical);
|
.depends_on(critical);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
@@ -217,13 +221,13 @@ async fn test_error_boundary_isolation() -> DagResult<()> {
|
|||||||
.add_task(task_fn(|_x: i32| async move {
|
.add_task(task_fn(|_x: i32| async move {
|
||||||
Err::<i32, &str>("Group A error")
|
Err::<i32, &str>("Group A error")
|
||||||
}))
|
}))
|
||||||
.depends_on(&a1);
|
.depends_on(a1);
|
||||||
|
|
||||||
// Group B: should be isolated from Group A's error
|
// Group B: should be isolated from Group A's error
|
||||||
let b1 = dag.add_task(task_fn(|_: ()| async { 20 }));
|
let b1 = dag.add_task(task_fn(|_: ()| async { 20 }));
|
||||||
let b2 = dag
|
let b2 = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
||||||
.depends_on(&b1);
|
.depends_on(b1);
|
||||||
|
|
||||||
// Group C: depends on B but not A
|
// Group C: depends on B but not A
|
||||||
let c1 = dag
|
let c1 = dag
|
||||||
@@ -268,10 +272,10 @@ async fn test_compensation_action() -> DagResult<()> {
|
|||||||
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
||||||
|
|
||||||
// Action failed
|
// Action failed
|
||||||
assert!(dag.get(&action).is_err());
|
assert!(dag.get(action).is_err());
|
||||||
|
|
||||||
// Compensation ran successfully
|
// Compensation ran successfully
|
||||||
assert_eq!(dag.get(&compensation)?, "Compensation executed");
|
assert_eq!(dag.get(compensation)?, "Compensation executed");
|
||||||
assert!(compensation_triggered.load(Ordering::SeqCst));
|
assert!(compensation_triggered.load(Ordering::SeqCst));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
+26
-24
@@ -29,7 +29,7 @@ async fn test_deep_chain() {
|
|||||||
let a = dag.add_task(task_fn(|_: ()| async { 1 }));
|
let a = dag.add_task(task_fn(|_: ()| async { 1 }));
|
||||||
let b = dag
|
let b = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
||||||
.depends_on(&a);
|
.depends_on(a);
|
||||||
let c = dag
|
let c = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
||||||
.depends_on(b);
|
.depends_on(b);
|
||||||
@@ -61,7 +61,7 @@ async fn test_wide_parallel() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for (i, task) in tasks.iter().enumerate() {
|
for (i, task) in tasks.into_iter().enumerate() {
|
||||||
assert_eq!(dag.get(task).unwrap(), i * 2);
|
assert_eq!(dag.get(task).unwrap(), i * 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,13 +70,13 @@ async fn test_wide_parallel() {
|
|||||||
async fn test_diamond_dependency() {
|
async fn test_diamond_dependency() {
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let a = dag.add_task(task_fn(|_: ()| async { 10 }));
|
let a: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 10 })).into();
|
||||||
let b = dag
|
let b = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 5 }))
|
.add_task(task_fn(|x: i32| async move { x + 5 }))
|
||||||
.depends_on(&a);
|
.depends_on(a);
|
||||||
let c = dag
|
let c = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
||||||
.depends_on(&a);
|
.depends_on(a);
|
||||||
let d = dag
|
let d = dag
|
||||||
.add_task(task_fn(|(x, y): (i32, i32)| async move { x + y }))
|
.add_task(task_fn(|(x, y): (i32, i32)| async move { x + y }))
|
||||||
.depends_on((&b, &c));
|
.depends_on((&b, &c));
|
||||||
@@ -124,12 +124,12 @@ async fn test_different_output_types() {
|
|||||||
async fn test_multiple_sinks() {
|
async fn test_multiple_sinks() {
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let source = dag.add_task(task_fn(|_: ()| async { 10 }));
|
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 10 })).into();
|
||||||
|
|
||||||
// Branch 1
|
// Branch 1
|
||||||
let branch1 = dag
|
let branch1 = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
let sink1 = dag
|
let sink1 = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
||||||
.depends_on(branch1);
|
.depends_on(branch1);
|
||||||
@@ -137,7 +137,7 @@ async fn test_multiple_sinks() {
|
|||||||
// Branch 2
|
// Branch 2
|
||||||
let branch2 = dag
|
let branch2 = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 5 }))
|
.add_task(task_fn(|x: i32| async move { x + 5 }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
let sink2 = dag
|
let sink2 = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 3 }))
|
.add_task(task_fn(|x: i32| async move { x * 3 }))
|
||||||
.depends_on(branch2);
|
.depends_on(branch2);
|
||||||
@@ -156,10 +156,10 @@ async fn test_single_task_inline_path() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
// Single task in its own layer
|
// Single task in its own layer
|
||||||
let t1 = dag.add_task(task_fn(|_: ()| async { 10 }));
|
let t1: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 10 })).into();
|
||||||
let t2 = dag
|
let t2 = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
||||||
.depends_on(&t1);
|
.depends_on(t1);
|
||||||
let t3 = dag
|
let t3 = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 5 }))
|
.add_task(task_fn(|x: i32| async move { x + 5 }))
|
||||||
.depends_on(t2);
|
.depends_on(t2);
|
||||||
@@ -176,18 +176,18 @@ async fn test_multi_task_spawn_path() {
|
|||||||
// Test multi-task layer spawn path (line 411-456 in runner.rs)
|
// Test multi-task layer spawn path (line 411-456 in runner.rs)
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let source = dag.add_task(task_fn(|_: ()| async { 100 }));
|
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 100 })).into();
|
||||||
|
|
||||||
// Create multiple tasks in the same layer (all depend on source)
|
// Create multiple tasks in the same layer (all depend on source)
|
||||||
let t1 = dag
|
let t1 = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
let t2 = dag
|
let t2 = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 2 }))
|
.add_task(task_fn(|x: i32| async move { x + 2 }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
let t3 = dag
|
let t3 = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 3 }))
|
.add_task(task_fn(|x: i32| async move { x + 3 }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -204,14 +204,16 @@ async fn test_producer_consumer_channels() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
// Producer
|
// Producer
|
||||||
let producer = dag.add_task(task_fn(|_: ()| async { vec![1, 2, 3, 4, 5] }));
|
let producer: TaskHandle<_> = dag
|
||||||
|
.add_task(task_fn(|_: ()| async { vec![1, 2, 3, 4, 5] }))
|
||||||
|
.into();
|
||||||
|
|
||||||
// Consumer
|
// Consumer
|
||||||
let consumer = dag
|
let consumer = dag
|
||||||
.add_task(task_fn(
|
.add_task(task_fn(
|
||||||
|data: Vec<i32>| async move { data.iter().sum::<i32>() },
|
|data: Vec<i32>| async move { data.iter().sum::<i32>() },
|
||||||
))
|
))
|
||||||
.depends_on(&producer);
|
.depends_on(producer);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -225,18 +227,18 @@ async fn test_multi_consumer_fanout() {
|
|||||||
// Test fanout with multiple consumers from one producer
|
// Test fanout with multiple consumers from one producer
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let producer = dag.add_task(task_fn(|_: ()| async { 42 }));
|
let producer: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 42 })).into();
|
||||||
|
|
||||||
// Multiple consumers
|
// Multiple consumers
|
||||||
let c1 = dag
|
let c1 = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
||||||
.depends_on(&producer);
|
.depends_on(producer);
|
||||||
let c2 = dag
|
let c2 = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 10 }))
|
.add_task(task_fn(|x: i32| async move { x + 10 }))
|
||||||
.depends_on(&producer);
|
.depends_on(producer);
|
||||||
let c3 = dag
|
let c3 = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x - 5 }))
|
.add_task(task_fn(|x: i32| async move { x - 5 }))
|
||||||
.depends_on(&producer);
|
.depends_on(producer);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -253,14 +255,14 @@ async fn test_compute_layers_with_dependents_tracking() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
// Create diamond pattern to ensure dependents are tracked correctly
|
// Create diamond pattern to ensure dependents are tracked correctly
|
||||||
let source = dag.add_task(task_fn(|_: ()| async { 1 }));
|
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 1 })).into();
|
||||||
|
|
||||||
let left = dag
|
let left = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
let right = dag
|
let right = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 2 }))
|
.add_task(task_fn(|x: i32| async move { x + 2 }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
|
|
||||||
let sink = dag
|
let sink = dag
|
||||||
.add_task(task_fn(|(l, r): (i32, i32)| async move { l + r }))
|
.add_task(task_fn(|(l, r): (i32, i32)| async move { l + r }))
|
||||||
@@ -301,7 +303,7 @@ async fn test_layer_execution_order() {
|
|||||||
x + 1
|
x + 1
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.depends_on(&t1);
|
.depends_on(t1);
|
||||||
|
|
||||||
let order3 = execution_order.clone();
|
let order3 = execution_order.clone();
|
||||||
let t3 = dag
|
let t3 = dag
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ use futures::FutureExt;
|
|||||||
async fn test_example_a_fan_out() {
|
async fn test_example_a_fan_out() {
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let base = dag.add_task(Seed::new(10)); // Input=() → no depends_on, returns TaskHandle
|
let base: TaskHandle<_> = dag.add_task(Seed::new(10)).into(); // Input=() → no depends_on, returns TaskHandle
|
||||||
let plus1 = dag.add_task(Inc::new()).depends_on(&base); // 11
|
let plus1 = dag.add_task(Inc::new()).depends_on(base); // 11
|
||||||
let plus2 = dag.add_task(Inc::new()).depends_on(&base); // 11
|
let plus2 = dag.add_task(Inc::new()).depends_on(base); // 11
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -29,7 +29,7 @@ async fn test_example_b_fan_in() {
|
|||||||
let n = dag.add_task(task_fn(|_: ()| async { 42usize }));
|
let n = dag.add_task(task_fn(|_: ()| async { 42usize }));
|
||||||
let f = dag.add_task(task_fn(|_: ()| async { true }));
|
let f = dag.add_task(task_fn(|_: ()| async { true }));
|
||||||
|
|
||||||
let output = dag.add_task(OutputResult::new()).depends_on((&s, &n, &f));
|
let output = dag.add_task(OutputResult::new()).depends_on((s, n, f));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -42,11 +42,11 @@ async fn test_example_c_many_to_many() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let x = dag.add_task(Seed::new(2));
|
let x = dag.add_task(Seed::new(2));
|
||||||
let y = dag.add_task(Seed::new(3));
|
let y = dag.add_task(Seed::new(3)).into();
|
||||||
let z = dag.add_task(Seed::new(5));
|
let z = dag.add_task(Seed::new(5));
|
||||||
|
|
||||||
let sum_xy = dag.add_task(Add::new()).depends_on((&x, &y)); // 5
|
let sum_xy = dag.add_task(Add::new()).depends_on((x, &y)); // 5
|
||||||
let prod_yz = dag.add_task(Mul::new()).depends_on((&y, &z)); // 15
|
let prod_yz = dag.add_task(Mul::new()).depends_on((&y, z)); // 15
|
||||||
let total = dag.add_task(Add::new()).depends_on((&sum_xy, &prod_yz)); // 20
|
let total = dag.add_task(Add::new()).depends_on((&sum_xy, &prod_yz)); // 20
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
@@ -59,12 +59,14 @@ async fn test_example_c_many_to_many() {
|
|||||||
async fn test_single_input_forms() {
|
async fn test_single_input_forms() {
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let s = dag.add_task(task_fn(|_: ()| async { "hello".to_owned() }));
|
let s = dag
|
||||||
|
.add_task(task_fn(|_: ()| async { "hello".to_owned() }))
|
||||||
|
.into();
|
||||||
|
|
||||||
// Preferred form: &node
|
// Preferred form: &node
|
||||||
let upper1 = dag
|
let upper1 = dag
|
||||||
.add_task(task_fn(|s: String| async move { s.to_uppercase() }))
|
.add_task(task_fn(|s: String| async move { s.to_uppercase() }))
|
||||||
.depends_on(&s);
|
.depends_on(s);
|
||||||
|
|
||||||
// Also allowed: (&node,)
|
// Also allowed: (&node,)
|
||||||
let upper2 = dag
|
let upper2 = dag
|
||||||
|
|||||||
@@ -59,9 +59,9 @@ async fn integration_test_etl_pipeline() -> DagResult<()> {
|
|||||||
let source3 = dag.add_task(LoadValue::new(30));
|
let source3 = dag.add_task(LoadValue::new(30));
|
||||||
|
|
||||||
// Transform: Process each source
|
// Transform: Process each source
|
||||||
let transform1 = dag.add_task(Multiply::new(2)).depends_on(&source1);
|
let transform1 = dag.add_task(Multiply::new(2)).depends_on(source1);
|
||||||
let transform2 = dag.add_task(Multiply::new(3)).depends_on(&source2);
|
let transform2 = dag.add_task(Multiply::new(3)).depends_on(source2);
|
||||||
let transform3 = dag.add_task(Multiply::new(4)).depends_on(&source3);
|
let transform3 = dag.add_task(Multiply::new(4)).depends_on(source3);
|
||||||
|
|
||||||
// Load: Aggregate results
|
// Load: Aggregate results
|
||||||
let combine12 = dag.add_task(Add).depends_on((&transform1, &transform2));
|
let combine12 = dag.add_task(Add).depends_on((&transform1, &transform2));
|
||||||
@@ -115,9 +115,9 @@ async fn integration_test_multiple_sinks() -> DagResult<()> {
|
|||||||
let source = dag.add_task(LoadValue::new(10));
|
let source = dag.add_task(LoadValue::new(10));
|
||||||
|
|
||||||
// Multiple independent branches
|
// Multiple independent branches
|
||||||
let branch1 = dag.add_task(Multiply::new(2)).depends_on(&source);
|
let branch1 = dag.add_task(Multiply::new(2)).depends_on(source);
|
||||||
let branch2 = dag.add_task(Multiply::new(3)).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);
|
let branch3 = dag.add_task(Multiply::new(4)).depends_on(source);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
@@ -136,8 +136,8 @@ async fn integration_test_diamond_cascade() -> DagResult<()> {
|
|||||||
|
|
||||||
// Diamond 1
|
// Diamond 1
|
||||||
let source1 = dag.add_task(LoadValue::new(10));
|
let source1 = dag.add_task(LoadValue::new(10));
|
||||||
let left1 = dag.add_task(Multiply::new(2)).depends_on(&source1);
|
let left1 = dag.add_task(Multiply::new(2)).depends_on(source1);
|
||||||
let right1 = dag.add_task(Multiply::new(3)).depends_on(&source1);
|
let right1 = dag.add_task(Multiply::new(3)).depends_on(source1);
|
||||||
let sink1 = dag.add_task(Add).depends_on((&left1, &right1));
|
let sink1 = dag.add_task(Add).depends_on((&left1, &right1));
|
||||||
|
|
||||||
// Diamond 2 (depends on Diamond 1)
|
// Diamond 2 (depends on Diamond 1)
|
||||||
@@ -193,7 +193,7 @@ async fn integration_test_error_recovery() -> DagResult<()> {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let source = dag.add_task(LoadValue::new(42));
|
let source = dag.add_task(LoadValue::new(42));
|
||||||
let transform = dag.add_task(Multiply::new(2)).depends_on(&source);
|
let transform = dag.add_task(Multiply::new(2)).depends_on(source);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
@@ -248,9 +248,9 @@ async fn integration_test_reused_values() -> DagResult<()> {
|
|||||||
let source = dag.add_task(LoadValue::new(100));
|
let source = dag.add_task(LoadValue::new(100));
|
||||||
|
|
||||||
// Use source in multiple different ways
|
// Use source in multiple different ways
|
||||||
let double = dag.add_task(Multiply::new(2)).depends_on(&source);
|
let double = dag.add_task(Multiply::new(2)).depends_on(source);
|
||||||
let triple = dag.add_task(Multiply::new(3)).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);
|
let quadruple = dag.add_task(Multiply::new(4)).depends_on(source);
|
||||||
|
|
||||||
// Combine some of the results
|
// Combine some of the results
|
||||||
let combined = dag.add_task(Add).depends_on((&double, &triple));
|
let combined = dag.add_task(Add).depends_on((&double, &triple));
|
||||||
@@ -328,7 +328,7 @@ async fn integration_test_parallel_vs_sequential_timing() -> DagResult<()> {
|
|||||||
sleep(sleep_duration).await;
|
sleep(sleep_duration).await;
|
||||||
x + 1
|
x + 1
|
||||||
}))
|
}))
|
||||||
.depends_on(¶llel_tasks[0]);
|
.depends_on(parallel_tasks[0]);
|
||||||
|
|
||||||
let seq2 = dag
|
let seq2 = dag
|
||||||
.add_task(task_fn(move |x: i32| async move {
|
.add_task(task_fn(move |x: i32| async move {
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ async fn test_stateful_tasks() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let input = dag.add_task(task_fn(|_: ()| async { 5 }));
|
let input = dag.add_task(task_fn(|_: ()| async { 5 }));
|
||||||
let counter = dag.add_task(Counter::new(10)).depends_on(&input);
|
let counter = dag.add_task(Counter::new(10)).depends_on(input);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -105,7 +105,7 @@ async fn test_task_fn_with_captured_state() {
|
|||||||
let base = dag.add_task(task_fn(|_: ()| async { 5 }));
|
let base = dag.add_task(task_fn(|_: ()| async { 5 }));
|
||||||
let scaled = dag
|
let scaled = dag
|
||||||
.add_task(task_fn(move |x: i32| async move { x * multiplier }))
|
.add_task(task_fn(move |x: i32| async move { x * multiplier }))
|
||||||
.depends_on(&base);
|
.depends_on(base);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ async fn test_concurrent_run_calls() {
|
|||||||
assert_eq!(error_count, 4);
|
assert_eq!(error_count, 4);
|
||||||
|
|
||||||
// Verify results are consistent from the successful run
|
// Verify results are consistent from the successful run
|
||||||
for (i, task) in tasks.iter().enumerate() {
|
for (i, task) in tasks.into_iter().enumerate() {
|
||||||
assert_eq!(dag.get(task).unwrap(), i);
|
assert_eq!(dag.get(task).unwrap(), i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ async fn test_concurrent_execution_and_building() {
|
|||||||
let mut handles = JoinSet::new();
|
let mut handles = JoinSet::new();
|
||||||
|
|
||||||
// Reader tasks
|
// Reader tasks
|
||||||
for task in &initial {
|
for task in initial {
|
||||||
let dag = Arc::clone(&dag);
|
let dag = Arc::clone(&dag);
|
||||||
let task_handle: dagx::TaskHandle<i32> = task.into();
|
let task_handle: dagx::TaskHandle<i32> = task.into();
|
||||||
handles.spawn(async move { dag.get(task_handle).unwrap() });
|
handles.spawn(async move { dag.get(task_handle).unwrap() });
|
||||||
@@ -116,7 +116,7 @@ async fn test_dag_builder_thread_safety() {
|
|||||||
|
|
||||||
let transform1 = dag
|
let transform1 = dag
|
||||||
.add_task(task_fn(move |x: i32| async move { x * 10 }))
|
.add_task(task_fn(move |x: i32| async move { x * 10 }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
|
|
||||||
let transform2 = dag
|
let transform2 = dag
|
||||||
.add_task(task_fn(move |x: i32| async move { x + thread_id }))
|
.add_task(task_fn(move |x: i32| async move { x + thread_id }))
|
||||||
@@ -165,7 +165,7 @@ async fn test_concurrent_get_operations() {
|
|||||||
// Now access results concurrently
|
// Now access results concurrently
|
||||||
let mut handles = JoinSet::new();
|
let mut handles = JoinSet::new();
|
||||||
|
|
||||||
for task in tasks.iter() {
|
for task in tasks.into_iter() {
|
||||||
let dag = Arc::clone(&dag);
|
let dag = Arc::clone(&dag);
|
||||||
let task_handle: dagx::TaskHandle<i32> = task.into();
|
let task_handle: dagx::TaskHandle<i32> = task.into();
|
||||||
handles.spawn(async move { dag.get(task_handle).unwrap() });
|
handles.spawn(async move { dag.get(task_handle).unwrap() });
|
||||||
@@ -193,7 +193,7 @@ async fn test_no_race_in_dependency_tracking() {
|
|||||||
let mut handles = JoinSet::new();
|
let mut handles = JoinSet::new();
|
||||||
|
|
||||||
// Multiple threads create tasks depending on the same source
|
// Multiple threads create tasks depending on the same source
|
||||||
let source_handle: dagx::TaskHandle<i32> = (&source).into();
|
let source_handle: dagx::TaskHandle<i32> = source.into();
|
||||||
for i in 0..50 {
|
for i in 0..50 {
|
||||||
let dag = Arc::clone(&dag);
|
let dag = Arc::clone(&dag);
|
||||||
let source_clone = source_handle;
|
let source_clone = source_handle;
|
||||||
@@ -245,14 +245,14 @@ async fn test_high_concurrency_stress() {
|
|||||||
// Add a task with a simple dependency
|
// Add a task with a simple dependency
|
||||||
let source = dag.add_task(task_fn(move |_: ()| async move { i }));
|
let source = dag.add_task(task_fn(move |_: ()| async move { i }));
|
||||||
dag.add_task(task_fn(move |x: i32| async move { x * 2 }))
|
dag.add_task(task_fn(move |x: i32| async move { x * 2 }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
}
|
}
|
||||||
2 => {
|
2 => {
|
||||||
// Add a more complex subgraph
|
// Add a more complex subgraph
|
||||||
let a = dag.add_task(task_fn(move |_: ()| async move { i }));
|
let a = dag.add_task(task_fn(move |_: ()| async move { i }));
|
||||||
let b = dag.add_task(task_fn(move |_: ()| async move { i + 1 }));
|
let b = dag.add_task(task_fn(move |_: ()| async move { i + 1 }));
|
||||||
dag.add_task(task_fn(move |(x, y): (i32, i32)| async move { x + y }))
|
dag.add_task(task_fn(move |(x, y): (i32, i32)| async move { x + y }))
|
||||||
.depends_on((&a, &b));
|
.depends_on((a, b));
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
@@ -332,7 +332,7 @@ async fn test_atomic_dag_operations() {
|
|||||||
let mut handles = JoinSet::new();
|
let mut handles = JoinSet::new();
|
||||||
let success_count = Arc::new(AtomicUsize::new(0));
|
let success_count = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
let base_handle: dagx::TaskHandle<i32> = (&base).into();
|
let base_handle: dagx::TaskHandle<i32> = base.into();
|
||||||
for i in 0..50 {
|
for i in 0..50 {
|
||||||
let dag = Arc::clone(&dag);
|
let dag = Arc::clone(&dag);
|
||||||
let base_clone = base_handle;
|
let base_clone = base_handle;
|
||||||
@@ -382,7 +382,7 @@ async fn test_no_deadlock_on_circular_waiting() {
|
|||||||
handles.spawn(async move {
|
handles.spawn(async move {
|
||||||
// Each thread creates a chain of tasks
|
// Each thread creates a chain of tasks
|
||||||
let first = dag.add_task(task_fn(move |_: ()| async move { i }));
|
let first = dag.add_task(task_fn(move |_: ()| async move { i }));
|
||||||
let mut chain = vec![(&first).into()];
|
let mut chain = vec![first.into()];
|
||||||
|
|
||||||
for j in 0..5 {
|
for j in 0..5 {
|
||||||
let next = dag
|
let next = dag
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Tests for execution correctness and parallelism
|
//! Tests for execution correctness and parallelism
|
||||||
|
|
||||||
use crate::common::task_fn;
|
use crate::common::task_fn;
|
||||||
use dagx::{DagResult, DagRunner};
|
use dagx::{DagResult, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
@@ -32,7 +32,7 @@ async fn test_layers_execute_in_parallel() -> DagResult<()> {
|
|||||||
|
|
||||||
// Layer 1: Tasks depending on layer 0
|
// Layer 1: Tasks depending on layer 0
|
||||||
let layer1: Vec<_> = sources
|
let layer1: Vec<_> = sources
|
||||||
.iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, source)| {
|
.map(|(i, source)| {
|
||||||
let timing = layer_timing.clone();
|
let timing = layer_timing.clone();
|
||||||
@@ -168,7 +168,7 @@ async fn test_completion_order_matches_dependencies() -> DagResult<()> {
|
|||||||
x + y
|
x + y
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.depends_on((&a, &b))
|
.depends_on((a, b))
|
||||||
};
|
};
|
||||||
|
|
||||||
let d = {
|
let d = {
|
||||||
@@ -216,7 +216,7 @@ async fn test_dependent_tasks_respect_ordering() -> DagResult<()> {
|
|||||||
let execution_order = Arc::new(Mutex::new(Vec::new()));
|
let execution_order = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
|
||||||
// Create a chain: A -> B -> C -> D
|
// Create a chain: A -> B -> C -> D
|
||||||
let a = {
|
let a: TaskHandle<_> = {
|
||||||
let order = execution_order.clone();
|
let order = execution_order.clone();
|
||||||
dag.add_task(task_fn(move |_: ()| {
|
dag.add_task(task_fn(move |_: ()| {
|
||||||
let order = order.clone();
|
let order = order.clone();
|
||||||
@@ -226,7 +226,8 @@ async fn test_dependent_tasks_respect_ordering() -> DagResult<()> {
|
|||||||
1
|
1
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
};
|
}
|
||||||
|
.into();
|
||||||
|
|
||||||
let b = {
|
let b = {
|
||||||
let order = execution_order.clone();
|
let order = execution_order.clone();
|
||||||
@@ -238,7 +239,7 @@ async fn test_dependent_tasks_respect_ordering() -> DagResult<()> {
|
|||||||
x + 1
|
x + 1
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.depends_on(&a)
|
.depends_on(a)
|
||||||
};
|
};
|
||||||
|
|
||||||
let c = {
|
let c = {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ async fn test_parallelism_proof_1000_tasks() -> DagResult<()> {
|
|||||||
let elapsed = start.elapsed();
|
let elapsed = start.elapsed();
|
||||||
|
|
||||||
// Verify all tasks completed
|
// Verify all tasks completed
|
||||||
for (i, task) in tasks.iter().enumerate() {
|
for (i, task) in tasks.into_iter().enumerate() {
|
||||||
assert_eq!(dag.get(task)?, i as u32);
|
assert_eq!(dag.get(task)?, i as u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ async fn test_spawner_actually_spawns_tasks() -> DagResult<()> {
|
|||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
// Verify all tasks were spawned
|
// Verify all tasks were spawned
|
||||||
for (i, task) in tasks.iter().enumerate() {
|
for (i, task) in tasks.into_iter().enumerate() {
|
||||||
assert_eq!(dag.get(task)?, i as i32);
|
assert_eq!(dag.get(task)?, i as i32);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ async fn test_tasks_not_running_inline() -> DagResult<()> {
|
|||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
// Verify results
|
// Verify results
|
||||||
for (i, task) in tasks.iter().enumerate() {
|
for (i, task) in tasks.into_iter().enumerate() {
|
||||||
assert_eq!(dag.get(task)?, i as i32);
|
assert_eq!(dag.get(task)?, i as i32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Tests for multi-threading and concurrent execution
|
//! Tests for multi-threading and concurrent execution
|
||||||
|
|
||||||
use crate::common::task_fn;
|
use crate::common::task_fn;
|
||||||
use dagx::{DagResult, DagRunner};
|
use dagx::{DagResult, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
@@ -44,7 +44,7 @@ async fn test_tasks_run_on_different_threads() -> DagResult<()> {
|
|||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
// Verify all tasks completed
|
// Verify all tasks completed
|
||||||
for task in tasks.iter() {
|
for task in tasks.into_iter() {
|
||||||
let _ = dag.get(task)?;
|
let _ = dag.get(task)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ async fn test_concurrent_execution_with_atomic_counter() -> DagResult<()> {
|
|||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
// Verify all tasks completed
|
// Verify all tasks completed
|
||||||
for (i, task) in tasks.iter().enumerate() {
|
for (i, task) in tasks.into_iter().enumerate() {
|
||||||
assert_eq!(dag.get(task)?, i);
|
assert_eq!(dag.get(task)?, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ async fn test_diamond_parallel_execution() -> DagResult<()> {
|
|||||||
|
|
||||||
let concurrent_middle = Arc::new(AtomicUsize::new(0));
|
let concurrent_middle = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
let source = dag.add_task(task_fn(|_: ()| async { 100 }));
|
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 100 })).into();
|
||||||
|
|
||||||
// These should run in parallel
|
// These should run in parallel
|
||||||
let parallel1 = {
|
let parallel1 = {
|
||||||
@@ -157,7 +157,7 @@ async fn test_diamond_parallel_execution() -> DagResult<()> {
|
|||||||
x * 2
|
x * 2
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.depends_on(&source)
|
.depends_on(source)
|
||||||
};
|
};
|
||||||
|
|
||||||
let parallel2 = {
|
let parallel2 = {
|
||||||
@@ -174,7 +174,7 @@ async fn test_diamond_parallel_execution() -> DagResult<()> {
|
|||||||
x * 3
|
x * 3
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.depends_on(&source)
|
.depends_on(source)
|
||||||
};
|
};
|
||||||
|
|
||||||
let sink = dag
|
let sink = dag
|
||||||
@@ -196,7 +196,7 @@ async fn test_parallel_branches_with_dependencies() -> DagResult<()> {
|
|||||||
let branch_progress = Arc::new(Mutex::new(Vec::new()));
|
let branch_progress = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
|
||||||
// Source node
|
// Source node
|
||||||
let source = dag.add_task(task_fn(|_: ()| async { 10 }));
|
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 10 })).into();
|
||||||
|
|
||||||
// Branch 1: source -> b1_1 -> b1_2
|
// Branch 1: source -> b1_1 -> b1_2
|
||||||
let b1_1 = {
|
let b1_1 = {
|
||||||
@@ -213,7 +213,7 @@ async fn test_parallel_branches_with_dependencies() -> DagResult<()> {
|
|||||||
x * 2
|
x * 2
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.depends_on(&source)
|
.depends_on(source)
|
||||||
};
|
};
|
||||||
|
|
||||||
let b1_2 = {
|
let b1_2 = {
|
||||||
@@ -248,7 +248,7 @@ async fn test_parallel_branches_with_dependencies() -> DagResult<()> {
|
|||||||
x * 3
|
x * 3
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.depends_on(&source)
|
.depends_on(source)
|
||||||
};
|
};
|
||||||
|
|
||||||
let b2_2 = {
|
let b2_2 = {
|
||||||
@@ -320,7 +320,7 @@ async fn test_wide_fanout_parallel_execution() -> DagResult<()> {
|
|||||||
let max_concurrent = Arc::new(AtomicUsize::new(0));
|
let max_concurrent = Arc::new(AtomicUsize::new(0));
|
||||||
let current_concurrent = Arc::new(AtomicUsize::new(0));
|
let current_concurrent = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
let source = dag.add_task(task_fn(|_: ()| async { 42 }));
|
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 42 })).into();
|
||||||
|
|
||||||
// Create 50 tasks that all depend on the source
|
// Create 50 tasks that all depend on the source
|
||||||
let dependents: Vec<_> = (0..50)
|
let dependents: Vec<_> = (0..50)
|
||||||
@@ -356,7 +356,7 @@ async fn test_wide_fanout_parallel_execution() -> DagResult<()> {
|
|||||||
x + i
|
x + i
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.depends_on(&source)
|
.depends_on(source)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -388,7 +388,7 @@ async fn test_massive_parallel_fanout() -> DagResult<()> {
|
|||||||
let max_concurrent = Arc::new(AtomicUsize::new(0));
|
let max_concurrent = Arc::new(AtomicUsize::new(0));
|
||||||
let current = Arc::new(AtomicUsize::new(0));
|
let current = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
let source = dag.add_task(task_fn(|_: ()| async { 1 }));
|
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 1 })).into();
|
||||||
|
|
||||||
// Create 1000 tasks all depending on the same source
|
// Create 1000 tasks all depending on the same source
|
||||||
let tasks: Vec<_> = (0..1000)
|
let tasks: Vec<_> = (0..1000)
|
||||||
@@ -426,7 +426,7 @@ async fn test_massive_parallel_fanout() -> DagResult<()> {
|
|||||||
x + i
|
x + i
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.depends_on(&source)
|
.depends_on(source)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use crate::common::task_fn;
|
use crate::common::task_fn;
|
||||||
|
|
||||||
use dagx::{DagResult, DagRunner};
|
use dagx::{DagResult, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
@@ -32,7 +32,7 @@ async fn test_parallel_execution_speedup() -> DagResult<()> {
|
|||||||
let elapsed = start.elapsed();
|
let elapsed = start.elapsed();
|
||||||
|
|
||||||
// Verify all tasks completed
|
// Verify all tasks completed
|
||||||
for (i, task) in tasks.iter().enumerate() {
|
for (i, task) in tasks.into_iter().enumerate() {
|
||||||
assert_eq!(dag.get(task)?, i);
|
assert_eq!(dag.get(task)?, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,15 +84,15 @@ async fn test_sync_tasks_dont_block_async_runtime() -> DagResult<()> {
|
|||||||
let start_times = Arc::new(Mutex::new(Vec::new()));
|
let start_times = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
|
||||||
// Mix of sync and async tasks
|
// Mix of sync and async tasks
|
||||||
let source = dag.add_task(task_fn(|_: ()| async { 10 }));
|
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 10 })).into();
|
||||||
|
|
||||||
// Sync tasks
|
// Sync tasks
|
||||||
let sync1 = dag.add_task(SyncTask).depends_on(&source);
|
let sync1 = dag.add_task(SyncTask).depends_on(source);
|
||||||
let sync2 = dag.add_task(SyncTask).depends_on(&source);
|
let sync2 = dag.add_task(SyncTask).depends_on(source);
|
||||||
|
|
||||||
// Async tasks
|
// Async tasks
|
||||||
let async1 = dag.add_task(AsyncTask).depends_on(&source);
|
let async1 = dag.add_task(AsyncTask).depends_on(source);
|
||||||
let async2 = dag.add_task(AsyncTask).depends_on(&source);
|
let async2 = dag.add_task(AsyncTask).depends_on(source);
|
||||||
|
|
||||||
// Track timing
|
// Track timing
|
||||||
let timing_task = {
|
let timing_task = {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Butterfly pattern DAG tests
|
//! Butterfly pattern DAG tests
|
||||||
|
|
||||||
use crate::common::task_fn;
|
use crate::common::task_fn;
|
||||||
use dagx::{DagResult, DagRunner};
|
use dagx::{DagResult, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -13,7 +13,10 @@ async fn test_butterfly_network() -> DagResult<()> {
|
|||||||
// Simplified butterfly network - avoid tuples entirely
|
// Simplified butterfly network - avoid tuples entirely
|
||||||
// Layer 0: inputs
|
// Layer 0: inputs
|
||||||
let inputs: Vec<_> = (0..8)
|
let inputs: Vec<_> = (0..8)
|
||||||
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i as f64 })))
|
.map(|i| {
|
||||||
|
dag.add_task(task_fn(move |_: ()| async move { i as f64 }))
|
||||||
|
.into()
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Layer 1: butterfly operations (compute pairs separately)
|
// Layer 1: butterfly operations (compute pairs separately)
|
||||||
@@ -92,7 +95,7 @@ async fn test_recursive_butterfly() -> DagResult<()> {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|val| {
|
.map(|val| {
|
||||||
let task = dag.add_task(task_fn(move |_: ()| async move { val }));
|
let task = dag.add_task(task_fn(move |_: ()| async move { val }));
|
||||||
(&task).into()
|
task.into()
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -115,7 +118,10 @@ async fn test_benes_network() -> DagResult<()> {
|
|||||||
|
|
||||||
// 4 inputs
|
// 4 inputs
|
||||||
let inputs: Vec<_> = (0..4)
|
let inputs: Vec<_> = (0..4)
|
||||||
.map(|i| dag.add_task(task_fn(move |_: ()| async move { 1 << i }))) // Powers of 2
|
.map(|i| {
|
||||||
|
dag.add_task(task_fn(move |_: ()| async move { 1 << i }))
|
||||||
|
.into()
|
||||||
|
}) // Powers of 2
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// First stage: sort pairs
|
// First stage: sort pairs
|
||||||
@@ -153,7 +159,7 @@ async fn test_shuffle_exchange_network() -> DagResult<()> {
|
|||||||
let counter = Arc::new(AtomicUsize::new(0));
|
let counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
// Create 8 inputs
|
// Create 8 inputs
|
||||||
let inputs: Vec<_> = (0..8)
|
let inputs: Vec<TaskHandle<_>> = (0..8)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let counter = counter.clone();
|
let counter = counter.clone();
|
||||||
dag.add_task(task_fn(move |_: ()| {
|
dag.add_task(task_fn(move |_: ()| {
|
||||||
@@ -163,6 +169,7 @@ async fn test_shuffle_exchange_network() -> DagResult<()> {
|
|||||||
i
|
i
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
.into()
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -172,7 +179,7 @@ async fn test_shuffle_exchange_network() -> DagResult<()> {
|
|||||||
// Shuffle pattern: rotate bits
|
// Shuffle pattern: rotate bits
|
||||||
let source = ((i << 1) | (i >> 2)) & 0x7;
|
let source = ((i << 1) | (i >> 2)) & 0x7;
|
||||||
dag.add_task(task_fn(move |x: i32| async move { x }))
|
dag.add_task(task_fn(move |x: i32| async move { x }))
|
||||||
.depends_on(&inputs[source])
|
.depends_on(inputs[source])
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|||||||
+12
-10
@@ -1,7 +1,7 @@
|
|||||||
//! Complex DAG pattern tests
|
//! Complex DAG pattern tests
|
||||||
|
|
||||||
use crate::common::task_fn;
|
use crate::common::task_fn;
|
||||||
use dagx::{DagResult, DagRunner};
|
use dagx::{DagResult, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -21,7 +21,7 @@ async fn test_workflow_with_conditional_paths() -> DagResult<()> {
|
|||||||
Err("Invalid input")
|
Err("Invalid input")
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.depends_on(&input);
|
.depends_on(input);
|
||||||
|
|
||||||
// Process valid input
|
// Process valid input
|
||||||
let process = dag
|
let process = dag
|
||||||
@@ -31,7 +31,7 @@ async fn test_workflow_with_conditional_paths() -> DagResult<()> {
|
|||||||
Err(_) => -1,
|
Err(_) => -1,
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.depends_on(&validate);
|
.depends_on(validate);
|
||||||
|
|
||||||
// Parallel enhancement steps
|
// Parallel enhancement steps
|
||||||
let enhance1 = dag
|
let enhance1 = dag
|
||||||
@@ -80,7 +80,7 @@ async fn test_map_reduce_pattern() -> DagResult<()> {
|
|||||||
// Simulate word count
|
// Simulate word count
|
||||||
vec![("word1", i % 3), ("word2", i % 5), ("word3", i % 7)]
|
vec![("word1", i % 3), ("word2", i % 5), ("word3", i % 7)]
|
||||||
}));
|
}));
|
||||||
(&task).into()
|
task.into()
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -166,7 +166,7 @@ async fn test_pipeline_with_feedback() -> DagResult<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.depends_on(&init);
|
.depends_on(init);
|
||||||
|
|
||||||
// Stage 2: Filter
|
// Stage 2: Filter
|
||||||
let stage2 = dag
|
let stage2 = dag
|
||||||
@@ -209,9 +209,11 @@ async fn test_scatter_gather_pattern() -> DagResult<()> {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
// Scatter: distribute work to multiple workers
|
// Scatter: distribute work to multiple workers
|
||||||
let source = dag.add_task(task_fn(|_: ()| async {
|
let source: TaskHandle<_> = dag
|
||||||
vec![10, 20, 30, 40, 50, 60, 70, 80]
|
.add_task(task_fn(|_: ()| async {
|
||||||
}));
|
vec![10, 20, 30, 40, 50, 60, 70, 80]
|
||||||
|
}))
|
||||||
|
.into();
|
||||||
|
|
||||||
// Workers process chunks
|
// Workers process chunks
|
||||||
let workers: Vec<_> = (0..4)
|
let workers: Vec<_> = (0..4)
|
||||||
@@ -228,7 +230,7 @@ async fn test_scatter_gather_pattern() -> DagResult<()> {
|
|||||||
|
|
||||||
data[start..end].iter().sum::<i32>()
|
data[start..end].iter().sum::<i32>()
|
||||||
}))
|
}))
|
||||||
.depends_on(&source)
|
.depends_on(source)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -266,7 +268,7 @@ async fn test_fork_join_with_barriers() -> DagResult<()> {
|
|||||||
|
|
||||||
// Multiple parallel paths
|
// Multiple parallel paths
|
||||||
let mut path1 = Vec::new();
|
let mut path1 = Vec::new();
|
||||||
let parent_handle: dagx::TaskHandle<i32> = (&parent).into();
|
let parent_handle: dagx::TaskHandle<i32> = parent.into();
|
||||||
let mut prev1 = parent_handle;
|
let mut prev1 = parent_handle;
|
||||||
for i in 0..3 {
|
for i in 0..3 {
|
||||||
let counter = phase_counter.clone();
|
let counter = phase_counter.clone();
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ async fn test_2d_grid_pattern() -> DagResult<()> {
|
|||||||
#[allow(clippy::needless_range_loop)]
|
#[allow(clippy::needless_range_loop)]
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
let task = dag.add_task(task_fn(move |_: ()| async move { i as i32 }));
|
let task = dag.add_task(task_fn(move |_: ()| async move { i as i32 }));
|
||||||
grid[0][i] = Some((&task).into());
|
grid[0][i] = Some(task.into());
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
let task = dag.add_task(task_fn(move |_: ()| async move { i as i32 * 10 }));
|
let task = dag.add_task(task_fn(move |_: ()| async move { i as i32 * 10 }));
|
||||||
grid[i][0] = Some((&task).into());
|
grid[i][0] = Some(task.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ async fn test_3d_grid_pattern() -> DagResult<()> {
|
|||||||
|
|
||||||
// Initialize origin
|
// Initialize origin
|
||||||
let origin = dag.add_task(task_fn(|_: ()| async { 1 }));
|
let origin = dag.add_task(task_fn(|_: ()| async { 1 }));
|
||||||
grid[0][0][0] = Some((&origin).into());
|
grid[0][0][0] = Some(origin.into());
|
||||||
|
|
||||||
// Fill the 3D grid
|
// Fill the 3D grid
|
||||||
for x in 0..size {
|
for x in 0..size {
|
||||||
@@ -77,7 +77,7 @@ async fn test_3d_grid_pattern() -> DagResult<()> {
|
|||||||
let task_handle: dagx::TaskHandle<usize> = match deps.len() {
|
let task_handle: dagx::TaskHandle<usize> = match deps.len() {
|
||||||
0 => {
|
0 => {
|
||||||
let t = dag.add_task(task_fn(move |_: ()| async move { x + y + z }));
|
let t = dag.add_task(task_fn(move |_: ()| async move { x + y + z }));
|
||||||
(&t).into()
|
t.into()
|
||||||
}
|
}
|
||||||
1 => dag
|
1 => dag
|
||||||
.add_task(task_fn(move |prev: usize| async move { prev + 1 }))
|
.add_task(task_fn(move |prev: usize| async move { prev + 1 }))
|
||||||
@@ -121,7 +121,7 @@ async fn test_hexagonal_grid() -> DagResult<()> {
|
|||||||
|
|
||||||
// Center hex
|
// Center hex
|
||||||
let center_builder = dag.add_task(task_fn(|_: ()| async { 100 }));
|
let center_builder = dag.add_task(task_fn(|_: ()| async { 100 }));
|
||||||
let center: dagx::TaskHandle<i32> = (¢er_builder).into();
|
let center: dagx::TaskHandle<i32> = center_builder.into();
|
||||||
hex_grid.insert((0, 0), center);
|
hex_grid.insert((0, 0), center);
|
||||||
|
|
||||||
// Ring 1 (6 hexes around center)
|
// Ring 1 (6 hexes around center)
|
||||||
@@ -168,7 +168,7 @@ async fn test_hexagonal_grid() -> DagResult<()> {
|
|||||||
.depends_on(neighbors[0])
|
.depends_on(neighbors[0])
|
||||||
} else {
|
} else {
|
||||||
let t = dag.add_task(task_fn(move |_: ()| async move { q + r }));
|
let t = dag.add_task(task_fn(move |_: ()| async move { q + r }));
|
||||||
(&t).into()
|
t.into()
|
||||||
};
|
};
|
||||||
|
|
||||||
hex_grid.insert((q, r), task);
|
hex_grid.insert((q, r), task);
|
||||||
@@ -194,7 +194,7 @@ async fn test_toroidal_grid() -> DagResult<()> {
|
|||||||
for i in 0..SIZE {
|
for i in 0..SIZE {
|
||||||
for j in 0..SIZE {
|
for j in 0..SIZE {
|
||||||
let task = dag.add_task(task_fn(move |_: ()| async move { (i * SIZE + j) as i32 }));
|
let task = dag.add_task(task_fn(move |_: ()| async move { (i * SIZE + j) as i32 }));
|
||||||
grid[i][j] = Some((&task).into());
|
grid[i][j] = Some(task.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-14
@@ -1,7 +1,7 @@
|
|||||||
//! Tree pattern DAG tests
|
//! Tree pattern DAG tests
|
||||||
|
|
||||||
use crate::common::task_fn;
|
use crate::common::task_fn;
|
||||||
use dagx::{DagResult, DagRunner};
|
use dagx::{DagResult, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -15,7 +15,7 @@ async fn test_binary_tree_reduction() -> DagResult<()> {
|
|||||||
|
|
||||||
// Build binary tree reduction
|
// Build binary tree reduction
|
||||||
let mut current_level: Vec<dagx::TaskHandle<i32>> =
|
let mut current_level: Vec<dagx::TaskHandle<i32>> =
|
||||||
leaves.into_iter().map(|t| (&t).into()).collect();
|
leaves.into_iter().map(|t| t.into()).collect();
|
||||||
|
|
||||||
while current_level.len() > 1 {
|
while current_level.len() > 1 {
|
||||||
let mut next_level = Vec::new();
|
let mut next_level = Vec::new();
|
||||||
@@ -52,7 +52,7 @@ async fn test_n_ary_tree() -> DagResult<()> {
|
|||||||
fn build_tree(dag: &DagRunner, depth: usize, value: i32) -> dagx::TaskHandle<i32> {
|
fn build_tree(dag: &DagRunner, depth: usize, value: i32) -> dagx::TaskHandle<i32> {
|
||||||
if depth == 0 {
|
if depth == 0 {
|
||||||
let task = dag.add_task(task_fn(move |_: ()| async move { value }));
|
let task = dag.add_task(task_fn(move |_: ()| async move { value }));
|
||||||
return (&task).into();
|
return task.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
let children: Vec<_> = (0..3)
|
let children: Vec<_> = (0..3)
|
||||||
@@ -88,19 +88,19 @@ async fn test_unbalanced_tree() -> DagResult<()> {
|
|||||||
// / \
|
// / \
|
||||||
// f g
|
// f g
|
||||||
|
|
||||||
let f = dag.add_task(task_fn(|_: ()| async { 1 }));
|
let f: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 1 })).into();
|
||||||
let g = dag.add_task(task_fn(|_: ()| async { 2 }));
|
let g = dag.add_task(task_fn(|_: ()| async { 2 }));
|
||||||
|
|
||||||
let c = dag
|
let c = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
||||||
.depends_on(&f);
|
.depends_on(f);
|
||||||
|
|
||||||
let d_builder = dag.add_task(task_fn(|_: ()| async { 3 }));
|
let d_builder = dag.add_task(task_fn(|_: ()| async { 3 }));
|
||||||
let d: dagx::TaskHandle<i32> = (&d_builder).into();
|
let d: dagx::TaskHandle<i32> = d_builder.into();
|
||||||
|
|
||||||
let e = dag
|
let e = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 3 }))
|
.add_task(task_fn(|x: i32| async move { x * 3 }))
|
||||||
.depends_on(&g);
|
.depends_on(g);
|
||||||
|
|
||||||
let a = dag
|
let a = dag
|
||||||
.add_task(task_fn(|(c, d): (i32, i32)| async move { c + d }))
|
.add_task(task_fn(|(c, d): (i32, i32)| async move { c + d }))
|
||||||
@@ -116,7 +116,7 @@ async fn test_unbalanced_tree() -> DagResult<()> {
|
|||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
assert_eq!(dag.get(&f)?, 1);
|
assert_eq!(dag.get(f)?, 1);
|
||||||
assert_eq!(dag.get(c)?, 2);
|
assert_eq!(dag.get(c)?, 2);
|
||||||
assert_eq!(dag.get(a)?, 5); // 2 + 3
|
assert_eq!(dag.get(a)?, 5); // 2 + 3
|
||||||
assert_eq!(dag.get(b)?, 16); // 6 + 10
|
assert_eq!(dag.get(b)?, 16); // 6 + 10
|
||||||
@@ -130,20 +130,22 @@ async fn test_trie_like_structure() -> DagResult<()> {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
// Build a trie-like structure for paths
|
// Build a trie-like structure for paths
|
||||||
let root = dag.add_task(task_fn(|_: ()| async { "root".to_string() }));
|
let root: TaskHandle<_> = dag
|
||||||
|
.add_task(task_fn(|_: ()| async { "root".to_string() }))
|
||||||
|
.into();
|
||||||
|
|
||||||
// First level
|
// First level
|
||||||
let usr = dag
|
let usr = dag
|
||||||
.add_task(task_fn(|r: String| async move { format!("{}/usr", r) }))
|
.add_task(task_fn(|r: String| async move { format!("{}/usr", r) }))
|
||||||
.depends_on(&root);
|
.depends_on(root);
|
||||||
|
|
||||||
let etc = dag
|
let etc = dag
|
||||||
.add_task(task_fn(|r: String| async move { format!("{}/etc", r) }))
|
.add_task(task_fn(|r: String| async move { format!("{}/etc", r) }))
|
||||||
.depends_on(&root);
|
.depends_on(root);
|
||||||
|
|
||||||
let var = dag
|
let var = dag
|
||||||
.add_task(task_fn(|r: String| async move { format!("{}/var", r) }))
|
.add_task(task_fn(|r: String| async move { format!("{}/var", r) }))
|
||||||
.depends_on(&root);
|
.depends_on(root);
|
||||||
|
|
||||||
// Second level
|
// Second level
|
||||||
let usr_bin = dag
|
let usr_bin = dag
|
||||||
@@ -188,7 +190,7 @@ async fn test_balanced_k_ary_tree() -> DagResult<()> {
|
|||||||
) -> dagx::TaskHandle<usize> {
|
) -> dagx::TaskHandle<usize> {
|
||||||
if depth == 0 {
|
if depth == 0 {
|
||||||
let task = dag.add_task(task_fn(move |_: ()| async move { node_id }));
|
let task = dag.add_task(task_fn(move |_: ()| async move { node_id }));
|
||||||
return (&task).into();
|
return task.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
let children: Vec<_> = (0..k)
|
let children: Vec<_> = (0..k)
|
||||||
@@ -243,7 +245,7 @@ async fn test_merkle_tree_pattern() -> DagResult<()> {
|
|||||||
|
|
||||||
// Convert to TaskHandles
|
// Convert to TaskHandles
|
||||||
let mut current_level: Vec<dagx::TaskHandle<String>> =
|
let mut current_level: Vec<dagx::TaskHandle<String>> =
|
||||||
data_blocks.into_iter().map(|t| (&t).into()).collect();
|
data_blocks.into_iter().map(|t| t.into()).collect();
|
||||||
|
|
||||||
while current_level.len() > 1 {
|
while current_level.len() > 1 {
|
||||||
let mut next_level = Vec::new();
|
let mut next_level = Vec::new();
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ async fn test_memory_usage_10000_nodes() {
|
|||||||
let before_build = memory_usage_hint();
|
let before_build = memory_usage_hint();
|
||||||
|
|
||||||
// Create 10,000 nodes
|
// Create 10,000 nodes
|
||||||
let tasks: Vec<_> = (0..10_000)
|
let mut tasks: Vec<_> = (0..10_000)
|
||||||
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i })))
|
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i })))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -34,8 +34,8 @@ async fn test_memory_usage_10000_nodes() {
|
|||||||
println!("Additional memory for execution: ~{} bytes", exec_memory);
|
println!("Additional memory for execution: ~{} bytes", exec_memory);
|
||||||
|
|
||||||
// Verify some results
|
// Verify some results
|
||||||
assert_eq!(dag.get(&tasks[0]).unwrap(), 0);
|
assert_eq!(dag.get(tasks.swap_remove(9999)).unwrap(), 9999);
|
||||||
assert_eq!(dag.get(&tasks[9999]).unwrap(), 9999);
|
assert_eq!(dag.get(tasks.swap_remove(0)).unwrap(), 0);
|
||||||
|
|
||||||
// Memory should be reasonable (< 100MB for 10k simple nodes)
|
// Memory should be reasonable (< 100MB for 10k simple nodes)
|
||||||
assert!(
|
assert!(
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ async fn test_deep_chain_100_levels() {
|
|||||||
// Create a chain 100 levels deep
|
// Create a chain 100 levels deep
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let first = dag.add_task(task_fn(|_: ()| async { 0 }));
|
let first = dag.add_task(task_fn(|_: ()| async { 0 }));
|
||||||
let mut handles = vec![(&first).into()];
|
let mut handles = vec![first.into()];
|
||||||
|
|
||||||
for _ in 1..=100 {
|
for _ in 1..=100 {
|
||||||
let next = dag
|
let next = dag
|
||||||
@@ -52,7 +52,7 @@ async fn test_stress_mixed_patterns() {
|
|||||||
for i in 0..10 {
|
for i in 0..10 {
|
||||||
// Linear chains
|
// Linear chains
|
||||||
let first = dag.add_task(task_fn(move |_: ()| async move { i * 1000 }));
|
let first = dag.add_task(task_fn(move |_: ()| async move { i * 1000 }));
|
||||||
let mut chain_handles = vec![(&first).into()];
|
let mut chain_handles = vec![first.into()];
|
||||||
for j in 1..10 {
|
for j in 1..10 {
|
||||||
let next = dag
|
let next = dag
|
||||||
.add_task(task_fn(move |x: i32| async move { x + j }))
|
.add_task(task_fn(move |x: i32| async move { x + j }))
|
||||||
@@ -63,7 +63,7 @@ async fn test_stress_mixed_patterns() {
|
|||||||
|
|
||||||
// Diamonds
|
// Diamonds
|
||||||
let source = dag.add_task(task_fn(move |_: ()| async move { i * 100 }));
|
let source = dag.add_task(task_fn(move |_: ()| async move { i * 100 }));
|
||||||
let source_handle: dagx::TaskHandle<i32> = (&source).into();
|
let source_handle: dagx::TaskHandle<i32> = source.into();
|
||||||
let left = dag
|
let left = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
||||||
.depends_on(source_handle);
|
.depends_on(source_handle);
|
||||||
@@ -77,7 +77,7 @@ async fn test_stress_mixed_patterns() {
|
|||||||
|
|
||||||
// Fan-outs
|
// Fan-outs
|
||||||
let hub = dag.add_task(task_fn(move |_: ()| async move { i }));
|
let hub = dag.add_task(task_fn(move |_: ()| async move { i }));
|
||||||
let hub_handle: dagx::TaskHandle<i32> = (&hub).into();
|
let hub_handle: dagx::TaskHandle<i32> = hub.into();
|
||||||
for j in 0..5 {
|
for j in 0..5 {
|
||||||
let fan = dag
|
let fan = dag
|
||||||
.add_task(task_fn(move |x: i32| async move { x + j * 10 }))
|
.add_task(task_fn(move |x: i32| async move { x + j * 10 }))
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ async fn test_basic_dag_async_std() {
|
|||||||
|
|
||||||
let x = dag.add_task(Value(2));
|
let x = dag.add_task(Value(2));
|
||||||
let y = dag.add_task(Value(3));
|
let y = dag.add_task(Value(3));
|
||||||
let sum = dag.add_task(Add).depends_on((&x, &y));
|
let sum = dag.add_task(Add).depends_on((x, y));
|
||||||
|
|
||||||
dag.run(async_std::task::spawn).await.unwrap();
|
dag.run(async_std::task::spawn).await.unwrap();
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ async fn test_parallel_execution_async_std() {
|
|||||||
|
|
||||||
dag.run(async_std::task::spawn).await.unwrap();
|
dag.run(async_std::task::spawn).await.unwrap();
|
||||||
|
|
||||||
for (i, task) in tasks.iter().enumerate() {
|
for (i, task) in tasks.into_iter().enumerate() {
|
||||||
assert_eq!(dag.get(task).unwrap(), i * 2);
|
assert_eq!(dag.get(task).unwrap(), i * 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ async fn test_complex_dependencies_async_std() {
|
|||||||
|
|
||||||
let a = dag.add_task(Value(10));
|
let a = dag.add_task(Value(10));
|
||||||
let b = dag.add_task(Value(20));
|
let b = dag.add_task(Value(20));
|
||||||
let sum = dag.add_task(Add).depends_on((&a, &b));
|
let sum = dag.add_task(Add).depends_on((a, b));
|
||||||
let double = dag
|
let double = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
||||||
.depends_on(sum);
|
.depends_on(sum);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ async fn runtime_integration_tokio() -> DagResult<()> {
|
|||||||
|
|
||||||
let a = dag.add_task(Value(10));
|
let a = dag.add_task(Value(10));
|
||||||
let b = dag.add_task(Value(20));
|
let b = dag.add_task(Value(20));
|
||||||
let sum = dag.add_task(Add).depends_on((&a, &b));
|
let sum = dag.add_task(Add).depends_on((a, b));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
|
|||||||
+18
-18
@@ -14,7 +14,7 @@
|
|||||||
#![allow(unused_must_use)]
|
#![allow(unused_must_use)]
|
||||||
|
|
||||||
use crate::common::task_fn;
|
use crate::common::task_fn;
|
||||||
use dagx::DagRunner;
|
use dagx::{DagRunner, TaskHandle};
|
||||||
use futures::task::SpawnExt;
|
use futures::task::SpawnExt;
|
||||||
use futures_executor::ThreadPool;
|
use futures_executor::ThreadPool;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
@@ -36,7 +36,7 @@ fn test_async_executor_basic() {
|
|||||||
let y = dag.add_task(task_fn(|_: ()| async { 20 }));
|
let y = dag.add_task(task_fn(|_: ()| async { 20 }));
|
||||||
let sum = dag
|
let sum = dag
|
||||||
.add_task(task_fn(|(a, b): (i32, i32)| async move { a + b }))
|
.add_task(task_fn(|(a, b): (i32, i32)| async move { a + b }))
|
||||||
.depends_on((&x, &y));
|
.depends_on((x, y));
|
||||||
|
|
||||||
dag.run(|fut| executor.spawn(fut)).await.unwrap();
|
dag.run(|fut| executor.spawn(fut)).await.unwrap();
|
||||||
|
|
||||||
@@ -54,15 +54,15 @@ fn test_async_executor_complex_dag() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
// Create a diamond pattern
|
// Create a diamond pattern
|
||||||
let source = dag.add_task(task_fn(|_: ()| async { 5 }));
|
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 5 })).into();
|
||||||
|
|
||||||
let left = dag
|
let left = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
|
|
||||||
let right = dag
|
let right = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 3 }))
|
.add_task(task_fn(|x: i32| async move { x * 3 }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
|
|
||||||
let sink = dag
|
let sink = dag
|
||||||
.add_task(task_fn(|(l, r): (i32, i32)| async move { l + r }))
|
.add_task(task_fn(|(l, r): (i32, i32)| async move { l + r }))
|
||||||
@@ -104,7 +104,7 @@ fn test_async_executor_parallel_tasks() {
|
|||||||
assert_eq!(counter.load(Ordering::SeqCst), 10);
|
assert_eq!(counter.load(Ordering::SeqCst), 10);
|
||||||
|
|
||||||
// Verify results
|
// Verify results
|
||||||
for (i, task) in tasks.iter().enumerate() {
|
for (i, task) in tasks.into_iter().enumerate() {
|
||||||
assert_eq!(dag.get(task).unwrap(), i as i32);
|
assert_eq!(dag.get(task).unwrap(), i as i32);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -141,7 +141,7 @@ fn test_async_executor_deep_chain() {
|
|||||||
let start = dag.add_task(task_fn(|_: ()| async { 0 }));
|
let start = dag.add_task(task_fn(|_: ()| async { 0 }));
|
||||||
let mut current = dag
|
let mut current = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
||||||
.depends_on(&start);
|
.depends_on(start);
|
||||||
|
|
||||||
for _ in 1..50 {
|
for _ in 1..50 {
|
||||||
current = dag
|
current = dag
|
||||||
@@ -170,7 +170,7 @@ fn test_pollster_basic() {
|
|||||||
let x = dag.add_task(task_fn(|_: ()| async { 100 }));
|
let x = dag.add_task(task_fn(|_: ()| async { 100 }));
|
||||||
let doubled = dag
|
let doubled = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
||||||
.depends_on(&x);
|
.depends_on(x);
|
||||||
|
|
||||||
dag.run(|fut| async { pollster::block_on(fut) })
|
dag.run(|fut| async { pollster::block_on(fut) })
|
||||||
.await
|
.await
|
||||||
@@ -186,12 +186,12 @@ fn test_pollster_complex() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
// Fan-out pattern
|
// Fan-out pattern
|
||||||
let source = dag.add_task(task_fn(|_: ()| async { 7 }));
|
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 7 })).into();
|
||||||
|
|
||||||
let tasks: Vec<_> = (0..5)
|
let tasks: Vec<_> = (0..5)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
dag.add_task(task_fn(move |x: i32| async move { x * i }))
|
dag.add_task(task_fn(move |x: i32| async move { x * i }))
|
||||||
.depends_on(&source)
|
.depends_on(source)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -234,7 +234,7 @@ fn test_futures_executor_basic() {
|
|||||||
let y = dag.add_task(task_fn(|_: ()| async { 25 }));
|
let y = dag.add_task(task_fn(|_: ()| async { 25 }));
|
||||||
let product = dag
|
let product = dag
|
||||||
.add_task(task_fn(|(a, b): (i32, i32)| async move { a * b }))
|
.add_task(task_fn(|(a, b): (i32, i32)| async move { a * b }))
|
||||||
.depends_on((&x, &y));
|
.depends_on((x, y));
|
||||||
|
|
||||||
let spawner = ThreadPool::new().unwrap();
|
let spawner = ThreadPool::new().unwrap();
|
||||||
|
|
||||||
@@ -263,7 +263,7 @@ fn test_futures_executor_threadpool() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for (i, task) in tasks.iter().enumerate() {
|
for (i, task) in tasks.into_iter().enumerate() {
|
||||||
assert_eq!(dag.get(task).unwrap(), (i * i) as i32);
|
assert_eq!(dag.get(task).unwrap(), (i * i) as i32);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -281,15 +281,15 @@ fn test_futures_executor_diamond_pattern() {
|
|||||||
futures_executor::block_on(async {
|
futures_executor::block_on(async {
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let a = dag.add_task(task_fn(|_: ()| async { 8 }));
|
let a: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 8 })).into();
|
||||||
|
|
||||||
let b = dag
|
let b = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x + 2 }))
|
.add_task(task_fn(|x: i32| async move { x + 2 }))
|
||||||
.depends_on(&a);
|
.depends_on(a);
|
||||||
|
|
||||||
let c = dag
|
let c = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x - 2 }))
|
.add_task(task_fn(|x: i32| async move { x - 2 }))
|
||||||
.depends_on(&a);
|
.depends_on(a);
|
||||||
|
|
||||||
let d = dag
|
let d = dag
|
||||||
.add_task(task_fn(|(x, y): (i32, i32)| async move { x * y }))
|
.add_task(task_fn(|(x, y): (i32, i32)| async move { x * y }))
|
||||||
@@ -395,7 +395,7 @@ fn test_async_executor_stress() {
|
|||||||
|
|
||||||
dag.run(|fut| executor.spawn(fut)).await.unwrap();
|
dag.run(|fut| executor.spawn(fut)).await.unwrap();
|
||||||
|
|
||||||
for (i, task) in tasks.iter().enumerate() {
|
for (i, task) in tasks.into_iter().enumerate() {
|
||||||
assert_eq!(dag.get(task).unwrap(), i as i32);
|
assert_eq!(dag.get(task).unwrap(), i as i32);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -461,7 +461,7 @@ fn test_sync_tasks_on_async_executor() {
|
|||||||
|
|
||||||
let x = dag.add_task(task_fn(|_: ()| async { 5 }));
|
let x = dag.add_task(task_fn(|_: ()| async { 5 }));
|
||||||
let y = dag.add_task(task_fn(|_: ()| async { 7 }));
|
let y = dag.add_task(task_fn(|_: ()| async { 7 }));
|
||||||
let sum = dag.add_task(SyncAdd).depends_on((&x, &y));
|
let sum = dag.add_task(SyncAdd).depends_on((x, y));
|
||||||
|
|
||||||
dag.run(|fut| executor.spawn(fut)).await.unwrap();
|
dag.run(|fut| executor.spawn(fut)).await.unwrap();
|
||||||
|
|
||||||
@@ -484,7 +484,7 @@ fn test_sync_tasks_on_pollster() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let x = dag.add_task(task_fn(|_: ()| async { 21 }));
|
let x = dag.add_task(task_fn(|_: ()| async { 21 }));
|
||||||
let doubled = dag.add_task(SyncDouble).depends_on(&x);
|
let doubled = dag.add_task(SyncDouble).depends_on(x);
|
||||||
|
|
||||||
dag.run(|fut| async { pollster::block_on(fut) })
|
dag.run(|fut| async { pollster::block_on(fut) })
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ fn test_basic_dag_smol() {
|
|||||||
|
|
||||||
let x = dag.add_task(Value(2));
|
let x = dag.add_task(Value(2));
|
||||||
let y = dag.add_task(Value(3));
|
let y = dag.add_task(Value(3));
|
||||||
let sum = dag.add_task(Add).depends_on((&x, &y));
|
let sum = dag.add_task(Add).depends_on((x, y));
|
||||||
|
|
||||||
dag.run(smol::spawn).await.unwrap();
|
dag.run(smol::spawn).await.unwrap();
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ fn test_parallel_execution_smol() {
|
|||||||
|
|
||||||
dag.run(smol::spawn).await.unwrap();
|
dag.run(smol::spawn).await.unwrap();
|
||||||
|
|
||||||
for (i, task) in tasks.iter().enumerate() {
|
for (i, task) in tasks.into_iter().enumerate() {
|
||||||
assert_eq!(dag.get(task).unwrap(), i * 2);
|
assert_eq!(dag.get(task).unwrap(), i * 2);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -58,7 +58,7 @@ fn test_complex_dependencies_smol() {
|
|||||||
|
|
||||||
let a = dag.add_task(Value(10));
|
let a = dag.add_task(Value(10));
|
||||||
let b = dag.add_task(Value(20));
|
let b = dag.add_task(Value(20));
|
||||||
let sum = dag.add_task(Add).depends_on((&a, &b));
|
let sum = dag.add_task(Add).depends_on((a, b));
|
||||||
let double = dag
|
let double = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
||||||
.depends_on(sum);
|
.depends_on(sum);
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ async fn test_all_three_task_patterns() {
|
|||||||
let y = dag.add_task(task_fn(|_: ()| async { 3 }));
|
let y = dag.add_task(task_fn(|_: ()| async { 3 }));
|
||||||
|
|
||||||
// Pattern 1: Stateless addition
|
// Pattern 1: Stateless addition
|
||||||
let sum = dag.add_task(StatelessAdd).depends_on((&x, &y)); // 5 + 3 = 8
|
let sum = dag.add_task(StatelessAdd).depends_on((x, y)); // 5 + 3 = 8
|
||||||
|
|
||||||
// Pattern 2: Read-only multiplication
|
// Pattern 2: Read-only multiplication
|
||||||
let doubled = dag.add_task(ReadOnlyMultiplier(2)).depends_on(sum); // 8 * 2 = 16
|
let doubled = dag.add_task(ReadOnlyMultiplier(2)).depends_on(sum); // 8 * 2 = 16
|
||||||
@@ -102,9 +102,9 @@ async fn test_parallel_execution() {
|
|||||||
|
|
||||||
// All three tasks should have run
|
// All three tasks should have run
|
||||||
assert_eq!(counter.load(Ordering::SeqCst), 3);
|
assert_eq!(counter.load(Ordering::SeqCst), 3);
|
||||||
assert_eq!(dag.get(&t1).unwrap(), 1);
|
assert_eq!(dag.get(t1).unwrap(), 1);
|
||||||
assert_eq!(dag.get(&t2).unwrap(), 2);
|
assert_eq!(dag.get(t2).unwrap(), 2);
|
||||||
assert_eq!(dag.get(&t3).unwrap(), 3);
|
assert_eq!(dag.get(t3).unwrap(), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -115,7 +115,7 @@ async fn test_task_fn_with_captured_state() {
|
|||||||
let base = dag.add_task(task_fn(|_: ()| async { 5 }));
|
let base = dag.add_task(task_fn(|_: ()| async { 5 }));
|
||||||
let scaled = dag
|
let scaled = dag
|
||||||
.add_task(task_fn(move |x: i32| async move { x * multiplier }))
|
.add_task(task_fn(move |x: i32| async move { x * multiplier }))
|
||||||
.depends_on(&base);
|
.depends_on(base);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ async fn test_readonly_state_with_input() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let input = dag.add_task(task_fn(|_: ()| async { 5 }));
|
let input = dag.add_task(task_fn(|_: ()| async { 5 }));
|
||||||
let result = dag.add_task(ReadOnlyMultiplier(7)).depends_on(&input);
|
let result = dag.add_task(ReadOnlyMultiplier(7)).depends_on(input);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -76,13 +76,13 @@ async fn test_readonly_state_multiple_uses() {
|
|||||||
offset: 5,
|
offset: 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result1 = dag.add_task(config).depends_on(&input1);
|
let result1 = dag.add_task(config).depends_on(input1);
|
||||||
// Note: Can't reuse config because it was moved, so create another
|
// Note: Can't reuse config because it was moved, so create another
|
||||||
let config2 = ReadOnlyConfig {
|
let config2 = ReadOnlyConfig {
|
||||||
multiplier: 3,
|
multiplier: 3,
|
||||||
offset: 5,
|
offset: 5,
|
||||||
};
|
};
|
||||||
let result2 = dag.add_task(config2).depends_on(&input2);
|
let result2 = dag.add_task(config2).depends_on(input2);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ async fn test_stateless_basic() {
|
|||||||
|
|
||||||
let x = dag.add_task(task_fn(|_: ()| async { 10 }));
|
let x = dag.add_task(task_fn(|_: ()| async { 10 }));
|
||||||
let y = dag.add_task(task_fn(|_: ()| async { 20 }));
|
let y = dag.add_task(task_fn(|_: ()| async { 20 }));
|
||||||
let sum = dag.add_task(StatelessAdd).depends_on((&x, &y));
|
let sum = dag.add_task(StatelessAdd).depends_on((x, y));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -98,7 +98,7 @@ async fn test_stateless_single_input() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let input = dag.add_task(task_fn(|_: ()| async { 21 }));
|
let input = dag.add_task(task_fn(|_: ()| async { 21 }));
|
||||||
let doubled = dag.add_task(StatelessDouble).depends_on(&input);
|
let doubled = dag.add_task(StatelessDouble).depends_on(input);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -128,9 +128,7 @@ async fn test_stateless_multiple_inputs() {
|
|||||||
let text = dag.add_task(task_fn(|_: ()| async { "test".to_string() }));
|
let text = dag.add_task(task_fn(|_: ()| async { "test".to_string() }));
|
||||||
let flag = dag.add_task(task_fn(|_: ()| async { true }));
|
let flag = dag.add_task(task_fn(|_: ()| async { true }));
|
||||||
|
|
||||||
let result = dag
|
let result = dag.add_task(StatelessFormat).depends_on((num, text, flag));
|
||||||
.add_task(StatelessFormat)
|
|
||||||
.depends_on((&num, &text, &flag));
|
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -166,7 +164,7 @@ async fn test_stateless_mixed_with_stateful() {
|
|||||||
let y = dag.add_task(task_fn(|_: ()| async { 3 }));
|
let y = dag.add_task(task_fn(|_: ()| async { 3 }));
|
||||||
|
|
||||||
// Stateless multiplication
|
// Stateless multiplication
|
||||||
let product = dag.add_task(StatelessMultiply).depends_on((&x, &y));
|
let product = dag.add_task(StatelessMultiply).depends_on((x, y));
|
||||||
|
|
||||||
// Stateful counter
|
// Stateful counter
|
||||||
let counted = dag.add_task(Counter::new(10)).depends_on(product);
|
let counted = dag.add_task(Counter::new(10)).depends_on(product);
|
||||||
@@ -185,7 +183,7 @@ async fn test_stateless_chain() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let start = dag.add_task(task_fn(|_: ()| async { 0 }));
|
let start = dag.add_task(task_fn(|_: ()| async { 0 }));
|
||||||
let step1 = dag.add_task(StatelessInc).depends_on(&start);
|
let step1 = dag.add_task(StatelessInc).depends_on(start);
|
||||||
let step2 = dag.add_task(StatelessInc).depends_on(step1);
|
let step2 = dag.add_task(StatelessInc).depends_on(step1);
|
||||||
let step3 = dag.add_task(StatelessInc).depends_on(step2);
|
let step3 = dag.add_task(StatelessInc).depends_on(step2);
|
||||||
|
|
||||||
@@ -206,7 +204,7 @@ async fn test_stateless_parallel() {
|
|||||||
let tasks: Vec<_> = (1..=5)
|
let tasks: Vec<_> = (1..=5)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let source = dag.add_task(task_fn(move |_: ()| async move { i }));
|
let source = dag.add_task(task_fn(move |_: ()| async move { i }));
|
||||||
dag.add_task(StatelessSquare).depends_on(&source)
|
dag.add_task(StatelessSquare).depends_on(source)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -229,7 +227,7 @@ async fn test_stateless_with_string_types() {
|
|||||||
let hello = dag.add_task(task_fn(|_: ()| async { "Hello, ".to_string() }));
|
let hello = dag.add_task(task_fn(|_: ()| async { "Hello, ".to_string() }));
|
||||||
let world = dag.add_task(task_fn(|_: ()| async { "World!".to_string() }));
|
let world = dag.add_task(task_fn(|_: ()| async { "World!".to_string() }));
|
||||||
|
|
||||||
let greeting = dag.add_task(StatelessConcat).depends_on((&hello, &world));
|
let greeting = dag.add_task(StatelessConcat).depends_on((hello, world));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Tests for concurrent access patterns and race conditions
|
//! Tests for concurrent access patterns and race conditions
|
||||||
|
|
||||||
use crate::common::task_fn;
|
use crate::common::task_fn;
|
||||||
use dagx::{DagResult, DagRunner};
|
use dagx::{DagResult, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -19,7 +19,7 @@ async fn test_concurrent_result_retrieval() -> DagResult<()> {
|
|||||||
tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
|
tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
|
||||||
i as usize
|
i as usize
|
||||||
}));
|
}));
|
||||||
(&task).into()
|
task.into()
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ async fn test_simultaneous_dag_building_and_execution() -> DagResult<()> {
|
|||||||
let mut tasks: Vec<dagx::TaskHandle<i32>> = Vec::new();
|
let mut tasks: Vec<dagx::TaskHandle<i32>> = Vec::new();
|
||||||
for i in 5..50 {
|
for i in 5..50 {
|
||||||
let task = dag_build.add_task(task_fn(move |_: ()| async move { i }));
|
let task = dag_build.add_task(task_fn(move |_: ()| async move { i }));
|
||||||
tasks.push((&task).into());
|
tasks.push(task.into());
|
||||||
tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
|
tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
|
||||||
}
|
}
|
||||||
building.store(true, Ordering::SeqCst);
|
building.store(true, Ordering::SeqCst);
|
||||||
@@ -81,7 +81,7 @@ async fn test_simultaneous_dag_building_and_execution() -> DagResult<()> {
|
|||||||
let additional_tasks = build_handle.await.unwrap();
|
let additional_tasks = build_handle.await.unwrap();
|
||||||
|
|
||||||
// Initial tasks should have results
|
// Initial tasks should have results
|
||||||
for (i, task) in initial.iter().enumerate() {
|
for (i, task) in initial.into_iter().enumerate() {
|
||||||
assert_eq!(dag.get(task)?, i);
|
assert_eq!(dag.get(task)?, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,16 +107,18 @@ async fn test_concurrent_dependency_resolution() -> DagResult<()> {
|
|||||||
let resolution_counter = Arc::new(AtomicUsize::new(0));
|
let resolution_counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
// Create a source task
|
// Create a source task
|
||||||
let source = dag.add_task(task_fn({
|
let source: TaskHandle<_> = dag
|
||||||
let counter = resolution_counter.clone();
|
.add_task(task_fn({
|
||||||
move |_: ()| {
|
let counter = resolution_counter.clone();
|
||||||
let counter = counter.clone();
|
move |_: ()| {
|
||||||
async move {
|
let counter = counter.clone();
|
||||||
counter.fetch_add(1, Ordering::SeqCst);
|
async move {
|
||||||
42
|
counter.fetch_add(1, Ordering::SeqCst);
|
||||||
|
42
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}))
|
||||||
}));
|
.into();
|
||||||
|
|
||||||
// Create many tasks depending on the same source
|
// Create many tasks depending on the same source
|
||||||
let dependents: Vec<_> = (0..100)
|
let dependents: Vec<_> = (0..100)
|
||||||
@@ -129,7 +131,7 @@ async fn test_concurrent_dependency_resolution() -> DagResult<()> {
|
|||||||
val + i
|
val + i
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.depends_on(&source)
|
.depends_on(source)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -166,6 +168,7 @@ async fn test_read_write_race_patterns() -> DagResult<()> {
|
|||||||
i
|
i
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
.into()
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -284,7 +287,7 @@ async fn test_multiple_dag_runners_interaction() -> DagResult<()> {
|
|||||||
// Collect results
|
// Collect results
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
for task in tasks {
|
for task in tasks {
|
||||||
results.push(dag.get(&task)?);
|
results.push(dag.get(task)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok::<_, dagx::DagError>(results)
|
Ok::<_, dagx::DagError>(results)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Tests for forced execution interleaving patterns
|
//! Tests for forced execution interleaving patterns
|
||||||
|
|
||||||
use crate::common::task_fn;
|
use crate::common::task_fn;
|
||||||
use dagx::{DagResult, DagRunner};
|
use dagx::{DagResult, DagRunner, TaskHandle};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -65,7 +65,7 @@ async fn test_forced_interleaving_with_barriers() -> DagResult<()> {
|
|||||||
.add_task(task_fn(
|
.add_task(task_fn(
|
||||||
|(a, b, c): (i32, i32, i32)| async move { a + b + c },
|
|(a, b, c): (i32, i32, i32)| async move { a + b + c },
|
||||||
))
|
))
|
||||||
.depends_on((&t1, &t2, &t3));
|
.depends_on((t1, t2, t3));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ async fn test_alternating_execution_pattern() -> DagResult<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
let mut chain_a: dagx::TaskHandle<i32> = (&first_a).into();
|
let mut chain_a: dagx::TaskHandle<i32> = first_a.into();
|
||||||
|
|
||||||
let first_b = dag.add_task(task_fn({
|
let first_b = dag.add_task(task_fn({
|
||||||
let turn = turn.clone();
|
let turn = turn.clone();
|
||||||
@@ -128,7 +128,7 @@ async fn test_alternating_execution_pattern() -> DagResult<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
let mut chain_b: dagx::TaskHandle<i32> = (&first_b).into();
|
let mut chain_b: dagx::TaskHandle<i32> = first_b.into();
|
||||||
|
|
||||||
for i in 1..5 {
|
for i in 1..5 {
|
||||||
let turn_clone = turn.clone();
|
let turn_clone = turn.clone();
|
||||||
@@ -197,6 +197,7 @@ async fn test_layered_interleaving() -> DagResult<()> {
|
|||||||
i as i32
|
i as i32
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
.into()
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -250,19 +251,21 @@ async fn test_cross_branch_synchronization() -> DagResult<()> {
|
|||||||
let events = Arc::new(parking_lot::Mutex::new(Vec::new()));
|
let events = Arc::new(parking_lot::Mutex::new(Vec::new()));
|
||||||
|
|
||||||
// Branch A
|
// Branch A
|
||||||
let a1 = dag.add_task(task_fn({
|
let a1: TaskHandle<_> = dag
|
||||||
let sync = sync_point.clone();
|
.add_task(task_fn({
|
||||||
let events = events.clone();
|
let sync = sync_point.clone();
|
||||||
move |_: ()| {
|
|
||||||
let sync = sync.clone();
|
|
||||||
let events = events.clone();
|
let events = events.clone();
|
||||||
async move {
|
move |_: ()| {
|
||||||
events.lock().push("A1");
|
let sync = sync.clone();
|
||||||
sync.fetch_add(1, Ordering::SeqCst);
|
let events = events.clone();
|
||||||
1
|
async move {
|
||||||
|
events.lock().push("A1");
|
||||||
|
sync.fetch_add(1, Ordering::SeqCst);
|
||||||
|
1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}))
|
||||||
}));
|
.into();
|
||||||
|
|
||||||
let a2 = dag
|
let a2 = dag
|
||||||
.add_task(task_fn({
|
.add_task(task_fn({
|
||||||
@@ -282,7 +285,7 @@ async fn test_cross_branch_synchronization() -> DagResult<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.depends_on(&a1);
|
.depends_on(a1);
|
||||||
|
|
||||||
// Branch B
|
// Branch B
|
||||||
let b1 = dag.add_task(task_fn({
|
let b1 = dag.add_task(task_fn({
|
||||||
@@ -320,7 +323,7 @@ async fn test_cross_branch_synchronization() -> DagResult<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.depends_on(&b1);
|
.depends_on(b1);
|
||||||
|
|
||||||
// Merge
|
// Merge
|
||||||
let merge = dag
|
let merge = dag
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ async fn test_tracing_with_subscriber() {
|
|||||||
|
|
||||||
let a = dag.add_task(Value(2));
|
let a = dag.add_task(Value(2));
|
||||||
let b = dag.add_task(Value(3));
|
let b = dag.add_task(Value(3));
|
||||||
let sum = dag.add_task(Add).depends_on((&a, &b));
|
let sum = dag.add_task(Add).depends_on((a, b));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -63,7 +63,7 @@ async fn test_tracing_with_complex_dag() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
// Create a diamond pattern
|
// Create a diamond pattern
|
||||||
let source = dag.add_task(Value(10));
|
let source = dag.add_task(Value(10)).into();
|
||||||
let left = dag.add_task(Add).depends_on((&source, &source));
|
let left = dag.add_task(Add).depends_on((&source, &source));
|
||||||
let right = dag.add_task(Multiply).depends_on((&source, &source));
|
let right = dag.add_task(Multiply).depends_on((&source, &source));
|
||||||
let sink = dag.add_task(Add).depends_on((&left, &right));
|
let sink = dag.add_task(Add).depends_on((&left, &right));
|
||||||
@@ -90,7 +90,7 @@ async fn test_tracing_inline_execution() {
|
|||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
// Create a linear chain (should trigger inline execution)
|
// Create a linear chain (should trigger inline execution)
|
||||||
let a = dag.add_task(Value(1));
|
let a = dag.add_task(Value(1)).into();
|
||||||
let b = dag.add_task(Add).depends_on((&a, &a));
|
let b = dag.add_task(Add).depends_on((&a, &a));
|
||||||
let c = dag.add_task(Multiply).depends_on((&b, &b));
|
let c = dag.add_task(Multiply).depends_on((&b, &b));
|
||||||
|
|
||||||
@@ -118,12 +118,12 @@ async fn test_tracing_multiple_layers() {
|
|||||||
|
|
||||||
// Layer 0
|
// Layer 0
|
||||||
let v1 = dag.add_task(Value(1));
|
let v1 = dag.add_task(Value(1));
|
||||||
let v2 = dag.add_task(Value(2));
|
let v2 = dag.add_task(Value(2)).into();
|
||||||
let v3 = dag.add_task(Value(3));
|
let v3 = dag.add_task(Value(3));
|
||||||
|
|
||||||
// Layer 1
|
// Layer 1
|
||||||
let sum12 = dag.add_task(Add).depends_on((&v1, &v2));
|
let sum12 = dag.add_task(Add).depends_on((v1, &v2));
|
||||||
let mul23 = dag.add_task(Multiply).depends_on((&v2, &v3));
|
let mul23 = dag.add_task(Multiply).depends_on((&v2, v3));
|
||||||
|
|
||||||
// Layer 2
|
// Layer 2
|
||||||
let final_sum = dag.add_task(Add).depends_on((&sum12, &mul23));
|
let final_sum = dag.add_task(Add).depends_on((&sum12, &mul23));
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ async fn test_basic_dag_without_tracing() {
|
|||||||
|
|
||||||
let a = dag.add_task(Value(2));
|
let a = dag.add_task(Value(2));
|
||||||
let b = dag.add_task(Value(3));
|
let b = dag.add_task(Value(3));
|
||||||
let sum = dag.add_task(Add).depends_on((&a, &b));
|
let sum = dag.add_task(Add).depends_on((a, b));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -46,8 +46,8 @@ async fn test_complex_dag_without_tracing() {
|
|||||||
let v3 = dag.add_task(Value(3));
|
let v3 = dag.add_task(Value(3));
|
||||||
let v4 = dag.add_task(Value(4));
|
let v4 = dag.add_task(Value(4));
|
||||||
|
|
||||||
let sum12 = dag.add_task(Add).depends_on((&v1, &v2));
|
let sum12 = dag.add_task(Add).depends_on((v1, v2));
|
||||||
let sum34 = dag.add_task(Add).depends_on((&v3, &v4));
|
let sum34 = dag.add_task(Add).depends_on((v3, v4));
|
||||||
let final_sum = dag.add_task(Add).depends_on((&sum12, &sum34));
|
let final_sum = dag.add_task(Add).depends_on((&sum12, &sum34));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ async fn test_complex_type_safety() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Correct type works
|
// Correct type works
|
||||||
let custom = dag.get(&custom_task).unwrap();
|
let custom = dag.get(custom_task).unwrap();
|
||||||
assert_eq!(custom.value, 42);
|
assert_eq!(custom.value, 42);
|
||||||
|
|
||||||
// Type safety is enforced at compile time - we cannot get with wrong types
|
// Type safety is enforced at compile time - we cannot get with wrong types
|
||||||
@@ -51,8 +51,8 @@ async fn test_generic_type_safety() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Correct types work
|
// Correct types work
|
||||||
assert_eq!(dag.get(&vec_i32).unwrap(), vec![1, 2, 3]);
|
assert_eq!(dag.get(vec_i32).unwrap(), vec![1, 2, 3]);
|
||||||
assert!(dag.get(&vec_string).is_ok());
|
assert!(dag.get(vec_string).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -71,8 +71,8 @@ async fn test_option_result_type_safety() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Correct wrapped types work
|
// Correct wrapped types work
|
||||||
assert_eq!(dag.get(&opt_task).unwrap(), Some(42));
|
assert_eq!(dag.get(opt_task).unwrap(), Some(42));
|
||||||
assert!(dag.get(&result_task).is_ok());
|
assert!(dag.get(result_task).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -94,7 +94,7 @@ async fn test_zero_sized_type_safety() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Correct ZSTs work
|
// Correct ZSTs work
|
||||||
assert!(dag.get(&zst1_task).is_ok());
|
assert!(dag.get(zst1_task).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -115,13 +115,13 @@ async fn test_type_safety_with_many_types() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Each type works correctly
|
// Each type works correctly
|
||||||
assert_eq!(dag.get(&i32_task).unwrap(), 42i32);
|
assert_eq!(dag.get(i32_task).unwrap(), 42i32);
|
||||||
assert_eq!(dag.get(&i64_task).unwrap(), 42i64);
|
assert_eq!(dag.get(i64_task).unwrap(), 42i64);
|
||||||
assert_eq!(dag.get(&u32_task).unwrap(), 42u32);
|
assert_eq!(dag.get(u32_task).unwrap(), 42u32);
|
||||||
assert_eq!(dag.get(&f32_task).unwrap(), 42.0f32);
|
assert_eq!(dag.get(f32_task).unwrap(), 42.0f32);
|
||||||
assert_eq!(dag.get(&f64_task).unwrap(), 42.0f64);
|
assert_eq!(dag.get(f64_task).unwrap(), 42.0f64);
|
||||||
assert_eq!(dag.get(&string_task).unwrap(), "42".to_string());
|
assert_eq!(dag.get(string_task).unwrap(), "42".to_string());
|
||||||
assert!(dag.get(&bool_task).unwrap());
|
assert!(dag.get(bool_task).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -134,7 +134,7 @@ async fn test_type_safety_single_dependency() {
|
|||||||
// This compiles because types match (i32 -> i32)
|
// This compiles because types match (i32 -> i32)
|
||||||
let transform = dag
|
let transform = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -156,7 +156,7 @@ async fn test_type_safety_tuple_dependencies() {
|
|||||||
.add_task(task_fn(|(x, s, b): (i32, String, bool)| async move {
|
.add_task(task_fn(|(x, s, b): (i32, String, bool)| async move {
|
||||||
format!("{}: {} ({})", s, x, b)
|
format!("{}: {} ({})", s, x, b)
|
||||||
}))
|
}))
|
||||||
.depends_on((&a, &b, &c));
|
.depends_on((a, b, c));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -177,9 +177,9 @@ async fn test_type_safety_different_output_types() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(dag.get(&int_source).unwrap(), 42);
|
assert_eq!(dag.get(int_source).unwrap(), 42);
|
||||||
assert_eq!(dag.get(&str_source).unwrap(), "test");
|
assert_eq!(dag.get(str_source).unwrap(), "test");
|
||||||
assert_eq!(dag.get(&vec_source).unwrap(), vec![1, 2, 3]);
|
assert_eq!(dag.get(vec_source).unwrap(), vec![1, 2, 3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -226,7 +226,7 @@ async fn test_type_safety_with_generics() {
|
|||||||
.add_task(task_fn(
|
.add_task(task_fn(
|
||||||
|opt: Option<i32>| async move { opt.map(|x| x * 2) },
|
|opt: Option<i32>| async move { opt.map(|x| x * 2) },
|
||||||
))
|
))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -240,7 +240,7 @@ async fn test_type_safety_with_generics() {
|
|||||||
.add_task(task_fn(
|
.add_task(task_fn(
|
||||||
|opt: Option<i32>| async move { opt.map(|x| x * 2) },
|
|opt: Option<i32>| async move { opt.map(|x| x * 2) },
|
||||||
))
|
))
|
||||||
.depends_on(&source2);
|
.depends_on(source2);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
@@ -262,7 +262,7 @@ async fn test_large_tuple_input() {
|
|||||||
.add_task(task_fn(
|
.add_task(task_fn(
|
||||||
|(a, b, c, d, e): (i32, i32, i32, i32, i32)| async move { a + b + c + d + e },
|
|(a, b, c, d, e): (i32, i32, i32, i32, i32)| async move { a + b + c + d + e },
|
||||||
))
|
))
|
||||||
.depends_on((&a, &b, &c, &d, &e));
|
.depends_on((a, b, c, d, e));
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ async fn test_get_with_wrong_type_returns_none() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Verify correct type works
|
// Verify correct type works
|
||||||
assert_eq!(dag.get(&int_task), Ok(42i32));
|
assert_eq!(dag.get(int_task), Ok(42i32));
|
||||||
|
|
||||||
// The type safety is enforced at compile time
|
// The type safety is enforced at compile time
|
||||||
// We cannot create fake handles with wrong types without accessing private fields
|
// We cannot create fake handles with wrong types without accessing private fields
|
||||||
@@ -42,8 +42,8 @@ async fn test_handle_preserves_type() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Get with correct types
|
// Get with correct types
|
||||||
assert_eq!(dag.get(&string_task).unwrap(), "hello".to_string());
|
assert_eq!(dag.get(string_task).unwrap(), "hello".to_string());
|
||||||
assert_eq!(dag.get(&int_task).unwrap(), 42);
|
assert_eq!(dag.get(int_task).unwrap(), 42);
|
||||||
|
|
||||||
// Type safety is enforced at compile time - we cannot create wrong-typed handles
|
// Type safety is enforced at compile time - we cannot create wrong-typed handles
|
||||||
}
|
}
|
||||||
@@ -53,15 +53,15 @@ async fn test_type_safety_handle_reuse() {
|
|||||||
// Verify that handles maintain type safety across reuse
|
// Verify that handles maintain type safety across reuse
|
||||||
let dag = DagRunner::new();
|
let dag = DagRunner::new();
|
||||||
|
|
||||||
let source = dag.add_task(task_fn(|_: ()| async { 100i32 }));
|
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 100i32 })).into();
|
||||||
|
|
||||||
// Use the same source handle multiple times
|
// Use the same source handle multiple times
|
||||||
let double = dag
|
let double = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
let triple = dag
|
let triple = dag
|
||||||
.add_task(task_fn(|x: i32| async move { x * 3 }))
|
.add_task(task_fn(|x: i32| async move { x * 3 }))
|
||||||
.depends_on(&source);
|
.depends_on(source);
|
||||||
|
|
||||||
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
|
||||||
.await
|
.await
|
||||||
|
|||||||
Reference in new issue
Block a user