Update tests, examples, benches

This commit is contained in:
TechnoPorg committed 2026-01-22 00:30:48 +01:00
1 parent bacac903b3
commit 85ccd7e507
89 files changed
+580 -1423

No files matched your search

+2 -2
View File
@@ -38,7 +38,7 @@ See [how it works](docs/CYCLE_PREVENTION.md).
```rust
let sum = dag.add_task(Add).depends_on((&x, &y));
dag.run().await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
```
That's it. No trait boilerplate, no manual channels, no node IDs.
@@ -90,7 +90,7 @@ async fn main() {
let sum = dag.add_task(Add).depends_on((&x, &y));
// Execute with true parallelism
dag.run().await.unwrap();
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
// Retrieve results
assert_eq!(dag.get(sum).unwrap(), 5);
+4 -9
View File
@@ -2,6 +2,7 @@
use criterion::Criterion;
use dagx::{task_fn, DagRunner, Task};
use futures::FutureExt;
pub fn bench_dag_execution(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
@@ -13,9 +14,7 @@ pub fn bench_dag_execution(c: &mut Criterion) {
for i in 0..10 {
dag.add_task(task_fn(move |_: ()| async move { i }));
}
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
})
@@ -29,9 +28,7 @@ pub fn bench_dag_execution(c: &mut Criterion) {
for i in 0..100 {
dag.add_task(task_fn(move |_: ()| async move { i }));
}
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
})
@@ -111,9 +108,7 @@ pub fn bench_dag_execution(c: &mut Criterion) {
.add_task(task_fn(|x: i32| async move { x + 1 }))
.depends_on(t18);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
})
+2 -3
View File
@@ -2,6 +2,7 @@
use criterion::{BenchmarkId, Criterion};
use dagx::{task_fn, DagRunner};
use futures::FutureExt;
pub fn bench_dag_scaling(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
@@ -18,9 +19,7 @@ pub fn bench_dag_scaling(c: &mut Criterion) {
for i in 0..size {
dag.add_task(task_fn(move |_: ()| async move { i }));
}
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
})
+2 -3
View File
@@ -3,6 +3,7 @@
//! Long sequential chain: 100 tasks in sequence
use criterion::Criterion;
use futures::FutureExt;
pub fn bench_deep_chain(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
@@ -27,9 +28,7 @@ pub fn bench_deep_chain(c: &mut Criterion) {
.depends_on(prev);
}
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
dag.get(prev).unwrap()
+2 -3
View File
@@ -3,6 +3,7 @@
//! Classic diamond: A → B,C → D
use criterion::Criterion;
use futures::FutureExt;
pub fn bench_diamond_pattern(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
@@ -27,9 +28,7 @@ pub fn bench_diamond_pattern(c: &mut Criterion) {
.add_task(task_fn(|(x, y): (i32, i32)| async move { x + y }))
.depends_on((&b, &c));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
dag.get(d).unwrap()
+2 -3
View File
@@ -3,6 +3,7 @@
//! Extract → Transform → Validate → Load pattern
use criterion::Criterion;
use futures::FutureExt;
pub fn bench_etl_pipeline(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
@@ -66,9 +67,7 @@ pub fn bench_etl_pipeline(c: &mut Criterion) {
}))
.depends_on(validate);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
dag.get(load).unwrap()
+2 -3
View File
@@ -3,6 +3,7 @@
//! 10,000 independent tasks to test scaling
use criterion::Criterion;
use futures::FutureExt;
pub fn bench_large_scale(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
@@ -21,9 +22,7 @@ pub fn bench_large_scale(c: &mut Criterion) {
dag.add_task(task_fn(move |_: ()| async move { i * 2 }));
}
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
})
+2 -3
View File
@@ -3,6 +3,7 @@
//! Simple chain of tasks: A → B → C → D → E
use criterion::Criterion;
use futures::FutureExt;
pub fn bench_linear_pipeline(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
@@ -30,9 +31,7 @@ pub fn bench_linear_pipeline(c: &mut Criterion) {
.add_task(task_fn(|x: i32| async move { x * 3 }))
.depends_on(d);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
dag.get(e).unwrap()
+2 -3
View File
@@ -3,6 +3,7 @@
//! One source task feeding 100 dependent tasks
use criterion::Criterion;
use futures::FutureExt;
pub fn bench_wide_fanout(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
@@ -23,9 +24,7 @@ pub fn bench_wide_fanout(c: &mut Criterion) {
.depends_on(&source);
}
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
})
+3 -6
View File
@@ -2,6 +2,7 @@
use criterion::Criterion;
use dagx::{task_fn, DagRunner};
use futures::FutureExt;
pub fn bench_10k_breakdown(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
@@ -26,9 +27,7 @@ pub fn bench_10k_breakdown(c: &mut Criterion) {
for i in 0..10_000 {
dag.add_task(task_fn(move |_: ()| async move { i }));
}
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
})
@@ -45,9 +44,7 @@ pub fn bench_10k_breakdown(c: &mut Criterion) {
b.iter(|| {
rt.block_on(async {
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
})
+2 -3
View File
@@ -2,6 +2,7 @@
use criterion::Criterion;
use dagx::{task_fn, DagRunner};
use futures::FutureExt;
pub fn bench_diamond(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
@@ -25,9 +26,7 @@ pub fn bench_diamond(c: &mut Criterion) {
.depends_on((&left, &right));
}
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
})
+4 -9
View File
@@ -2,6 +2,7 @@
use criterion::Criterion;
use dagx::{task_fn, DagRunner};
use futures::FutureExt;
pub fn bench_fanout(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
@@ -18,9 +19,7 @@ pub fn bench_fanout(c: &mut Criterion) {
.depends_on(&source);
}
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
})
@@ -45,9 +44,7 @@ pub fn bench_fanout(c: &mut Criterion) {
.depends_on(&source);
}
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
})
@@ -72,9 +69,7 @@ pub fn bench_fanout(c: &mut Criterion) {
.depends_on(&source);
}
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
})
+2 -3
View File
@@ -2,6 +2,7 @@
use criterion::{BenchmarkId, Criterion};
use dagx::{task_fn, DagRunner};
use futures::FutureExt;
use std::collections::HashMap;
use std::hint::black_box;
@@ -51,9 +52,7 @@ pub fn bench_config_broadcast(c: &mut Criterion) {
.depends_on(&config);
}
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
})
+2 -3
View File
@@ -2,6 +2,7 @@
use criterion::{BenchmarkId, Criterion};
use dagx::{task_fn, DagRunner};
use futures::FutureExt;
pub fn bench_etl_pipeline(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
@@ -90,9 +91,7 @@ pub fn bench_etl_pipeline(c: &mut Criterion) {
))
.depends_on((&validate, &scores, &names));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
})
+2 -3
View File
@@ -2,6 +2,7 @@
use criterion::{BenchmarkId, Criterion};
use dagx::{task_fn, DagRunner};
use futures::FutureExt;
use std::hint::black_box;
pub fn bench_memory_efficiency(c: &mut Criterion) {
@@ -38,9 +39,7 @@ pub fn bench_memory_efficiency(c: &mut Criterion) {
.depends_on(&source);
}
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
})
+2 -3
View File
@@ -2,6 +2,7 @@
use criterion::Criterion;
use dagx::{task_fn, DagRunner};
use futures::FutureExt;
pub fn bench_mixed_patterns(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
@@ -50,9 +51,7 @@ pub fn bench_mixed_patterns(c: &mut Criterion) {
.depends_on(summary);
}
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
})
+2 -2
View File
@@ -19,7 +19,7 @@ let dag = DagRunner::new();
let x = dag.add_task(Value(2));
let y = dag.add_task(Value(3));
let sum = dag.add_task(Add).depends_on((&x, &y));
dag.run().await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
```
**dagrs** (Complex: traits + channels + IDs + manual wiring):
@@ -227,7 +227,7 @@ impl Task<String, Bytes> for MyTask {
let mut dag = Dag::default();
dag.add_vertex("one", || async move { /* task */ });
dag.add_edge("one", "two");
dag.run().await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
```
### tasksitter (Dynamic Workflows)
+1 -1
View File
@@ -13,7 +13,7 @@ let a = dag.add_task(TaskA);
let b = dag.add_task(TaskB);
a.depends_on(b);
b.depends_on(a); // Compiles fine...
dag.run()
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
```
**dagx prevents this at compile time:**
+2 -3
View File
@@ -31,6 +31,7 @@
//! ```
use dagx::{task, DagRunner, Task};
use futures::FutureExt;
// Step 1: Define a source task (no dependencies)
//
@@ -105,9 +106,7 @@ async fn main() {
// 1. Tasks with no dependencies run first (x and y in parallel)
// 2. Once x and y complete, sum runs (it depends on both)
println!("Running DAG...\n");
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+2 -3
View File
@@ -56,6 +56,7 @@
//! ```
use dagx::{task, DagRunner, Task};
use futures::FutureExt;
// Source task with a value field
struct Source {
@@ -140,9 +141,7 @@ async fn main() {
// Run the DAG
println!("Running fan-out DAG...\n");
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+2 -3
View File
@@ -62,6 +62,7 @@
//! ```
use dagx::{task, DagRunner, Task};
use futures::FutureExt;
struct FetchName;
@@ -133,9 +134,7 @@ async fn main() {
// Run the DAG
println!("Running fan-in DAG...\n");
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+2 -4
View File
@@ -53,6 +53,7 @@
//! ```
use dagx::{task, DagResult, DagRunner, Task};
use futures::FutureExt;
use std::time::{Duration, Instant};
use tokio::time::sleep;
@@ -126,10 +127,7 @@ async fn main() -> DagResult<()> {
.add_task(Aggregate)
.depends_on((&sum1, &sum2, &sum3, &sum4));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
let result = dag.get(total)?;
let elapsed = start.elapsed();
+8 -21
View File
@@ -151,6 +151,7 @@
//! ```
use dagx::{task, DagRunner, Task};
use futures::FutureExt;
use std::sync::Arc;
use std::time::Instant;
use tokio::sync::Mutex;
@@ -389,9 +390,7 @@ async fn main() {
false, // success
));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -405,9 +404,7 @@ async fn main() {
true, // fail
));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -421,9 +418,7 @@ async fn main() {
true, // fail
));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -437,9 +432,7 @@ async fn main() {
true, // fail
));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -457,9 +450,7 @@ async fn main() {
.add_task(FallbackService::new("ServiceCall-5"))
.depends_on(&call5);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -478,9 +469,7 @@ async fn main() {
false, // success
));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -494,9 +483,7 @@ async fn main() {
false, // success
));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+2 -4
View File
@@ -67,6 +67,7 @@
//! ```
use dagx::{task, DagResult, DagRunner, Task};
use futures::FutureExt;
// Extract: Load data from different sources
struct LoadData {
@@ -201,10 +202,7 @@ async fn main() -> DagResult<()> {
// Execute the entire ETL pipeline
println!("Executing pipeline...\n");
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Display final report
println!("\n{}", dag.get(report)?);
+2 -4
View File
@@ -56,6 +56,7 @@
//! ```
use dagx::{task, DagResult, DagRunner, Task};
use futures::FutureExt;
// Task that validates and processes input
struct ValidateAndDouble {
@@ -137,10 +138,7 @@ async fn main() -> DagResult<()> {
.depends_on((&cat1, &cat2, &cat3));
// Execute
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Display results
println!("\nFinal summary: {}", dag.get(summary)?);
+2 -3
View File
@@ -37,6 +37,7 @@
//! ```
use dagx::{task, DagRunner, Task};
use futures::FutureExt;
// Custom type - just derive Clone!
#[derive(Clone, Debug)]
@@ -107,9 +108,7 @@ async fn main() {
let validation = dag.add_task(ValidateUser).depends_on(&user_task);
// Execute the DAG
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+2 -3
View File
@@ -76,6 +76,7 @@
//! ```
use dagx::{task, DagRunner, Task};
use futures::FutureExt;
// Data source tasks
struct LoadValue1;
@@ -220,9 +221,7 @@ async fn main() {
// Execute the pipeline
println!("Executing pipeline...\n");
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+3 -6
View File
@@ -115,6 +115,7 @@
//! ```
use dagx::{task, DagRunner, Task};
use futures::FutureExt;
use std::time::Instant;
use tokio::time::{sleep, Duration};
@@ -400,9 +401,7 @@ async fn main() {
.depends_on((&inventory, &payment, &shipping));
// Run the DAG
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -442,9 +441,7 @@ async fn main() {
true, // will fail
));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+5 -12
View File
@@ -115,6 +115,7 @@
//! ```
use dagx::{task, DagRunner, Task};
use futures::FutureExt;
// Source task that provides a string input
struct StringSource(&'static str);
@@ -222,9 +223,7 @@ async fn main() {
let processed = dag.add_task(ProcessData).depends_on(validated);
let logged = dag.add_task(LogError).depends_on(processed);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -250,9 +249,7 @@ async fn main() {
let processed = dag.add_task(ProcessData).depends_on(validated);
let logged = dag.add_task(LogError).depends_on(processed);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -278,9 +275,7 @@ async fn main() {
let processed = dag.add_task(ProcessData).depends_on(validated);
let logged = dag.add_task(LogError).depends_on(processed);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -303,9 +298,7 @@ async fn main() {
.add_task(WithFallback { fallback: 0 })
.depends_on(parsed);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+6 -15
View File
@@ -110,6 +110,7 @@
//! ```
use dagx::{task, DagRunner, Task};
use futures::FutureExt;
use std::time::Instant;
// Simple computation task
@@ -181,9 +182,7 @@ async fn main() {
println!(" Build time: {:?}", build_time);
let exec_start = Instant::now();
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
let exec_time = exec_start.elapsed();
@@ -223,9 +222,7 @@ async fn main() {
println!(" Build time: {:?}", build_time);
let exec_start = Instant::now();
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
let exec_time = exec_start.elapsed();
@@ -276,9 +273,7 @@ async fn main() {
println!(" Tree depth: {}", level);
let exec_start = Instant::now();
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
let exec_time = exec_start.elapsed();
@@ -336,9 +331,7 @@ async fn main() {
println!(" Build time: {:?}", build_time);
let exec_start = Instant::now();
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
let exec_time = exec_start.elapsed();
@@ -362,9 +355,7 @@ async fn main() {
.map(|i| dag.add_task(FormatNumber { id: i }))
.collect();
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+2 -3
View File
@@ -48,6 +48,7 @@
//! ```
use dagx::{task, DagRunner, Task};
use futures::FutureExt;
use std::time::{Duration, Instant};
use tokio::time::sleep;
@@ -86,9 +87,7 @@ async fn main() {
// Measure execution time
let start = Instant::now();
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
let elapsed = start.elapsed();
+6 -15
View File
@@ -153,6 +153,7 @@
//! ```
use dagx::{task, DagRunner, Task};
use futures::FutureExt;
use tokio::time::{sleep, Duration};
// Retry strategy configuration
@@ -355,9 +356,7 @@ async fn main() {
],
));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -379,9 +378,7 @@ async fn main() {
],
));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -407,9 +404,7 @@ async fn main() {
],
));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -428,9 +423,7 @@ async fn main() {
vec![ApiError::AuthenticationFailed],
));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -457,9 +450,7 @@ async fn main() {
],
));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+7 -16
View File
@@ -24,7 +24,7 @@
//!
//! **2. DAG-Level Timeout** - Entire workflow with timeout
//! ```rust
//! match timeout(dag_timeout, dag.run(spawner)).await {
//! match timeout(dag_timeout, dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await {
//! Ok(result) => { /* DAG completed */ },
//! Err(_) => { /* DAG timed out */ },
//! }
@@ -99,6 +99,7 @@
//! ```
use dagx::{task, DagRunner, Task};
use futures::FutureExt;
use tokio::time::{sleep, timeout, Duration};
// Fast task that completes quickly
@@ -177,9 +178,7 @@ async fn main() {
timeout_ms: 500,
});
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -199,9 +198,7 @@ async fn main() {
let processed = dag.add_task(ProcessResult).depends_on(&timed);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -228,9 +225,7 @@ async fn main() {
timeout_ms: 200,
});
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -254,9 +249,7 @@ async fn main() {
let processed1 = dag.add_task(ProcessResult).depends_on(&source);
let processed2 = dag.add_task(ProcessResult).depends_on(&source);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -277,9 +270,7 @@ async fn main() {
let dag_timeout = Duration::from_millis(500);
match timeout(
dag_timeout,
dag.run(|fut| {
tokio::spawn(fut);
}),
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)),
)
.await
{
+4 -9
View File
@@ -34,6 +34,7 @@
//!
use dagx::{task, DagRunner, Task};
use futures::FutureExt;
use tracing_subscriber::{fmt, EnvFilter};
// Simple source task
@@ -95,9 +96,7 @@ async fn main() {
// Layer 2: Combine results
let result = dag.add_task(Add).depends_on((&left, &right));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -118,9 +117,7 @@ async fn main() {
let c = dag.add_task(Multiply).depends_on((&b, &b));
let d = dag.add_task(Add).depends_on((&c, &c));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -149,9 +146,7 @@ async fn main() {
let sum_all = dag.add_task(Add).depends_on((&sum12, &sum23));
let final_result = dag.add_task(Add).depends_on((&sum_all, &sum34));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+4 -2
View File
@@ -31,6 +31,7 @@ impl IsUnitType for () {}
///
/// ```no_run
/// # use dagx::{task, DagRunner, Task};
/// # use futures::FutureExt;
/// // Using tuple struct for simple constants
/// struct Constant(i32);
///
@@ -65,7 +66,7 @@ impl IsUnitType for () {}
/// let b = b.depends_on(&a);
/// // Now b is a TaskHandle<i32>
///
/// dag.run().await.unwrap();
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
/// assert_eq!(dag.get(&b).unwrap(), 20);
/// # };
/// ```
@@ -89,6 +90,7 @@ impl<'a, Tk: Task, Deps> TaskBuilder<'a, Tk, Deps> {
///
/// ```no_run
/// # use dagx::{task, DagRunner, Task};
/// # use futures::FutureExt;
/// // Tuple struct
/// struct Value(i32);
///
@@ -125,7 +127,7 @@ impl<'a, Tk: Task, Deps> TaskBuilder<'a, Tk, Deps> {
/// // Multiple dependencies: tuple form
/// let sum = dag.add_task(Add).depends_on((&x, &y));
///
/// dag.run().await.unwrap();
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
/// # };
/// ```
pub fn depends_on<D>(self, deps: D) -> TaskHandle<Tk::Output>
+1 -1
View File
@@ -63,7 +63,7 @@ impl std::fmt::Display for DagError {
f,
"Result not found for task #{}.\n\
\n\
Call dag.run().",
Call dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).",
task_id
)
}
+1 -1
View File
@@ -51,7 +51,7 @@ fn test_dag_error_display_result_not_found() {
assert!(display.contains("Result not found"));
assert!(display.contains("task #7"));
assert!(display.contains("Call dag.run()"));
assert!(display.contains("Call dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))"));
}
#[test]
+33 -115
View File
@@ -5,7 +5,6 @@
//! We directly call extract_from_deps with mismatched types.
use crate::extract::ExtractInput;
use futures::channel::oneshot;
use std::any::Any;
use std::collections::HashMap;
use std::sync::Arc;
@@ -13,17 +12,10 @@ use std::sync::Arc;
#[tokio::test]
async fn test_result_type_mismatch_coverage() {
// Tests line 95 in extract.rs - Result<T,E> type mismatch error message
// Create a channel that sends the WRONG type (String instead of Result<i32, String>)
let (tx, rx) = oneshot::channel::<Arc<String>>();
// Send a String value
tokio::spawn(async move {
let _ = tx.send(Arc::new("hello".to_string()));
});
// Create a dependency that provides the WRONG type (String instead of Result<i32, String>)
// Try to extract as Result<i32, String> - this will fail with type mismatch
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
let result = Result::<i32, String>::extract_from_channels(deps).await;
let deps = vec![Arc::new("hello") as Arc<dyn Any + Send + Sync>];
let result = Result::<i32, String>::extract_from_deps(deps).await;
// Verify the error message contains the expected type information
assert!(result.is_err());
@@ -45,17 +37,10 @@ async fn test_result_type_mismatch_coverage() {
#[tokio::test]
async fn test_option_type_mismatch_coverage() {
// Tests line 124 in extract.rs - Option<T> type mismatch error message
// Create a channel that sends the WRONG type (i32 instead of Option<i32>)
let (tx, rx) = oneshot::channel::<Arc<i32>>();
// Send an i32 value
tokio::spawn(async move {
let _ = tx.send(Arc::new(42));
});
// Create a dependency that provides the WRONG type (i32 instead of Option<i32>)
// Try to extract as Option<i32> - this will fail with type mismatch
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
let result = Option::<i32>::extract_from_channels(deps).await;
let deps = vec![Arc::new(42) as Arc<dyn Any + Send + Sync>];
let result = Option::<i32>::extract_from_deps(deps).await;
// Verify the error message contains the expected type information
assert!(result.is_err());
@@ -77,17 +62,10 @@ async fn test_option_type_mismatch_coverage() {
#[tokio::test]
async fn test_vec_type_mismatch_coverage() {
// Tests line 152 in extract.rs - Vec<T> type mismatch error message
// Create a channel that sends the WRONG type (String instead of Vec<u8>)
let (tx, rx) = oneshot::channel::<Arc<String>>();
// Send a String value
tokio::spawn(async move {
let _ = tx.send(Arc::new("not a vec".to_string()));
});
// Create a dependency that provides the WRONG type (String instead of Vec<u8>)
// Try to extract as Vec<u8> - this will fail with type mismatch
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
let result = Vec::<u8>::extract_from_channels(deps).await;
let deps = vec![Arc::new("not a vec".to_string()) as Arc<dyn Any + Send + Sync>];
let result = Vec::<u8>::extract_from_deps(deps).await;
// Verify the error message contains the expected type information
assert!(result.is_err());
@@ -109,17 +87,10 @@ async fn test_vec_type_mismatch_coverage() {
#[tokio::test]
async fn test_hashmap_type_mismatch_coverage() {
// Tests line 181 in extract.rs - HashMap<K,V> type mismatch error message
// Create a channel that sends the WRONG type (Vec<i32> instead of HashMap)
let (tx, rx) = oneshot::channel::<Arc<Vec<i32>>>();
// Send a Vec value
tokio::spawn(async move {
let _ = tx.send(Arc::new(vec![1, 2, 3]));
});
// Create a dependency that provides the WRONG type (Vec<i32> instead of HashMap)
// Try to extract as HashMap<String, i32> - this will fail with type mismatch
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
let result = HashMap::<String, i32>::extract_from_channels(deps).await;
let deps = vec![Arc::new(vec![1, 2, 3]) as Arc<dyn Any + Send + Sync>];
let result = HashMap::<String, i32>::extract_from_deps(deps).await;
// Verify the error message contains the expected type information
assert!(result.is_err());
@@ -141,12 +112,12 @@ async fn test_hashmap_type_mismatch_coverage() {
#[tokio::test]
async fn test_arc_type_mismatch_coverage() {
// Tests line 211 in extract.rs - Arc<T> type mismatch error message
// Arc is special: it expects Arc<Arc<T>> from dependency
// Arc is special: it expects Arc<Arc<T>> from channel
// Create a dependency that provides Arc<String> instead of Arc<Arc<String>>
let deps = vec![Arc::new("not double arc".to_string()) as Arc<dyn Any + Send + Sync>];
// Try to extract as Arc<String> - this will fail with type mismatch
// because it expects Arc<Arc<String>> in the dependency
// because it expects Arc<Arc<String>> in the channel
let result = Arc::<String>::extract_from_deps(deps).await;
// Verify the error message contains the expected type information
@@ -171,15 +142,9 @@ async fn test_arc_type_mismatch_coverage() {
#[tokio::test]
async fn test_result_with_wrong_ok_type() {
// Test Result with wrong Ok type
let (tx, rx) = oneshot::channel::<Arc<Result<String, String>>>();
tokio::spawn(async move {
let _ = tx.send(Arc::new(Ok("test".to_string())));
});
// Try to extract as Result<i32, String> - wrong Ok type
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
let result = Result::<i32, String>::extract_from_channels(deps).await;
let deps = vec![Arc::new(Ok::<_, String>("test".to_string())) as Arc<dyn Any + Send + Sync>];
let result = Result::<i32, String>::extract_from_deps(deps).await;
assert!(result.is_err());
let err_msg = result.unwrap_err();
@@ -193,15 +158,9 @@ async fn test_result_with_wrong_ok_type() {
#[tokio::test]
async fn test_result_with_wrong_err_type() {
// Test Result with wrong Err type
let (tx, rx) = oneshot::channel::<Arc<Result<i32, i32>>>();
tokio::spawn(async move {
let _ = tx.send(Arc::new(Err(42)));
});
// Try to extract as Result<i32, String> - wrong Err type
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
let result = Result::<i32, String>::extract_from_channels(deps).await;
let deps = vec![Arc::new(Err::<i32, i32>(42)) as Arc<dyn Any + Send + Sync>];
let result = Result::<i32, String>::extract_from_deps(deps).await;
assert!(result.is_err());
let err_msg = result.unwrap_err();
@@ -215,15 +174,9 @@ async fn test_result_with_wrong_err_type() {
#[tokio::test]
async fn test_option_with_wrong_inner_type() {
// Test Option with wrong inner type
let (tx, rx) = oneshot::channel::<Arc<Option<String>>>();
tokio::spawn(async move {
let _ = tx.send(Arc::new(Some("test".to_string())));
});
// Try to extract as Option<i32> - wrong inner type
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
let result = Option::<i32>::extract_from_channels(deps).await;
let deps = vec![Arc::new(Some("test".to_string())) as Arc<dyn Any + Send + Sync>];
let result = Option::<i32>::extract_from_deps(deps).await;
assert!(result.is_err());
let err_msg = result.unwrap_err();
@@ -237,15 +190,9 @@ async fn test_option_with_wrong_inner_type() {
#[tokio::test]
async fn test_vec_with_wrong_element_type() {
// Test Vec with wrong element type
let (tx, rx) = oneshot::channel::<Arc<Vec<String>>>();
tokio::spawn(async move {
let _ = tx.send(Arc::new(vec!["a".to_string(), "b".to_string()]));
});
// Try to extract as Vec<i32> - wrong element type
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
let result = Vec::<i32>::extract_from_channels(deps).await;
let deps = vec![Arc::new(vec!["a".to_string(), "b".to_string()]) as Arc<dyn Any + Send + Sync>];
let result = Vec::<i32>::extract_from_deps(deps).await;
assert!(result.is_err());
let err_msg = result.unwrap_err();
@@ -259,17 +206,11 @@ async fn test_vec_with_wrong_element_type() {
#[tokio::test]
async fn test_hashmap_with_wrong_key_type() {
// Test HashMap with wrong key type
let (tx, rx) = oneshot::channel::<Arc<HashMap<i32, String>>>();
tokio::spawn(async move {
let mut map = HashMap::new();
map.insert(1, "one".to_string());
let _ = tx.send(Arc::new(map));
});
// Try to extract as HashMap<String, String> - wrong key type
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
let result = HashMap::<String, String>::extract_from_channels(deps).await;
let deps = vec![Arc::new(map) as Arc<dyn Any + Send + Sync>];
let result = HashMap::<String, String>::extract_from_deps(deps).await;
assert!(result.is_err());
let err_msg = result.unwrap_err();
@@ -283,17 +224,11 @@ async fn test_hashmap_with_wrong_key_type() {
#[tokio::test]
async fn test_hashmap_with_wrong_value_type() {
// Test HashMap with wrong value type
let (tx, rx) = oneshot::channel::<Arc<HashMap<String, i32>>>();
tokio::spawn(async move {
let mut map = HashMap::new();
map.insert("key".to_string(), 42);
let _ = tx.send(Arc::new(map));
});
// Try to extract as HashMap<String, String> - wrong value type
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
let result = HashMap::<String, String>::extract_from_channels(deps).await;
let deps = vec![Arc::new(map) as Arc<dyn Any + Send + Sync>];
let result = HashMap::<String, String>::extract_from_deps(deps).await;
assert!(result.is_err());
let err_msg = result.unwrap_err();
@@ -308,15 +243,9 @@ async fn test_hashmap_with_wrong_value_type() {
async fn test_arc_with_wrong_inner_type() {
// Test Arc with completely wrong inner type
// Send Arc<i32> but expect Arc<String>
let (tx, rx) = oneshot::channel::<Arc<Arc<i32>>>();
tokio::spawn(async move {
let _ = tx.send(Arc::new(Arc::new(42)));
});
// Try to extract as Arc<String> - wrong inner type
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
let result = Arc::<String>::extract_from_channels(deps).await;
let deps = vec![Arc::new(Arc::new(Arc::new(42))) as Arc<dyn Any + Send + Sync>];
let result = Arc::<String>::extract_from_deps(deps).await;
assert!(result.is_err());
let err_msg = result.unwrap_err();
@@ -330,15 +259,9 @@ async fn test_arc_with_wrong_inner_type() {
#[tokio::test]
async fn test_primitive_type_mismatch_for_completeness() {
// Test primitive type mismatch (already covered in existing tests but good for completeness)
let (tx, rx) = oneshot::channel::<Arc<String>>();
tokio::spawn(async move {
let _ = tx.send(Arc::new("not an i32".to_string()));
});
// Try to extract as i32 - type mismatch
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
let result = i32::extract_from_channels(deps).await;
let deps = vec![Arc::new("not an i32".to_string()) as Arc<dyn Any + Send + Sync>];
let result = i32::extract_from_deps(deps).await;
assert!(result.is_err());
let err_msg = result.unwrap_err();
@@ -352,15 +275,10 @@ async fn test_primitive_type_mismatch_for_completeness() {
#[tokio::test]
async fn test_complex_nested_result_option() {
// Test complex nested type: send Result<String, String>, expect Result<Option<i32>, String>
let (tx, rx) = oneshot::channel::<Arc<Result<String, String>>>();
tokio::spawn(async move {
let _ = tx.send(Arc::new(Ok("test".to_string())));
});
// Try to extract as Result<Option<i32>, String> - nested type mismatch
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
let result = Result::<Option<i32>, String>::extract_from_channels(deps).await;
let deps =
vec![Arc::new(Ok::<String, String>("test".to_string())) as Arc<dyn Any + Send + Sync>];
let result = Result::<Option<i32>, String>::extract_from_deps(deps).await;
assert!(result.is_err());
let err_msg = result.unwrap_err();
+50 -130
View File
@@ -38,11 +38,11 @@ impl TestTaskWithInput {
async fn test_extract_result_type_single_dependency_error() {
// Test Result<T,E> with wrong dependency count
let deps = vec![
Box::new(oneshot::channel::<Result<i32, String>>().1) as Box<dyn Any + Send>,
Box::new(oneshot::channel::<Result<i32, String>>().1) as Box<dyn Any + Send>,
Arc::new(Ok::<i32, String>(5)) as Arc<dyn Any + Send + Sync>,
Arc::new(Err::<i32, String>("test".to_string())) as Arc<dyn Any + Send + Sync>,
]; // Two dependencies when expecting 1
let result = Result::<i32, String>::extract_from_channels(deps).await;
let result = Result::<i32, String>::extract_from_deps(deps).await;
assert!(result.is_err());
assert!(result.unwrap_err().contains("Expected 1 dependency, got 2"));
}
@@ -50,15 +50,9 @@ async fn test_extract_result_type_single_dependency_error() {
#[tokio::test]
async fn test_extract_result_type_success() {
// Test Result<T,E> successful extraction
let (tx, rx) = oneshot::channel::<Arc<Result<i32, String>>>();
// Send value in background
tokio::spawn(async move {
let _ = tx.send(Arc::new(Ok(42)));
});
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
let result = Result::<i32, String>::extract_from_channels(deps).await;
let deps = vec![Arc::new(Ok::<i32, String>(42)) as Arc<dyn Any + Send + Sync>];
let result = Result::<i32, String>::extract_from_deps(deps).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), Ok(42));
}
@@ -67,12 +61,12 @@ async fn test_extract_result_type_success() {
async fn test_extract_option_type_single_dependency_error() {
// Test Option<T> with wrong dependency count
let deps = vec![
Box::new(oneshot::channel::<Option<String>>().1) as Box<dyn Any + Send>,
Box::new(oneshot::channel::<Option<String>>().1) as Box<dyn Any + Send>,
Box::new(oneshot::channel::<Option<String>>().1) as Box<dyn Any + Send>,
Arc::new(None::<String>) as Arc<dyn Any + Send + Sync>,
Arc::new(None::<String>) as Arc<dyn Any + Send + Sync>,
Arc::new(None::<String>) as Arc<dyn Any + Send + Sync>,
]; // Three dependencies when expecting 1
let result = Option::<String>::extract_from_channels(deps).await;
let result = Option::<String>::extract_from_deps(deps).await;
assert!(result.is_err());
assert!(result.unwrap_err().contains("Expected 1 dependency, got 3"));
}
@@ -80,15 +74,8 @@ async fn test_extract_option_type_single_dependency_error() {
#[tokio::test]
async fn test_extract_option_type_success() {
// Test Option<T> successful extraction
let (tx, rx) = oneshot::channel::<Arc<Option<i32>>>();
// Send value in background
tokio::spawn(async move {
let _ = tx.send(Arc::new(Some(99)));
});
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
let result = Option::<i32>::extract_from_channels(deps).await;
let deps = vec![Arc::new(Some(99)) as Arc<dyn Any + Send + Sync>];
let result = Option::<i32>::extract_from_deps(deps).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), Some(99));
}
@@ -98,7 +85,7 @@ async fn test_extract_vec_type_single_dependency_error() {
// Test Vec<T> with wrong dependency count
let deps = vec![]; // Zero dependencies when expecting 1
let result = Vec::<u8>::extract_from_channels(deps).await;
let result = Vec::<u8>::extract_from_deps(deps).await;
assert!(result.is_err());
assert!(result.unwrap_err().contains("Expected 1 dependency, got 0"));
}
@@ -106,15 +93,8 @@ async fn test_extract_vec_type_single_dependency_error() {
#[tokio::test]
async fn test_extract_vec_type_success() {
// Test Vec<T> successful extraction
let (tx, rx) = oneshot::channel::<Arc<Vec<i32>>>();
// Send value in background
tokio::spawn(async move {
let _ = tx.send(Arc::new(vec![1, 2, 3]));
});
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
let result = Vec::<i32>::extract_from_channels(deps).await;
let deps = vec![Arc::new(vec![1, 2, 3]) as Arc<dyn Any + Send + Sync>];
let result = Vec::<i32>::extract_from_deps(deps).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec![1, 2, 3]);
}
@@ -123,29 +103,29 @@ async fn test_extract_vec_type_success() {
async fn test_extract_primitive_single_dependency_error() {
// Test primitive types with wrong dependency count
let deps = vec![
Box::new(oneshot::channel::<i32>().1) as Box<dyn Any + Send>,
Box::new(oneshot::channel::<i32>().1) as Box<dyn Any + Send>,
Arc::new(0) as Arc<dyn Any + Send + Sync>,
Arc::new(0) as Arc<dyn Any + Send + Sync>,
]; // Two dependencies when expecting 1
let result = i32::extract_from_channels(deps).await;
let result = i32::extract_from_deps(deps).await;
assert!(result.is_err());
assert!(result.unwrap_err().contains("Expected 1 dependency, got 2"));
// Test with String
let deps = vec![
Box::new(oneshot::channel::<String>().1) as Box<dyn Any + Send>,
Box::new(oneshot::channel::<String>().1) as Box<dyn Any + Send>,
Arc::new(String::default()) as Arc<dyn Any + Send + Sync>,
Arc::new(String::default()) as Arc<dyn Any + Send + Sync>,
];
let result = String::extract_from_channels(deps).await;
let result = String::extract_from_deps(deps).await;
assert!(result.is_err());
assert!(result.unwrap_err().contains("Expected 1 dependency, got 2"));
// Test with f64
let deps = vec![
Box::new(oneshot::channel::<f64>().1) as Box<dyn Any + Send>,
Box::new(oneshot::channel::<f64>().1) as Box<dyn Any + Send>,
Arc::new(0.0) as Arc<dyn Any + Send + Sync>,
Arc::new(0.0) as Arc<dyn Any + Send + Sync>,
];
let result = f64::extract_from_channels(deps).await;
let result = f64::extract_from_deps(deps).await;
assert!(result.is_err());
assert!(result.unwrap_err().contains("Expected 1 dependency, got 2"));
}
@@ -154,58 +134,36 @@ async fn test_extract_primitive_single_dependency_error() {
async fn test_extract_unit_type() {
// Test that unit type always succeeds regardless of dependencies
let deps = vec![];
let result = <()>::extract_from_channels(deps).await;
let result = <()>::extract_from_deps(deps).await;
assert!(result.is_ok());
// Even with dependencies (ignored), unit type succeeds
let deps = vec![
Box::new(oneshot::channel::<i32>().1) as Box<dyn Any + Send>,
Box::new(oneshot::channel::<i32>().1) as Box<dyn Any + Send>,
Arc::new(0) as Arc<dyn Any + Send + Sync>,
Arc::new(0) as Arc<dyn Any + Send + Sync>,
];
let result = <()>::extract_from_channels(deps).await;
let result = <()>::extract_from_deps(deps).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_extract_tuple_basic() {
// Test tuple extraction with channels
let (tx1, rx1) = oneshot::channel::<Arc<i32>>();
let (tx2, rx2) = oneshot::channel::<Arc<i32>>();
// Send values in background
tokio::spawn(async move {
let _ = tx1.send(Arc::new(42));
let _ = tx2.send(Arc::new(100));
});
// Test tuple extraction
let deps = vec![
Box::new(rx1) as Box<dyn Any + Send>,
Box::new(rx2) as Box<dyn Any + Send>,
Arc::new(42) as Arc<dyn Any + Send + Sync>,
Arc::new(100) as Arc<dyn Any + Send + Sync>,
];
// Extract a tuple (i32, i32)
let result = <(i32, i32)>::extract_from_channels(deps).await;
let result = <(i32, i32)>::extract_from_deps(deps).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), (42, 100));
}
#[tokio::test]
async fn test_extract_channel_closed() {
// Test error when channel is closed
let (tx, rx) = oneshot::channel::<Arc<i32>>();
// Explicitly drop tx to close the channel
drop(tx);
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
let result = i32::extract_from_channels(deps).await;
assert!(result.is_err());
assert!(result.unwrap_err().contains("Channel closed"));
}
#[tokio::test]
async fn test_extract_type_mismatch() {
// Test type mismatch during downcast
// Try to extract as wrong type - put wrong parameter type in deps
// Try to extract as wrong type - put wrong receiver type in deps
let deps = vec![Arc::new("hello".to_string()) as Arc<dyn Any + Send + Sync>];
let result = i32::extract_from_deps(deps).await;
assert!(result.is_err());
@@ -217,14 +175,8 @@ async fn test_extract_all_primitive_types() {
// Test that all primitive types in impl_extract_single! work correctly
macro_rules! test_type {
($t:ty, $val:expr) => {
let (tx, rx) = oneshot::channel::<Arc<$t>>();
tokio::spawn(async move {
let _ = tx.send(Arc::new($val));
});
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
let result = <$t>::extract_from_channels(deps).await;
let deps = vec![Arc::new($val) as Arc<dyn Any + Send + Sync>];
let result = <$t>::extract_from_deps(deps).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), $val);
};
@@ -240,31 +192,16 @@ async fn test_extract_all_primitive_types() {
#[tokio::test]
async fn test_extract_larger_tuples() {
// Test extraction for larger tuple sizes
let (tx1, rx1) = oneshot::channel::<Arc<i32>>();
let (tx2, rx2) = oneshot::channel::<Arc<i32>>();
let (tx3, rx3) = oneshot::channel::<Arc<i32>>();
let (tx4, rx4) = oneshot::channel::<Arc<i32>>();
let (tx5, rx5) = oneshot::channel::<Arc<i32>>();
// Send values in background
tokio::spawn(async move {
let _ = tx1.send(Arc::new(0));
let _ = tx2.send(Arc::new(1));
let _ = tx3.send(Arc::new(2));
let _ = tx4.send(Arc::new(3));
let _ = tx5.send(Arc::new(4));
});
let deps = vec![
Box::new(rx1) as Box<dyn Any + Send>,
Box::new(rx2) as Box<dyn Any + Send>,
Box::new(rx3) as Box<dyn Any + Send>,
Box::new(rx4) as Box<dyn Any + Send>,
Box::new(rx5) as Box<dyn Any + Send>,
Arc::new(0) as Arc<dyn Any + Send + Sync>,
Arc::new(1) as Arc<dyn Any + Send + Sync>,
Arc::new(2) as Arc<dyn Any + Send + Sync>,
Arc::new(3) as Arc<dyn Any + Send + Sync>,
Arc::new(4) as Arc<dyn Any + Send + Sync>,
];
// Extract a 5-element tuple
let result = <(i32, i32, i32, i32, i32)>::extract_from_channels(deps).await;
let result = <(i32, i32, i32, i32, i32)>::extract_from_deps(deps).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), (0, 1, 2, 3, 4));
}
@@ -272,17 +209,11 @@ async fn test_extract_larger_tuples() {
#[tokio::test]
async fn test_extract_arc_type() {
// Test Arc<T> extraction
let (tx, rx) = oneshot::channel::<Arc<Arc<String>>>();
let test_arc = Arc::new("test".to_string());
let test_arc_clone = test_arc.clone();
tokio::spawn(async move {
let _ = tx.send(Arc::new(test_arc));
});
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
let result = Arc::<String>::extract_from_channels(deps).await;
let deps = vec![Arc::new(test_arc) as Arc<dyn Any + Send + Sync>];
let result = Arc::<String>::extract_from_deps(deps).await;
assert!(result.is_ok());
assert_eq!(*result.unwrap(), *test_arc_clone);
}
@@ -293,11 +224,11 @@ use std::collections::HashMap;
#[tokio::test]
async fn test_hashmap_extract_wrong_dependency_count() {
let deps = vec![
Box::new(oneshot::channel::<Arc<HashMap<String, i32>>>().1) as Box<dyn Any + Send>,
Box::new(oneshot::channel::<Arc<HashMap<String, i32>>>().1) as Box<dyn Any + Send>,
Arc::new(HashMap::<String, i32>::default()) as Arc<dyn Any + Send + Sync>,
Arc::new(HashMap::<String, i32>::default()) as Arc<dyn Any + Send + Sync>,
];
let result = HashMap::<String, i32>::extract_from_channels(deps).await;
let result = HashMap::<String, i32>::extract_from_deps(deps).await;
assert!(result.is_err());
assert!(result.unwrap_err().contains("Expected 1 dependency, got 2"));
}
@@ -310,17 +241,6 @@ async fn test_hashmap_extract_type_mismatch() {
assert!(result.unwrap_err().contains("Type mismatch"));
}
#[tokio::test]
async fn test_hashmap_extract_channel_closed() {
let (tx, rx) = oneshot::channel::<Arc<HashMap<String, i32>>>();
drop(tx);
let deps = vec![Box::new(rx) as Box<dyn Any + Send>];
let result = HashMap::<String, i32>::extract_from_channels(deps).await;
assert!(result.is_err());
assert!(result.unwrap_err().contains("Channel closed"));
}
#[tokio::test]
async fn test_result_extract_downcast_failure() {
let deps = vec![Arc::new("wrong".to_string()) as Arc<dyn Any + Send + Sync>];
@@ -348,7 +268,7 @@ async fn test_vec_extract_downcast_failure() {
#[tokio::test]
async fn test_arc_extract_wrong_dependency_count() {
let deps = vec![];
let result = Arc::<i32>::extract_from_channels(deps).await;
let result = Arc::<i32>::extract_from_deps(deps).await;
assert!(result.is_err());
assert!(result.unwrap_err().contains("Expected 1 dependency, got 0"));
}
@@ -364,12 +284,12 @@ async fn test_arc_extract_downcast_failure() {
#[tokio::test]
async fn test_tuple_wrong_dependency_count() {
let deps = vec![
Box::new(oneshot::channel::<Arc<i32>>().1) as Box<dyn Any + Send>,
Box::new(oneshot::channel::<Arc<i32>>().1) as Box<dyn Any + Send>,
Box::new(oneshot::channel::<Arc<i32>>().1) as Box<dyn Any + Send>,
Arc::new(0) as Arc<dyn Any + Send + Sync>,
Arc::new(0) as Arc<dyn Any + Send + Sync>,
Arc::new(0) as Arc<dyn Any + Send + Sync>,
];
let result = <(i32, i32)>::extract_from_channels(deps).await;
let result = <(i32, i32)>::extract_from_deps(deps).await;
assert!(result.is_err());
assert!(result
.unwrap_err()
+17 -10
View File
@@ -24,6 +24,7 @@
//!
//! ```no_run
//! use dagx::{task, DagRunner, Task};
//! use futures::FutureExt; // for FutureExt::map
//!
//! // Source task with read-only state (tuple struct)
//! struct Value(i32);
@@ -52,7 +53,7 @@
//! let sum = dag.add_task(Add).depends_on((&x, &y));
//!
//! // Execute and retrieve results
//! dag.run().await.unwrap();
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
//! assert_eq!(dag.get(sum).unwrap(), 5);
//! # };
//! ```
@@ -290,6 +291,7 @@
//!
//! ```no_run
//! use dagx::{task, DagRunner, Task};
//! use futures::FutureExt;
//!
//! // Source task (tuple struct)
//! struct Value(i32);
@@ -322,7 +324,7 @@
//! let plus1 = dag.add_task(Add(1)).depends_on(&base);
//! let times2 = dag.add_task(Scale(2)).depends_on(&base);
//!
//! dag.run().await.unwrap();
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
//!
//! assert_eq!(dag.get(plus1).unwrap(), 11);
//! assert_eq!(dag.get(times2).unwrap(), 20);
@@ -335,6 +337,7 @@
//!
//! ```no_run
//! use dagx::{task, DagRunner, Task};
//! use futures::FutureExt;
//!
//! // Source task for String (tuple struct)
//! struct Name(String);
@@ -378,7 +381,7 @@
//! let active = dag.add_task(Active(true));
//! let result = dag.add_task(FormatUser).depends_on((&name, &age, &active));
//!
//! dag.run().await.unwrap();
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
//!
//! assert_eq!(dag.get(result).unwrap(), "User: Alice, Age: 30, Active: true");
//! # };
@@ -390,6 +393,7 @@
//!
//! ```no_run
//! use dagx::{task, DagRunner, Task};
//! use futures::FutureExt;
//!
//! // Source task (tuple struct)
//! struct Value(i32);
@@ -430,7 +434,7 @@
//! // Layer 3: Final result
//! let total = dag.add_task(Add).depends_on((&sum_xy, &prod_yz)); // 5 + 15 = 20
//!
//! dag.run().await.unwrap();
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
//!
//! assert_eq!(dag.get(total).unwrap(), 20);
//! # };
@@ -442,7 +446,8 @@
//! the `#[task]` macro generates the necessary extraction logic:
//!
//! ```no_run
//! use dagx::{task, DagRunner, Task};
//! # use dagx::{task, DagRunner, Task};
//! # use futures::FutureExt;
//!
//! // Just derive Clone - that's all you need!
//! #[derive(Clone)]
@@ -473,7 +478,7 @@
//! let user = dag.add_task(CreateUser);
//! let formatted = dag.add_task(FormatUser).depends_on(&user);
//!
//! dag.run().await.unwrap();
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
//!
//! assert_eq!(dag.get(formatted).unwrap(), "Alice is 30 years old");
//! # };
@@ -530,6 +535,7 @@
//!
//! ```no_run
//! # use dagx::{task, DagRunner, Task};
//! # use futures::FutureExt;
//! # struct Value(i32);
//! # #[task]
//! # impl Value {
@@ -539,11 +545,11 @@
//! # let dag = DagRunner::new();
//! # let node = dag.add_task(Value(42));
//! // Simple approach with .unwrap()
//! dag.run().await.unwrap();
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
//! let result = dag.get(node).unwrap();
//!
//! // Or handle errors explicitly
//! match dag.run().await {
//! match dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await {
//! Ok(_) => println!("DAG executed successfully"),
//! Err(e) => eprintln!("DAG execution failed: {}", e),
//! }
@@ -626,7 +632,8 @@
//! You output `T`, the framework handles the Arc wrapping:
//!
//! ```
//! use dagx::{task, DagRunner, Task};
//! # use dagx::{task, DagRunner, Task};
//! # use futures::FutureExt;
//!
//! // ✅ CORRECT: Just output Vec<String>, framework wraps in Arc internally
//! struct FetchData;
@@ -656,7 +663,7 @@
//! let task2 = dag.add_task(ProcessData).depends_on(&data);
//! let task3 = dag.add_task(ProcessData).depends_on(&data);
//!
//! dag.run().await.unwrap();
//! dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
//! # };
//! ```
//!
+13 -63
View File
@@ -1,5 +1,7 @@
//! Unit tests for node module
use std::sync::Arc;
use crate::node::{ExecutableNode, TypedNode};
use crate::task::Task;
use crate::types::NodeId;
@@ -16,28 +18,6 @@ impl TestTask {
}
}
#[test]
fn test_create_output_channels() {
let node = TypedNode::new(NodeId(0), TestTask { value: 42 });
// Create channels for 3 dependents
let (senders, receivers) = node.create_output_channels(3);
assert_eq!(senders.len(), 3);
assert_eq!(receivers.len(), 3);
}
#[test]
fn test_create_output_channels_zero_dependents() {
let node = TypedNode::new(NodeId(0), TestTask { value: 42 });
// Create channels for 0 dependents (sink node)
let (senders, receivers) = node.create_output_channels(0);
assert_eq!(senders.len(), 0);
assert_eq!(receivers.len(), 0);
}
struct TaskWithDependency;
#[crate::task]
@@ -48,15 +28,14 @@ impl TaskWithDependency {
}
#[tokio::test]
async fn test_execute_with_channels_missing_input() {
async fn test_execute_with_deps_missing_input() {
// Test when task expects input but doesn't receive it
let node = Box::new(TypedNode::new(NodeId(0), TaskWithDependency));
// TaskWithDependency expects an i32 input, but we provide no receivers
let receivers = vec![];
let senders = vec![];
// TaskWithDependency expects an i32 input, but we provide no dependencies
let dependencies = vec![];
let result = node.execute_with_channels(receivers, senders).await;
let result = node.execute_with_deps(dependencies).await;
assert!(result.is_err());
if let Err(crate::error::DagError::InvalidDependency { task_id }) = result {
@@ -76,11 +55,11 @@ async fn test_execute_with_deps_success() {
let dep_dependencies = vec![];
// Execute in background
let dep_handle = tokio::spawn(async move {
dependency_node
.execute_with_channels(dep_receivers, dep_senders)
let dep_result =
tokio::spawn(async move { dependency_node.execute_with_deps(dep_dependencies).await })
.await
});
.unwrap()
.unwrap();
// Execute dependent node with receiver
let dependencies = vec![dep_result as Arc<dyn std::any::Any + Send + Sync>];
@@ -91,14 +70,14 @@ async fn test_execute_with_deps_success() {
}
#[tokio::test]
async fn test_execute_with_channels_sink_node() {
async fn test_execute_with_deps_sink_node() {
// Test that sink nodes (no dependents) return their output
let node = Box::new(TypedNode::new(NodeId(0), TestTask { value: 42 }));
// No senders means this is a sink node
let dependencies = vec![];
let result = node.execute_with_channels(receivers, senders).await;
let result = node.execute_with_deps(dependencies).await;
assert!(result.is_ok());
// Check output was returned (Arc-wrapped)
@@ -114,41 +93,12 @@ async fn test_execute_with_deps_non_sink_node() {
// Execute with senders (non-sink node)
let node = Box::new(node);
let exec_receivers = vec![];
let result = node.execute_with_channels(exec_receivers, senders).await;
let result = node.execute_with_deps(vec![]).await;
assert!(result.is_ok());
// Verify output was sent through channel (Arc-wrapped)
let arc_value = result.unwrap().downcast::<i32>().unwrap();
// Also verify output was returned (Arc-wrapped)
let output = result.unwrap();
let arc_value = output.downcast::<i32>().unwrap();
assert_eq!(*arc_value, 42);
}
#[tokio::test]
async fn test_execute_with_multiple_dependents() {
// Test that output is sent to multiple dependents
let node = TypedNode::new(NodeId(0), TestTask { value: 100 });
// Create output channels for three dependents
let (senders, receivers) = node.create_output_channels(3);
// Execute node
let node = Box::new(node);
let exec_receivers = vec![];
let result = node.execute_with_channels(exec_receivers, senders).await;
assert!(result.is_ok());
// Verify all three dependents receive the output (Arc-wrapped)
for receiver in receivers {
let rx = receiver
.downcast::<futures::channel::oneshot::Receiver<std::sync::Arc<i32>>>()
.unwrap();
let received_arc = rx.await;
assert!(received_arc.is_ok());
assert_eq!(*received_arc.unwrap(), 100);
}
}
+8 -4
View File
@@ -56,6 +56,7 @@ impl<'a> Drop for RunGuard<'a> {
///
/// ```no_run
/// # use dagx::{task, DagRunner, Task};
/// # use futures::FutureExt;
/// // Task with state constructed via ::new()
/// struct LoadValue { value: i32 }
///
@@ -84,7 +85,7 @@ impl<'a> Drop for RunGuard<'a> {
/// let y = dag.add_task(LoadValue::new(3));
/// let sum = dag.add_task(Add).depends_on((&x, &y));
///
/// dag.run().await.unwrap();
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
///
/// assert_eq!(dag.get(sum).unwrap(), 5);
/// # };
@@ -149,6 +150,7 @@ impl DagRunner {
///
/// ```no_run
/// # use dagx::{task, DagRunner, Task};
/// # use futures::FutureExt;
/// // Task with state - shows you construct with specific value
/// struct LoadValue {
/// initial: i32,
@@ -190,7 +192,7 @@ impl DagRunner {
/// // Construct task with offset of 1
/// let inc = dag.add_task(AddOffset::new(1)).depends_on(&base);
///
/// dag.run().await.unwrap();
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
/// assert_eq!(dag.get(&inc).unwrap(), 11);
/// # };
/// ```
@@ -254,6 +256,7 @@ impl DagRunner {
///
/// ```
/// # use dagx::{task, DagRunner, Task};
/// # use futures::FutureExt;
/// // Tuple struct
/// struct Value(i32);
///
@@ -277,7 +280,7 @@ impl DagRunner {
/// let b = dag.add_task(Value(2));
/// let sum = dag.add_task(Add).depends_on((&a, &b));
///
/// dag.run().await.unwrap(); // Executes all tasks
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap(); // Executes all tasks
/// # };
/// ```
#[inline]
@@ -527,6 +530,7 @@ impl DagRunner {
///
/// ```no_run
/// # use dagx::{task, DagRunner, Task};
/// # use futures::FutureExt;
/// struct Configuration {
/// setting: i32,
/// }
@@ -548,7 +552,7 @@ impl DagRunner {
/// // Construct task with specific setting value
/// let task = dag.add_task(Configuration::new(42));
///
/// dag.run().await.unwrap();
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
///
/// assert_eq!(dag.get(task).unwrap(), 42);
/// # };
+7 -18
View File
@@ -6,6 +6,7 @@ use crate::error::DagError;
use crate::runner::DagRunner;
use crate::task::Task;
use crate::types::TaskHandle;
use futures::FutureExt;
use std::collections::HashMap;
#[tokio::test]
@@ -20,9 +21,7 @@ async fn test_get_type_mismatch_error() {
// Task outputs i32
let source = dag.add_task(task_fn(|_: ()| async { 42i32 }));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -56,9 +55,7 @@ async fn test_get_type_mismatch_i32_to_vec() {
// Task outputs i32
let int_task = dag.add_task(task_fn(|_: ()| async { 123i32 }));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -102,9 +99,7 @@ async fn test_get_type_mismatch_struct_to_hashmap() {
let dag = DagRunner::new();
let task_handle = dag.add_task(CustomTask);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -136,9 +131,7 @@ async fn test_get_type_mismatch_vec_different_types() {
// Task outputs Vec<i32>
let vec_task = dag.add_task(task_fn(|_: ()| async { vec![1, 2, 3] }));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -170,9 +163,7 @@ async fn test_get_type_mismatch_option_types() {
// Task outputs Option<i32>
let opt_task = dag.add_task(task_fn(|_: ()| async { Some(42) }));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -206,9 +197,7 @@ async fn test_multiple_type_mismatches_same_dag() {
let string_task = dag.add_task(task_fn(|_: ()| async { "hello".to_string() }));
let bool_task = dag.add_task(task_fn(|_: ()| async { true }));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+8 -22
View File
@@ -5,6 +5,8 @@ use crate::runner::DagRunner;
use crate::task::Task;
use crate::types::TaskHandle;
use futures::FutureExt;
// Initialize tracing subscriber for tests (idempotent)
#[cfg(feature = "tracing")]
fn init_tracing() {
@@ -68,9 +70,7 @@ async fn test_get_wrong_type() {
let dag = DagRunner::new();
let handle = dag.add_task(TestTask { value: 42 });
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -145,18 +145,12 @@ async fn test_concurrent_run_protection() {
// Start two runs concurrently
let handle1 = tokio::spawn(async move {
barrier1.wait().await;
dag1.run(|fut| {
tokio::spawn(fut);
})
.await
dag1.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await
});
let handle2 = tokio::spawn(async move {
barrier2.wait().await;
dag2.run(|fut| {
tokio::spawn(fut);
})
.await
dag2.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await
});
// One should succeed, one should fail (concurrent execution not supported)
@@ -205,9 +199,7 @@ async fn test_multiple_get_calls() {
let dag = DagRunner::new();
let handle = dag.add_task(TestTask { value: 100 });
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -233,9 +225,7 @@ async fn test_invalid_node_id_in_get() {
_phantom: std::marker::PhantomData,
};
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -288,11 +278,7 @@ async fn test_task_panic_in_multi_task_layer() {
let _good_task = dag.add_task(GoodTask).depends_on(&source);
// Run the DAG - the panic should be caught
let result = dag
.run(|fut| {
tokio::spawn(fut);
})
.await;
let result = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
// The run should fail due to the panic
// Note: Tokio catches panics in spawned tasks, so the behavior depends on the runtime
+2 -1
View File
@@ -3,6 +3,7 @@
use crate::task::{task_fn, Task};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
// Test the task_fn function and TaskFn struct
#[tokio::test]
@@ -87,7 +88,7 @@ impl Task for CustomTask {
#[allow(refining_impl_trait)]
fn extract_and_run(
self,
_receivers: Vec<Box<dyn std::any::Any + Send>>,
_dependencies: Vec<Arc<dyn std::any::Any + Send + Sync>>,
) -> Pin<Box<dyn Future<Output = Result<Self::Output, String>> + Send>> {
Box::pin(async move {
// This is a test helper - just return an error
+4 -3
View File
@@ -6,9 +6,9 @@
use crate::builder::{IsUnitType, TaskBuilder};
use crate::task::Task;
/// Opaque node identifier (internal use)
/// Opaque node identifier
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) struct NodeId(pub usize);
pub struct NodeId(pub usize);
/// Opaque, typed token for a node's output.
///
@@ -23,6 +23,7 @@ pub(crate) struct NodeId(pub usize);
///
/// ```no_run
/// # use dagx::{task, DagRunner, Task};
/// # use futures::FutureExt;
/// # struct LoadValue { value: i32 }
/// # impl LoadValue { pub fn new(v: i32) -> Self { Self { value: v } } }
/// # #[task]
@@ -33,7 +34,7 @@ pub(crate) struct NodeId(pub usize);
/// let dag = DagRunner::new();
/// let node = dag.add_task(LoadValue::new(42));
///
/// dag.run().await.unwrap();
/// dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
///
/// assert_eq!(dag.get(node).unwrap(), 42);
/// # };
+6 -15
View File
@@ -4,6 +4,7 @@
use crate::types::TaskHandle;
use crate::{task, DagRunner, Task};
use futures::FutureExt;
#[tokio::test]
async fn test_unit_type_taskbuilder_to_handle_conversion() {
@@ -31,9 +32,7 @@ async fn test_unit_type_taskbuilder_to_handle_conversion() {
// specifically covering line 71
let handle: TaskHandle<i32> = builder.into();
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -81,9 +80,7 @@ async fn test_unit_type_conversion_with_multiple_tasks() {
let second_handle: TaskHandle<i32> = second_builder.into();
let third_handle: TaskHandle<bool> = third_builder.into();
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -122,9 +119,7 @@ async fn test_unit_struct_with_state() {
// Explicit conversion - triggers line 71
let handle: TaskHandle<i32> = builder.into();
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -151,9 +146,7 @@ async fn test_taskbuilder_conversion_assignment() {
let handle: TaskHandle<Vec<i32>> = builder.into();
let id = handle.id;
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -197,9 +190,7 @@ async fn test_mixed_unit_and_dependent_tasks() {
// Doubler needs dependencies, so we use it differently
let doubler = dag.add_task(Doubler).depends_on(source_handle);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+20 -57
View File
@@ -10,6 +10,7 @@
//! These tests serve as both validation and documentation for proper Arc usage.
use dagx::{task, DagRunner, Task};
use futures::FutureExt;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use std::time::{Duration, Instant};
@@ -43,9 +44,7 @@ async fn test_arc_basic_pipeline() {
let producer = dag.add_task(Producer);
let consumer = dag.add_task(Consumer).depends_on(&producer);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -101,9 +100,7 @@ async fn test_arc_with_primitives() {
.add_task(AllConsumer)
.depends_on((&i32_prod, &f64_prod, &bool_prod));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -177,9 +174,7 @@ async fn test_arc_with_collections() {
.add_task(CollectionConsumer)
.depends_on((&vec_prod, &map_prod, &set_prod));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -250,9 +245,7 @@ async fn test_arc_with_custom_structs() {
let age_ext = dag.add_task(AgeExtractor).depends_on(&person_prod);
let email_ext = dag.add_task(EmailCountExtractor).depends_on(&person_prod);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -318,9 +311,7 @@ async fn test_arc_fan_out_three_consumers() {
let c2 = dag.add_task(FirstConsumer).depends_on(&producer);
let c3 = dag.add_task(JoinConsumer).depends_on(&producer);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -394,9 +385,7 @@ async fn test_arc_fan_out_five_consumers_large_data() {
let c4 = dag.add_task(PatternCounter).depends_on(&producer);
let c5 = dag.add_task(TotalBytesComputer).depends_on(&producer);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -486,9 +475,7 @@ async fn test_arc_nested_fan_out_tree() {
let l2_negate = dag.add_task(Level2Negate).depends_on(l1_prod);
let l2_mod = dag.add_task(Level2Mod10).depends_on(l1_prod);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -556,9 +543,7 @@ async fn test_arc_performance_vs_clone() {
let start = Instant::now();
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -658,9 +643,7 @@ async fn test_arc_with_result() {
let consumer3 = dag.add_task(ArcResultConsumer2).depends_on(&arc_result_err);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -767,9 +750,7 @@ async fn test_arc_with_option() {
let c3 = dag.add_task(OptionArcConsumer1).depends_on(&opt_arc_some);
let c4 = dag.add_task(OptionArcConsumer2).depends_on(&opt_arc_none);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -824,9 +805,7 @@ async fn test_arc_diamond_dependency() {
let right = dag.add_task(RightBranch).depends_on(&top);
let bottom = dag.add_task(Bottom).depends_on((&left, &right));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -894,9 +873,7 @@ async fn test_arc_empty_collections() {
.add_task(EmptyConsumer)
.depends_on((&vec_task, &string_task, &map_task));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -967,9 +944,7 @@ async fn test_arc_very_large_data() {
let last = dag.add_task(LastSampler).depends_on(&producer);
let middle = dag.add_task(MiddleSampler).depends_on(&producer);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -1006,9 +981,7 @@ async fn test_arc_of_arc() {
let producer = dag.add_task(NestedProducer);
let consumer = dag.add_task(NestedConsumer).depends_on(&producer);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -1072,9 +1045,7 @@ async fn test_arc_mixed_with_non_arc() {
let unwrap = dag.add_task(Unwrapper).depends_on(&arc_prod);
let wrap = dag.add_task(Wrapper).depends_on(&non_arc_prod);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -1142,9 +1113,7 @@ async fn test_arc_parallel_execution() {
let start = Instant::now();
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -1193,9 +1162,7 @@ async fn test_arc_reference_counting() {
let producer = dag.add_task(RefCountProducer);
let checker = dag.add_task(RefCountChecker).depends_on(&producer);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -1271,9 +1238,7 @@ async fn test_arc_massive_fanout_performance_comparison() {
let start_without_arc = Instant::now();
dag_without_arc
.run(|fut| {
tokio::spawn(fut);
})
.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
let duration_without_arc = start_without_arc.elapsed();
@@ -1325,9 +1290,7 @@ async fn test_arc_massive_fanout_performance_comparison() {
let start_with_arc = Instant::now();
dag_with_arc
.run(|fut| {
tokio::spawn(fut);
})
.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
let duration_with_arc = start_with_arc.elapsed();
+10 -36
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::{DagResult, DagRunner};
use futures::FutureExt;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
@@ -19,10 +20,7 @@ async fn test_1000_level_deep_chain() -> DagResult<()> {
.depends_on(current);
}
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(current)?, 999);
Ok(())
@@ -52,10 +50,7 @@ async fn test_deep_chain_with_branches() -> DagResult<()> {
}
}
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(main_chain)?, 499);
@@ -113,10 +108,7 @@ async fn test_multiple_parallel_deep_chains() -> DagResult<()> {
&chain_ends[7],
));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Each chain: start + 500
// Chain 0: 0 + 500 = 500
@@ -161,10 +153,7 @@ async fn test_deep_chain_execution_order() -> DagResult<()> {
.depends_on(current);
}
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(order.load(Ordering::SeqCst), 100);
assert_eq!(dag.get(current)?, 99);
@@ -192,10 +181,7 @@ async fn test_deep_binary_tree() -> DagResult<()> {
let root = build_tree(&dag, 10, 1);
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Sum of all values in a complete binary tree with values 1024..2047
// This is sum from 2^10 to 2^11-1 = 1024 * 1023 / 2 + 1024 * 512 = 1047552
@@ -225,10 +211,7 @@ async fn test_fibonacci_chain() -> DagResult<()> {
prev1 = next;
}
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// 50th Fibonacci number (0-indexed) = 7778742049
assert_eq!(dag.get(prev1)?, 7778742049);
@@ -251,10 +234,7 @@ async fn test_deep_chain_with_accumulator() -> DagResult<()> {
.depends_on(current);
}
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
let result = dag.get(current)?;
assert_eq!(result, 20100); // sum of 1..=200
@@ -292,10 +272,7 @@ async fn test_zigzag_dependencies() -> DagResult<()> {
.add_task(task_fn(|(a, b): (i32, i32)| async move { a + b }))
.depends_on((&chain_a, &chain_b));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
let result = dag.get(final_task)?;
assert!(result > 100);
@@ -318,10 +295,7 @@ async fn test_10000_level_chain_stress() -> DagResult<()> {
.depends_on(current);
}
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(current)?, 9_999);
Ok(())
+6 -20
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::{DagResult, DagRunner};
use futures::FutureExt;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
@@ -25,10 +26,7 @@ async fn test_10000_independent_tasks() -> DagResult<()> {
})
.collect();
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify all tasks completed
assert_eq!(counter.load(Ordering::Relaxed), 10_000);
@@ -103,10 +101,7 @@ async fn test_5000_node_pyramid() -> DagResult<()> {
.add_task(task_fn(|(a, b): (i32, i32)| async move { a + b }))
.depends_on((&current_layer[0], &current_layer[1]));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// We created 1024 ones and 1024 zeros at the bottom
// The sum should propagate up to 1024
@@ -139,10 +134,7 @@ async fn test_wide_dag_1000_sources_1000_sinks() -> DagResult<()> {
})
.collect();
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify some sinks completed correctly
let result0 = dag.get(sinks[0])?;
@@ -195,10 +187,7 @@ async fn test_10000_node_linear_chain_segments() -> DagResult<()> {
chain_ends.push(current);
}
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// All 10,000 tasks should have executed
assert_eq!(counter.load(Ordering::Relaxed), 10_000);
@@ -239,10 +228,7 @@ async fn test_100000_nodes_stress() -> DagResult<()> {
.collect();
}
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(completed.load(Ordering::Relaxed), 100_000);
Ok(())
+11 -40
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::{DagResult, DagRunner};
use futures::FutureExt;
use std::marker::PhantomData;
#[tokio::test]
@@ -38,10 +39,7 @@ async fn test_zero_sized_types_throughout() -> DagResult<()> {
}))
.depends_on(&t2);
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(t1)?, Empty);
assert_eq!(dag.get(t2)?, 42);
@@ -76,10 +74,7 @@ async fn test_large_value_types() -> DagResult<()> {
}))
.depends_on(&t1);
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(t2)?, 1042);
@@ -98,10 +93,7 @@ async fn test_unit_type_chains() -> DagResult<()> {
.add_task(task_fn(|_: ()| async { "done" }))
.depends_on(&t3);
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
dag.get(t1)?; // Verify no error
dag.get(t2)?;
@@ -132,10 +124,7 @@ async fn test_nested_option_result_types() -> DagResult<()> {
}))
.depends_on(&t1);
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(t2)?, 55);
@@ -159,10 +148,7 @@ async fn test_tuple_of_tuples() -> DagResult<()> {
))
.depends_on((&t1, &t2));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(t3)?, 36);
@@ -196,10 +182,7 @@ async fn test_string_types_variety() -> DagResult<()> {
))
.depends_on((&t1, &t2, &t3, &t4, &t5, &t6));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
let result = dag.get(combined)?;
assert!(result.contains("static str"));
@@ -228,10 +211,7 @@ async fn test_generic_type_constraints() -> DagResult<()> {
))
.depends_on((&t1, &t2, &t3));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
let result = dag.get(combined)?;
assert!(result.contains("Wrapper(42)"));
@@ -257,10 +237,7 @@ async fn test_reference_wrapper_types() -> DagResult<()> {
.add_task(task_fn(|val: i32| async move { val * 2 }))
.depends_on(&t1);
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(t2)?, 200);
@@ -285,10 +262,7 @@ async fn test_array_types() -> DagResult<()> {
}))
.depends_on((&t1, &t2));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(t3)?, 55);
@@ -327,10 +301,7 @@ async fn test_enum_variants() -> DagResult<()> {
))
.depends_on((&t1, &t2, &t3, &t4));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert!(dag.get(combined)?);
+12 -44
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::{task, DagResult, DagRunner, Task};
use futures::FutureExt;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
@@ -10,10 +11,7 @@ async fn test_empty_dag_execution() -> DagResult<()> {
// Test that an empty DAG can be created and run without issues
let dag = DagRunner::new();
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Should complete successfully with no tasks
Ok(())
@@ -26,10 +24,7 @@ async fn test_single_task_no_deps() -> DagResult<()> {
let task = dag.add_task(task_fn(|_: ()| async { 42 }));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(task)?, 42);
Ok(())
@@ -50,10 +45,7 @@ async fn test_single_task_unit_input_output() -> DagResult<()> {
}
}));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
dag.get(task)?; // Just verify it doesn't error
assert_eq!(executed.load(Ordering::SeqCst), 1);
@@ -70,10 +62,7 @@ async fn test_zero_sized_type_task() -> DagResult<()> {
let task = dag.add_task(task_fn(|_: ()| async { ZeroSized }));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(&task)?, ZeroSized);
Ok(())
@@ -85,10 +74,7 @@ async fn test_empty_dag_multiple_runs() -> DagResult<()> {
let dag = DagRunner::new();
for _ in 0..10 {
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
}
Ok(())
@@ -103,10 +89,7 @@ async fn test_dag_with_only_source_tasks() -> DagResult<()> {
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i })))
.collect();
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
for (i, task) in tasks.iter().enumerate() {
assert_eq!(dag.get(task)?, i);
@@ -132,10 +115,7 @@ async fn test_single_self_contained_stateful_task() -> DagResult<()> {
let dag = DagRunner::new();
let task = dag.add_task(Counter { value: 21 });
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(task)?, 42);
Ok(())
@@ -149,17 +129,11 @@ async fn test_alternating_empty_and_filled_dag_runs() -> DagResult<()> {
if i % 2 == 0 {
// Empty DAG
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
} else {
// DAG with a task
let task = dag.add_task(task_fn(move |_: ()| async move { i }));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(task)?, i);
}
}
@@ -179,10 +153,7 @@ async fn test_dag_with_never_type_simulation() -> DagResult<()> {
// This task completes immediately but returns a Result containing "never" type
let task = dag.add_task(task_fn(|_: ()| async { Result::<i32, Never>::Ok(42) }));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(&task)?, Ok(42));
Ok(())
@@ -199,10 +170,7 @@ async fn test_minimal_task_with_minimal_async_work() -> DagResult<()> {
1337
}));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(task)?, 1337);
Ok(())
+5 -12
View File
@@ -1,5 +1,6 @@
// Test that custom types work without any trait implementations
use dagx::{task, DagRunner, Task};
use futures::FutureExt;
#[derive(Clone, Debug, PartialEq)]
struct Person {
@@ -56,9 +57,7 @@ async fn test_custom_type_single_dependency() {
let fetch = dag.add_task(FetchPerson);
let process = dag.add_task(ProcessPerson).depends_on(&fetch);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -74,9 +73,7 @@ async fn test_custom_type_passthrough() {
let transform = dag.add_task(PersonToCompany).depends_on(&fetch);
let process = dag.add_task(ProcessPerson).depends_on(&fetch);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -99,9 +96,7 @@ async fn test_custom_type_diamond_pattern() {
let path_a = dag.add_task(ProcessPerson).depends_on(&fetch);
let path_b = dag.add_task(PersonToCompany).depends_on(&fetch);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -121,9 +116,7 @@ async fn test_custom_type_fan_out() {
let process2 = dag.add_task(ProcessPerson).depends_on(&fetch);
let process3 = dag.add_task(ProcessPerson).depends_on(&fetch);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+2 -3
View File
@@ -4,6 +4,7 @@
//! prevents it through the type-state pattern.
use dagx::{task, DagRunner, Task, TaskHandle};
use futures::FutureExt;
/// Demonstrates that TaskBuilder is consumed by depends_on,
/// preventing you from wiring the same task multiple times
@@ -270,9 +271,7 @@ async fn test_type_safety_prevents_invalid_graphs() {
let result = dag.add_task(Aggregate).depends_on((&t1, &t2));
// Execute and verify
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+5 -16
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::{DagResult, DagRunner};
use futures::FutureExt;
use std::sync::{Arc, Mutex};
#[tokio::test]
@@ -81,10 +82,7 @@ async fn test_dependency_resolution_order() -> DagResult<()> {
.depends_on(d)
};
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(e)?, 5);
@@ -118,10 +116,7 @@ async fn test_transitive_dependencies() -> DagResult<()> {
.add_task(task_fn(|x: i32| async move { x + 5 }))
.depends_on(b);
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(&a)?, 10);
assert_eq!(dag.get(b)?, 20);
@@ -144,10 +139,7 @@ async fn test_deep_chain_50_levels() -> DagResult<()> {
handles.push(next);
}
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(handles.last().unwrap())?, 50);
Ok(())
@@ -166,10 +158,7 @@ async fn test_deep_chain_200_levels() -> DagResult<()> {
.depends_on(current);
}
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(current)?, 201);
Ok(())
+6 -20
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::{DagResult, DagRunner};
use futures::FutureExt;
#[tokio::test]
async fn test_single_dependency() -> DagResult<()> {
@@ -12,10 +13,7 @@ async fn test_single_dependency() -> DagResult<()> {
.add_task(task_fn(|x: i32| async move { x + 1 }))
.depends_on(&source);
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(dependent)?, 43);
Ok(())
@@ -42,10 +40,7 @@ async fn test_dependency_data_flow() -> DagResult<()> {
.add_task(task_fn(|(s, p): (i32, i32)| async move { s + p }))
.depends_on((&sum, &product));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Note: sum and product are not sinks (final_result depends on them)
// Only final_result is a sink and can be retrieved
@@ -69,10 +64,7 @@ async fn test_dependencies_with_different_types() -> DagResult<()> {
}))
.depends_on((&int_source, &string_source, &bool_source));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(combined)?, "hello: 42 (true)");
Ok(())
@@ -93,10 +85,7 @@ async fn test_shared_dependencies() -> DagResult<()> {
})
.collect();
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify all dependents got the correct value from shared
for (i, handle) in dependents.iter().enumerate() {
@@ -115,10 +104,7 @@ async fn test_multiple_source_nodes() -> DagResult<()> {
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i * 10 })))
.collect();
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
for (i, source) in sources.iter().enumerate() {
assert_eq!(dag.get(source)?, i as i32 * 10);
+8 -28
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::{DagResult, DagRunner};
use futures::FutureExt;
#[tokio::test]
async fn test_two_dependencies() -> DagResult<()> {
@@ -13,10 +14,7 @@ async fn test_two_dependencies() -> DagResult<()> {
.add_task(task_fn(|(x, y): (i32, i32)| async move { x + y }))
.depends_on((&a, &b));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(sum)?, 30);
Ok(())
@@ -35,10 +33,7 @@ async fn test_three_dependencies() -> DagResult<()> {
))
.depends_on((&a, &b, &c));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(sum)?, 6);
Ok(())
@@ -58,10 +53,7 @@ async fn test_four_dependencies() -> DagResult<()> {
}))
.depends_on((&deps[0], &deps[1], &deps[2], &deps[3]));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(sum)?, 10); // 1+2+3+4
Ok(())
@@ -81,10 +73,7 @@ async fn test_five_dependencies() -> DagResult<()> {
))
.depends_on((&deps[0], &deps[1], &deps[2], &deps[3], &deps[4]));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(sum)?, 15); // 1+2+3+4+5
Ok(())
@@ -106,10 +95,7 @@ async fn test_six_dependencies() -> DagResult<()> {
&deps[0], &deps[1], &deps[2], &deps[3], &deps[4], &deps[5]
));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(sum)?, 21); // 1+2+3+4+5+6
Ok(())
@@ -133,10 +119,7 @@ async fn test_seven_dependencies() -> DagResult<()> {
&deps[0], &deps[1], &deps[2], &deps[3], &deps[4], &deps[5], &deps[6],
));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(sum)?, 28); // 1+2+3+4+5+6+7
Ok(())
@@ -160,10 +143,7 @@ async fn test_eight_dependencies() -> DagResult<()> {
&deps[0], &deps[1], &deps[2], &deps[3], &deps[4], &deps[5], &deps[6], &deps[7],
));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(sum)?, 36); // Sum of 1..=8
Ok(())
+8 -25
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::{DagError, DagRunner};
use futures::FutureExt;
#[tokio::test]
async fn test_result_not_found_error() {
@@ -14,9 +15,7 @@ async fn test_result_not_found_error() {
assert!(matches!(result, Err(DagError::ResultNotFound { .. })));
// Run and verify it works after
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -34,9 +33,7 @@ async fn test_nested_result_error_handling() {
let task2 = dag.add_task(task_fn(|_: ()| async { Result::<i32, String>::Ok(42) }));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -51,9 +48,7 @@ async fn test_option_none_not_error() {
let task = dag.add_task(task_fn(|_: ()| async { None::<i32> }));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -82,9 +77,7 @@ async fn test_custom_error_types() {
})
}));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -113,11 +106,7 @@ async fn test_error_in_tuple_dependency() {
.add_task(task_fn(|(a, b): (i32, i32)| async move { a + b }))
.depends_on((&ok_task, &err_task));
let result = dag
.run(|fut| {
tokio::spawn(fut);
})
.await;
let result = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
assert!(result.is_err());
assert!(dag.get(dependent).is_err());
@@ -135,11 +124,7 @@ async fn test_string_panic_vs_structured_panic() {
panic!("Formatted panic: value={}, code={}", 42, "ERROR");
}));
let _ = dag
.run(|fut| {
tokio::spawn(fut);
})
.await;
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
// Both should result in errors
assert!(dag.get(&string_panic).is_err());
@@ -159,9 +144,7 @@ async fn test_anyhow_like_error_chain() {
outer
}));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+9 -36
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::DagRunner;
use futures::FutureExt;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
@@ -22,11 +23,7 @@ async fn test_linear_error_propagation() {
.add_task(task_fn(|x: i32| async move { x * 2 }))
.depends_on(t3);
let _ = dag
.run(|fut| {
tokio::spawn(fut);
})
.await;
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
// t1 succeeds
assert_eq!(dag.get(&t1).unwrap(), 1);
@@ -65,11 +62,7 @@ async fn test_diamond_error_propagation() {
.add_task(task_fn(|(l, r): (i32, i32)| async move { l + r }))
.depends_on((&left, &right));
let _ = dag
.run(|fut| {
tokio::spawn(fut);
})
.await;
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
assert_eq!(dag.get(&source).unwrap(), 100);
assert_eq!(dag.get(left).unwrap(), 50);
@@ -119,11 +112,7 @@ async fn test_error_stops_at_boundary() {
}
}));
let _ = dag
.run(|fut| {
tokio::spawn(fut);
})
.await;
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
// Error branch: source runs but dependent doesn't
assert!(dag.get(&error_source).is_err());
@@ -156,11 +145,7 @@ async fn test_multiple_error_sources_convergence() {
.add_task(task_fn(|(_, _, _): (i32, i32, i32)| async { 100 }))
.depends_on((&error1, &error2, &success));
let _ = dag
.run(|fut| {
tokio::spawn(fut);
})
.await;
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
// All error sources fail
assert!(dag.get(&error1).is_err());
@@ -189,11 +174,7 @@ async fn test_error_in_wide_fanout() {
})
.collect();
let _ = dag
.run(|fut| {
tokio::spawn(fut);
})
.await;
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
// Source fails
assert!(dag.get(&source).is_err());
@@ -234,9 +215,7 @@ async fn test_selective_error_propagation() {
}))
.depends_on(handler);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -294,11 +273,7 @@ async fn test_error_propagation_timing() {
}))
.depends_on(&t2);
let _ = dag
.run(|fut| {
tokio::spawn(fut);
})
.await;
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
let order = propagation_order.lock().clone();
@@ -373,9 +348,7 @@ async fn test_partial_branch_failure_propagation() {
))
.depends_on((&a3, &b3, &c3));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.ok();
+9 -35
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::{DagResult, DagRunner};
use futures::FutureExt;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
@@ -23,11 +24,7 @@ async fn test_fallback_on_error() -> DagResult<()> {
let _selector = dag.add_task(task_fn(|_: ()| async {}));
// Run DAG
let _ = dag
.run(|fut| {
tokio::spawn(fut);
})
.await;
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
// Primary should fail
assert!(dag.get(&primary).is_err());
@@ -63,10 +60,7 @@ async fn test_retry_pattern_simulation() -> DagResult<()> {
}
}));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Task completes with error on first "attempt"
assert_eq!(dag.get(&task)?, Err("Simulated failure"));
@@ -109,10 +103,7 @@ async fn test_circuit_breaker_pattern() -> DagResult<()> {
})
.collect();
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// First few tasks fail and open circuit
for task in &tasks[0..3] {
@@ -144,11 +135,7 @@ async fn test_partial_recovery_with_defaults() -> DagResult<()> {
vec![0, 10, 20, 0, 40, 50, 0, 70, 80, 0] // 0 for failed tasks
}));
let _ = dag
.run(|fut| {
tokio::spawn(fut);
})
.await;
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
let result = dag.get(&aggregator)?;
assert_eq!(result.len(), 10);
@@ -178,10 +165,7 @@ async fn test_error_accumulation_pattern() -> DagResult<()> {
})
.collect();
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Check accumulated errors
let error_list = errors.lock().clone();
@@ -215,10 +199,7 @@ async fn test_graceful_degradation() -> DagResult<()> {
}))
.depends_on(&critical);
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Critical path works
assert_eq!(dag.get(final_task)?, 15);
@@ -249,10 +230,7 @@ async fn test_error_boundary_isolation() -> DagResult<()> {
.add_task(task_fn(|x: i32| async move { x + 5 }))
.depends_on(b2);
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Group A fails (returns Err)
assert_eq!(dag.get(a2)?, Err("Group A error"));
@@ -287,11 +265,7 @@ async fn test_compensation_action() -> DagResult<()> {
}
}));
let _ = dag
.run(|fut| {
tokio::spawn(fut);
})
.await;
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
// Action failed
assert!(dag.get(&action).is_err());
+14 -13
View File
@@ -1,12 +1,13 @@
// Basic execution tests for DagRunner
use crate::common::task_fn;
use futures::FutureExt;
use dagx::*;
#[tokio::test]
async fn test_empty_dag() {
let dag = DagRunner::new();
dag.run()
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap(); // Should complete without error
}
@@ -15,7 +16,7 @@ async fn test_empty_dag() {
async fn test_single_task() {
let dag = DagRunner::new();
let task = dag.add_task(task_fn(|_: ()| async { 42 }));
dag.run()
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
assert_eq!(dag.get(task).unwrap(), 42);
@@ -39,7 +40,7 @@ async fn test_deep_chain() {
.add_task(task_fn(|x: i32| async move { x + 1 }))
.depends_on(d);
dag.run()
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -56,7 +57,7 @@ async fn test_wide_parallel() {
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i * 2 })))
.collect();
dag.run()
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -80,7 +81,7 @@ async fn test_diamond_dependency() {
.add_task(task_fn(|(x, y): (i32, i32)| async move { x + y }))
.depends_on((&b, &c));
dag.run()
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -108,7 +109,7 @@ async fn test_different_output_types() {
}
}));
dag.run()
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -141,7 +142,7 @@ async fn test_multiple_sinks() {
.add_task(task_fn(|x: i32| async move { x * 3 }))
.depends_on(branch2);
dag.run()
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap(); // Should wait for both sinks
@@ -163,7 +164,7 @@ async fn test_single_task_inline_path() {
.add_task(task_fn(|x: i32| async move { x + 5 }))
.depends_on(t2);
dag.run()
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -188,7 +189,7 @@ async fn test_multi_task_spawn_path() {
.add_task(task_fn(|x: i32| async move { x + 3 }))
.depends_on(&source);
dag.run()
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -212,7 +213,7 @@ async fn test_producer_consumer_channels() {
))
.depends_on(&producer);
dag.run()
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -237,7 +238,7 @@ async fn test_multi_consumer_fanout() {
.add_task(task_fn(|x: i32| async move { x - 5 }))
.depends_on(&producer);
dag.run()
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -265,7 +266,7 @@ async fn test_compute_layers_with_dependents_tracking() {
.add_task(task_fn(|(l, r): (i32, i32)| async move { l + r }))
.depends_on((&left, &right));
dag.run()
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -313,7 +314,7 @@ async fn test_layer_execution_order() {
}))
.depends_on(t2);
dag.run()
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+5 -12
View File
@@ -3,6 +3,7 @@ use crate::common::task_fn;
use crate::common::tasks::*;
use dagx::*;
use futures::FutureExt;
#[tokio::test]
async fn test_example_a_fan_out() {
@@ -12,9 +13,7 @@ async fn test_example_a_fan_out() {
let plus1 = 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);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap(); // SPEC API with spawner
@@ -32,9 +31,7 @@ async fn test_example_b_fan_in() {
let output = dag.add_task(OutputResult::new()).depends_on((&s, &n, &f));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
assert_eq!(&dag.get(output).unwrap(), "hi:42:true");
@@ -52,9 +49,7 @@ async fn test_example_c_many_to_many() {
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
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
assert_eq!(dag.get(total).unwrap(), 20);
@@ -76,9 +71,7 @@ async fn test_single_input_forms() {
.add_task(task_fn(|s: String| async move { s.to_uppercase() }))
.depends_on((&s,));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+24 -17
View File
@@ -1,7 +1,8 @@
//! Integration tests for end-to-end functionality
use dagx::{DagResult, DagRunner, Task};
use crate::common::task_fn;
use dagx::{DagResult, DagRunner, Task};
use futures::FutureExt;
use std::time::Instant;
use tokio::time::{sleep, Duration};
@@ -67,7 +68,7 @@ async fn integration_test_etl_pipeline() -> DagResult<()> {
let final_result = dag.add_task(Add).depends_on((&combine12, &transform3));
// Execute pipeline
dag.run(|fut| { tokio::spawn(fut); }).await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify
let result = dag.get(final_result)?;
@@ -97,7 +98,7 @@ async fn integration_test_multi_layer_dag() -> DagResult<()> {
// Layer 4: Final result
let final_result = dag.add_task(Add).depends_on((&double_ab, &double_bc));
dag.run(|fut| { tokio::spawn(fut); }).await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
let result = dag.get(final_result)?;
// (1+2)*2 + (2+3)*2 = 6 + 10 = 16
@@ -118,7 +119,7 @@ async fn integration_test_multiple_sinks() -> DagResult<()> {
let branch2 = dag.add_task(Multiply::new(3)).depends_on(&source);
let branch3 = dag.add_task(Multiply::new(4)).depends_on(&source);
dag.run(|fut| { tokio::spawn(fut); }).await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// All branches should complete
assert_eq!(dag.get(branch1)?, 20);
@@ -144,7 +145,7 @@ async fn integration_test_diamond_cascade() -> DagResult<()> {
let right2 = dag.add_task(Multiply::new(3)).depends_on(sink1);
let sink2 = dag.add_task(Add).depends_on((&left2, &right2));
dag.run(|fut| { tokio::spawn(fut); }).await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
let result = dag.get(sink2)?;
// Diamond 1: (10*2) + (10*3) = 50
@@ -175,7 +176,7 @@ async fn integration_test_wide_and_deep() -> DagResult<()> {
final_nodes.push(step3);
}
dag.run(|fut| { tokio::spawn(fut); }).await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify each chain
for (i, node) in final_nodes.iter().enumerate() {
@@ -194,7 +195,7 @@ async fn integration_test_error_recovery() -> DagResult<()> {
let source = dag.add_task(LoadValue::new(42));
let transform = dag.add_task(Multiply::new(2)).depends_on(&source);
dag.run(|fut| { tokio::spawn(fut); }).await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Should be able to access results
assert_eq!(dag.get(transform)?, 84);
@@ -230,7 +231,7 @@ async fn integration_test_large_scale() -> DagResult<()> {
})
.collect();
dag.run(|fut| { tokio::spawn(fut); }).await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify some results
assert_eq!(dag.get(aggregations[0])?, 1); // First two
@@ -254,7 +255,7 @@ async fn integration_test_reused_values() -> DagResult<()> {
// Combine some of the results
let combined = dag.add_task(Add).depends_on((&double, &triple));
dag.run(|fut| { tokio::spawn(fut); }).await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(double)?, 200);
assert_eq!(dag.get(triple)?, 300);
@@ -283,7 +284,7 @@ async fn integration_test_parallel_execution_timing() -> DagResult<()> {
.collect();
let start = Instant::now();
dag.run(|fut| { tokio::spawn(fut); }).await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
let elapsed = start.elapsed();
// Verify all tasks completed
@@ -322,23 +323,29 @@ async fn integration_test_parallel_vs_sequential_timing() -> DagResult<()> {
// Layer 2: Sequential chain of 3 tasks depending on the first parallel task
// (should add ~150ms)
let seq1 = dag.add_task(task_fn(move |x: i32| async move {
let seq1 = dag
.add_task(task_fn(move |x: i32| async move {
sleep(sleep_duration).await;
x + 1
})).depends_on(&parallel_tasks[0]);
}))
.depends_on(&parallel_tasks[0]);
let seq2 = dag.add_task(task_fn(move |x: i32| async move {
let seq2 = dag
.add_task(task_fn(move |x: i32| async move {
sleep(sleep_duration).await;
x + 1
})).depends_on(seq1);
}))
.depends_on(seq1);
let seq3 = dag.add_task(task_fn(move |x: i32| async move {
let seq3 = dag
.add_task(task_fn(move |x: i32| async move {
sleep(sleep_duration).await;
x + 1
})).depends_on(seq2);
}))
.depends_on(seq2);
let start = Instant::now();
dag.run(|fut| { tokio::spawn(fut); }).await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
let elapsed = start.elapsed();
// Verify results
+7 -19
View File
@@ -3,6 +3,7 @@ use crate::common::task_fn;
use crate::common::tasks::Counter;
use dagx::*;
use futures::FutureExt;
#[tokio::test]
async fn test_result_type() {
@@ -13,9 +14,7 @@ async fn test_result_type() {
Err::<i32, String>("error".to_string())
}));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -30,10 +29,7 @@ async fn test_dag_result_type_works() {
// Test that DagResult<()> works with ? operator pattern
async fn run_dag(dag: &DagRunner) -> DagResult<()> {
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
Ok(())
}
@@ -68,7 +64,7 @@ async fn test_error_messages_are_helpful() {
let err_msg = format!("{}", result.unwrap_err());
assert!(err_msg.contains("Result not found"));
assert!(err_msg.contains("task #0")); // Task ID
assert!(err_msg.contains("Call dag.run()"));
assert!(err_msg.contains("Call dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))"));
}
#[tokio::test]
@@ -77,11 +73,7 @@ async fn test_successful_execution_returns_ok() {
let task = dag.add_task(task_fn(|_: ()| async { 42 }));
// Should return Ok(())
let result = dag
.run(|fut| {
tokio::spawn(fut);
})
.await;
let result = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
assert!(result.is_ok());
// Should be able to get value
@@ -95,9 +87,7 @@ async fn test_stateful_tasks() {
let input = dag.add_task(task_fn(|_: ()| async { 5 }));
let counter = dag.add_task(Counter::new(10)).depends_on(&input);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -117,9 +107,7 @@ async fn test_task_fn_with_captured_state() {
.add_task(task_fn(move |x: i32| async move { x * multiplier }))
.depends_on(&base);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+16 -31
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::DagRunner;
use futures::FutureExt;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
@@ -14,7 +15,12 @@ async fn test_concurrent_run_calls() {
// Add some tasks
let tasks: Vec<_> = (0..10)
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i })))
.map(|i| {
dag.add_task(task_fn(move |_: ()| async move {
tokio::time::sleep(Duration::from_millis(5)).await;
i
}))
})
.collect();
// Try to run the DAG multiple times concurrently
@@ -22,12 +28,7 @@ async fn test_concurrent_run_calls() {
for _ in 0..5 {
let dag = Arc::clone(&dag);
handles.spawn(async move {
dag.run(|fut| {
tokio::spawn(fut);
})
.await
});
handles.spawn(async move { dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await });
}
// Only one run should succeed, others should fail with concurrent execution error
@@ -62,9 +63,7 @@ async fn test_concurrent_execution_and_building() {
.collect();
// Execute the initial DAG
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -93,9 +92,7 @@ async fn test_concurrent_execution_and_building() {
}
// Execute again with the new tasks
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
}
@@ -139,9 +136,7 @@ async fn test_dag_builder_thread_safety() {
assert_eq!(counter.load(Ordering::SeqCst), 10);
// Execute the combined DAG
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -163,9 +158,7 @@ async fn test_concurrent_get_operations() {
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i })))
.collect();
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -217,9 +210,7 @@ async fn test_no_race_in_dependency_tracking() {
}
// Execute
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -277,9 +268,7 @@ async fn test_high_concurrency_stress() {
assert_eq!(completed.load(Ordering::SeqCst), 1000);
// The DAG should still be executable
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
}
@@ -369,9 +358,7 @@ async fn test_atomic_dag_operations() {
assert_eq!(deps.len(), 50);
// Execute and verify
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -424,9 +411,7 @@ async fn test_no_deadlock_on_circular_waiting() {
assert!(result.is_ok(), "Deadlock detected - operation timed out");
// Should be able to execute the DAG
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
}
+5 -16
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::{DagResult, DagRunner};
use futures::FutureExt;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use tokio::time::sleep;
@@ -62,10 +63,7 @@ async fn test_layers_execute_in_parallel() -> DagResult<()> {
.depends_on((&layer1[0], &layer1[1], &layer1[2]))
};
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(final_task)?, 33); // (0+1) + (10+1) + (20+1)
@@ -185,10 +183,7 @@ async fn test_completion_order_matches_dependencies() -> DagResult<()> {
.depends_on(c)
};
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(d)?, 6); // (1 + 2) * 2
@@ -271,10 +266,7 @@ async fn test_dependent_tasks_respect_ordering() -> DagResult<()> {
.depends_on(c)
};
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify results
assert_eq!(dag.get(a)?, 1);
@@ -344,10 +336,7 @@ async fn test_independent_tasks_dont_wait() -> DagResult<()> {
}))
};
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Check results
assert_eq!(dag.get(fast1)?, "fast1");
+2 -4
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::{DagResult, DagRunner};
use futures::FutureExt;
use std::time::{Duration, Instant};
use tokio::time::sleep;
@@ -24,10 +25,7 @@ async fn test_parallelism_proof_1000_tasks() -> DagResult<()> {
.collect();
let start = Instant::now();
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
let elapsed = start.elapsed();
// Verify all tasks completed
+3 -5
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::{DagResult, DagRunner};
use futures::FutureExt;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::sync::Mutex;
@@ -31,7 +32,7 @@ async fn test_spawner_actually_spawns_tasks() -> DagResult<()> {
.collect();
// Custom spawner that counts invocations
dag.run().await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify all tasks were spawned
for (i, task) in tasks.iter().enumerate() {
@@ -77,10 +78,7 @@ async fn test_tasks_not_running_inline() -> DagResult<()> {
})
.collect();
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify results
for (i, task) in tasks.iter().enumerate() {
+7 -24
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::{DagResult, DagRunner};
use futures::FutureExt;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
@@ -40,10 +41,7 @@ async fn test_tasks_run_on_different_threads() -> DagResult<()> {
})
.collect();
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify all tasks completed
for task in tasks.iter() {
@@ -112,10 +110,7 @@ async fn test_concurrent_execution_with_atomic_counter() -> DagResult<()> {
})
.collect();
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify all tasks completed
for (i, task) in tasks.iter().enumerate() {
@@ -186,10 +181,7 @@ async fn test_diamond_parallel_execution() -> DagResult<()> {
.add_task(task_fn(|(a, b): (i32, i32)| async move { a + b }))
.depends_on((&parallel1, &parallel2));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(sink)?, 500); // (100 * 2) + (100 * 3)
@@ -276,10 +268,7 @@ async fn test_parallel_branches_with_dependencies() -> DagResult<()> {
.depends_on(b2_1)
};
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify results
assert_eq!(dag.get(b1_2)?, 21); // (10 * 2) + 1
@@ -371,10 +360,7 @@ async fn test_wide_fanout_parallel_execution() -> DagResult<()> {
})
.collect();
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify results
for (i, task) in dependents.iter().enumerate() {
@@ -445,10 +431,7 @@ async fn test_massive_parallel_fanout() -> DagResult<()> {
.collect();
let start = Instant::now();
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
let elapsed = start.elapsed();
// All should complete
+3 -8
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::{DagResult, DagRunner, Task};
use futures::FutureExt;
use std::sync::Arc;
use std::sync::Mutex;
use std::time::{Duration, Instant};
@@ -26,10 +27,7 @@ async fn test_parallel_execution_speedup() -> DagResult<()> {
.collect();
let start = Instant::now();
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
let elapsed = start.elapsed();
// Verify all tasks completed
@@ -113,10 +111,7 @@ async fn test_sync_tasks_dont_block_async_runtime() -> DagResult<()> {
}))
};
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify results
assert_eq!(dag.get(sync1)?, 20);
+5 -16
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::{DagResult, DagRunner};
use futures::FutureExt;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
@@ -44,10 +45,7 @@ async fn test_butterfly_network() -> DagResult<()> {
.depends_on((&layer1[3], &layer1[7])),
];
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify some outputs exist
for task in &layer2 {
@@ -100,10 +98,7 @@ async fn test_recursive_butterfly() -> DagResult<()> {
let outputs = build_butterfly(&dag, inputs);
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(outputs.len(), 4);
for output in outputs {
@@ -143,10 +138,7 @@ async fn test_benes_network() -> DagResult<()> {
.depends_on((&stage1[1], &stage1[3])),
];
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
for output in outputs {
let _ = dag.get(output)?;
@@ -209,10 +201,7 @@ async fn test_shuffle_exchange_network() -> DagResult<()> {
})
.collect();
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(counter.load(Ordering::Relaxed), 8);
+6 -20
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::{DagResult, DagRunner};
use futures::FutureExt;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
@@ -59,10 +60,7 @@ async fn test_workflow_with_conditional_paths() -> DagResult<()> {
}))
.depends_on(combine);
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(process)?, 84);
let result = dag.get(final_result)?;
@@ -140,10 +138,7 @@ async fn test_map_reduce_pattern() -> DagResult<()> {
))
.depends_on((&reducers[0], &reducers[1], &reducers[2], &reducers[3]));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
let result = dag.get(final_task)?;
assert!(result > 0);
@@ -200,10 +195,7 @@ async fn test_pipeline_with_feedback() -> DagResult<()> {
}))
.depends_on(stage3);
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(stage1)?, 2);
assert_eq!(dag.get(stage3)?, 12);
@@ -247,10 +239,7 @@ async fn test_scatter_gather_pattern() -> DagResult<()> {
))
.depends_on((&workers[0], &workers[1], &workers[2], &workers[3]));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Sum of 10+20+30+40+50+60+70+80 = 360
assert_eq!(dag.get(gather)?, 360);
@@ -339,10 +328,7 @@ async fn test_fork_join_with_barriers() -> DagResult<()> {
}))
.depends_on(barrier);
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
let _ = dag.get(join)?;
assert_eq!(phase_counter.load(Ordering::SeqCst), 2);
+5 -16
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::{DagResult, DagRunner};
use futures::FutureExt;
#[tokio::test]
async fn test_2d_grid_pattern() -> DagResult<()> {
@@ -33,10 +34,7 @@ async fn test_2d_grid_pattern() -> DagResult<()> {
}
}
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Check corner values
let bottom_right = dag.get(grid[3][3].as_ref().unwrap())?;
@@ -99,10 +97,7 @@ async fn test_3d_grid_pattern() -> DagResult<()> {
}
}
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Check that all cells have values
for row in &grid {
@@ -178,10 +173,7 @@ async fn test_hexagonal_grid() -> DagResult<()> {
hex_grid.insert((q, r), task);
}
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify center and some ring values
assert_eq!(dag.get(hex_grid.get(&(0, 0)).unwrap())?, 100);
@@ -230,10 +222,7 @@ async fn test_toroidal_grid() -> DagResult<()> {
}
}
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify all torus tasks completed
for task in torus_tasks {
+7 -24
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::{DagResult, DagRunner};
use futures::FutureExt;
#[tokio::test]
async fn test_binary_tree_reduction() -> DagResult<()> {
@@ -35,10 +36,7 @@ async fn test_binary_tree_reduction() -> DagResult<()> {
let root = current_level[0];
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Sum of powers of 2 from 0 to 7: 1+2+4+8+16+32+64+128 = 255
assert_eq!(dag.get(root)?, 255);
@@ -69,10 +67,7 @@ async fn test_n_ary_tree() -> DagResult<()> {
let root = build_tree(&dag, 3, 1);
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
let result = dag.get(root)?;
assert!(result > 0);
@@ -119,10 +114,7 @@ async fn test_unbalanced_tree() -> DagResult<()> {
.add_task(task_fn(|(a, b): (i32, i32)| async move { a * b }))
.depends_on((&a, &b));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(&f)?, 1);
assert_eq!(dag.get(c)?, 2);
@@ -170,10 +162,7 @@ async fn test_trie_like_structure() -> DagResult<()> {
.add_task(task_fn(|v: String| async move { format!("{}/log", v) }))
.depends_on(var);
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(usr_bin)?, "root/usr/bin");
assert_eq!(dag.get(usr_lib)?, "root/usr/lib");
@@ -230,10 +219,7 @@ async fn test_balanced_k_ary_tree() -> DagResult<()> {
let root = create_balanced_tree(&dag, DEPTH, K, 0);
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
let result = dag.get(root)?;
assert!(result > 0);
@@ -280,10 +266,7 @@ async fn test_merkle_tree_pattern() -> DagResult<()> {
let root_hash = current_level[0];
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
let result = dag.get(root_hash)?;
assert!(result.starts_with("hash"));
+2 -3
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::DagRunner;
use futures::FutureExt;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
@@ -23,9 +24,7 @@ async fn test_memory_usage_10000_nodes() {
println!("Memory for 10k nodes: ~{} bytes", build_memory);
// Execute
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+3 -6
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::DagRunner;
use futures::FutureExt;
use std::sync::Arc;
use std::time::{Duration, Instant};
@@ -26,9 +27,7 @@ async fn test_deep_chain_100_levels() {
// Execute
let start = Instant::now();
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
let exec_time = start.elapsed();
@@ -89,9 +88,7 @@ async fn test_stress_mixed_patterns() {
// Execute
let start = Instant::now();
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
let exec_time = start.elapsed();
+4 -4
View File
@@ -1,7 +1,7 @@
//! Runtime compatibility tests for async-std
use dagx::{DagRunner, Task};
use crate::common::task_fn;
use dagx::{DagRunner, Task};
struct Value(i32);
#[dagx::task]
@@ -27,7 +27,7 @@ async fn test_basic_dag_async_std() {
let y = dag.add_task(Value(3));
let sum = dag.add_task(Add).depends_on((&x, &y));
dag.run(|fut| { async_std::task::spawn(fut); }).await.unwrap();
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
assert_eq!(dag.get(sum).unwrap(), 5);
}
@@ -40,7 +40,7 @@ async fn test_parallel_execution_async_std() {
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i * 2 })))
.collect();
dag.run(|fut| { async_std::task::spawn(fut); }).await.unwrap();
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
for (i, task) in tasks.iter().enumerate() {
assert_eq!(dag.get(task).unwrap(), i * 2);
@@ -58,7 +58,7 @@ async fn test_complex_dependencies_async_std() {
.add_task(task_fn(|x: i32| async move { x * 2 }))
.depends_on(sum);
dag.run(|fut| { async_std::task::spawn(fut); }).await.unwrap();
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await.unwrap();
assert_eq!(dag.get(double).unwrap(), 60);
}
+2 -4
View File
@@ -1,6 +1,7 @@
//! Runtime integration tests - verify dagx works correctly across different runtimes
use dagx::{DagResult, DagRunner, Task};
use futures::FutureExt;
struct Value(i32);
#[dagx::task]
@@ -26,10 +27,7 @@ async fn runtime_integration_tokio() -> DagResult<()> {
let b = dag.add_task(Value(20));
let sum = dag.add_task(Add).depends_on((&a, &b));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(sum)?, 30);
Ok(())
+34 -82
View File
@@ -15,6 +15,8 @@
use crate::common::task_fn;
use dagx::DagRunner;
use futures::task::SpawnExt;
use futures_executor::ThreadPool;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
@@ -36,11 +38,7 @@ fn test_async_executor_basic() {
.add_task(task_fn(|(a, b): (i32, i32)| async move { a + b }))
.depends_on((&x, &y));
dag.run(|fut| {
executor.spawn(fut).detach();
})
.await
.unwrap();
dag.run(|fut| executor.spawn(fut)).await.unwrap();
assert_eq!(dag.get(sum).unwrap(), 30);
};
@@ -70,11 +68,7 @@ fn test_async_executor_complex_dag() {
.add_task(task_fn(|(l, r): (i32, i32)| async move { l + r }))
.depends_on((&left, &right));
dag.run(|fut| {
executor.spawn(fut).detach();
})
.await
.unwrap();
dag.run(|fut| executor.spawn(fut)).await.unwrap();
assert_eq!(dag.get(sink).unwrap(), 25); // (5*2) + (5*3) = 25
};
@@ -104,11 +98,7 @@ fn test_async_executor_parallel_tasks() {
})
.collect();
dag.run(|fut| {
executor.spawn(fut).detach();
})
.await
.unwrap();
dag.run(|fut| executor.spawn(fut)).await.unwrap();
// All tasks should have run
assert_eq!(counter.load(Ordering::SeqCst), 10);
@@ -132,11 +122,7 @@ fn test_async_executor_with_local_spawn() {
let x = dag.add_task(task_fn(|_: ()| async { 42 }));
dag.run(|fut| {
executor.spawn(fut).detach();
})
.await
.unwrap();
dag.run(|fut| executor.spawn(fut)).await.unwrap();
assert_eq!(dag.get(x).unwrap(), 42);
};
@@ -163,11 +149,7 @@ fn test_async_executor_deep_chain() {
.depends_on(current);
}
dag.run(|fut| {
executor.spawn(fut).detach();
})
.await
.unwrap();
dag.run(|fut| executor.spawn(fut)).await.unwrap();
assert_eq!(dag.get(current).unwrap(), 50);
};
@@ -190,11 +172,7 @@ fn test_pollster_basic() {
.add_task(task_fn(|x: i32| async move { x * 2 }))
.depends_on(&x);
dag.run(|fut| {
// pollster blocks, so we need to spawn on a different executor
// Use futures::executor for spawning
futures_executor::block_on(fut);
})
dag.run(|fut| async { pollster::block_on(fut) })
.await
.unwrap();
@@ -217,9 +195,7 @@ fn test_pollster_complex() {
})
.collect();
dag.run(|fut| {
futures_executor::block_on(fut);
})
dag.run(|fut| async { pollster::block_on(fut) })
.await
.unwrap();
@@ -237,9 +213,7 @@ fn test_pollster_error_handling() {
// Task that returns Result
let task = dag.add_task(task_fn(|_: ()| async { Ok::<i32, String>(42) }));
dag.run(|fut| {
futures_executor::block_on(fut);
})
dag.run(|fut| async { pollster::block_on(fut) })
.await
.unwrap();
@@ -262,11 +236,9 @@ fn test_futures_executor_basic() {
.add_task(task_fn(|(a, b): (i32, i32)| async move { a * b }))
.depends_on((&x, &y));
dag.run(|fut| {
// Use ThreadPool for spawning
let pool = futures_executor::ThreadPool::new().unwrap();
pool.spawn_ok(fut);
})
let spawner = ThreadPool::new().unwrap();
dag.run(|fut| spawner.spawn_with_handle(fut).unwrap())
.await
.unwrap();
@@ -276,8 +248,8 @@ fn test_futures_executor_basic() {
#[test]
fn test_futures_executor_threadpool() {
let pool = std::sync::Arc::new(futures_executor::ThreadPool::new().unwrap());
let pool_clone = pool.clone();
let pool = futures_executor::ThreadPool::new().unwrap();
let spawner = pool.clone();
pool.spawn_ok(async move {
let dag = DagRunner::new();
@@ -287,9 +259,7 @@ fn test_futures_executor_threadpool() {
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i * i })))
.collect();
dag.run(|fut| {
pool_clone.spawn_ok(fut);
})
dag.run(|fut| spawner.spawn_with_handle(fut).unwrap())
.await
.unwrap();
@@ -325,10 +295,9 @@ fn test_futures_executor_diamond_pattern() {
.add_task(task_fn(|(x, y): (i32, i32)| async move { x * y }))
.depends_on((&b, &c));
let pool = futures_executor::ThreadPool::new().unwrap();
dag.run(|fut| {
pool.spawn_ok(fut);
})
let spawner = ThreadPool::new().unwrap();
dag.run(|fut| spawner.spawn_with_handle(fut).unwrap())
.await
.unwrap();
@@ -350,7 +319,7 @@ fn test_all_runtimes_compatibility() {
executor.run(async {
let dag = DagRunner::new();
let x = dag.add_task(task_fn(|_: ()| async { 123 }));
dag.run(|fut| executor.spawn(fut).detach()).await.unwrap();
dag.run(|fut| executor.spawn(fut)).await.unwrap();
assert_eq!(dag.get(x).unwrap(), 123);
});
}
@@ -360,7 +329,9 @@ fn test_all_runtimes_compatibility() {
pollster::block_on(async {
let dag = DagRunner::new();
let x = dag.add_task(task_fn(|_: ()| async { 123 }));
dag.run(futures_executor::block_on).await.unwrap();
dag.run(|fut| async { pollster::block_on(fut) })
.await
.unwrap();
assert_eq!(dag.get(x).unwrap(), 123);
});
}
@@ -370,8 +341,10 @@ fn test_all_runtimes_compatibility() {
futures_executor::block_on(async {
let dag = DagRunner::new();
let x = dag.add_task(task_fn(|_: ()| async { 123 }));
let pool = futures_executor::ThreadPool::new().unwrap();
dag.run(|fut| pool.spawn_ok(fut)).await.unwrap();
let spawner = ThreadPool::new().unwrap();
dag.run(|fut| spawner.spawn_with_handle(fut).unwrap())
.await
.unwrap();
std::thread::sleep(std::time::Duration::from_millis(10));
assert_eq!(dag.get(x).unwrap(), 123);
});
@@ -393,11 +366,7 @@ fn test_runtime_edge_cases() {
42
}));
dag.run(|fut| {
executor.spawn(fut).detach();
})
.await
.unwrap();
dag.run(|fut| executor.spawn(fut)).await.unwrap();
assert_eq!(dag.get(x).unwrap(), 42);
});
@@ -424,11 +393,7 @@ fn test_async_executor_stress() {
})
.collect();
dag.run(|fut| {
executor.spawn(fut).detach();
})
.await
.unwrap();
dag.run(|fut| executor.spawn(fut)).await.unwrap();
for (i, task) in tasks.iter().enumerate() {
assert_eq!(dag.get(task).unwrap(), i as i32);
@@ -443,11 +408,7 @@ fn test_pollster_nested_blocks() {
let dag1 = DagRunner::new();
let x = dag1.add_task(task_fn(|_: ()| async { 10 }));
dag1.run(|fut| {
pollster::block_on(async {
futures_executor::block_on(fut);
});
})
dag1.run(|fut| async { pollster::block_on(fut) })
.await
.unwrap();
@@ -459,17 +420,14 @@ fn test_pollster_nested_blocks() {
fn test_futures_executor_multi_pool() {
// Test with multiple thread pools
let pool1 = futures_executor::ThreadPool::new().unwrap();
let pool2 = futures_executor::ThreadPool::new().unwrap();
let spawner = pool1.clone();
pool1.spawn_ok(async move {
let dag = DagRunner::new();
let x = dag.add_task(task_fn(|_: ()| async { 50 }));
dag.run(|fut| {
// Spawn on different pool
pool2.spawn_ok(fut);
})
dag.run(|fut| spawner.spawn_with_handle(fut).unwrap())
.await
.unwrap();
@@ -507,11 +465,7 @@ fn test_sync_tasks_on_async_executor() {
let y = dag.add_task(task_fn(|_: ()| async { 7 }));
let sum = dag.add_task(SyncAdd).depends_on((&x, &y));
dag.run(|fut| {
executor.spawn(fut).detach();
})
.await
.unwrap();
dag.run(|fut| executor.spawn(fut)).await.unwrap();
assert_eq!(dag.get(sum).unwrap(), 12);
});
@@ -536,9 +490,7 @@ fn test_sync_tasks_on_pollster() {
let x = dag.add_task(task_fn(|_: ()| async { 21 }));
let doubled = dag.add_task(SyncDouble).depends_on(&x);
dag.run(|fut| {
futures_executor::block_on(fut);
})
dag.run(|fut| async { pollster::block_on(fut) })
.await
.unwrap();
+10 -4
View File
@@ -1,7 +1,7 @@
//! Runtime compatibility tests for smol
use dagx::{DagRunner, Task};
use crate::common::task_fn;
use dagx::{DagRunner, Task};
struct Value(i32);
#[dagx::task]
@@ -28,7 +28,9 @@ fn test_basic_dag_smol() {
let y = dag.add_task(Value(3));
let sum = dag.add_task(Add).depends_on((&x, &y));
dag.run(|fut| { smol::spawn(fut).detach(); }).await.unwrap();
dag.run(|fut| smol::spawn(fut).map(Result::unwrap))
.await
.unwrap();
assert_eq!(dag.get(sum).unwrap(), 5);
});
@@ -43,7 +45,9 @@ fn test_parallel_execution_smol() {
.map(|i| dag.add_task(task_fn(move |_: ()| async move { i * 2 })))
.collect();
dag.run(|fut| { smol::spawn(fut).detach(); }).await.unwrap();
dag.run(|fut| smol::spawn(fut).map(Result::unwrap))
.await
.unwrap();
for (i, task) in tasks.iter().enumerate() {
assert_eq!(dag.get(task).unwrap(), i * 2);
@@ -63,7 +67,9 @@ fn test_complex_dependencies_smol() {
.add_task(task_fn(|x: i32| async move { x * 2 }))
.depends_on(sum);
dag.run(|fut| { smol::spawn(fut).detach(); }).await.unwrap();
dag.run(|fut| smol::spawn(fut).map(Result::unwrap))
.await
.unwrap();
assert_eq!(dag.get(double).unwrap(), 60);
});
+4 -9
View File
@@ -3,6 +3,7 @@ use crate::common::task_fn;
// Comprehensive test showing all three patterns working together
use dagx::*;
use futures::FutureExt;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
@@ -52,9 +53,7 @@ async fn test_all_three_task_patterns() {
// Pattern 3: Mutable accumulation
let accumulated = dag.add_task(MutableAccumulator(10)).depends_on(doubled); // 10 + 16 = 26
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -97,9 +96,7 @@ async fn test_parallel_execution() {
}
}));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -120,9 +117,7 @@ async fn test_task_fn_with_captured_state() {
.add_task(task_fn(move |x: i32| async move { x * multiplier }))
.depends_on(&base);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+4 -9
View File
@@ -3,6 +3,7 @@ use crate::common::task_fn;
// These tests verify the read-only state pattern using &self
use dagx::*;
use futures::FutureExt;
// Read-only state tasks
struct ReadOnlyValue(i32);
@@ -42,9 +43,7 @@ async fn test_readonly_state_basic() {
let value = dag.add_task(ReadOnlyValue(42));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
assert_eq!(dag.get(value).unwrap(), 42);
@@ -58,9 +57,7 @@ async fn test_readonly_state_with_input() {
let input = dag.add_task(task_fn(|_: ()| async { 5 }));
let result = dag.add_task(ReadOnlyMultiplier(7)).depends_on(&input);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
assert_eq!(dag.get(result).unwrap(), 35);
@@ -87,9 +84,7 @@ async fn test_readonly_state_multiple_uses() {
};
let result2 = dag.add_task(config2).depends_on(&input2);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
assert_eq!(dag.get(result1).unwrap(), 35); // (10 * 3) + 5
+9 -24
View File
@@ -1,5 +1,6 @@
// Stateless task pattern tests
use crate::common::task_fn;
use futures::FutureExt;
use dagx::*;
@@ -85,9 +86,7 @@ async fn test_stateless_basic() {
let y = dag.add_task(task_fn(|_: ()| async { 20 }));
let sum = dag.add_task(StatelessAdd).depends_on((&x, &y));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
assert_eq!(dag.get(sum).unwrap(), 30);
@@ -101,9 +100,7 @@ async fn test_stateless_single_input() {
let input = dag.add_task(task_fn(|_: ()| async { 21 }));
let doubled = dag.add_task(StatelessDouble).depends_on(&input);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
assert_eq!(dag.get(doubled).unwrap(), 42);
@@ -116,9 +113,7 @@ async fn test_stateless_no_input() {
let source = dag.add_task(StatelessSource);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
assert_eq!(dag.get(source).unwrap(), 42);
@@ -137,9 +132,7 @@ async fn test_stateless_multiple_inputs() {
.add_task(StatelessFormat)
.depends_on((&num, &text, &flag));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
assert_eq!(dag.get(result).unwrap(), "test: 42 (true)");
@@ -178,9 +171,7 @@ async fn test_stateless_mixed_with_stateful() {
// Stateful counter
let counted = dag.add_task(Counter::new(10)).depends_on(product);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -198,9 +189,7 @@ async fn test_stateless_chain() {
let step2 = dag.add_task(StatelessInc).depends_on(step1);
let step3 = dag.add_task(StatelessInc).depends_on(step2);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -221,9 +210,7 @@ async fn test_stateless_parallel() {
})
.collect();
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -244,9 +231,7 @@ async fn test_stateless_with_string_types() {
let greeting = dag.add_task(StatelessConcat).depends_on((&hello, &world));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+8 -28
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::{DagResult, DagRunner};
use futures::FutureExt;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use tokio::sync::RwLock;
@@ -23,10 +24,7 @@ async fn test_concurrent_result_retrieval() -> DagResult<()> {
.collect();
// Execute the DAG
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Now try to get results concurrently from multiple threads
let mut get_handles = Vec::new();
@@ -77,10 +75,7 @@ async fn test_simultaneous_dag_building_and_execution() -> DagResult<()> {
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
// Execute the DAG
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Get the additional tasks
let additional_tasks = build_handle.await.unwrap();
@@ -94,10 +89,7 @@ async fn test_simultaneous_dag_building_and_execution() -> DagResult<()> {
// (they need another run cycle)
if building_complete.load(Ordering::SeqCst) {
// Run again for the new tasks
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Now they should have results
for (i, task) in additional_tasks.iter().enumerate() {
@@ -141,10 +133,7 @@ async fn test_concurrent_dependency_resolution() -> DagResult<()> {
})
.collect();
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Check all completed
assert_eq!(resolution_counter.load(Ordering::SeqCst), 101);
@@ -198,10 +187,7 @@ async fn test_read_write_race_patterns() -> DagResult<()> {
})
.collect();
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Verify readers saw their writer's data
for reader in &readers {
@@ -257,10 +243,7 @@ async fn test_atomic_counter_races() -> DagResult<()> {
})
.collect();
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Final counter should be 200 * 100 = 20000
assert_eq!(counter.load(Ordering::SeqCst), 20000);
@@ -296,10 +279,7 @@ async fn test_multiple_dag_runners_interaction() -> DagResult<()> {
})
.collect();
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Collect results
let mut results = Vec::new();
+5 -16
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::{DagResult, DagRunner};
use futures::FutureExt;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use tokio::sync::Barrier;
@@ -66,10 +67,7 @@ async fn test_forced_interleaving_with_barriers() -> DagResult<()> {
))
.depends_on((&t1, &t2, &t3));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(collector)?, 6);
@@ -168,10 +166,7 @@ async fn test_alternating_execution_pattern() -> DagResult<()> {
.depends_on(chain_b);
}
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Check alternating pattern
let event_list = events.lock().clone();
@@ -239,10 +234,7 @@ async fn test_layered_interleaving() -> DagResult<()> {
}))
.depends_on((&layer2[0], &layer2[1]));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
// Sum of 0+1+2+3 = 6
assert_eq!(dag.get(layer3)?, 6);
@@ -335,10 +327,7 @@ async fn test_cross_branch_synchronization() -> DagResult<()> {
.add_task(task_fn(|(a, b): (i32, i32)| async move { a + b }))
.depends_on((&a2, &b2));
dag.run(|fut| {
tokio::spawn(fut);
})
.await?;
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
assert_eq!(dag.get(merge)?, 13); // 2 + 11
+4 -12
View File
@@ -44,9 +44,7 @@ async fn test_tracing_with_subscriber() {
let b = dag.add_task(Value(3));
let sum = dag.add_task(Add).depends_on((&a, &b));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -69,9 +67,7 @@ async fn test_tracing_with_complex_dag() {
let right = dag.add_task(Multiply).depends_on((&source, &source));
let sink = dag.add_task(Add).depends_on((&left, &right));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -97,9 +93,7 @@ async fn test_tracing_inline_execution() {
let b = dag.add_task(Add).depends_on((&a, &a));
let c = dag.add_task(Multiply).depends_on((&b, &b));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -133,9 +127,7 @@ async fn test_tracing_multiple_layers() {
// Layer 2
let final_sum = dag.add_task(Add).depends_on((&sum12, &mul23));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+3 -6
View File
@@ -1,6 +1,7 @@
//! Tests that the library works correctly without tracing feature
use dagx::{task, DagRunner, Task};
use futures::FutureExt;
struct Value(i32);
@@ -28,9 +29,7 @@ async fn test_basic_dag_without_tracing() {
let b = dag.add_task(Value(3));
let sum = dag.add_task(Add).depends_on((&a, &b));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -51,9 +50,7 @@ async fn test_complex_dag_without_tracing() {
let sum34 = dag.add_task(Add).depends_on((&v3, &v4));
let final_sum = dag.add_task(Add).depends_on((&sum12, &sum34));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+13 -36
View File
@@ -2,6 +2,7 @@
use crate::common::task_fn;
use dagx::*;
use futures::FutureExt;
#[tokio::test]
async fn test_complex_type_safety() {
@@ -21,9 +22,7 @@ async fn test_complex_type_safety() {
}
}));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -47,9 +46,7 @@ async fn test_generic_type_safety() {
vec!["a".to_string(), "b".to_string()]
}));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -69,9 +66,7 @@ async fn test_option_result_type_safety() {
// Task returning Result<i32, String>
let result_task = dag.add_task(task_fn(|_: ()| async { Ok::<i32, String>(42) }));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -94,9 +89,7 @@ async fn test_zero_sized_type_safety() {
let zst1_task = dag.add_task(task_fn(|_: ()| async { Zst1 }));
let _zst2_task = dag.add_task(task_fn(|_: ()| async { Zst2 }));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -117,9 +110,7 @@ async fn test_type_safety_with_many_types() {
let string_task = dag.add_task(task_fn(|_: ()| async { "42".to_string() }));
let bool_task = dag.add_task(task_fn(|_: ()| async { true }));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -145,9 +136,7 @@ async fn test_type_safety_single_dependency() {
.add_task(task_fn(|x: i32| async move { x * 2 }))
.depends_on(&source);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
assert_eq!(dag.get(transform).unwrap(), 84);
@@ -169,9 +158,7 @@ async fn test_type_safety_tuple_dependencies() {
}))
.depends_on((&a, &b, &c));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
assert_eq!(dag.get(combine).unwrap(), "hello: 10 (true)");
@@ -186,9 +173,7 @@ async fn test_type_safety_different_output_types() {
let str_source = dag.add_task(task_fn(|_: ()| async { "test".to_string() }));
let vec_source = dag.add_task(task_fn(|_: ()| async { vec![1, 2, 3] }));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -214,9 +199,7 @@ async fn test_type_safety_complex_types() {
vec![Some(1), None, Some(2), Some(3)]
}));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -245,9 +228,7 @@ async fn test_type_safety_with_generics() {
))
.depends_on(&source);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -261,9 +242,7 @@ async fn test_type_safety_with_generics() {
))
.depends_on(&source2);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
assert_eq!(dag.get(transform2).unwrap(), None);
@@ -285,9 +264,7 @@ async fn test_large_tuple_input() {
))
.depends_on((&a, &b, &c, &d, &e));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
+4 -9
View File
@@ -1,5 +1,6 @@
// Type safety tests for TaskHandle
use crate::common::task_fn;
use futures::FutureExt;
// These tests verify dagx's compile-time type safety guarantees.
// Tests cover both runtime behavior (wrong types return None) and
// compile-time checking (type mismatches fail to compile).
@@ -15,9 +16,7 @@ async fn test_get_with_wrong_type_returns_none() {
let int_task = dag.add_task(task_fn(|_: ()| async { 42i32 }));
// Run DAG
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -38,9 +37,7 @@ async fn test_handle_preserves_type() {
let string_task = dag.add_task(task_fn(|_: ()| async { "hello".to_string() }));
let int_task = dag.add_task(task_fn(|_: ()| async { 42i32 }));
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();
@@ -66,9 +63,7 @@ async fn test_type_safety_handle_reuse() {
.add_task(task_fn(|x: i32| async move { x * 3 }))
.depends_on(&source);
dag.run(|fut| {
tokio::spawn(fut);
})
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap))
.await
.unwrap();