// Stateless task pattern tests use crate::common::task_fn; use futures::FutureExt; use dagx::*; // Stateless task structs struct StatelessAdd; #[task] impl StatelessAdd { async fn run(a: &i32, b: &i32) -> i32 { a + b } } struct StatelessDouble; #[task] impl StatelessDouble { async fn run(x: &i32) -> i32 { x * 2 } } struct StatelessSource; #[task] impl StatelessSource { async fn run() -> i32 { 42 } } struct StatelessFormat; #[task] impl StatelessFormat { async fn run(n: &i32, s: &String, b: &bool) -> String { format!("{}: {} ({})", s, n, b) } } struct StatelessMultiply; #[task] impl StatelessMultiply { async fn run(a: &i32, b: &i32) -> i32 { a * b } } struct StatelessInc; #[task] impl StatelessInc { async fn run(x: &i32) -> i32 { x + 1 } } struct StatelessSquare; #[task] impl StatelessSquare { async fn run(x: &i32) -> i32 { x * x } } struct StatelessConcat; #[task] impl StatelessConcat { async fn run(a: &String, b: &String) -> String { format!("{}{}", a, b) } } #[tokio::test] async fn test_stateless_basic() { // Test basic stateless task with two inputs let dag = DagRunner::new(); let x = dag.add_task(task_fn::<(), _, _>(|_: ()| 10)); let y = dag.add_task(task_fn::<(), _, _>(|_: ()| 20)); let sum = dag.add_task(StatelessAdd).depends_on((x, y)); dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) .await .unwrap(); assert_eq!(dag.get(sum).unwrap(), 30); } #[tokio::test] async fn test_stateless_single_input() { // Test stateless task with single input let dag = DagRunner::new(); let input = dag.add_task(task_fn::<(), _, _>(|_: ()| 21)); let doubled = dag.add_task(StatelessDouble).depends_on(input); dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) .await .unwrap(); assert_eq!(dag.get(doubled).unwrap(), 42); } #[tokio::test] async fn test_stateless_no_input() { // Test stateless source task with no inputs let dag = DagRunner::new(); let source = dag.add_task(StatelessSource); dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) .await .unwrap(); assert_eq!(dag.get(source).unwrap(), 42); } #[tokio::test] async fn test_stateless_multiple_inputs() { // Test stateless task with multiple inputs of different types let dag = DagRunner::new(); let num = dag.add_task(task_fn::<(), _, _>(|_: ()| 42)); let text = dag.add_task(task_fn::<(), _, _>(|_: ()| "test".to_string())); let flag = dag.add_task(task_fn::<(), _, _>(|_: ()| true)); let result = dag.add_task(StatelessFormat).depends_on((num, text, flag)); dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) .await .unwrap(); assert_eq!(dag.get(result).unwrap(), "test: 42 (true)"); } #[tokio::test] async fn test_stateless_mixed_with_stateful() { // Test mixing stateless and stateful tasks // Define Counter inline for this test struct Counter { count: i32, } impl Counter { fn new(initial: i32) -> Self { Self { count: initial } } } #[task] impl Counter { async fn run(&mut self, input: &i32) -> i32 { self.count += input; self.count } } let dag = DagRunner::new(); let x = dag.add_task(task_fn::<(), _, _>(|_: ()| 5)); let y = dag.add_task(task_fn::<(), _, _>(|_: ()| 3)); // Stateless multiplication let product = dag.add_task(StatelessMultiply).depends_on((x, y)); // Stateful counter let counted = dag.add_task(Counter::new(10)).depends_on(product); 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 } #[tokio::test] async fn test_stateless_chain() { // Test chaining stateless tasks let dag = DagRunner::new(); let start = dag.add_task(task_fn::<(), _, _>(|_: ()| 0)); let step1 = dag.add_task(StatelessInc).depends_on(start); let step2 = dag.add_task(StatelessInc).depends_on(step1); let step3 = dag.add_task(StatelessInc).depends_on(step2); 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); assert_eq!(dag.get(step3).unwrap(), 3); } #[tokio::test] async fn test_stateless_parallel() { // Test parallel execution of stateless tasks let dag = DagRunner::new(); let tasks: Vec<_> = (1..=5) .map(|i| { let source = dag.add_task(task_fn::<(), _, _>(move |_: ()| i)); dag.add_task(StatelessSquare).depends_on(source) }) .collect(); 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); assert_eq!(dag.get(tasks[2]).unwrap(), 9); assert_eq!(dag.get(tasks[3]).unwrap(), 16); assert_eq!(dag.get(tasks[4]).unwrap(), 25); } #[tokio::test] async fn test_stateless_with_string_types() { // Test stateless tasks with complex types let dag = DagRunner::new(); let hello = dag.add_task(task_fn::<(), _, _>(|_: ()| "Hello, ".to_string())); let world = dag.add_task(task_fn::<(), _, _>(|_: ()| "World!".to_string())); let greeting = dag.add_task(StatelessConcat).depends_on((hello, world)); dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)) .await .unwrap(); assert_eq!(dag.get(greeting).unwrap(), "Hello, World!"); }