refactor: consolidate some tests/examples/benchmarks

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

No files matched your search

-161
View File
@@ -1,161 +0,0 @@
//! Tests for different error types and error handling
use crate::common::task_fn;
use dagx::{DagError, DagRunner, TaskHandle};
#[tokio::test]
async fn test_result_not_found_error() {
let dag = DagRunner::new();
let t1: TaskHandle<_> = dag.add_task(task_fn::<(), _, _>(|_: ()| 42)).into();
// Try to get result before running
let result = dag.get(t1);
assert!(matches!(result, Err(DagError::ResultNotFound { .. })));
// Run and verify it works after
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
assert_eq!(dag.get(t1).unwrap(), 42);
}
#[tokio::test]
async fn test_nested_result_error_handling() {
let dag = DagRunner::new();
// Task that returns a Result
let task1 = dag.add_task(task_fn::<(), _, _>(|_: ()| {
Result::<i32, String>::Err("Internal error".to_string())
}));
let task2 = dag.add_task(task_fn::<(), _, _>(|_: ()| Result::<i32, String>::Ok(42)));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
// Both tasks complete successfully (they return Results)
assert_eq!(dag.get(task1).unwrap(), Err("Internal error".to_string()));
assert_eq!(dag.get(task2).unwrap(), Ok(42));
}
#[tokio::test]
async fn test_option_none_not_error() {
let dag = DagRunner::new();
let task = dag.add_task(task_fn::<(), _, _>(|_: ()| None::<i32>));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
// None is a valid result, not an error
assert_eq!(dag.get(task).unwrap(), None);
}
#[tokio::test]
async fn test_custom_error_types() {
#[derive(Debug, Clone, PartialEq)]
enum CustomError {
ValidationError(String),
ProcessingError { code: i32, details: String },
}
let dag = DagRunner::new();
let task1 = dag.add_task(task_fn::<(), _, _>(|_: ()| {
Result::<i32, CustomError>::Err(CustomError::ValidationError("Invalid input".to_string()))
}));
let task2 = dag.add_task(task_fn::<(), _, _>(|_: ()| {
Result::<i32, CustomError>::Err(CustomError::ProcessingError {
code: 500,
details: "Processing failed".to_string(),
})
}));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
// Check custom errors are preserved
match dag.get(task1).unwrap().as_ref() {
Err(CustomError::ValidationError(msg)) => assert_eq!(msg, "Invalid input"),
_ => panic!("Wrong error type"),
}
match dag.get(task2).unwrap().as_ref() {
Err(CustomError::ProcessingError { code, .. }) => assert_eq!(*code, 500),
_ => panic!("Wrong error type"),
}
}
#[tokio::test]
async fn test_error_in_tuple_dependency() {
let dag = DagRunner::new();
let ok_task = dag.add_task(task_fn::<(), _, _>(|_: ()| 10));
let err_task = dag.add_task(task_fn::<(), _, _>(|_: ()| {
panic!("Error in dependency");
}));
let dependent = dag
.add_task(task_fn::<(i32, i32), _, _>(|(a, b): (&i32, &i32)| a + b))
.depends_on((ok_task, err_task));
let result = dag
.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await;
assert!(result.is_err());
assert!(dag.get(dependent).is_err());
}
#[tokio::test]
async fn test_string_panic_vs_structured_panic() {
let dag = DagRunner::new();
let string_panic = dag.add_task(task_fn::<(), _, _>(|_: ()| {
panic!("String panic message");
}));
let formatted_panic = dag.add_task(task_fn::<(), _, _>(|_: ()| {
panic!("Formatted panic: value={}, code={}", 42, "ERROR");
}));
let _ = dag
.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await;
// Both should result in errors
assert!(dag.get(string_panic).is_err());
assert!(dag.get(formatted_panic).is_err());
}
#[tokio::test]
async fn test_anyhow_like_error_chain() {
// Test error chains with simple string-based errors
let dag = DagRunner::new();
let task = dag.add_task(task_fn::<(), _, _>(|_: ()| {
// Simulate an error chain with nested Results
let _inner: Result<i32, String> = Err("Inner error".to_string());
let outer: Result<Result<i32, String>, String> = Err("Outer error".to_string());
outer
}));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.unwrap();
// Get the chained error
let result = dag.get(task).unwrap();
assert!(result.is_err());
if let Err(e) = result.as_ref() {
assert_eq!(e, "Outer error");
}
}
-1
View File
@@ -2,6 +2,5 @@
//!
//! Tests for error scenarios and recovery patterns.
mod error_types;
mod propagation;
mod recovery;
+1 -178
View File
@@ -1,9 +1,8 @@
//! Tests for error propagation through the DAG
use crate::common::task_fn;
use dagx::task_fn;
use dagx::{task, DagRunner, TaskHandle};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::time::sleep;
@@ -76,100 +75,6 @@ async fn test_diamond_error_propagation() {
assert!(dag.get(sink).is_err()); // Fails due to right dependency
}
#[tokio::test]
async fn test_error_stops_at_boundary() {
let dag = DagRunner::new();
let execution_tracker = Arc::new(AtomicUsize::new(0));
// Branch with error
let error_source: TaskHandle<_> = dag
.add_task(task_fn::<(), _, _>({
let tracker = execution_tracker.clone();
move |_: ()| -> i32 {
let tracker = tracker.clone();
tracker.fetch_add(1, Ordering::SeqCst);
panic!("Error source");
}
}))
.into();
let error_dependent = dag
.add_task(task_fn::<i32, _, _>({
let tracker = execution_tracker.clone();
move |_: &i32| {
let tracker = tracker.clone();
tracker.fetch_add(100, Ordering::SeqCst); // Should not execute
42
}
}))
.depends_on(error_source);
// Independent branch
let independent = dag.add_task(task_fn::<(), _, _>({
let tracker = execution_tracker.clone();
move |_: ()| {
let tracker = tracker.clone();
tracker.fetch_add(10, Ordering::SeqCst);
10
}
}));
let _ = dag
.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await;
// Error branch: source runs but dependent doesn't
assert!(dag.get(error_source).is_err());
assert!(dag.get(error_dependent).is_err());
// Independent branch runs
assert_eq!(dag.get(independent).unwrap(), 10);
// Check execution: 1 (error_source) + 10 (independent) = 11
assert_eq!(execution_tracker.load(Ordering::SeqCst), 11);
}
#[tokio::test]
async fn test_multiple_error_sources_convergence() {
let dag = DagRunner::new();
// Multiple error sources
let error1 = dag
.add_task(task_fn::<(), _, _>(|_: ()| {
panic!("Error source 1");
}))
.into();
let error2 = dag
.add_task(task_fn::<(), _, _>(|_: ()| {
panic!("Error source 2");
}))
.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), _, _>(
|(_, _, _): (&i32, &i32, &i32)| 100,
))
.depends_on((&error1, &error2, &success));
let _ = dag
.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await;
// All error sources fail
assert!(dag.get(error1).is_err());
assert!(dag.get(error2).is_err());
// Success task works
assert_eq!(dag.get(success).unwrap(), 42);
// Convergence fails due to any error dependency
assert!(dag.get(convergence).is_err());
}
#[tokio::test]
async fn test_error_in_wide_fanout() {
let dag = DagRunner::new();
@@ -312,85 +217,3 @@ async fn test_error_propagation_timing() {
assert!(order.contains(&"t2_start"));
assert!(!order.contains(&"t3_should_not_run"));
}
#[tokio::test]
async fn test_partial_branch_failure_propagation() {
let dag = DagRunner::new();
// root
// / | \
// a1 b1 c1
// | | |
// a2 b2 c2
// | (err) |
// a3 b3 c3
// \ | /
// sink
let root: TaskHandle<_> = dag.add_task(task_fn::<(), _, _>(|_: ()| 100)).into();
// Branch A
let a1 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 1))
.depends_on(root);
let a2 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x + 2))
.depends_on(a1);
let a3 = dag
.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::<i32, _, _>(|&x: &i32| Ok::<i32, &str>(x * 2)))
.depends_on(root);
let b2 = dag
.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::<Result<_, _>, _, _>(|x: &Result<i32, &str>| {
x.map(|v| v * 3)
}))
.depends_on(b2);
// Branch C
let c1 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x - 1))
.depends_on(root);
let c2 = dag
.add_task(task_fn::<i32, _, _>(|&x: &i32| x - 2))
.depends_on(c1);
let c3 = dag
.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::<(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));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await
.ok();
// Branch A succeeds
assert_eq!(dag.get(a3).unwrap(), 106);
// Branch B fails at b2
assert_eq!(dag.get(b1).unwrap(), Ok(200));
assert_eq!(dag.get(b2).unwrap(), Err("Branch B fails at b2"));
assert_eq!(dag.get(b3).unwrap(), Err("Branch B fails at b2")); // Propagated error
// Branch C succeeds
assert_eq!(dag.get(c3).unwrap(), 94);
// Sink fails due to branch B
assert_eq!(dag.get(sink).unwrap(), Err("Branch B fails at b2"));
}
+1 -129
View File
@@ -1,42 +1,11 @@
//! Tests for error recovery patterns
use crate::common::task_fn;
use dagx::task_fn;
use dagx::{DagResult, DagRunner};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
#[tokio::test]
async fn test_fallback_on_error() -> DagResult<()> {
let dag = DagRunner::new();
// Primary task that fails
let primary = dag.add_task(task_fn::<(), _, _>(|_: ()| {
panic!("Primary task failed");
#[allow(unreachable_code)]
42
}));
// Fallback task
let fallback = dag.add_task(task_fn::<(), _, _>(|_: ()| 100));
// Selector that uses fallback if primary fails
let _selector = dag.add_task(task_fn::<(), _, _>(|_: ()| {}));
// Run DAG
let _ = dag
.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await;
// Primary should fail
assert!(dag.get(primary).is_err());
// Fallback should succeed
assert_eq!(dag.get(fallback)?, 100);
Ok(())
}
#[tokio::test]
async fn test_retry_pattern_simulation() -> DagResult<()> {
let dag = DagRunner::new();
@@ -113,38 +82,6 @@ async fn test_circuit_breaker_pattern() -> DagResult<()> {
Ok(())
}
#[tokio::test]
async fn test_partial_recovery_with_defaults() -> DagResult<()> {
let dag = DagRunner::new();
// Some tasks that might fail
let _tasks: Vec<_> = (0..10)
.map(|i| {
dag.add_task(task_fn::<(), _, _>(move |_: ()| {
if i % 3 == 0 {
panic!("Task {} failed", i);
}
i * 10
}))
})
.collect();
// Aggregator that handles failures gracefully
let aggregator = dag.add_task(task_fn::<(), _, _>(|_: ()| {
// In real scenario, would check task results and use defaults
vec![0, 10, 20, 0, 40, 50, 0, 70, 80, 0] // 0 for failed tasks
}));
let _ = dag
.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await;
let result = dag.get(aggregator)?;
assert_eq!(result.len(), 10);
Ok(())
}
#[tokio::test]
async fn test_error_accumulation_pattern() -> DagResult<()> {
let dag = DagRunner::new();
@@ -183,36 +120,6 @@ async fn test_error_accumulation_pattern() -> DagResult<()> {
Ok(())
}
#[tokio::test]
async fn test_graceful_degradation() -> DagResult<()> {
let dag = DagRunner::new();
// Critical task
let critical = dag.add_task(task_fn::<(), _, _>(|_: ()| vec![1, 2, 3, 4, 5]));
// Optional enhancement that fails - returns Result
let _enhancement = dag.add_task(task_fn::<(), _, _>(|_: ()| {
Err::<Vec<i32>, &str>("Enhancement failed")
}));
// Final task that works with what's available
let final_task = dag
.add_task(task_fn::<Vec<_>, _, _>(|base: &Vec<i32>| {
// Would combine with enhancement if available
// Here just uses base
base.iter().sum::<i32>()
}))
.depends_on(critical);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
// Critical path works
assert_eq!(dag.get(final_task)?, 15);
Ok(())
}
#[tokio::test]
async fn test_error_boundary_isolation() -> DagResult<()> {
let dag = DagRunner::new();
@@ -248,38 +155,3 @@ async fn test_error_boundary_isolation() -> DagResult<()> {
Ok(())
}
#[tokio::test]
async fn test_compensation_action() -> DagResult<()> {
let dag = DagRunner::new();
let compensation_triggered = Arc::new(AtomicBool::new(false));
// Action that fails
let action = dag.add_task(task_fn::<(), _, _>(|_: ()| {
// Perform some action that fails
panic!("Action failed");
}));
// Compensation task (runs independently)
let compensation = dag.add_task(task_fn::<(), _, _>({
let triggered = compensation_triggered.clone();
move |_: ()| {
let triggered = triggered.clone();
triggered.store(true, Ordering::SeqCst);
"Compensation executed"
}
}));
let _ = dag
.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await;
// Action failed
assert!(dag.get(action).is_err());
// Compensation ran successfully
assert_eq!(dag.get(compensation)?, "Compensation executed");
assert!(compensation_triggered.load(Ordering::SeqCst));
Ok(())
}