Files
dagx/tests/boundaries/zero_tasks.rs
T

194 lines
4.9 KiB
Rust

//! Tests for empty DAGs and minimal task configurations
use crate::common::task_fn;
use dagx::{task, DagResult, DagRunner};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use tokio::task::yield_now;
#[tokio::test]
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| async move { tokio::spawn(fut).await.unwrap() })
.await?;
// Should complete successfully with no tasks
Ok(())
}
#[tokio::test]
async fn test_single_task_no_deps() -> DagResult<()> {
// Test the absolute minimum: one task with no dependencies
let dag = DagRunner::new();
let task = dag.add_task(task_fn::<(), _, _>(|_: ()| 42));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(task)?, 42);
Ok(())
}
#[tokio::test]
async fn test_single_task_unit_input_output() -> DagResult<()> {
// Test a task that takes unit and returns unit
let dag = DagRunner::new();
let executed = Arc::new(AtomicUsize::new(0));
let exec_clone = executed.clone();
let task = dag.add_task(task_fn::<(), _, _>(move |_: ()| {
let exec = exec_clone.clone();
exec.fetch_add(1, Ordering::SeqCst);
}));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
dag.get(task)?; // Just verify it doesn't error
assert_eq!(executed.load(Ordering::SeqCst), 1);
Ok(())
}
#[tokio::test]
async fn test_zero_sized_type_task() -> DagResult<()> {
// Test with zero-sized types
#[derive(Debug, Clone, PartialEq)]
struct ZeroSized;
let dag = DagRunner::new();
let task = dag.add_task(task_fn::<(), _, _>(|_: ()| ZeroSized));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(task)?, ZeroSized);
Ok(())
}
#[tokio::test]
async fn test_empty_dag_multiple_runs() -> DagResult<()> {
// Test that an empty DAG can be run multiple times
let dag = DagRunner::new();
for _ in 0..10 {
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
}
Ok(())
}
#[tokio::test]
async fn test_dag_with_only_source_tasks() -> DagResult<()> {
// Test a DAG with multiple source tasks but no dependencies
let dag = DagRunner::new();
let tasks: Vec<_> = (0..100)
.map(|i| dag.add_task(task_fn::<(), _, _>(move |_: ()| i)))
.collect();
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
for (i, task) in tasks.into_iter().enumerate() {
assert_eq!(dag.get(task)?, i);
}
Ok(())
}
#[tokio::test]
async fn test_single_self_contained_stateful_task() -> DagResult<()> {
// Test a single stateful task with internal state
struct Counter {
value: i32,
}
#[task]
impl Counter {
async fn run(&self) -> i32 {
self.value * 2
}
}
let dag = DagRunner::new();
let task = dag.add_task(Counter { value: 21 });
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(task)?, 42);
Ok(())
}
#[tokio::test]
async fn test_alternating_empty_and_filled_dag_runs() -> DagResult<()> {
// Test alternating between empty DAG and DAG with tasks
for i in 0..5 {
let dag = DagRunner::new();
if i % 2 == 0 {
// Empty DAG
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
} else {
// DAG with a task
let task = dag.add_task(task_fn::<(), _, _>(move |_: ()| i));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(task)?, i);
}
}
Ok(())
}
#[tokio::test]
async fn test_dag_with_never_type_simulation() -> DagResult<()> {
// Test handling of tasks that conceptually "never" complete
// (simulated by immediate completion with special marker)
#[derive(Debug, Clone, PartialEq)]
enum Never {}
let dag = DagRunner::new();
// This task completes immediately but returns a Result containing "never" type
let task = dag.add_task(task_fn::<(), _, _>(|_: ()| Result::<i32, Never>::Ok(42)));
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(task)?, Ok(42));
Ok(())
}
#[tokio::test]
async fn test_minimal_task_with_minimal_async_work() -> DagResult<()> {
// Test a task that does the absolute minimum async work
let dag = DagRunner::new();
struct YieldTask;
#[task]
impl YieldTask {
async fn run() -> i32 {
yield_now().await;
1337
}
}
let task = dag.add_task(YieldTask);
dag.run(|fut| async move { tokio::spawn(fut).await.unwrap() })
.await?;
assert_eq!(dag.get(task)?, 1337);
Ok(())
}