Implements dagx-11: Add comprehensive testing infrastructure including fuzzing, property-based testing, and mutation testing configuration. **Fuzzing Infrastructure:** - Created fuzz/ directory with cargo-fuzz setup - 4 fuzz targets covering different scenarios: - fuzz_dag_execution: Basic DAG execution with various patterns - fuzz_dependencies: Dependency management (linear, diamond, complex) - fuzz_type_safety: Type-safe output retrieval - fuzz_concurrent: Concurrent execution and thread safety - Comprehensive README with usage instructions - Integrated with justfile (fuzz, fuzz-all, fuzz-extended, fuzz-build) **Property-Based Testing:** - Added proptest dependency to Cargo.toml - Created tests/property_tests.rs with 14 property tests: - Value preservation through DAG - Linear chain correctness - Fan-out pattern verification - Diamond pattern correctness - String, Vec, Option, Result handling - HashMap and custom struct preservation - DAG isolation - Concurrent execution scaling - Integrated with justfile (proptest recipe) **Mutation Testing:** - Created .cargo-mutants.toml configuration - Focused on critical functions: - DagRunner::run and ::get - TaskBuilder::depends_on - ExecutableNode::execute_with_listeners - ExtractFromListeners::extract - DepsTuple::to_node_ids - Excluded low-value mutations (fmt, clone, default) - Configured for nextest and parallel execution - Already integrated via existing justfile mutants recipe **Justfile Updates:** - Added fuzzing section with 5 new recipes - Added proptest recipe for property-based tests - All recipes follow existing patterns and naming conventions Similar setup to ../announcement project but tailored for DAG-specific testing.
138 lines
3.6 KiB
Rust
138 lines
3.6 KiB
Rust
#![no_main]
|
|
|
|
use libfuzzer_sys::fuzz_target;
|
|
use dagx::{task, DagRunner};
|
|
use std::sync::Arc;
|
|
|
|
fuzz_target!(|data: &[u8]| {
|
|
if data.is_empty() || data.len() > 50 {
|
|
return;
|
|
}
|
|
|
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
|
|
|
// Test concurrent task execution
|
|
struct SlowTask { val: i32, delay_ms: u64 }
|
|
#[task]
|
|
impl SlowTask {
|
|
async fn run(&self) -> i32 {
|
|
tokio::time::sleep(tokio::time::Duration::from_millis(self.delay_ms)).await;
|
|
self.val
|
|
}
|
|
}
|
|
|
|
struct FastTask { val: i32 }
|
|
#[task]
|
|
impl FastTask {
|
|
async fn run(&self) -> i32 { self.val }
|
|
}
|
|
|
|
// Test concurrent independent tasks
|
|
{
|
|
let dag = DagRunner::new();
|
|
|
|
// Create multiple independent tasks
|
|
let num_tasks = (data[0] % 5 + 1) as usize;
|
|
let mut tasks = Vec::new();
|
|
|
|
for i in 0..num_tasks {
|
|
let val = data.get(i).copied().unwrap_or(0) as i32;
|
|
let delay = (val.abs() as u64 % 10) + 1;
|
|
|
|
let task = dag.add_task(SlowTask { val, delay_ms: delay });
|
|
tasks.push((task, val));
|
|
}
|
|
|
|
if rt.block_on(async {
|
|
dag.run(|fut| { tokio::spawn(fut); }).await
|
|
}).is_ok() {
|
|
// Verify all tasks completed with correct values
|
|
for (handle, expected) in tasks {
|
|
if let Ok(result) = dag.get(handle) {
|
|
assert_eq!(result, expected);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test concurrent dependent tasks
|
|
{
|
|
let dag = DagRunner::new();
|
|
|
|
let source = dag.add_task(FastTask { val: data[0] as i32 });
|
|
|
|
// Create multiple tasks that depend on source
|
|
struct AddOne;
|
|
#[task]
|
|
impl AddOne {
|
|
async fn run(input: &i32) -> i32 {
|
|
input.wrapping_add(1)
|
|
}
|
|
}
|
|
|
|
struct AddTwo;
|
|
#[task]
|
|
impl AddTwo {
|
|
async fn run(input: &i32) -> i32 {
|
|
input.wrapping_add(2)
|
|
}
|
|
}
|
|
|
|
struct AddThree;
|
|
#[task]
|
|
impl AddThree {
|
|
async fn run(input: &i32) -> i32 {
|
|
input.wrapping_add(3)
|
|
}
|
|
}
|
|
|
|
let t1 = dag.add_task(AddOne).depends_on(&source);
|
|
let t2 = dag.add_task(AddTwo).depends_on(&source);
|
|
let t3 = dag.add_task(AddThree).depends_on(&source);
|
|
|
|
if rt.block_on(async {
|
|
dag.run(|fut| { tokio::spawn(fut); }).await
|
|
}).is_ok() {
|
|
let base = data[0] as i32;
|
|
if let Ok(r1) = dag.get(t1) {
|
|
assert_eq!(r1, base.wrapping_add(1));
|
|
}
|
|
if let Ok(r2) = dag.get(t2) {
|
|
assert_eq!(r2, base.wrapping_add(2));
|
|
}
|
|
if let Ok(r3) = dag.get(t3) {
|
|
assert_eq!(r3, base.wrapping_add(3));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test Arc sharing across concurrent tasks
|
|
{
|
|
let dag = Arc::new(DagRunner::new());
|
|
|
|
struct SharedTask { val: i32 }
|
|
#[task]
|
|
impl SharedTask {
|
|
async fn run(&self) -> i32 { self.val }
|
|
}
|
|
|
|
let task = dag.add_task(SharedTask { val: data[0] as i32 });
|
|
let task_copy = task;
|
|
|
|
let dag1 = Arc::clone(&dag);
|
|
let dag2 = Arc::clone(&dag);
|
|
|
|
if rt.block_on(async {
|
|
dag.run(|fut| { tokio::spawn(fut); }).await
|
|
}).is_ok() {
|
|
// Multiple threads can access result
|
|
if let Ok(r1) = dag1.get(task_copy) {
|
|
if let Ok(r2) = dag2.get(task_copy) {
|
|
assert_eq!(r1, r2);
|
|
assert_eq!(r1, data[0] as i32);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|