Update tests, examples, benches
This commit is contained in:
1 parent
0137296fb4
commit
86787b3a0d
71 files changed
+1791
-3547
No files matched your search
+92
-86
@@ -1,26 +1,28 @@
|
||||
//! Tests for error propagation through the DAG
|
||||
|
||||
use crate::common::task_fn;
|
||||
use dagx::{DagRunner, TaskHandle};
|
||||
use dagx::{task, DagRunner, TaskHandle};
|
||||
use futures::FutureExt;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_linear_error_propagation() {
|
||||
let dag = DagRunner::new();
|
||||
|
||||
let t1: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 1 })).into();
|
||||
let t1: TaskHandle<_> = dag.add_task(task_fn::<(), _, _>(|_: ()| 1)).into();
|
||||
let t2 = dag
|
||||
.add_task(task_fn(|x: i32| async move {
|
||||
.add_task(task_fn::<i32, _, _>(|&x: &i32| -> i32 {
|
||||
panic!("Error at t2 with input {}", x);
|
||||
}))
|
||||
.depends_on(t1);
|
||||
let t3 = dag
|
||||
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
||||
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 1))
|
||||
.depends_on(t2);
|
||||
let t4 = dag
|
||||
.add_task(task_fn(|x: i32| async move { x * 2 }))
|
||||
.add_task(task_fn::<i32, _, _>(|&x: &i32| x * 2))
|
||||
.depends_on(t3);
|
||||
|
||||
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
||||
@@ -46,20 +48,20 @@ async fn test_diamond_error_propagation() {
|
||||
// \ /
|
||||
// sink
|
||||
|
||||
let source: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 100 })).into();
|
||||
let source: TaskHandle<_> = dag.add_task(task_fn::<(), _, _>(|_: ()| 100)).into();
|
||||
|
||||
let left = dag
|
||||
.add_task(task_fn(|x: i32| async move { x / 2 }))
|
||||
.add_task(task_fn::<i32, _, _>(|&x: &i32| x / 2))
|
||||
.depends_on(source);
|
||||
|
||||
let right = dag
|
||||
.add_task(task_fn(|x: i32| async move {
|
||||
.add_task(task_fn::<i32, _, _>(|&x: &i32| {
|
||||
panic!("Right path fails with {}", x);
|
||||
}))
|
||||
.depends_on(source);
|
||||
|
||||
let sink = dag
|
||||
.add_task(task_fn(|(l, r): (i32, i32)| async move { l + r }))
|
||||
.add_task(task_fn::<(i32, i32), _, _>(|(l, r): (&i32, &i32)| l + r))
|
||||
.depends_on((&left, &right));
|
||||
|
||||
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
||||
@@ -77,40 +79,34 @@ async fn test_error_stops_at_boundary() {
|
||||
|
||||
// Branch with error
|
||||
let error_source: TaskHandle<_> = dag
|
||||
.add_task(task_fn({
|
||||
.add_task(task_fn::<(), _, _>({
|
||||
let tracker = execution_tracker.clone();
|
||||
move |_: ()| {
|
||||
move |_: ()| -> i32 {
|
||||
let tracker = tracker.clone();
|
||||
async move {
|
||||
tracker.fetch_add(1, Ordering::SeqCst);
|
||||
panic!("Error source");
|
||||
}
|
||||
tracker.fetch_add(1, Ordering::SeqCst);
|
||||
panic!("Error source");
|
||||
}
|
||||
}))
|
||||
.into();
|
||||
|
||||
let error_dependent = dag
|
||||
.add_task(task_fn({
|
||||
.add_task(task_fn::<i32, _, _>({
|
||||
let tracker = execution_tracker.clone();
|
||||
move |_: i32| {
|
||||
move |_: &i32| {
|
||||
let tracker = tracker.clone();
|
||||
async move {
|
||||
tracker.fetch_add(100, Ordering::SeqCst); // Should not execute
|
||||
42
|
||||
}
|
||||
tracker.fetch_add(100, Ordering::SeqCst); // Should not execute
|
||||
42
|
||||
}
|
||||
}))
|
||||
.depends_on(error_source);
|
||||
|
||||
// Independent branch
|
||||
let independent = dag.add_task(task_fn({
|
||||
let independent = dag.add_task(task_fn::<(), _, _>({
|
||||
let tracker = execution_tracker.clone();
|
||||
move |_: ()| {
|
||||
let tracker = tracker.clone();
|
||||
async move {
|
||||
tracker.fetch_add(10, Ordering::SeqCst);
|
||||
10
|
||||
}
|
||||
tracker.fetch_add(10, Ordering::SeqCst);
|
||||
10
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -133,22 +129,24 @@ async fn test_multiple_error_sources_convergence() {
|
||||
|
||||
// Multiple error sources
|
||||
let error1 = dag
|
||||
.add_task(task_fn(|_: ()| async {
|
||||
.add_task(task_fn::<(), _, _>(|_: ()| {
|
||||
panic!("Error source 1");
|
||||
}))
|
||||
.into();
|
||||
|
||||
let error2 = dag
|
||||
.add_task(task_fn(|_: ()| async {
|
||||
.add_task(task_fn::<(), _, _>(|_: ()| {
|
||||
panic!("Error source 2");
|
||||
}))
|
||||
.into();
|
||||
|
||||
let success = dag.add_task(task_fn(|_: ()| async { 42 })).into();
|
||||
let success = dag.add_task(task_fn::<(), _, _>(|_: ()| 42)).into();
|
||||
|
||||
// Convergence point depends on all three
|
||||
let convergence = dag
|
||||
.add_task(task_fn(|(_, _, _): (i32, i32, i32)| async { 100 }))
|
||||
.add_task(task_fn::<(i32, i32, i32), _, _>(
|
||||
|(_, _, _): (&i32, &i32, &i32)| 100,
|
||||
))
|
||||
.depends_on((&error1, &error2, &success));
|
||||
|
||||
let _ = dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await;
|
||||
@@ -169,7 +167,7 @@ async fn test_error_in_wide_fanout() {
|
||||
let dag = DagRunner::new();
|
||||
|
||||
let source: TaskHandle<_> = dag
|
||||
.add_task(task_fn(|_: ()| async {
|
||||
.add_task(task_fn::<(), _, _>(|_: ()| -> i32 {
|
||||
panic!("Source fails");
|
||||
}))
|
||||
.into();
|
||||
@@ -177,7 +175,7 @@ async fn test_error_in_wide_fanout() {
|
||||
// Create wide fanout from failing source
|
||||
let dependents: Vec<_> = (0..20)
|
||||
.map(|i| {
|
||||
dag.add_task(task_fn(move |x: i32| async move { x + i }))
|
||||
dag.add_task(task_fn::<i32, _, _>(move |&x: &i32| x + i))
|
||||
.depends_on(source)
|
||||
})
|
||||
.collect();
|
||||
@@ -199,24 +197,26 @@ async fn test_selective_error_propagation() {
|
||||
|
||||
// Source that produces a Result
|
||||
let source: TaskHandle<_> = dag
|
||||
.add_task(task_fn(|_: ()| async {
|
||||
.add_task(task_fn::<(), _, _>(|_: ()| {
|
||||
Result::<i32, String>::Err("Source error".to_string())
|
||||
}))
|
||||
.into();
|
||||
|
||||
// Handler that processes the Result
|
||||
let handler = dag
|
||||
.add_task(task_fn(|result: Result<i32, String>| async move {
|
||||
match result {
|
||||
Ok(val) => val * 2,
|
||||
Err(_) => -1, // Default value on error
|
||||
}
|
||||
}))
|
||||
.add_task(task_fn::<Result<_, _>, _, _>(
|
||||
|result: &Result<i32, String>| {
|
||||
match result {
|
||||
Ok(val) => val * 2,
|
||||
Err(_) => -1, // Default value on error
|
||||
}
|
||||
},
|
||||
))
|
||||
.depends_on(source);
|
||||
|
||||
// Further processing
|
||||
let final_task = dag
|
||||
.add_task(task_fn(|val: i32| async move {
|
||||
.add_task(task_fn::<i32, _, _>(|&val: &i32| {
|
||||
if val < 0 {
|
||||
"Handled error case".to_string()
|
||||
} else {
|
||||
@@ -244,41 +244,49 @@ async fn test_error_propagation_timing() {
|
||||
let dag = DagRunner::new();
|
||||
let propagation_order = Arc::new(parking_lot::Mutex::new(Vec::new()));
|
||||
|
||||
let t1 = dag.add_task(task_fn({
|
||||
let order = propagation_order.clone();
|
||||
move |_: ()| {
|
||||
let order = order.clone();
|
||||
async move {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
|
||||
order.lock().push("t1_complete");
|
||||
1
|
||||
}
|
||||
struct T1Task {
|
||||
order: Arc<parking_lot::Mutex<Vec<&'static str>>>,
|
||||
}
|
||||
|
||||
#[task]
|
||||
impl T1Task {
|
||||
async fn run(&self) -> i32 {
|
||||
sleep(Duration::from_millis(10)).await;
|
||||
self.order.lock().push("t1_complete");
|
||||
1
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
let t1 = dag.add_task(T1Task {
|
||||
order: propagation_order.clone(),
|
||||
});
|
||||
|
||||
struct T2Task {
|
||||
order: Arc<parking_lot::Mutex<Vec<&'static str>>>,
|
||||
}
|
||||
|
||||
#[task]
|
||||
impl T2Task {
|
||||
async fn run(&self, x: &i32) -> i32 {
|
||||
self.order.lock().push("t2_start");
|
||||
sleep(Duration::from_millis(10)).await;
|
||||
panic!("t2 fails with {x}");
|
||||
}
|
||||
}
|
||||
|
||||
let t2 = dag
|
||||
.add_task(task_fn({
|
||||
let order = propagation_order.clone();
|
||||
move |x: i32| {
|
||||
let order = order.clone();
|
||||
async move {
|
||||
order.lock().push("t2_start");
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
|
||||
panic!("t2 fails with {}", x);
|
||||
}
|
||||
}
|
||||
}))
|
||||
.add_task(T2Task {
|
||||
order: propagation_order.clone(),
|
||||
})
|
||||
.depends_on(t1);
|
||||
|
||||
let _t3 = dag
|
||||
.add_task(task_fn({
|
||||
.add_task(task_fn::<i32, _, _>({
|
||||
let order = propagation_order.clone();
|
||||
move |x: i32| {
|
||||
move |x: &i32| {
|
||||
let order = order.clone();
|
||||
async move {
|
||||
order.lock().push("t3_should_not_run");
|
||||
x + 1
|
||||
}
|
||||
order.lock().push("t3_should_not_run");
|
||||
x + 1
|
||||
}
|
||||
}))
|
||||
.depends_on(t2);
|
||||
@@ -307,53 +315,51 @@ async fn test_partial_branch_failure_propagation() {
|
||||
// \ | /
|
||||
// sink
|
||||
|
||||
let root: TaskHandle<_> = dag.add_task(task_fn(|_: ()| async { 100 })).into();
|
||||
let root: TaskHandle<_> = dag.add_task(task_fn::<(), _, _>(|_: ()| 100)).into();
|
||||
|
||||
// Branch A
|
||||
let a1 = dag
|
||||
.add_task(task_fn(|x: i32| async move { x + 1 }))
|
||||
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 1))
|
||||
.depends_on(root);
|
||||
let a2 = dag
|
||||
.add_task(task_fn(|x: i32| async move { x + 2 }))
|
||||
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 2))
|
||||
.depends_on(a1);
|
||||
let a3 = dag
|
||||
.add_task(task_fn(|x: i32| async move { x + 3 }))
|
||||
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 3))
|
||||
.depends_on(a2);
|
||||
|
||||
// Branch B (with error) - use Result instead of panic
|
||||
let b1 = dag
|
||||
.add_task(task_fn(|x: i32| async move { Ok(x * 2) }))
|
||||
.add_task(task_fn::<i32, _, _>(|&x: &i32| Ok::<i32, &str>(x * 2)))
|
||||
.depends_on(root);
|
||||
let b2 = dag
|
||||
.add_task(task_fn(|_x: Result<i32, &str>| async move {
|
||||
Err("Branch B fails at b2")
|
||||
.add_task(task_fn::<Result<_, _>, _, _>(|_x: &Result<i32, &str>| {
|
||||
Err::<i32, &str>("Branch B fails at b2")
|
||||
}))
|
||||
.depends_on(b1);
|
||||
let b3 = dag
|
||||
.add_task(task_fn(
|
||||
|x: Result<i32, &str>| async move { x.map(|v| v * 3) },
|
||||
))
|
||||
.add_task(task_fn::<Result<_, _>, _, _>(|x: &Result<i32, &str>| {
|
||||
x.map(|v| v * 3)
|
||||
}))
|
||||
.depends_on(b2);
|
||||
|
||||
// Branch C
|
||||
let c1 = dag
|
||||
.add_task(task_fn(|x: i32| async move { x - 1 }))
|
||||
.add_task(task_fn::<i32, _, _>(|&x: &i32| x - 1))
|
||||
.depends_on(root);
|
||||
let c2 = dag
|
||||
.add_task(task_fn(|x: i32| async move { x - 2 }))
|
||||
.add_task(task_fn::<i32, _, _>(|&x: &i32| x - 2))
|
||||
.depends_on(c1);
|
||||
let c3 = dag
|
||||
.add_task(task_fn(|x: i32| async move { x - 3 }))
|
||||
.add_task(task_fn::<i32, _, _>(|&x: &i32| x - 3))
|
||||
.depends_on(c2);
|
||||
|
||||
// Sink depends on all branches - handle Result from B
|
||||
let sink = dag
|
||||
.add_task(task_fn(
|
||||
|(a, b, c): (i32, Result<i32, &str>, i32)| async move {
|
||||
match b {
|
||||
Ok(b_val) => Ok(a + b_val + c),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
.add_task(task_fn::<(i32, Result<_, _>, _), _, _>(
|
||||
|(a, b, c): (&i32, &Result<i32, &str>, &i32)| match b {
|
||||
Ok(b_val) => Ok(a + b_val + c),
|
||||
Err(e) => Err(*e),
|
||||
},
|
||||
))
|
||||
.depends_on((&a3, &b3, &c3));
|
||||
|
||||
Reference in new issue
Block a user