use miniz_oxide::deflate::compress_to_vec;
use miniz_oxide::inflate::{decompress_to_vec, decompress_to_vec_with_limit};
use proptest::prelude::*;
use super::*;
use crate::Error;
const LIMIT: usize = 32 << 20;
fn plaintext() -> impl Strategy<Value = Vec<u8>> {
prop_oneof![
prop::collection::vec(any::<u8>(), 0..2048),
prop::collection::vec(0u8..4, 0..8192),
(prop::collection::vec(any::<u8>(), 1..300), 1usize..120)
.prop_map(|(chunk, n)| chunk.repeat(n)),
]
}
fn assert_agree(input: &[u8]) -> std::result::Result<(), TestCaseError> {
let ours = inflate(input, LIMIT);
let theirs = decompress_to_vec_with_limit(input, LIMIT);
match (&ours, &theirs) {
(Ok(a), Ok(b)) => prop_assert_eq!(&a[..], &b[..]),
(Err(_), Err(_)) => {}
(Err(Error::InvalidFormat(SINGLE_LENGTH_CODE)), Ok(_)) => {}
_ => prop_assert!(
false,
"disagree: ours {:?}, miniz {:?}",
ours.as_ref().map(|v| v.len()),
theirs.as_ref().map(|v| v.len()).map_err(|e| e.status)
),
}
Ok(())
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(1000))]
#[test]
fn miniz_inflates_our_deflate(input in plaintext()) {
let compressed = deflate(&input).unwrap();
prop_assert_eq!(decompress_to_vec(&compressed).unwrap(), input);
}
#[test]
fn we_inflate_miniz_at_every_level(input in plaintext()) {
for level in 0..=9u8 {
let compressed = compress_to_vec(&input, level);
let out = inflate(&compressed, input.len()).unwrap();
prop_assert_eq!(&out[..], &input[..], "level {}", level);
}
}
#[test]
fn agree_on_random_bytes(input in prop::collection::vec(any::<u8>(), 0..2048)) {
assert_agree(&input)?;
}
#[test]
fn agree_on_corrupted_streams(
input in plaintext(),
level in 0..=9u8,
at in any::<prop::sample::Index>(),
bit in 0..8u32,
byte in any::<u8>(),
kind in 0..3u8,
) {
let mut stream = compress_to_vec(&input, level);
let i = at.index(stream.len());
match kind {
0 => stream[i] ^= 1 << bit,
1 => stream[i] = byte,
_ => stream.truncate(i),
}
assert_agree(&stream)?;
}
}
#[test]
fn we_inflate_miniz_empty_input() {
for level in 0..=9u8 {
let compressed = compress_to_vec(b"", level);
assert!(inflate(&compressed, 0).unwrap().is_empty(), "level {level}");
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(1000))]
#[test]
fn agree_on_random_dynamic_headers(mut input in prop::collection::vec(any::<u8>(), 1..512)) {
input[0] = (input[0] & 0xF8) | 0b101;
assert_agree(&input)?;
}
#[test]
fn agree_on_random_nonfinal_dynamic_headers(mut input in prop::collection::vec(any::<u8>(), 1..512)) {
input[0] = (input[0] & 0xF8) | 0b100;
assert_agree(&input)?;
}
#[test]
fn agree_on_header_corruption(
edits in prop::collection::vec((0usize..64, any::<u8>()), 1..6),
repeat in 1usize..40,
) {
let text = b"It was the best of times, it was the worst of times, ".repeat(repeat);
let mut stream = compress_to_vec(&text, 9);
for (at, byte) in edits {
let i = at.min(stream.len() - 1);
stream[i] = byte;
}
assert_agree(&stream)?;
}
#[test]
fn expansion_bound_guard_never_fires(
mut input in prop::collection::vec(any::<u8>(), 1..2048),
mutate_real in any::<bool>(),
repeat in 1usize..200,
at in any::<prop::sample::Index>(),
byte in any::<u8>(),
) {
if mutate_real {
let text = b"abcabcabcabd".repeat(repeat);
input = compress_to_vec(&text, 6);
let i = at.index(input.len());
input[i] = byte;
} else {
input[0] = (input[0] & 0xF8) | 0b101;
}
let mut capacity = inflate::Capacity::default();
let result = inflate::inflate_tracked(&input, usize::MAX, &mut capacity);
prop_assert!(!matches!(result, Err(Error::InvalidFormat(EXPANSION_BOUND))));
if let Ok(out) = &result {
prop_assert!(out.len() <= inflate::max_expansion(input.len()));
}
prop_assert!(capacity.initial <= inflate::INITIAL_RESERVATION);
prop_assert!(capacity.last <= inflate::max_expansion(input.len()));
}
}
#[test]
fn stored_blocks_and_tiny_blocks_stay_within_the_allocation() {
let mut full = vec![1, 0xFF, 0xFF, 0x00, 0x00];
full.extend(std::iter::repeat_n(7u8, 65_535));
let mut capacity = inflate::Capacity::default();
let out = inflate::inflate_tracked(&full, usize::MAX, &mut capacity).unwrap();
assert_eq!(out.len(), 65_535);
assert_eq!(capacity.last, capacity.initial);
let mut tiny = Vec::new();
for _ in 0..10_000 {
tiny.extend_from_slice(&[0, 0, 0, 0xFF, 0xFF]);
}
tiny.extend_from_slice(&[1, 0, 0, 0xFF, 0xFF]);
let mut capacity = inflate::Capacity::default();
let out = inflate::inflate_tracked(&tiny, usize::MAX, &mut capacity).unwrap();
assert!(out.is_empty());
assert_eq!(capacity.last, capacity.initial);
assert_eq!(inflate(&tiny, 0).unwrap().len(), 0);
}