diff --git a/README.md b/README.md index 4fb48b9..704fa6c 100644 --- a/README.md +++ b/README.md @@ -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); diff --git a/benches/basic/execution.rs b/benches/basic/execution.rs index e6d6326..e7dafcb 100644 --- a/benches/basic/execution.rs +++ b/benches/basic/execution.rs @@ -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,11 +14,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); }) }); }); @@ -29,11 +28,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); }) }); }); @@ -111,11 +108,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); }) }); }); diff --git a/benches/basic/scaling.rs b/benches/basic/scaling.rs index b7f8719..7fd1501 100644 --- a/benches/basic/scaling.rs +++ b/benches/basic/scaling.rs @@ -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,11 +19,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); }) }); }); diff --git a/benches/comparisons/deep_chain.rs b/benches/comparisons/deep_chain.rs index 428a5e8..a5993ab 100644 --- a/benches/comparisons/deep_chain.rs +++ b/benches/comparisons/deep_chain.rs @@ -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,11 +28,9 @@ pub fn bench_deep_chain(c: &mut Criterion) { .depends_on(prev); } - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); dag.get(prev).unwrap() }) }); diff --git a/benches/comparisons/diamond_pattern.rs b/benches/comparisons/diamond_pattern.rs index 509065a..c7c2e52 100644 --- a/benches/comparisons/diamond_pattern.rs +++ b/benches/comparisons/diamond_pattern.rs @@ -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,11 +28,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); dag.get(d).unwrap() }) }); diff --git a/benches/comparisons/etl_pipeline.rs b/benches/comparisons/etl_pipeline.rs index fa068f3..73bcf5e 100644 --- a/benches/comparisons/etl_pipeline.rs +++ b/benches/comparisons/etl_pipeline.rs @@ -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,11 +67,9 @@ pub fn bench_etl_pipeline(c: &mut Criterion) { })) .depends_on(validate); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); dag.get(load).unwrap() }) }); diff --git a/benches/comparisons/large_scale.rs b/benches/comparisons/large_scale.rs index ef531a3..dd76cc0 100644 --- a/benches/comparisons/large_scale.rs +++ b/benches/comparisons/large_scale.rs @@ -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,11 +22,9 @@ pub fn bench_large_scale(c: &mut Criterion) { dag.add_task(task_fn(move |_: ()| async move { i * 2 })); } - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); }) }); }); diff --git a/benches/comparisons/linear_pipeline.rs b/benches/comparisons/linear_pipeline.rs index bf8ffab..9df20c5 100644 --- a/benches/comparisons/linear_pipeline.rs +++ b/benches/comparisons/linear_pipeline.rs @@ -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,11 +31,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); dag.get(e).unwrap() }) }); diff --git a/benches/comparisons/wide_fanout.rs b/benches/comparisons/wide_fanout.rs index 67b4ee9..c80f063 100644 --- a/benches/comparisons/wide_fanout.rs +++ b/benches/comparisons/wide_fanout.rs @@ -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,11 +24,9 @@ pub fn bench_wide_fanout(c: &mut Criterion) { .depends_on(&source); } - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); }) }); }); diff --git a/benches/patterns/breakdown.rs b/benches/patterns/breakdown.rs index 7c59a6b..d887449 100644 --- a/benches/patterns/breakdown.rs +++ b/benches/patterns/breakdown.rs @@ -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,11 +27,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); }) }); }); @@ -45,11 +44,9 @@ pub fn bench_10k_breakdown(c: &mut Criterion) { b.iter(|| { rt.block_on(async { - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); }) }); }); diff --git a/benches/patterns/diamond.rs b/benches/patterns/diamond.rs index 4e32441..f129cc1 100644 --- a/benches/patterns/diamond.rs +++ b/benches/patterns/diamond.rs @@ -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,11 +26,9 @@ pub fn bench_diamond(c: &mut Criterion) { .depends_on((&left, &right)); } - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); }) }); }); diff --git a/benches/patterns/fanout.rs b/benches/patterns/fanout.rs index 6cf4848..7a06f4e 100644 --- a/benches/patterns/fanout.rs +++ b/benches/patterns/fanout.rs @@ -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,11 +19,9 @@ pub fn bench_fanout(c: &mut Criterion) { .depends_on(&source); } - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); }) }); }); @@ -45,11 +44,9 @@ pub fn bench_fanout(c: &mut Criterion) { .depends_on(&source); } - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); }) }); }); @@ -72,11 +69,9 @@ pub fn bench_fanout(c: &mut Criterion) { .depends_on(&source); } - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); }) }); }); diff --git a/benches/realistic/config_broadcast.rs b/benches/realistic/config_broadcast.rs index e3cc5e6..2bce80f 100644 --- a/benches/realistic/config_broadcast.rs +++ b/benches/realistic/config_broadcast.rs @@ -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,11 +52,9 @@ pub fn bench_config_broadcast(c: &mut Criterion) { .depends_on(&config); } - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); }) }); }, diff --git a/benches/realistic/etl.rs b/benches/realistic/etl.rs index 3de4a3f..d0a39b8 100644 --- a/benches/realistic/etl.rs +++ b/benches/realistic/etl.rs @@ -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(); }) diff --git a/benches/realistic/memory_efficiency.rs b/benches/realistic/memory_efficiency.rs index a5c6345..1d6a548 100644 --- a/benches/realistic/memory_efficiency.rs +++ b/benches/realistic/memory_efficiency.rs @@ -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,11 +39,9 @@ pub fn bench_memory_efficiency(c: &mut Criterion) { .depends_on(&source); } - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); }) }); }, diff --git a/benches/realistic/mixed.rs b/benches/realistic/mixed.rs index 1561ffa..c1110c1 100644 --- a/benches/realistic/mixed.rs +++ b/benches/realistic/mixed.rs @@ -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,11 +51,9 @@ pub fn bench_mixed_patterns(c: &mut Criterion) { .depends_on(summary); } - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); }) }); }); diff --git a/docs/COMPARISONS.md b/docs/COMPARISONS.md index 2c53ccc..0386632 100644 --- a/docs/COMPARISONS.md +++ b/docs/COMPARISONS.md @@ -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 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) diff --git a/docs/CYCLE_PREVENTION.md b/docs/CYCLE_PREVENTION.md index 839609c..1e26b9c 100644 --- a/docs/CYCLE_PREVENTION.md +++ b/docs/CYCLE_PREVENTION.md @@ -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:** diff --git a/examples/01_basic.rs b/examples/01_basic.rs index f58d42f..6e9ec5d 100644 --- a/examples/01_basic.rs +++ b/examples/01_basic.rs @@ -31,6 +31,7 @@ //! ``` use dagx::{task, DagRunner, Task}; +use futures::FutureExt; // Step 1: Define a source task (no dependencies) // @@ -105,11 +106,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Step 7: Retrieve results // diff --git a/examples/02_fan_out.rs b/examples/02_fan_out.rs index 838d410..d7b9569 100644 --- a/examples/02_fan_out.rs +++ b/examples/02_fan_out.rs @@ -56,6 +56,7 @@ //! ``` use dagx::{task, DagRunner, Task}; +use futures::FutureExt; // Source task with a value field struct Source { @@ -140,11 +141,9 @@ async fn main() { // Run the DAG println!("Running fan-out DAG...\n"); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Print all results println!("\nResults:"); diff --git a/examples/03_fan_in.rs b/examples/03_fan_in.rs index 8d9d9d0..5390fc5 100644 --- a/examples/03_fan_in.rs +++ b/examples/03_fan_in.rs @@ -62,6 +62,7 @@ //! ``` use dagx::{task, DagRunner, Task}; +use futures::FutureExt; struct FetchName; @@ -133,11 +134,9 @@ async fn main() { // Run the DAG println!("Running fan-in DAG...\n"); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Print result println!("\n{}", dag.get(profile).unwrap()); diff --git a/examples/04_parallel_computation.rs b/examples/04_parallel_computation.rs index db03e6b..7329591 100644 --- a/examples/04_parallel_computation.rs +++ b/examples/04_parallel_computation.rs @@ -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(); diff --git a/examples/circuit_breaker.rs b/examples/circuit_breaker.rs index 8fb8938..23b5d1a 100644 --- a/examples/circuit_breaker.rs +++ b/examples/circuit_breaker.rs @@ -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,11 +390,9 @@ async fn main() { false, // success )); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); println!("Result 1: {:?}\n", dag.get(call1).unwrap()); @@ -405,11 +404,9 @@ async fn main() { true, // fail )); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); println!("Result 2: {:?}\n", dag.get(call2).unwrap()); @@ -421,11 +418,9 @@ async fn main() { true, // fail )); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); println!("Result 3: {:?}\n", dag.get(call3).unwrap()); @@ -437,11 +432,9 @@ async fn main() { true, // fail )); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); println!("Result 4: {:?}\n", dag.get(call4).unwrap()); @@ -457,11 +450,9 @@ async fn main() { .add_task(FallbackService::new("ServiceCall-5")) .depends_on(&call5); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); println!("Result 5: {:?}\n", dag.get(fallback5).unwrap()); @@ -478,11 +469,9 @@ async fn main() { false, // success )); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); println!("Result 6: {:?}\n", dag.get(call6).unwrap()); @@ -494,11 +483,9 @@ async fn main() { false, // success )); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); println!("Result 7: {:?}\n", dag.get(call7).unwrap()); } diff --git a/examples/complex_dag.rs b/examples/complex_dag.rs index 339a71a..5bc4d18 100644 --- a/examples/complex_dag.rs +++ b/examples/complex_dag.rs @@ -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)?); diff --git a/examples/conditional_workflow.rs b/examples/conditional_workflow.rs index 9d1d1e3..5d719ac 100644 --- a/examples/conditional_workflow.rs +++ b/examples/conditional_workflow.rs @@ -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)?); diff --git a/examples/custom_types.rs b/examples/custom_types.rs index f98914b..35f33ed 100644 --- a/examples/custom_types.rs +++ b/examples/custom_types.rs @@ -37,6 +37,7 @@ //! ``` use dagx::{task, DagRunner, Task}; +use futures::FutureExt; // Custom type - just derive Clone! #[derive(Clone, Debug)] @@ -107,11 +108,9 @@ async fn main() { let validation = dag.add_task(ValidateUser).depends_on(&user_task); // Execute the DAG - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Print results println!("Greeting: {}", dag.get(greeting).unwrap()); diff --git a/examples/data_pipeline.rs b/examples/data_pipeline.rs index 0c27fe8..d96798e 100644 --- a/examples/data_pipeline.rs +++ b/examples/data_pipeline.rs @@ -76,6 +76,7 @@ //! ``` use dagx::{task, DagRunner, Task}; +use futures::FutureExt; // Data source tasks struct LoadValue1; @@ -220,11 +221,9 @@ async fn main() { // Execute the pipeline println!("Executing pipeline...\n"); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Print final report println!("{}", dag.get(report).unwrap()); diff --git a/examples/debug_tracing.rs b/examples/debug_tracing.rs index 119d8c9..126a620 100644 --- a/examples/debug_tracing.rs +++ b/examples/debug_tracing.rs @@ -115,6 +115,7 @@ //! ``` use dagx::{task, DagRunner, Task}; +use futures::FutureExt; use std::time::Instant; use tokio::time::{sleep, Duration}; @@ -400,11 +401,9 @@ async fn main() { .depends_on((&inventory, &payment, &shipping)); // Run the DAG - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Print summary dag.get(summary).unwrap().print(); @@ -442,11 +441,9 @@ async fn main() { true, // will fail )); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Check results match dag.get(payment).unwrap() { diff --git a/examples/error_handling.rs b/examples/error_handling.rs index 158cbab..6b0e272 100644 --- a/examples/error_handling.rs +++ b/examples/error_handling.rs @@ -115,6 +115,7 @@ //! ``` use dagx::{task, DagRunner, Task}; +use futures::FutureExt; // Source task that provides a string input struct StringSource(&'static str); @@ -222,11 +223,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); match dag.get(logged).unwrap() { Ok(result) => println!("Final result: {}\n", result), @@ -250,11 +249,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); match dag.get(logged).unwrap() { Ok(result) => println!("Final result: {}\n", result), @@ -278,11 +275,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); match dag.get(logged).unwrap() { Ok(result) => println!("Final result: {}\n", result), @@ -303,11 +298,9 @@ async fn main() { .add_task(WithFallback { fallback: 0 }) .depends_on(parsed); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); let result = dag.get(with_fallback).unwrap(); println!("Recovered with fallback: {}\n", result); diff --git a/examples/large_dag.rs b/examples/large_dag.rs index b6ba2f7..d0baec5 100644 --- a/examples/large_dag.rs +++ b/examples/large_dag.rs @@ -110,6 +110,7 @@ //! ``` use dagx::{task, DagRunner, Task}; +use futures::FutureExt; use std::time::Instant; // Simple computation task @@ -181,11 +182,9 @@ async fn main() { println!(" Build time: {:?}", build_time); let exec_start = Instant::now(); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); let exec_time = exec_start.elapsed(); println!(" Execution time: {:?}", exec_time); @@ -223,11 +222,9 @@ async fn main() { println!(" Build time: {:?}", build_time); let exec_start = Instant::now(); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); let exec_time = exec_start.elapsed(); println!(" Execution time: {:?}", exec_time); @@ -276,11 +273,9 @@ async fn main() { println!(" Tree depth: {}", level); let exec_start = Instant::now(); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); let exec_time = exec_start.elapsed(); println!(" Execution time: {:?}", exec_time); @@ -336,11 +331,9 @@ async fn main() { println!(" Build time: {:?}", build_time); let exec_start = Instant::now(); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); let exec_time = exec_start.elapsed(); println!(" Execution time: {:?}", exec_time); @@ -362,11 +355,9 @@ async fn main() { .map(|i| dag.add_task(FormatNumber { id: i })) .collect(); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Verify results are accessible let i32_sum: i32 = small_tasks diff --git a/examples/parallelism_proof.rs b/examples/parallelism_proof.rs index 1be2ba8..8c62fd2 100644 --- a/examples/parallelism_proof.rs +++ b/examples/parallelism_proof.rs @@ -48,6 +48,7 @@ //! ``` use dagx::{task, DagRunner, Task}; +use futures::FutureExt; use std::time::{Duration, Instant}; use tokio::time::sleep; @@ -86,11 +87,9 @@ async fn main() { // Measure execution time let start = Instant::now(); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); let elapsed = start.elapsed(); // Verify all tasks completed correctly diff --git a/examples/retry_strategies.rs b/examples/retry_strategies.rs index 278ad42..05d9510 100644 --- a/examples/retry_strategies.rs +++ b/examples/retry_strategies.rs @@ -153,6 +153,7 @@ //! ``` use dagx::{task, DagRunner, Task}; +use futures::FutureExt; use tokio::time::{sleep, Duration}; // Retry strategy configuration @@ -355,11 +356,9 @@ async fn main() { ], )); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); println!("Result: {:?}\n", dag.get(api_call).unwrap()); } @@ -379,11 +378,9 @@ async fn main() { ], )); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); println!("Result: {:?}\n", dag.get(api_call).unwrap()); } @@ -407,11 +404,9 @@ async fn main() { ], )); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); println!("Result: {:?}\n", dag.get(api_call).unwrap()); } @@ -428,11 +423,9 @@ async fn main() { vec![ApiError::AuthenticationFailed], )); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); println!("Result: {:?}\n", dag.get(api_call).unwrap()); } @@ -457,11 +450,9 @@ async fn main() { ], )); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); println!("Result: {:?}\n", dag.get(api_call).unwrap()); } diff --git a/examples/timeout.rs b/examples/timeout.rs index 5f5c246..7f68205 100644 --- a/examples/timeout.rs +++ b/examples/timeout.rs @@ -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,11 +178,9 @@ async fn main() { timeout_ms: 500, }); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); println!("Fast task: {:?}", dag.get(fast).unwrap()); println!("Timed task: {:?}\n", dag.get(timed).unwrap()); @@ -199,11 +198,9 @@ async fn main() { let processed = dag.add_task(ProcessResult).depends_on(&timed); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); println!("Result: {}\n", dag.get(processed).unwrap()); } @@ -228,11 +225,9 @@ async fn main() { timeout_ms: 200, }); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); println!("Fast task 1: {:?}", dag.get(fast1).unwrap()); println!("Fast task 2: {:?}", dag.get(fast2).unwrap()); @@ -254,11 +249,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); println!("Processed 1: {}", dag.get(processed1).unwrap()); println!("Processed 2: {}\n", dag.get(processed2).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 { diff --git a/examples/tracing_example.rs b/examples/tracing_example.rs index 87924eb..30bc713 100644 --- a/examples/tracing_example.rs +++ b/examples/tracing_example.rs @@ -34,6 +34,7 @@ //! use dagx::{task, DagRunner, Task}; +use futures::FutureExt; use tracing_subscriber::{fmt, EnvFilter}; // Simple source task @@ -95,11 +96,9 @@ async fn main() { // Layer 2: Combine results let result = dag.add_task(Add).depends_on((&left, &right)); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); println!("\nResult: {}", dag.get(result).unwrap()); println!("(Expected: 10+10 = 20, 10*10 = 100, 20+100 = 120)\n"); @@ -118,11 +117,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); println!("\nResult: {}", dag.get(d).unwrap()); println!("(Expected: 1+1 = 2, 2*2 = 4, 4+4 = 8)\n"); @@ -149,11 +146,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); println!("\nResult: {}", dag.get(final_result).unwrap()); println!("(Expected: (1+2)+(2+3) = 8, 8+(3+4) = 15)\n"); diff --git a/src/builder.rs b/src/builder.rs index b4e6b40..8818db3 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -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 /// -/// 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(self, deps: D) -> TaskHandle diff --git a/src/error.rs b/src/error.rs index 7cbb01a..8529bec 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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 ) } diff --git a/src/error/tests.rs b/src/error/tests.rs index 64a2b8d..cb2e65a 100644 --- a/src/error/tests.rs +++ b/src/error/tests.rs @@ -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] diff --git a/src/extract/coverage_tests.rs b/src/extract/coverage_tests.rs index 60ea9f8..40768ba 100644 --- a/src/extract/coverage_tests.rs +++ b/src/extract/coverage_tests.rs @@ -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 type mismatch error message - // Create a channel that sends the WRONG type (String instead of Result) - let (tx, rx) = oneshot::channel::>(); - - // 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) // Try to extract as Result - this will fail with type mismatch - let deps = vec![Box::new(rx) as Box]; - let result = Result::::extract_from_channels(deps).await; + let deps = vec![Arc::new("hello") as Arc]; + let result = Result::::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 type mismatch error message - // Create a channel that sends the WRONG type (i32 instead of Option) - let (tx, rx) = oneshot::channel::>(); - - // 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) // Try to extract as Option - this will fail with type mismatch - let deps = vec![Box::new(rx) as Box]; - let result = Option::::extract_from_channels(deps).await; + let deps = vec![Arc::new(42) as Arc]; + let result = Option::::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 type mismatch error message - // Create a channel that sends the WRONG type (String instead of Vec) - let (tx, rx) = oneshot::channel::>(); - - // 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) // Try to extract as Vec - this will fail with type mismatch - let deps = vec![Box::new(rx) as Box]; - let result = Vec::::extract_from_channels(deps).await; + let deps = vec![Arc::new("not a vec".to_string()) as Arc]; + let result = Vec::::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 type mismatch error message - // Create a channel that sends the WRONG type (Vec instead of HashMap) - let (tx, rx) = oneshot::channel::>>(); - - // 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 instead of HashMap) // Try to extract as HashMap - this will fail with type mismatch - let deps = vec![Box::new(rx) as Box]; - let result = HashMap::::extract_from_channels(deps).await; + let deps = vec![Arc::new(vec![1, 2, 3]) as Arc]; + let result = HashMap::::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 type mismatch error message - // Arc is special: it expects Arc> from dependency + // Arc is special: it expects Arc> from channel // Create a dependency that provides Arc instead of Arc> let deps = vec![Arc::new("not double arc".to_string()) as Arc]; // Try to extract as Arc - this will fail with type mismatch - // because it expects Arc> in the dependency + // because it expects Arc> in the channel let result = Arc::::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::>>(); - - tokio::spawn(async move { - let _ = tx.send(Arc::new(Ok("test".to_string()))); - }); - // Try to extract as Result - wrong Ok type - let deps = vec![Box::new(rx) as Box]; - let result = Result::::extract_from_channels(deps).await; + let deps = vec![Arc::new(Ok::<_, String>("test".to_string())) as Arc]; + let result = Result::::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::>>(); - - tokio::spawn(async move { - let _ = tx.send(Arc::new(Err(42))); - }); - // Try to extract as Result - wrong Err type - let deps = vec![Box::new(rx) as Box]; - let result = Result::::extract_from_channels(deps).await; + let deps = vec![Arc::new(Err::(42)) as Arc]; + let result = Result::::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::>>(); - - tokio::spawn(async move { - let _ = tx.send(Arc::new(Some("test".to_string()))); - }); - // Try to extract as Option - wrong inner type - let deps = vec![Box::new(rx) as Box]; - let result = Option::::extract_from_channels(deps).await; + let deps = vec![Arc::new(Some("test".to_string())) as Arc]; + let result = Option::::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::>>(); - - tokio::spawn(async move { - let _ = tx.send(Arc::new(vec!["a".to_string(), "b".to_string()])); - }); - // Try to extract as Vec - wrong element type - let deps = vec![Box::new(rx) as Box]; - let result = Vec::::extract_from_channels(deps).await; + let deps = vec![Arc::new(vec!["a".to_string(), "b".to_string()]) as Arc]; + let result = Vec::::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::>>(); - - tokio::spawn(async move { - let mut map = HashMap::new(); - map.insert(1, "one".to_string()); - let _ = tx.send(Arc::new(map)); - }); - + let mut map = HashMap::new(); + map.insert(1, "one".to_string()); // Try to extract as HashMap - wrong key type - let deps = vec![Box::new(rx) as Box]; - let result = HashMap::::extract_from_channels(deps).await; + let deps = vec![Arc::new(map) as Arc]; + let result = HashMap::::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::>>(); - - tokio::spawn(async move { - let mut map = HashMap::new(); - map.insert("key".to_string(), 42); - let _ = tx.send(Arc::new(map)); - }); - + let mut map = HashMap::new(); + map.insert("key".to_string(), 42); // Try to extract as HashMap - wrong value type - let deps = vec![Box::new(rx) as Box]; - let result = HashMap::::extract_from_channels(deps).await; + let deps = vec![Arc::new(map) as Arc]; + let result = HashMap::::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 but expect Arc - let (tx, rx) = oneshot::channel::>>(); - - tokio::spawn(async move { - let _ = tx.send(Arc::new(Arc::new(42))); - }); - // Try to extract as Arc - wrong inner type - let deps = vec![Box::new(rx) as Box]; - let result = Arc::::extract_from_channels(deps).await; + let deps = vec![Arc::new(Arc::new(Arc::new(42))) as Arc]; + let result = Arc::::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::>(); - - 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]; - let result = i32::extract_from_channels(deps).await; + let deps = vec![Arc::new("not an i32".to_string()) as Arc]; + 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, expect Result, String> - let (tx, rx) = oneshot::channel::>>(); - - tokio::spawn(async move { - let _ = tx.send(Arc::new(Ok("test".to_string()))); - }); - // Try to extract as Result, String> - nested type mismatch - let deps = vec![Box::new(rx) as Box]; - let result = Result::, String>::extract_from_channels(deps).await; + let deps = + vec![Arc::new(Ok::("test".to_string())) as Arc]; + let result = Result::, String>::extract_from_deps(deps).await; assert!(result.is_err()); let err_msg = result.unwrap_err(); diff --git a/src/extract/tests.rs b/src/extract/tests.rs index 3102d59..2967b61 100644 --- a/src/extract/tests.rs +++ b/src/extract/tests.rs @@ -38,11 +38,11 @@ impl TestTaskWithInput { async fn test_extract_result_type_single_dependency_error() { // Test Result with wrong dependency count let deps = vec![ - Box::new(oneshot::channel::>().1) as Box, - Box::new(oneshot::channel::>().1) as Box, + Arc::new(Ok::(5)) as Arc, + Arc::new(Err::("test".to_string())) as Arc, ]; // Two dependencies when expecting 1 - let result = Result::::extract_from_channels(deps).await; + let result = Result::::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 successful extraction - let (tx, rx) = oneshot::channel::>>(); - // Send value in background - tokio::spawn(async move { - let _ = tx.send(Arc::new(Ok(42))); - }); - - let deps = vec![Box::new(rx) as Box]; - let result = Result::::extract_from_channels(deps).await; + let deps = vec![Arc::new(Ok::(42)) as Arc]; + let result = Result::::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 with wrong dependency count let deps = vec![ - Box::new(oneshot::channel::>().1) as Box, - Box::new(oneshot::channel::>().1) as Box, - Box::new(oneshot::channel::>().1) as Box, + Arc::new(None::) as Arc, + Arc::new(None::) as Arc, + Arc::new(None::) as Arc, ]; // Three dependencies when expecting 1 - let result = Option::::extract_from_channels(deps).await; + let result = Option::::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 successful extraction - let (tx, rx) = oneshot::channel::>>(); - - // Send value in background - tokio::spawn(async move { - let _ = tx.send(Arc::new(Some(99))); - }); - - let deps = vec![Box::new(rx) as Box]; - let result = Option::::extract_from_channels(deps).await; + let deps = vec![Arc::new(Some(99)) as Arc]; + let result = Option::::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 with wrong dependency count let deps = vec![]; // Zero dependencies when expecting 1 - let result = Vec::::extract_from_channels(deps).await; + let result = Vec::::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 successful extraction - let (tx, rx) = oneshot::channel::>>(); - - // 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]; - let result = Vec::::extract_from_channels(deps).await; + let deps = vec![Arc::new(vec![1, 2, 3]) as Arc]; + let result = Vec::::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::().1) as Box, - Box::new(oneshot::channel::().1) as Box, + Arc::new(0) as Arc, + Arc::new(0) as Arc, ]; // 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::().1) as Box, - Box::new(oneshot::channel::().1) as Box, + Arc::new(String::default()) as Arc, + Arc::new(String::default()) as Arc, ]; - 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::().1) as Box, - Box::new(oneshot::channel::().1) as Box, + Arc::new(0.0) as Arc, + Arc::new(0.0) as Arc, ]; - 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::().1) as Box, - Box::new(oneshot::channel::().1) as Box, + Arc::new(0) as Arc, + Arc::new(0) as Arc, ]; - 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::>(); - let (tx2, rx2) = oneshot::channel::>(); - - // 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, - Box::new(rx2) as Box, + Arc::new(42) as Arc, + Arc::new(100) as Arc, ]; // 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::>(); - // Explicitly drop tx to close the channel - drop(tx); - - let deps = vec![Box::new(rx) as Box]; - 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]; 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::>(); - - tokio::spawn(async move { - let _ = tx.send(Arc::new($val)); - }); - - let deps = vec![Box::new(rx) as Box]; - let result = <$t>::extract_from_channels(deps).await; + let deps = vec![Arc::new($val) as Arc]; + 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::>(); - let (tx2, rx2) = oneshot::channel::>(); - let (tx3, rx3) = oneshot::channel::>(); - let (tx4, rx4) = oneshot::channel::>(); - let (tx5, rx5) = oneshot::channel::>(); - - // 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, - Box::new(rx2) as Box, - Box::new(rx3) as Box, - Box::new(rx4) as Box, - Box::new(rx5) as Box, + Arc::new(0) as Arc, + Arc::new(1) as Arc, + Arc::new(2) as Arc, + Arc::new(3) as Arc, + Arc::new(4) as Arc, ]; // 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 extraction - let (tx, rx) = oneshot::channel::>>(); - 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]; - let result = Arc::::extract_from_channels(deps).await; + let deps = vec![Arc::new(test_arc) as Arc]; + let result = Arc::::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::>>().1) as Box, - Box::new(oneshot::channel::>>().1) as Box, + Arc::new(HashMap::::default()) as Arc, + Arc::new(HashMap::::default()) as Arc, ]; - let result = HashMap::::extract_from_channels(deps).await; + let result = HashMap::::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::>>(); - drop(tx); - - let deps = vec![Box::new(rx) as Box]; - let result = HashMap::::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]; @@ -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::::extract_from_channels(deps).await; + let result = Arc::::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::>().1) as Box, - Box::new(oneshot::channel::>().1) as Box, - Box::new(oneshot::channel::>().1) as Box, + Arc::new(0) as Arc, + Arc::new(0) as Arc, + Arc::new(0) as Arc, ]; - 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() diff --git a/src/lib.rs b/src/lib.rs index 7495f44..492cc60 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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, 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(); //! # }; //! ``` //! diff --git a/src/node/tests.rs b/src/node/tests.rs index c74a0b2..8f3b351 100644 --- a/src/node/tests.rs +++ b/src/node/tests.rs @@ -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]; @@ -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::().unwrap(); // Also verify output was returned (Arc-wrapped) - let output = result.unwrap(); - let arc_value = output.downcast::().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::>>() - .unwrap(); - - let received_arc = rx.await; - assert!(received_arc.is_ok()); - assert_eq!(*received_arc.unwrap(), 100); - } -} diff --git a/src/runner.rs b/src/runner.rs index 243d9d7..c3b8d6b 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -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); /// # }; diff --git a/src/runner/coverage_tests.rs b/src/runner/coverage_tests.rs index 7ad7bb8..1e6ed6e 100644 --- a/src/runner/coverage_tests.rs +++ b/src/runner/coverage_tests.rs @@ -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,11 +21,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Try to get as String - TYPE MISMATCH! // Create a handle with the same ID but wrong type @@ -56,11 +55,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Try to get as Vec let wrong_handle: TaskHandle> = TaskHandle { @@ -102,11 +99,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Try to get as HashMap let wrong_handle: TaskHandle> = TaskHandle { @@ -136,11 +131,9 @@ async fn test_get_type_mismatch_vec_different_types() { // Task outputs Vec let vec_task = dag.add_task(task_fn(|_: ()| async { vec![1, 2, 3] })); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Try to get as Vec let wrong_handle: TaskHandle> = TaskHandle { @@ -170,11 +163,9 @@ async fn test_get_type_mismatch_option_types() { // Task outputs Option let opt_task = dag.add_task(task_fn(|_: ()| async { Some(42) })); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Try to get as Option let wrong_handle: TaskHandle> = TaskHandle { @@ -206,11 +197,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Try to get int_task as String let wrong_int_handle: TaskHandle = TaskHandle { diff --git a/src/runner/tests.rs b/src/runner/tests.rs index c4d0671..f48e0d6 100644 --- a/src/runner/tests.rs +++ b/src/runner/tests.rs @@ -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,11 +70,9 @@ async fn test_get_wrong_type() { let dag = DagRunner::new(); let handle = dag.add_task(TestTask { value: 42 }); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Try to get with wrong type - downcast will fail let fake_handle: TaskHandle = TaskHandle { @@ -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,11 +199,9 @@ async fn test_multiple_get_calls() { let dag = DagRunner::new(); let handle = dag.add_task(TestTask { value: 100 }); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Multiple get calls should all return the same value let result1 = dag.get(&handle).unwrap(); @@ -233,11 +225,9 @@ async fn test_invalid_node_id_in_get() { _phantom: std::marker::PhantomData, }; - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); let result = dag.get(invalid_handle); assert!(result.is_err()); @@ -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 diff --git a/src/task/tests.rs b/src/task/tests.rs index ee2b74b..901ded5 100644 --- a/src/task/tests.rs +++ b/src/task/tests.rs @@ -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>, + _dependencies: Vec>, ) -> Pin> + Send>> { Box::pin(async move { // This is a test helper - just return an error diff --git a/src/types.rs b/src/types.rs index 7968d60..70a1467 100644 --- a/src/types.rs +++ b/src/types.rs @@ -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); /// # }; diff --git a/src/types/coverage_tests.rs b/src/types/coverage_tests.rs index e5b5dd2..37f4a67 100644 --- a/src/types/coverage_tests.rs +++ b/src/types/coverage_tests.rs @@ -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,11 +32,9 @@ async fn test_unit_type_taskbuilder_to_handle_conversion() { // specifically covering line 71 let handle: TaskHandle = builder.into(); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Verify the task ran and we can get its output assert_eq!(dag.get(handle).unwrap(), 42); @@ -81,11 +80,9 @@ async fn test_unit_type_conversion_with_multiple_tasks() { let second_handle: TaskHandle = second_builder.into(); let third_handle: TaskHandle = third_builder.into(); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Verify all outputs assert_eq!(dag.get(first_handle).unwrap(), "first"); @@ -122,11 +119,9 @@ async fn test_unit_struct_with_state() { // Explicit conversion - triggers line 71 let handle: TaskHandle = builder.into(); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(handle).unwrap(), 42); } @@ -151,11 +146,9 @@ async fn test_taskbuilder_conversion_assignment() { let handle: TaskHandle> = builder.into(); let id = handle.id; - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); let result = dag.get(handle).unwrap(); assert_eq!(result, vec![1, 2, 3]); @@ -197,11 +190,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(source_handle).unwrap(), 10); assert_eq!(dag.get(doubler).unwrap(), 20); diff --git a/tests/arc_tests.rs b/tests/arc_tests.rs index 98d3490..eb21e24 100644 --- a/tests/arc_tests.rs +++ b/tests/arc_tests.rs @@ -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,11 +44,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Note: producer is not a sink (consumer depends on it), so we can't get() its value // Only sink nodes (nodes with no dependents) store their outputs for retrieval @@ -101,11 +100,9 @@ async fn test_arc_with_primitives() { .add_task(AllConsumer) .depends_on((&i32_prod, &f64_prod, &bool_prod)); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Note: producers are not sinks (consumer depends on them), so we can't get() their values // The consumer output verifies all three producers worked correctly @@ -177,11 +174,9 @@ async fn test_arc_with_collections() { .add_task(CollectionConsumer) .depends_on((&vec_prod, &map_prod, &set_prod)); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Note: producers are not sinks (consumer depends on them), so we can't get() their values // The consumer output verifies all three collection producers worked correctly @@ -250,11 +245,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Note: person_prod is not a sink (3 extractors depend on it), so we can't get() its value // The extractor outputs verify the person was created and shared correctly @@ -318,11 +311,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(c1).unwrap(), 5); assert_eq!(dag.get(c2).unwrap(), "item1"); @@ -394,11 +385,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Note: producer is not a sink (has 5 consumers), so we can't get() its value // We verify correctness through the consumer outputs instead @@ -486,11 +475,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Note: l1_sum and l1_prod are not sinks (they have level 2 tasks depending on them) // Only the level 2 tasks are sinks and can be retrieved @@ -556,11 +543,9 @@ async fn test_arc_performance_vs_clone() { let start = Instant::now(); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); let elapsed = start.elapsed(); @@ -658,11 +643,9 @@ async fn test_arc_with_result() { let consumer3 = dag.add_task(ArcResultConsumer2).depends_on(&arc_result_err); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(consumer1).unwrap(), "Got Ok: Success"); assert_eq!(dag.get(consumer2).unwrap(), "Got Arc: Success"); @@ -767,11 +750,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(c1).unwrap(), "Some(42)"); assert_eq!(dag.get(c2).unwrap(), "None"); @@ -824,11 +805,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(bottom).unwrap(), "Sum: 15, Product: 120"); } @@ -894,11 +873,9 @@ async fn test_arc_empty_collections() { .add_task(EmptyConsumer) .depends_on((&vec_task, &string_task, &map_task)); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!( dag.get(consumer).unwrap(), @@ -967,11 +944,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(counter).unwrap(), SIZE); assert!(dag.get(first).unwrap().contains("Item number 0")); @@ -1006,11 +981,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(consumer).unwrap(), "Value: Nested Arc"); } @@ -1072,11 +1045,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!( dag.get(mixed).unwrap(), @@ -1142,11 +1113,9 @@ async fn test_arc_parallel_execution() { let start = Instant::now(); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); let elapsed = start.elapsed(); @@ -1193,11 +1162,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Note: producer is not a sink (checker depends on it), so we can't get() its value // The checker task verifies the Arc was passed correctly @@ -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(); diff --git a/tests/boundaries/deep_chains.rs b/tests/boundaries/deep_chains.rs index 7fc790c..28fb521 100644 --- a/tests/boundaries/deep_chains.rs +++ b/tests/boundaries/deep_chains.rs @@ -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(()) diff --git a/tests/boundaries/large_scale.rs b/tests/boundaries/large_scale.rs index e3b5768..520974d 100644 --- a/tests/boundaries/large_scale.rs +++ b/tests/boundaries/large_scale.rs @@ -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((¤t_layer[0], ¤t_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(()) diff --git a/tests/boundaries/type_limits.rs b/tests/boundaries/type_limits.rs index 7de122c..173aa62 100644 --- a/tests/boundaries/type_limits.rs +++ b/tests/boundaries/type_limits.rs @@ -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)?); diff --git a/tests/boundaries/zero_tasks.rs b/tests/boundaries/zero_tasks.rs index 974cc37..c745ceb 100644 --- a/tests/boundaries/zero_tasks.rs +++ b/tests/boundaries/zero_tasks.rs @@ -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::::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(()) diff --git a/tests/custom_type_test.rs b/tests/custom_type_test.rs index 623d330..f953ca5 100644 --- a/tests/custom_type_test.rs +++ b/tests/custom_type_test.rs @@ -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,11 +57,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); let result = dag.get(process).unwrap(); assert_eq!(result, "Alice is 30 years old"); @@ -74,11 +73,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); let person_result = dag.get(process).unwrap(); let company_result = dag.get(transform).unwrap(); @@ -99,11 +96,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); let result_a = dag.get(path_a).unwrap(); let result_b = dag.get(path_b).unwrap(); @@ -121,11 +116,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); let result1 = dag.get(process1).unwrap(); let result2 = dag.get(process2).unwrap(); diff --git a/tests/cycle_prevention.rs b/tests/cycle_prevention.rs index d6cf1a7..7aab098 100644 --- a/tests/cycle_prevention.rs +++ b/tests/cycle_prevention.rs @@ -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,11 +271,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(result).unwrap(), 40); // (10*2) + (10*2) diff --git a/tests/dependencies/resolution.rs b/tests/dependencies/resolution.rs index 6461e47..c68317b 100644 --- a/tests/dependencies/resolution.rs +++ b/tests/dependencies/resolution.rs @@ -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(()) diff --git a/tests/dependencies/single.rs b/tests/dependencies/single.rs index 26fa742..cff5237 100644 --- a/tests/dependencies/single.rs +++ b/tests/dependencies/single.rs @@ -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); diff --git a/tests/dependencies/tuples.rs b/tests/dependencies/tuples.rs index 513c21d..1126334 100644 --- a/tests/dependencies/tuples.rs +++ b/tests/dependencies/tuples.rs @@ -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(()) diff --git a/tests/errors/error_types.rs b/tests/errors/error_types.rs index 9842286..68e9fc1 100644 --- a/tests/errors/error_types.rs +++ b/tests/errors/error_types.rs @@ -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,11 +15,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(&t1).unwrap(), 42); } @@ -34,11 +33,9 @@ async fn test_nested_result_error_handling() { let task2 = dag.add_task(task_fn(|_: ()| async { Result::::Ok(42) })); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Both tasks complete successfully (they return Results) assert_eq!(dag.get(&task1).unwrap(), Err("Internal error".to_string())); @@ -51,11 +48,9 @@ async fn test_option_none_not_error() { let task = dag.add_task(task_fn(|_: ()| async { None:: })); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // None is a valid result, not an error assert_eq!(dag.get(&task).unwrap(), None); @@ -82,11 +77,9 @@ async fn test_custom_error_types() { }) })); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Check custom errors are preserved match dag.get(&task1).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,11 +144,9 @@ async fn test_anyhow_like_error_chain() { outer })); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Get the chained error let result = dag.get(&task).unwrap(); diff --git a/tests/errors/propagation.rs b/tests/errors/propagation.rs index 3e4a713..be3c01a 100644 --- a/tests/errors/propagation.rs +++ b/tests/errors/propagation.rs @@ -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,11 +215,9 @@ async fn test_selective_error_propagation() { })) .depends_on(handler); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Source returns an Err variant (not a panic) assert_eq!(dag.get(&source).unwrap(), Err("Source error".to_string())); @@ -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,11 +348,9 @@ async fn test_partial_branch_failure_propagation() { )) .depends_on((&a3, &b3, &c3)); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .ok(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .ok(); // Branch A succeeds assert_eq!(dag.get(a3).unwrap(), 106); diff --git a/tests/errors/recovery.rs b/tests/errors/recovery.rs index 33f741d..84e63a5 100644 --- a/tests/errors/recovery.rs +++ b/tests/errors/recovery.rs @@ -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()); diff --git a/tests/execution/basic.rs b/tests/execution/basic.rs index 565a803..f94185d 100644 --- a/tests/execution/basic.rs +++ b/tests/execution/basic.rs @@ -1,23 +1,24 @@ // 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() - .await - .unwrap(); // Should complete without error + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Should complete without error } #[tokio::test] async fn test_single_task() { let dag = DagRunner::new(); let task = dag.add_task(task_fn(|_: ()| async { 42 })); - dag.run() - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(task).unwrap(), 42); } @@ -39,9 +40,9 @@ async fn test_deep_chain() { .add_task(task_fn(|x: i32| async move { x + 1 })) .depends_on(d); - dag.run() - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Note: a, b, c, d are not sinks (they have dependents in the chain) // Only e is a sink and can be retrieved @@ -56,9 +57,9 @@ async fn test_wide_parallel() { .map(|i| dag.add_task(task_fn(move |_: ()| async move { i * 2 }))) .collect(); - dag.run() - .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); @@ -80,9 +81,9 @@ async fn test_diamond_dependency() { .add_task(task_fn(|(x, y): (i32, i32)| async move { x + y })) .depends_on((&b, &c)); - dag.run() - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Note: a, b, and c are not sinks (they have dependents) // Only d is a sink and can be retrieved @@ -108,9 +109,9 @@ async fn test_different_output_types() { } })); - dag.run() - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(&dag.get(string_task).unwrap(), "hello"); assert_eq!(dag.get(vec_task).unwrap(), vec![1, 2, 3]); @@ -141,9 +142,9 @@ async fn test_multiple_sinks() { .add_task(task_fn(|x: i32| async move { x * 3 })) .depends_on(branch2); - dag.run() - .await - .unwrap(); // Should wait for both sinks + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Should wait for both sinks assert_eq!(dag.get(sink1).unwrap(), 21); // (10 * 2) + 1 assert_eq!(dag.get(sink2).unwrap(), 45); // (10 + 5) * 3 @@ -163,9 +164,9 @@ async fn test_single_task_inline_path() { .add_task(task_fn(|x: i32| async move { x + 5 })) .depends_on(t2); - dag.run() - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(t3).unwrap(), 25); // (10 * 2) + 5 } @@ -188,9 +189,9 @@ async fn test_multi_task_spawn_path() { .add_task(task_fn(|x: i32| async move { x + 3 })) .depends_on(&source); - dag.run() - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(t1).unwrap(), 101); assert_eq!(dag.get(t2).unwrap(), 102); @@ -212,9 +213,9 @@ async fn test_producer_consumer_channels() { )) .depends_on(&producer); - dag.run() - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(consumer).unwrap(), 15); } @@ -237,9 +238,9 @@ async fn test_multi_consumer_fanout() { .add_task(task_fn(|x: i32| async move { x - 5 })) .depends_on(&producer); - dag.run() - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(c1).unwrap(), 84); assert_eq!(dag.get(c2).unwrap(), 52); @@ -265,9 +266,9 @@ 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() - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(sink).unwrap(), 5); // (1+1) + (1+2) = 5 } @@ -313,9 +314,9 @@ async fn test_layer_execution_order() { })) .depends_on(t2); - dag.run() - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); let order = execution_order.lock().clone(); assert_eq!(order, vec![1, 2, 3]); diff --git a/tests/execution/examples.rs b/tests/execution/examples.rs index 88e4f04..79b8abb 100644 --- a/tests/execution/examples.rs +++ b/tests/execution/examples.rs @@ -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,11 +13,9 @@ 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); - }) - .await - .unwrap(); // SPEC API with spawner + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // SPEC API with spawner assert_eq!(dag.get(plus1).unwrap(), 11); assert_eq!(dag.get(plus2).unwrap(), 11); @@ -32,11 +31,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(&dag.get(output).unwrap(), "hi:42:true"); } @@ -52,11 +49,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(total).unwrap(), 20); } @@ -76,11 +71,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(&dag.get(upper1).unwrap(), "HELLO"); assert_eq!(&dag.get(upper2).unwrap(), "HELLO"); diff --git a/tests/execution/integration.rs b/tests/execution/integration.rs index 7f539dc..1aa7955 100644 --- a/tests/execution/integration.rs +++ b/tests/execution/integration.rs @@ -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}; @@ -18,7 +19,7 @@ impl LoadValue { #[dagx::task] impl LoadValue { async fn run(&self) -> i32 { - self.value // Read-only state: use &self + self.value // Read-only state: use &self } } @@ -33,7 +34,7 @@ impl Multiply { #[dagx::task] impl Multiply { async fn run(&self, input: &i32) -> i32 { - input * self.factor // Read-only state: use &self + input * self.factor // Read-only state: use &self } } @@ -41,7 +42,7 @@ struct Add; #[dagx::task] impl Add { async fn run(a: &i32, b: &i32) -> i32 { - a + b // Stateless: no self needed + a + b // Stateless: no self needed } } @@ -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 { - sleep(sleep_duration).await; - x + 1 - })).depends_on(¶llel_tasks[0]); + let seq1 = dag + .add_task(task_fn(move |x: i32| async move { + sleep(sleep_duration).await; + x + 1 + })) + .depends_on(¶llel_tasks[0]); - let seq2 = dag.add_task(task_fn(move |x: i32| async move { - sleep(sleep_duration).await; - x + 1 - })).depends_on(seq1); + let seq2 = dag + .add_task(task_fn(move |x: i32| async move { + sleep(sleep_duration).await; + x + 1 + })) + .depends_on(seq1); - let seq3 = dag.add_task(task_fn(move |x: i32| async move { - sleep(sleep_duration).await; - x + 1 - })).depends_on(seq2); + let seq3 = dag + .add_task(task_fn(move |x: i32| async move { + sleep(sleep_duration).await; + x + 1 + })) + .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 @@ -361,4 +368,4 @@ async fn integration_test_parallel_vs_sequential_timing() -> DagResult<()> { ); Ok(()) -} \ No newline at end of file +} diff --git a/tests/execution/results.rs b/tests/execution/results.rs index 269d877..e32f4b3 100644 --- a/tests/execution/results.rs +++ b/tests/execution/results.rs @@ -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,11 +14,9 @@ async fn test_result_type() { Err::("error".to_string()) })); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(success_task).unwrap(), Ok(42)); assert_eq!(dag.get(error_task).unwrap(), Err("error".to_string())); @@ -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,11 +87,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(counter).unwrap(), 15); // 10 + 5 } @@ -117,11 +107,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(scaled).unwrap(), 35); } diff --git a/tests/parallelism/concurrent.rs b/tests/parallelism/concurrent.rs index c0055de..3119e4b 100644 --- a/tests/parallelism/concurrent.rs +++ b/tests/parallelism/concurrent.rs @@ -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,11 +63,9 @@ async fn test_concurrent_execution_and_building() { .collect(); // Execute the initial DAG - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Now try to add more tasks while also reading results let mut handles = JoinSet::new(); @@ -93,11 +92,9 @@ async fn test_concurrent_execution_and_building() { } // Execute again with the new tasks - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); } #[tokio::test] @@ -139,11 +136,9 @@ async fn test_dag_builder_thread_safety() { assert_eq!(counter.load(Ordering::SeqCst), 10); // Execute the combined DAG - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Verify each pipeline computed correctly for task in final_tasks.iter() { @@ -163,11 +158,9 @@ async fn test_concurrent_get_operations() { .map(|i| dag.add_task(task_fn(move |_: ()| async move { i }))) .collect(); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Now access results concurrently let mut handles = JoinSet::new(); @@ -217,11 +210,9 @@ async fn test_no_race_in_dependency_tracking() { } // Execute - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // All dependents should have correct values for dep in dependents.iter() { @@ -277,11 +268,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); } #[tokio::test] @@ -369,11 +358,9 @@ async fn test_atomic_dag_operations() { assert_eq!(deps.len(), 50); // Execute and verify - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); for dep in deps.iter() { let result = dag.get(dep).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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); } diff --git a/tests/parallelism/execution.rs b/tests/parallelism/execution.rs index 8320939..cb63cb8 100644 --- a/tests/parallelism/execution.rs +++ b/tests/parallelism/execution.rs @@ -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"); diff --git a/tests/parallelism/proof.rs b/tests/parallelism/proof.rs index b593239..98a56e3 100644 --- a/tests/parallelism/proof.rs +++ b/tests/parallelism/proof.rs @@ -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 diff --git a/tests/parallelism/spawning.rs b/tests/parallelism/spawning.rs index 634ad45..0bebe59 100644 --- a/tests/parallelism/spawning.rs +++ b/tests/parallelism/spawning.rs @@ -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() { diff --git a/tests/parallelism/threads.rs b/tests/parallelism/threads.rs index b81172a..0b7e5d1 100644 --- a/tests/parallelism/threads.rs +++ b/tests/parallelism/threads.rs @@ -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((¶llel1, ¶llel2)); - 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 diff --git a/tests/parallelism/timing.rs b/tests/parallelism/timing.rs index 52b48c3..600eba6 100644 --- a/tests/parallelism/timing.rs +++ b/tests/parallelism/timing.rs @@ -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); diff --git a/tests/patterns/butterfly.rs b/tests/patterns/butterfly.rs index bec71f3..81aa07a 100644 --- a/tests/patterns/butterfly.rs +++ b/tests/patterns/butterfly.rs @@ -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); diff --git a/tests/patterns/complex.rs b/tests/patterns/complex.rs index 58b92b2..abb6c86 100644 --- a/tests/patterns/complex.rs +++ b/tests/patterns/complex.rs @@ -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); diff --git a/tests/patterns/grid.rs b/tests/patterns/grid.rs index 6c40f18..e15735d 100644 --- a/tests/patterns/grid.rs +++ b/tests/patterns/grid.rs @@ -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 { diff --git a/tests/patterns/tree.rs b/tests/patterns/tree.rs index dc72a28..448b004 100644 --- a/tests/patterns/tree.rs +++ b/tests/patterns/tree.rs @@ -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")); diff --git a/tests/performance/memory.rs b/tests/performance/memory.rs index 62850cd..6528461 100644 --- a/tests/performance/memory.rs +++ b/tests/performance/memory.rs @@ -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,11 +24,9 @@ async fn test_memory_usage_10000_nodes() { println!("Memory for 10k nodes: ~{} bytes", build_memory); // Execute - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); let after_exec = memory_usage_hint(); let exec_memory = after_exec.saturating_sub(after_build); diff --git a/tests/performance/stress.rs b/tests/performance/stress.rs index 2e2579c..1fa63a1 100644 --- a/tests/performance/stress.rs +++ b/tests/performance/stress.rs @@ -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,11 +27,9 @@ async fn test_deep_chain_100_levels() { // Execute let start = Instant::now(); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); let exec_time = start.elapsed(); println!("Executed 100-level chain in {:?}", exec_time); @@ -89,11 +88,9 @@ async fn test_stress_mixed_patterns() { // Execute let start = Instant::now(); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); let exec_time = start.elapsed(); println!("Executed mixed pattern stress test in {:?}", exec_time); diff --git a/tests/runtimes/async_std.rs b/tests/runtimes/async_std.rs index edb55b7..9e45153 100644 --- a/tests/runtimes/async_std.rs +++ b/tests/runtimes/async_std.rs @@ -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); -} \ No newline at end of file +} diff --git a/tests/runtimes/compatibility.rs b/tests/runtimes/compatibility.rs index b3ec541..f77e871 100644 --- a/tests/runtimes/compatibility.rs +++ b/tests/runtimes/compatibility.rs @@ -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(()) diff --git a/tests/runtimes/quirky.rs b/tests/runtimes/quirky.rs index eccc8f7..42ef655 100644 --- a/tests/runtimes/quirky.rs +++ b/tests/runtimes/quirky.rs @@ -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,13 +172,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| async { pollster::block_on(fut) }) + .await + .unwrap(); assert_eq!(dag.get(doubled).unwrap(), 200); }); @@ -217,11 +195,9 @@ fn test_pollster_complex() { }) .collect(); - dag.run(|fut| { - futures_executor::block_on(fut); - }) - .await - .unwrap(); + dag.run(|fut| async { pollster::block_on(fut) }) + .await + .unwrap(); for (i, task) in tasks.iter().enumerate() { assert_eq!(dag.get(task).unwrap(), 7 * i as i32); @@ -237,11 +213,9 @@ fn test_pollster_error_handling() { // Task that returns Result let task = dag.add_task(task_fn(|_: ()| async { Ok::(42) })); - dag.run(|fut| { - futures_executor::block_on(fut); - }) - .await - .unwrap(); + dag.run(|fut| async { pollster::block_on(fut) }) + .await + .unwrap(); assert_eq!(dag.get(task).unwrap(), Ok(42)); }); @@ -262,13 +236,11 @@ 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); - }) - .await - .unwrap(); + let spawner = ThreadPool::new().unwrap(); + + dag.run(|fut| spawner.spawn_with_handle(fut).unwrap()) + .await + .unwrap(); assert_eq!(dag.get(product).unwrap(), 375); // 15 * 25 }); @@ -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,11 +259,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| spawner.spawn_with_handle(fut).unwrap()) + .await + .unwrap(); for (i, task) in tasks.iter().enumerate() { assert_eq!(dag.get(task).unwrap(), (i * i) as i32); @@ -325,12 +295,11 @@ 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); - }) - .await - .unwrap(); + let spawner = ThreadPool::new().unwrap(); + + dag.run(|fut| spawner.spawn_with_handle(fut).unwrap()) + .await + .unwrap(); assert_eq!(dag.get(d).unwrap(), 60); // (8+2) * (8-2) = 10 * 6 }); @@ -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,13 +408,9 @@ 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); - }); - }) - .await - .unwrap(); + dag1.run(|fut| async { pollster::block_on(fut) }) + .await + .unwrap(); assert_eq!(dag1.get(x).unwrap(), 10); }); @@ -459,19 +420,16 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| spawner.spawn_with_handle(fut).unwrap()) + .await + .unwrap(); std::thread::sleep(std::time::Duration::from_millis(50)); assert_eq!(dag.get(x).unwrap(), 50); @@ -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,11 +490,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| async { pollster::block_on(fut) }) + .await + .unwrap(); assert_eq!(dag.get(doubled).unwrap(), 42); }); diff --git a/tests/runtimes/smol.rs b/tests/runtimes/smol.rs index e4e4c21..3f41694 100644 --- a/tests/runtimes/smol.rs +++ b/tests/runtimes/smol.rs @@ -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,8 +67,10 @@ 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); }); -} \ No newline at end of file +} diff --git a/tests/task_patterns/combined.rs b/tests/task_patterns/combined.rs index 2feb4a5..e7126f4 100644 --- a/tests/task_patterns/combined.rs +++ b/tests/task_patterns/combined.rs @@ -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,11 +53,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(sum).unwrap(), 8); assert_eq!(dag.get(doubled).unwrap(), 16); @@ -97,11 +96,9 @@ async fn test_parallel_execution() { } })); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // All three tasks should have run assert_eq!(counter.load(Ordering::SeqCst), 3); @@ -120,11 +117,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(scaled).unwrap(), 35); } diff --git a/tests/task_patterns/readonly.rs b/tests/task_patterns/readonly.rs index 56753b8..f2407b1 100644 --- a/tests/task_patterns/readonly.rs +++ b/tests/task_patterns/readonly.rs @@ -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,11 +43,9 @@ async fn test_readonly_state_basic() { let value = dag.add_task(ReadOnlyValue(42)); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(value).unwrap(), 42); } @@ -58,11 +57,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(result).unwrap(), 35); } @@ -87,11 +84,9 @@ async fn test_readonly_state_multiple_uses() { }; let result2 = dag.add_task(config2).depends_on(&input2); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(result1).unwrap(), 35); // (10 * 3) + 5 assert_eq!(dag.get(result2).unwrap(), 65); // (20 * 3) + 5 } diff --git a/tests/task_patterns/stateless.rs b/tests/task_patterns/stateless.rs index 29a9c55..9b7fc84 100644 --- a/tests/task_patterns/stateless.rs +++ b/tests/task_patterns/stateless.rs @@ -1,5 +1,6 @@ // Stateless task pattern tests use crate::common::task_fn; +use futures::FutureExt; use dagx::*; @@ -85,11 +86,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(sum).unwrap(), 30); } @@ -101,11 +100,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(doubled).unwrap(), 42); } @@ -116,11 +113,9 @@ async fn test_stateless_no_input() { let source = dag.add_task(StatelessSource); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(source).unwrap(), 42); } @@ -137,11 +132,9 @@ async fn test_stateless_multiple_inputs() { .add_task(StatelessFormat) .depends_on((&num, &text, &flag)); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(result).unwrap(), "test: 42 (true)"); } @@ -178,11 +171,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(product).unwrap(), 15); assert_eq!(dag.get(counted).unwrap(), 25); // 10 + 15 @@ -198,11 +189,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(step1).unwrap(), 1); assert_eq!(dag.get(step2).unwrap(), 2); @@ -221,11 +210,9 @@ async fn test_stateless_parallel() { }) .collect(); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(tasks[0]).unwrap(), 1); assert_eq!(dag.get(tasks[1]).unwrap(), 4); @@ -244,11 +231,9 @@ async fn test_stateless_with_string_types() { let greeting = dag.add_task(StatelessConcat).depends_on((&hello, &world)); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(greeting).unwrap(), "Hello, World!"); } diff --git a/tests/timing/concurrent_access.rs b/tests/timing/concurrent_access.rs index 4ff250f..641ea9e 100644 --- a/tests/timing/concurrent_access.rs +++ b/tests/timing/concurrent_access.rs @@ -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(); diff --git a/tests/timing/interleaving.rs b/tests/timing/interleaving.rs index bc74806..4cf8d50 100644 --- a/tests/timing/interleaving.rs +++ b/tests/timing/interleaving.rs @@ -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 diff --git a/tests/tracing/with_tracing.rs b/tests/tracing/with_tracing.rs index 3cb962e..5690824 100644 --- a/tests/tracing/with_tracing.rs +++ b/tests/tracing/with_tracing.rs @@ -44,11 +44,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(sum).unwrap(), 5); } @@ -69,11 +67,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // source = 10 // left = 10 + 10 = 20 @@ -97,11 +93,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // a = 1 // b = 1 + 1 = 2 @@ -133,11 +127,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // v1 = 1, v2 = 2, v3 = 3 // sum12 = 1 + 2 = 3 diff --git a/tests/tracing/without_tracing.rs b/tests/tracing/without_tracing.rs index a6712f2..9a8bd8c 100644 --- a/tests/tracing/without_tracing.rs +++ b/tests/tracing/without_tracing.rs @@ -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,11 +29,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(sum).unwrap(), 5); } @@ -51,11 +50,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(final_sum).unwrap(), 10); } diff --git a/tests/type_safety/complex.rs b/tests/type_safety/complex.rs index 160a2b0..19bf16b 100644 --- a/tests/type_safety/complex.rs +++ b/tests/type_safety/complex.rs @@ -2,6 +2,7 @@ use crate::common::task_fn; use dagx::*; +use futures::FutureExt; #[tokio::test] async fn test_complex_type_safety() { @@ -21,11 +22,9 @@ async fn test_complex_type_safety() { } })); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Correct type works let custom = dag.get(&custom_task).unwrap(); @@ -47,11 +46,9 @@ async fn test_generic_type_safety() { vec!["a".to_string(), "b".to_string()] })); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Correct types work assert_eq!(dag.get(&vec_i32).unwrap(), vec![1, 2, 3]); @@ -69,11 +66,9 @@ async fn test_option_result_type_safety() { // Task returning Result let result_task = dag.add_task(task_fn(|_: ()| async { Ok::(42) })); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Correct wrapped types work assert_eq!(dag.get(&opt_task).unwrap(), Some(42)); @@ -94,11 +89,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Correct ZSTs work assert!(dag.get(&zst1_task).is_ok()); @@ -117,11 +110,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Each type works correctly assert_eq!(dag.get(&i32_task).unwrap(), 42i32); @@ -145,11 +136,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(transform).unwrap(), 84); } @@ -169,11 +158,9 @@ async fn test_type_safety_tuple_dependencies() { })) .depends_on((&a, &b, &c)); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(combine).unwrap(), "hello: 10 (true)"); } @@ -186,11 +173,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(&int_source).unwrap(), 42); assert_eq!(dag.get(&str_source).unwrap(), "test"); @@ -214,11 +199,9 @@ async fn test_type_safety_complex_types() { vec![Some(1), None, Some(2), Some(3)] })); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); let tuple_result = dag.get(tuple_task).unwrap(); assert_eq!(tuple_result.0, 42); @@ -245,11 +228,9 @@ async fn test_type_safety_with_generics() { )) .depends_on(&source); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(transform).unwrap(), Some(84)); @@ -261,11 +242,9 @@ async fn test_type_safety_with_generics() { )) .depends_on(&source2); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(transform2).unwrap(), None); } @@ -285,11 +264,9 @@ async fn test_large_tuple_input() { )) .depends_on((&a, &b, &c, &d, &e)); - dag.run(|fut| { - tokio::spawn(fut); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(sum).unwrap(), 15); } diff --git a/tests/type_safety/handles.rs b/tests/type_safety/handles.rs index 011ccdc..9372107 100644 --- a/tests/type_safety/handles.rs +++ b/tests/type_safety/handles.rs @@ -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,11 +16,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Verify correct type works assert_eq!(dag.get(&int_task), Ok(42i32)); @@ -38,11 +37,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); // Get with correct types assert_eq!(dag.get(&string_task).unwrap(), "hello".to_string()); @@ -66,11 +63,9 @@ 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); - }) - .await - .unwrap(); + dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) + .await + .unwrap(); assert_eq!(dag.get(double).unwrap(), 200); assert_eq!(dag.get(triple).unwrap(), 300);