use proptest::prelude::*;
use serializer::zero::{
DxZeroHeader, HeaderError, MAGIC, VERSION,
FLAG_LITTLE_ENDIAN, FLAG_HAS_HEAP, FLAG_HAS_INTERN, FLAG_HAS_LENGTH_TABLE,
};
fn invalid_magic_bytes() -> impl Strategy<Value = Vec<u8>> {
prop::collection::vec(prop::num::u8::ANY, 4..20)
.prop_filter("not valid magic", |bytes| {
bytes.len() >= 2 && (bytes[0] != MAGIC[0] || bytes[1] != MAGIC[1])
})
}
fn invalid_version_bytes() -> impl Strategy<Value = Vec<u8>> {
(2u8..=255u8)
.prop_filter("not current version", |&v| v != VERSION)
.prop_map(|version| {
vec![MAGIC[0], MAGIC[1], version, FLAG_LITTLE_ENDIAN]
})
}
fn reserved_flags_bytes() -> impl Strategy<Value = Vec<u8>> {
(0b0001_0000u8..=0b1111_0000u8)
.prop_map(|reserved| {
vec![MAGIC[0], MAGIC[1], VERSION, FLAG_LITTLE_ENDIAN | reserved]
})
}
fn valid_header_bytes() -> impl Strategy<Value = Vec<u8>> {
prop::collection::vec(prop::num::u8::ANY, 0..100)
.prop_map(|mut extra| {
let mut bytes = vec![MAGIC[0], MAGIC[1], VERSION, FLAG_LITTLE_ENDIAN];
bytes.append(&mut extra);
bytes
})
}
fn invalid_header_bytes() -> impl Strategy<Value = Vec<u8>> {
prop_oneof![
invalid_magic_bytes(),
invalid_version_bytes(),
reserved_flags_bytes(),
prop::collection::vec(prop::num::u8::ANY, 0..4),
]
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn prop_header_validation_invalid_magic(bytes in invalid_magic_bytes()) {
let result = DxZeroHeader::from_bytes(&bytes);
prop_assert!(
result.is_err(),
"Invalid magic should be rejected: {:?}", bytes
);
if let Err(e) = result {
match e {
HeaderError::InvalidMagic { expected, found } => {
prop_assert_eq!(expected, MAGIC);
if bytes.len() >= 2 {
prop_assert_eq!(found, [bytes[0], bytes[1]]);
}
}
HeaderError::BufferTooSmall => {
prop_assert!(bytes.len() < 4);
}
_ => {
}
}
}
}
#[test]
fn prop_header_validation_invalid_version(bytes in invalid_version_bytes()) {
let result = DxZeroHeader::from_bytes(&bytes);
prop_assert!(
result.is_err(),
"Invalid version should be rejected: {:?}", bytes
);
if let Err(HeaderError::UnsupportedVersion { supported, found }) = result {
prop_assert_eq!(supported, VERSION);
prop_assert_ne!(found, VERSION);
}
}
#[test]
fn prop_header_validation_reserved_flags(bytes in reserved_flags_bytes()) {
let result = DxZeroHeader::from_bytes(&bytes);
prop_assert!(
result.is_err(),
"Reserved flags should be rejected: {:?}", bytes
);
prop_assert!(
matches!(result, Err(HeaderError::ReservedFlagsSet)),
"Expected ReservedFlagsSet error, got {:?}", result
);
}
#[test]
fn prop_header_validation_valid(bytes in valid_header_bytes()) {
let result = DxZeroHeader::from_bytes(&bytes);
prop_assert!(
result.is_ok(),
"Valid header should be accepted: {:?}", result.err()
);
let header = result.unwrap();
prop_assert_eq!(header.magic, MAGIC);
prop_assert_eq!(header.version, VERSION);
}
#[test]
fn prop_heap_bounds_checking(
buffer_size in 10usize..100,
heap_offset in 0u32..200,
heap_length in 1u32..100
) {
let mut buffer = vec![0u8; buffer_size];
buffer[0] = MAGIC[0];
buffer[1] = MAGIC[1];
buffer[2] = VERSION;
buffer[3] = FLAG_LITTLE_ENDIAN | FLAG_HAS_HEAP;
let end_offset = heap_offset as usize + heap_length as usize;
let is_out_of_bounds = end_offset > buffer_size;
let header_result = DxZeroHeader::from_bytes(&buffer);
prop_assert!(header_result.is_ok());
if is_out_of_bounds {
prop_assert!(
end_offset > buffer_size,
"Out of bounds check failed: offset={}, length={}, buffer_size={}",
heap_offset, heap_length, buffer_size
);
}
}
#[test]
fn prop_header_roundtrip(
has_heap in any::<bool>(),
has_intern in any::<bool>(),
has_length_table in any::<bool>()
) {
let mut flags = FLAG_LITTLE_ENDIAN;
if has_heap { flags |= FLAG_HAS_HEAP; }
if has_intern { flags |= FLAG_HAS_INTERN; }
if has_length_table { flags |= FLAG_HAS_LENGTH_TABLE; }
let header = DxZeroHeader::with_flags(flags);
let mut bytes = [0u8; 4];
header.write_to(&mut bytes);
let parsed = DxZeroHeader::from_bytes(&bytes);
prop_assert!(parsed.is_ok());
let parsed = parsed.unwrap();
prop_assert_eq!(parsed.magic, header.magic);
prop_assert_eq!(parsed.version, header.version);
prop_assert_eq!(parsed.has_heap(), has_heap);
prop_assert_eq!(parsed.has_intern_table(), has_intern);
prop_assert_eq!(parsed.has_length_table(), has_length_table);
}
}
#[cfg(test)]
mod unit_tests {
use super::*;
#[test]
fn test_valid_header() {
let bytes = [MAGIC[0], MAGIC[1], VERSION, FLAG_LITTLE_ENDIAN];
let result = DxZeroHeader::from_bytes(&bytes);
assert!(result.is_ok());
}
#[test]
fn test_invalid_magic_first_byte() {
let bytes = [0x00, MAGIC[1], VERSION, FLAG_LITTLE_ENDIAN];
let result = DxZeroHeader::from_bytes(&bytes);
assert!(matches!(result, Err(HeaderError::InvalidMagic { .. })));
}
#[test]
fn test_invalid_magic_second_byte() {
let bytes = [MAGIC[0], 0x00, VERSION, FLAG_LITTLE_ENDIAN];
let result = DxZeroHeader::from_bytes(&bytes);
assert!(matches!(result, Err(HeaderError::InvalidMagic { .. })));
}
#[test]
fn test_unsupported_version() {
let bytes = [MAGIC[0], MAGIC[1], 0x99, FLAG_LITTLE_ENDIAN];
let result = DxZeroHeader::from_bytes(&bytes);
assert!(matches!(result, Err(HeaderError::UnsupportedVersion { .. })));
}
#[test]
fn test_reserved_flags() {
let bytes = [MAGIC[0], MAGIC[1], VERSION, FLAG_LITTLE_ENDIAN | 0b1000_0000];
let result = DxZeroHeader::from_bytes(&bytes);
assert!(matches!(result, Err(HeaderError::ReservedFlagsSet)));
}
#[test]
fn test_buffer_too_small() {
let bytes = [MAGIC[0], MAGIC[1]]; let result = DxZeroHeader::from_bytes(&bytes);
assert!(matches!(result, Err(HeaderError::BufferTooSmall)));
}
#[test]
fn test_empty_buffer() {
let bytes: [u8; 0] = [];
let result = DxZeroHeader::from_bytes(&bytes);
assert!(matches!(result, Err(HeaderError::BufferTooSmall)));
}
#[test]
fn test_header_with_all_valid_flags() {
let flags = FLAG_LITTLE_ENDIAN | FLAG_HAS_HEAP | FLAG_HAS_INTERN | FLAG_HAS_LENGTH_TABLE;
let bytes = [MAGIC[0], MAGIC[1], VERSION, flags];
let result = DxZeroHeader::from_bytes(&bytes);
assert!(result.is_ok());
let header = result.unwrap();
assert!(header.has_heap());
assert!(header.has_intern_table());
assert!(header.has_length_table());
}
}