feat: dagrunner, initial version
This commit is contained in:
122 files changed
+23101
No files matched your search
@@ -0,0 +1,130 @@
|
||||
// Combined task pattern tests
|
||||
use crate::common::task_fn;
|
||||
// Comprehensive test showing all three patterns working together
|
||||
|
||||
use dagx::*;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
// Pattern 1: Stateless - Pure computation, no state
|
||||
struct StatelessAdd;
|
||||
#[task]
|
||||
impl StatelessAdd {
|
||||
async fn run(a: &i32, b: &i32) -> i32 {
|
||||
a + b
|
||||
}
|
||||
}
|
||||
|
||||
// Pattern 2: Read-only state - Configuration data
|
||||
struct ReadOnlyMultiplier(i32);
|
||||
#[task]
|
||||
impl ReadOnlyMultiplier {
|
||||
async fn run(&self, input: &i32) -> i32 {
|
||||
input * self.0 // Read-only access
|
||||
}
|
||||
}
|
||||
|
||||
// Pattern 3: Mutable state - Accumulates state
|
||||
struct MutableAccumulator(i32);
|
||||
#[task]
|
||||
impl MutableAccumulator {
|
||||
async fn run(&mut self, input: &i32) -> i32 {
|
||||
self.0 += input; // Modifies state
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_all_three_task_patterns() {
|
||||
// Comprehensive test showing all three patterns working together
|
||||
let dag = DagRunner::new();
|
||||
|
||||
// Create initial values
|
||||
let x = dag.add_task(task_fn(|_: ()| async { 5 }));
|
||||
let y = dag.add_task(task_fn(|_: ()| async { 3 }));
|
||||
|
||||
// Pattern 1: Stateless addition
|
||||
let sum = dag.add_task(StatelessAdd).depends_on((&x, &y)); // 5 + 3 = 8
|
||||
|
||||
// Pattern 2: Read-only multiplication
|
||||
let doubled = dag.add_task(ReadOnlyMultiplier(2)).depends_on(sum); // 8 * 2 = 16
|
||||
|
||||
// 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();
|
||||
|
||||
assert_eq!(dag.get(sum).unwrap(), 8);
|
||||
assert_eq!(dag.get(doubled).unwrap(), 16);
|
||||
assert_eq!(dag.get(accumulated).unwrap(), 26);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_parallel_execution() {
|
||||
let dag = DagRunner::new();
|
||||
let counter = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
// Create 3 independent tasks that should run in parallel
|
||||
let c1 = counter.clone();
|
||||
let c2 = counter.clone();
|
||||
let c3 = counter.clone();
|
||||
|
||||
let t1 = dag.add_task(task_fn(move |_: ()| {
|
||||
let c = c1.clone();
|
||||
async move {
|
||||
c.fetch_add(1, Ordering::SeqCst);
|
||||
1
|
||||
}
|
||||
}));
|
||||
|
||||
let t2 = dag.add_task(task_fn(move |_: ()| {
|
||||
let c = c2.clone();
|
||||
async move {
|
||||
c.fetch_add(1, Ordering::SeqCst);
|
||||
2
|
||||
}
|
||||
}));
|
||||
|
||||
let t3 = dag.add_task(task_fn(move |_: ()| {
|
||||
let c = c3.clone();
|
||||
async move {
|
||||
c.fetch_add(1, Ordering::SeqCst);
|
||||
3
|
||||
}
|
||||
}));
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// All three tasks should have run
|
||||
assert_eq!(counter.load(Ordering::SeqCst), 3);
|
||||
assert_eq!(dag.get(&t1).unwrap(), 1);
|
||||
assert_eq!(dag.get(&t2).unwrap(), 2);
|
||||
assert_eq!(dag.get(&t3).unwrap(), 3);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_task_fn_with_captured_state() {
|
||||
let dag = DagRunner::new();
|
||||
|
||||
let multiplier = 7;
|
||||
let base = dag.add_task(task_fn(|_: ()| async { 5 }));
|
||||
let scaled = dag
|
||||
.add_task(task_fn(move |x: i32| async move { x * multiplier }))
|
||||
.depends_on(&base);
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(dag.get(scaled).unwrap(), 35);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Test module for task pattern tests
|
||||
|
||||
mod combined;
|
||||
mod readonly;
|
||||
mod stateless;
|
||||
@@ -0,0 +1,97 @@
|
||||
// Read-only state task pattern tests
|
||||
use crate::common::task_fn;
|
||||
// These tests verify the read-only state pattern using &self
|
||||
|
||||
use dagx::*;
|
||||
|
||||
// Read-only state tasks
|
||||
struct ReadOnlyValue(i32);
|
||||
|
||||
#[task]
|
||||
impl ReadOnlyValue {
|
||||
async fn run(&self) -> i32 {
|
||||
self.0 // Read-only access
|
||||
}
|
||||
}
|
||||
|
||||
struct ReadOnlyMultiplier(i32);
|
||||
|
||||
#[task]
|
||||
impl ReadOnlyMultiplier {
|
||||
async fn run(&self, input: &i32) -> i32 {
|
||||
input * self.0 // Read-only access to state
|
||||
}
|
||||
}
|
||||
|
||||
struct ReadOnlyConfig {
|
||||
multiplier: i32,
|
||||
offset: i32,
|
||||
}
|
||||
|
||||
#[task]
|
||||
impl ReadOnlyConfig {
|
||||
async fn run(&self, input: &i32) -> i32 {
|
||||
(input * self.multiplier) + self.offset
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_readonly_state_basic() {
|
||||
// Test basic read-only state task using &self
|
||||
let dag = DagRunner::new();
|
||||
|
||||
let value = dag.add_task(ReadOnlyValue(42));
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(dag.get(value).unwrap(), 42);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_readonly_state_with_input() {
|
||||
// Test read-only state task with input using &self
|
||||
let dag = DagRunner::new();
|
||||
|
||||
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();
|
||||
assert_eq!(dag.get(result).unwrap(), 35);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_readonly_state_multiple_uses() {
|
||||
// Test that read-only state can be used multiple times
|
||||
let dag = DagRunner::new();
|
||||
|
||||
let input1 = dag.add_task(task_fn(|_: ()| async { 10 }));
|
||||
let input2 = dag.add_task(task_fn(|_: ()| async { 20 }));
|
||||
|
||||
let config = ReadOnlyConfig {
|
||||
multiplier: 3,
|
||||
offset: 5,
|
||||
};
|
||||
|
||||
let result1 = dag.add_task(config).depends_on(&input1);
|
||||
// Note: Can't reuse config because it was moved, so create another
|
||||
let config2 = ReadOnlyConfig {
|
||||
multiplier: 3,
|
||||
offset: 5,
|
||||
};
|
||||
let result2 = dag.add_task(config2).depends_on(&input2);
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(dag.get(result1).unwrap(), 35); // (10 * 3) + 5
|
||||
assert_eq!(dag.get(result2).unwrap(), 65); // (20 * 3) + 5
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
// Stateless task pattern tests
|
||||
use crate::common::task_fn;
|
||||
|
||||
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(|_: ()| async { 10 }));
|
||||
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();
|
||||
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(|_: ()| async { 21 }));
|
||||
let doubled = dag.add_task(StatelessDouble).depends_on(&input);
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.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);
|
||||
})
|
||||
.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(|_: ()| async { 42 }));
|
||||
let text = dag.add_task(task_fn(|_: ()| async { "test".to_string() }));
|
||||
let flag = dag.add_task(task_fn(|_: ()| async { true }));
|
||||
|
||||
let result = dag
|
||||
.add_task(StatelessFormat)
|
||||
.depends_on((&num, &text, &flag));
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.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(|_: ()| async { 5 }));
|
||||
let y = dag.add_task(task_fn(|_: ()| async { 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);
|
||||
})
|
||||
.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(|_: ()| async { 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);
|
||||
})
|
||||
.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 |_: ()| async move { i }));
|
||||
dag.add_task(StatelessSquare).depends_on(&source)
|
||||
})
|
||||
.collect();
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.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(|_: ()| async { "Hello, ".to_string() }));
|
||||
let world = dag.add_task(task_fn(|_: ()| async { "World!".to_string() }));
|
||||
|
||||
let greeting = dag.add_task(StatelessConcat).depends_on((&hello, &world));
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(dag.get(greeting).unwrap(), "Hello, World!");
|
||||
}
|
||||
Reference in new issue
Block a user