133 lines
3.6 KiB
Rust
133 lines
3.6 KiB
Rust
//! Tests for timing and speedup verification
|
|
|
|
use crate::common::task_fn;
|
|
use dagx::{DagResult, DagRunner, Task};
|
|
use futures::FutureExt;
|
|
use std::sync::Arc;
|
|
use std::sync::Mutex;
|
|
use std::time::{Duration, Instant};
|
|
use tokio::time::sleep;
|
|
|
|
#[tokio::test]
|
|
async fn test_parallel_execution_speedup() -> DagResult<()> {
|
|
// Prove parallel execution by comparing timing
|
|
let dag = DagRunner::new();
|
|
|
|
let work_duration = Duration::from_millis(50);
|
|
let num_tasks = 10;
|
|
|
|
// Create independent tasks that each take 50ms
|
|
let tasks: Vec<_> = (0..num_tasks)
|
|
.map(|i| {
|
|
dag.add_task(task_fn(move |_: ()| async move {
|
|
sleep(work_duration).await;
|
|
i
|
|
}))
|
|
})
|
|
.collect();
|
|
|
|
let start = Instant::now();
|
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
|
let elapsed = start.elapsed();
|
|
|
|
// Verify all tasks completed
|
|
for (i, task) in tasks.iter().enumerate() {
|
|
assert_eq!(dag.get(task)?, i);
|
|
}
|
|
|
|
// If tasks ran sequentially: 10 * 50ms = 500ms
|
|
// If tasks ran in parallel: ~50ms (plus overhead)
|
|
let sequential_time = work_duration * num_tasks as u32;
|
|
|
|
println!(
|
|
"Parallel execution took {:?}, sequential would be {:?}",
|
|
elapsed, sequential_time
|
|
);
|
|
|
|
// Parallel execution should be significantly faster
|
|
assert!(
|
|
elapsed < sequential_time / 2,
|
|
"Parallel execution too slow: {:?} vs sequential {:?}",
|
|
elapsed,
|
|
sequential_time
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
struct SyncTask;
|
|
#[dagx::task]
|
|
impl SyncTask {
|
|
fn run(&self, x: &i32) -> i32 {
|
|
// Synchronous task - no async/await
|
|
std::thread::sleep(Duration::from_millis(1)); // Blocking sleep
|
|
x * 2
|
|
}
|
|
}
|
|
|
|
struct AsyncTask;
|
|
#[dagx::task]
|
|
impl AsyncTask {
|
|
async fn run(&self, x: &i32) -> i32 {
|
|
// Async task
|
|
sleep(Duration::from_millis(1)).await; // Non-blocking sleep
|
|
x * 3
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_sync_tasks_dont_block_async_runtime() -> DagResult<()> {
|
|
// Test that sync tasks don't block the async runtime
|
|
let dag = DagRunner::new();
|
|
|
|
let start_times = Arc::new(Mutex::new(Vec::new()));
|
|
|
|
// Mix of sync and async tasks
|
|
let source = dag.add_task(task_fn(|_: ()| async { 10 }));
|
|
|
|
// Sync tasks
|
|
let sync1 = dag.add_task(SyncTask).depends_on(&source);
|
|
let sync2 = dag.add_task(SyncTask).depends_on(&source);
|
|
|
|
// Async tasks
|
|
let async1 = dag.add_task(AsyncTask).depends_on(&source);
|
|
let async2 = dag.add_task(AsyncTask).depends_on(&source);
|
|
|
|
// Track timing
|
|
let timing_task = {
|
|
let times = start_times.clone();
|
|
dag.add_task(task_fn(move |_: ()| {
|
|
let times = times.clone();
|
|
async move {
|
|
for i in 0..5 {
|
|
times
|
|
.lock()
|
|
.unwrap()
|
|
.push((format!("tick_{}", i), Instant::now()));
|
|
sleep(Duration::from_millis(5)).await;
|
|
}
|
|
"timing"
|
|
}
|
|
}))
|
|
};
|
|
|
|
dag.run(|fut| tokio::spawn(fut).map(Result::unwrap)).await?;
|
|
|
|
// Verify results
|
|
assert_eq!(dag.get(sync1)?, 20);
|
|
assert_eq!(dag.get(sync2)?, 20);
|
|
assert_eq!(dag.get(async1)?, 30);
|
|
assert_eq!(dag.get(async2)?, 30);
|
|
assert_eq!(dag.get(timing_task)?, "timing");
|
|
|
|
// Check that timing task made progress (wasn't blocked by sync tasks)
|
|
let times = start_times.lock().unwrap();
|
|
assert_eq!(
|
|
times.len(),
|
|
5,
|
|
"Timing task should have completed all ticks"
|
|
);
|
|
|
|
Ok(())
|
|
}
|