test: proptest property testing + cargo-fuzz fuzzing for donglora-cobs
This commit is contained in:
8 files changed
+567
-9
No files matched your search
@@ -10,4 +10,5 @@ default = []
|
||||
|
||||
[dev-dependencies]
|
||||
corncobs = "0.1"
|
||||
proptest = "1"
|
||||
rand = "0.8"
|
||||
@@ -21,11 +21,21 @@ benchmark-optimized for streaming applications.
|
||||
## Testing
|
||||
|
||||
```sh
|
||||
# Unit tests + property tests + interop
|
||||
cargo test -p donglora-cobs
|
||||
|
||||
# Fuzz testing (requires cargo-fuzz: cargo install cargo-fuzz)
|
||||
cd crates/donglora-cobs
|
||||
cargo +nightly fuzz run fuzz_decode -- -max_total_time=60
|
||||
cargo +nightly fuzz run fuzz_roundtrip -- -max_total_time=60
|
||||
```
|
||||
|
||||
Tests include:
|
||||
- 18 canonical COBS vectors (Wikipedia / IEEE/ACM spec)
|
||||
- Round-trip verification for various payload sizes
|
||||
- 8 interop tests cross-validating against `corncobs` (dev-dependency)
|
||||
- 8 interop tests cross-validating against `corncobs`
|
||||
- 500 randomized round-trip payloads verified against `corncobs`
|
||||
- **5 proptest properties** (~1280 random inputs): round-trip, interop
|
||||
encode/decode both directions, length bounds, decode-never-panics
|
||||
- **2 fuzz targets**: `fuzz_decode` (arbitrary bytes → must not panic)
|
||||
and `fuzz_roundtrip` (encode→decode must match)
|
||||
@@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "donglora-cobs-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4"
|
||||
donglora-cobs = { path = ".." }
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_decode"
|
||||
path = "fuzz_targets/fuzz_decode.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_roundtrip"
|
||||
path = "fuzz_targets/fuzz_roundtrip.rs"
|
||||
test = false
|
||||
doc = false
|
||||
@@ -0,0 +1,9 @@
|
||||
//! Fuzz target: feed arbitrary bytes to decode(). Must never panic.
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
let mut buf = [0u8; 2048];
|
||||
// Must not panic — may return None on invalid input.
|
||||
let _ = donglora_cobs::decode(data, &mut buf);
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
//! Fuzz target: encode then decode arbitrary input. Output must match input.
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
if data.len() > 1024 {
|
||||
return; // skip huge inputs to keep fuzzing fast
|
||||
}
|
||||
|
||||
let max_enc = donglora_cobs::max_encoded_len(data.len()) + 1;
|
||||
let mut enc_buf = vec![0u8; max_enc];
|
||||
|
||||
let enc_len = match donglora_cobs::encode(data, &mut enc_buf) {
|
||||
Some(n) => n,
|
||||
None => return, // encode failed (buffer too small shouldn't happen, but skip)
|
||||
};
|
||||
let encoded = &enc_buf[..enc_len];
|
||||
|
||||
// Encoded output must never contain 0x00.
|
||||
assert!(!encoded.contains(&0x00), "encoded contains sentinel");
|
||||
|
||||
let mut dec_buf = vec![0u8; data.len() + 1];
|
||||
let dec_len = donglora_cobs::decode(encoded, &mut dec_buf)
|
||||
.expect("decode failed after successful encode");
|
||||
|
||||
assert_eq!(
|
||||
&dec_buf[..dec_len], data,
|
||||
"round-trip mismatch: encode→decode produced different output"
|
||||
);
|
||||
});
|
||||
@@ -486,3 +486,72 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Property-based tests (proptest) ─────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod proptests {
|
||||
extern crate alloc;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use super::*;
|
||||
use proptest::prelude::*;
|
||||
|
||||
// Round-trip: encode then decode must recover the original.
|
||||
proptest! {
|
||||
#[test]
|
||||
fn roundtrip_any(data in proptest::collection::vec(any::<u8>(), 0..1024)) {
|
||||
let mut enc_buf = vec![0u8; max_encoded_len(data.len()) + 1];
|
||||
let enc_len = encode(&data, &mut enc_buf).unwrap();
|
||||
let encoded = &enc_buf[..enc_len];
|
||||
|
||||
// Encoded output must never contain 0x00.
|
||||
prop_assert!(!encoded.contains(&0x00));
|
||||
|
||||
let mut dec_buf = vec![0u8; data.len() + 1];
|
||||
let dec_len = decode(encoded, &mut dec_buf).unwrap();
|
||||
prop_assert_eq!(&dec_buf[..dec_len], &data[..]);
|
||||
}
|
||||
|
||||
// Our encode → corncobs decode must match.
|
||||
#[test]
|
||||
fn interop_our_encode_their_decode(data in proptest::collection::vec(any::<u8>(), 0..1024)) {
|
||||
let mut enc_buf = vec![0u8; max_encoded_len(data.len()) + 2];
|
||||
let enc_len = encode(&data, &mut enc_buf).unwrap();
|
||||
// Append sentinel for corncobs.
|
||||
enc_buf[enc_len] = 0x00;
|
||||
|
||||
let mut dec_buf = vec![0u8; data.len() + 1];
|
||||
let dec_len = corncobs::decode_buf(&enc_buf[..enc_len + 1], &mut dec_buf).unwrap();
|
||||
prop_assert_eq!(&dec_buf[..dec_len], &data[..]);
|
||||
}
|
||||
|
||||
// corncobs encode → our decode must match.
|
||||
#[test]
|
||||
fn interop_their_encode_our_decode(data in proptest::collection::vec(any::<u8>(), 0..1024)) {
|
||||
let mut enc_buf = vec![0u8; corncobs::max_encoded_len(data.len())];
|
||||
let enc_len = corncobs::encode_buf(&data, &mut enc_buf);
|
||||
// Strip trailing sentinel for our decoder.
|
||||
let data_end = enc_buf[..enc_len].iter().rposition(|&b| b != 0x00).map_or(0, |i| i + 1);
|
||||
|
||||
let mut dec_buf = vec![0u8; data.len() + 1];
|
||||
let dec_len = decode(&enc_buf[..data_end], &mut dec_buf).unwrap();
|
||||
prop_assert_eq!(&dec_buf[..dec_len], &data[..]);
|
||||
}
|
||||
|
||||
// Encoded length is always within the theoretical maximum.
|
||||
#[test]
|
||||
fn encoded_length_bounded(data in proptest::collection::vec(any::<u8>(), 0..1024)) {
|
||||
let mut enc_buf = vec![0u8; max_encoded_len(data.len()) + 1];
|
||||
let enc_len = encode(&data, &mut enc_buf).unwrap();
|
||||
prop_assert!(enc_len <= max_encoded_len(data.len()));
|
||||
}
|
||||
|
||||
// Decoding random garbage must never panic (returns None on invalid input).
|
||||
#[test]
|
||||
fn decode_never_panics(data in proptest::collection::vec(any::<u8>(), 0..512)) {
|
||||
let mut dec_buf = vec![0u8; data.len() + 256];
|
||||
let _ = decode(&data, &mut dec_buf); // may return None, must not panic
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user