This commit adds comprehensive property-based tests and fuzzing coverage for critical router and parser invariants as specified in plan §8. **Property Tests (proptest) - router.rs:** - `prop_write_targets_count`: Verifies |write_targets| == RG × RF - `prop_write_targets_rf_per_group`: Verifies each group contributes exactly RF nodes - `prop_covering_set_covers_all_shards`: Verifies covering set includes all shards - `prop_reshuffle_bound_on_add`: Verifies reshuffle bounded by 4 × RF × ceil(S / Ng_new) - `prop_determinism`: Verifies same inputs produce same outputs - `prop_shard_for_key_uniformity`: Verifies uniform key distribution across shards **Fuzz Targets (cargo-fuzz):** - `config_parser.rs`: Feeds random UTF-8 to Config::from_yaml - `filter_parser.rs`: Feeds random strings to query planner filter grammar - `canonical_json.rs`: Roundtrips random JSON through canonicalizer **Bug Fixes:** - Fixed missing `mode_b_type` import in mode_b_coordinator.rs - Fixed missing `Arc` import in scatter.rs All property tests pass at 1024 cases per property. Fuzz targets are ready for integration with weekly CI fuzz runs via Argo Workflow. Closes: miroir-89x.6
50 lines
1.7 KiB
Rust
50 lines
1.7 KiB
Rust
#![no_main]
|
|
use libfuzzer_sys::fuzz_target;
|
|
use serde_json::Value;
|
|
use std::collections::BTreeMap;
|
|
use twox_hash::XxHash64;
|
|
use std::hash::{Hash, Hasher};
|
|
|
|
/// Canonicalize a JSON value by sorting object keys.
|
|
fn canonicalize_json(value: &Value) -> String {
|
|
match value {
|
|
Value::Object(map) => {
|
|
let sorted: BTreeMap<_, _> = map.iter().collect();
|
|
serde_json::to_string(&sorted).unwrap_or_else(|_| "{}".to_string())
|
|
}
|
|
_ => serde_json::to_string(value).unwrap_or_else(|_| "null".to_string()),
|
|
}
|
|
}
|
|
|
|
/// Compute hash of canonicalized JSON.
|
|
fn hash_canonical(value: &Value) -> u64 {
|
|
let canonical = canonicalize_json(value);
|
|
let mut hasher = XxHash64::with_seed(0);
|
|
hasher.write(canonical.as_bytes());
|
|
hasher.finish()
|
|
}
|
|
|
|
fuzz_target!(|data: &[u8]| {
|
|
// Try to parse as JSON
|
|
let json1 = match serde_json::from_slice::<Value>(data) {
|
|
Ok(v) => v,
|
|
Err(_) => return, // Skip invalid JSON
|
|
};
|
|
|
|
// Canonicalize and hash - should never panic
|
|
let hash1 = hash_canonical(&json1);
|
|
|
|
// Round-trip through canonical string and verify
|
|
let canonical = canonicalize_json(&json1);
|
|
if let Ok(json2) = serde_json::from_str::<Value>(&canonical) {
|
|
let hash2 = hash_canonical(&json2);
|
|
// Hashes must be identical after roundtrip
|
|
assert_eq!(hash1, hash2, "Canonical JSON roundtrip produced different hash");
|
|
}
|
|
|
|
// Verify that parsing the canonical string again produces the same hash
|
|
if let Ok(json3) = serde_json::from_str::<Value>(&canonical) {
|
|
let hash3 = hash_canonical(&json3);
|
|
assert_eq!(hash1, hash3, "Second canonicalization produced different hash");
|
|
}
|
|
});
|