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.
121 lines
3.3 KiB
Rust
121 lines
3.3 KiB
Rust
#![no_main]
|
|
|
|
use libfuzzer_sys::fuzz_target;
|
|
use dagx::{task, DagRunner};
|
|
|
|
fuzz_target!(|data: &[u8]| {
|
|
// Only fuzz with reasonable data sizes to avoid timeouts
|
|
if data.is_empty() || data.len() > 1000 {
|
|
return;
|
|
}
|
|
|
|
// Convert bytes to values for tasks
|
|
let value1 = data.get(0).copied().unwrap_or(0) as i32;
|
|
let value2 = data.get(1).copied().unwrap_or(0) as i32;
|
|
let value3 = data.get(2).copied().unwrap_or(0) as i32;
|
|
|
|
// Test simple linear DAG execution
|
|
{
|
|
struct Source1 { val: i32 }
|
|
#[task]
|
|
impl Source1 {
|
|
async fn run(&self) -> i32 { self.val }
|
|
}
|
|
|
|
struct Source2 { val: i32 }
|
|
#[task]
|
|
impl Source2 {
|
|
async fn run(&self) -> i32 { self.val }
|
|
}
|
|
|
|
struct Compute;
|
|
#[task]
|
|
impl Compute {
|
|
async fn run(a: &i32, b: &i32) -> i32 {
|
|
a.wrapping_add(*b)
|
|
}
|
|
}
|
|
|
|
let dag = DagRunner::new();
|
|
let s1 = dag.add_task(Source1 { val: value1 });
|
|
let s2 = dag.add_task(Source2 { val: value2 });
|
|
let compute = dag.add_task(Compute).depends_on((&s1, &s2));
|
|
|
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
|
if let Ok(()) = rt.block_on(async {
|
|
dag.run(|fut| { tokio::spawn(fut); }).await
|
|
}) {
|
|
// Verify output
|
|
if let Ok(result) = dag.get(compute) {
|
|
assert_eq!(result, value1.wrapping_add(value2));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test fan-out pattern
|
|
{
|
|
struct Source { val: i32 }
|
|
#[task]
|
|
impl Source {
|
|
async fn run(&self) -> i32 { self.val }
|
|
}
|
|
|
|
struct Double;
|
|
#[task]
|
|
impl Double {
|
|
async fn run(input: &i32) -> i32 {
|
|
input.wrapping_mul(2)
|
|
}
|
|
}
|
|
|
|
struct Triple;
|
|
#[task]
|
|
impl Triple {
|
|
async fn run(input: &i32) -> i32 {
|
|
input.wrapping_mul(3)
|
|
}
|
|
}
|
|
|
|
let dag = DagRunner::new();
|
|
let source = dag.add_task(Source { val: value3 });
|
|
let double = dag.add_task(Double).depends_on(&source);
|
|
let triple = dag.add_task(Triple).depends_on(&source);
|
|
|
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
|
if let Ok(()) = rt.block_on(async {
|
|
dag.run(|fut| { tokio::spawn(fut); }).await
|
|
}) {
|
|
// Verify outputs
|
|
if let (Ok(d), Ok(t)) = (dag.get(double), dag.get(triple)) {
|
|
assert_eq!(d, value3.wrapping_mul(2));
|
|
assert_eq!(t, value3.wrapping_mul(3));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test error-free execution with various data
|
|
if data.len() >= 4 {
|
|
struct DataTask { data: Vec<u8> }
|
|
#[task]
|
|
impl DataTask {
|
|
async fn run(&self) -> Vec<u8> {
|
|
self.data.clone()
|
|
}
|
|
}
|
|
|
|
let dag = DagRunner::new();
|
|
let task = dag.add_task(DataTask {
|
|
data: data[..4.min(data.len())].to_vec()
|
|
});
|
|
|
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
|
if let Ok(()) = rt.block_on(async {
|
|
dag.run(|fut| { tokio::spawn(fut); }).await
|
|
}) {
|
|
if let Ok(result) = dag.get(task) {
|
|
assert_eq!(result.len(), 4.min(data.len()));
|
|
}
|
|
}
|
|
}
|
|
});
|