#![cfg(feature = "compression")]
#![cfg(not(feature = "no_std"))]
mod support;
use mbrotli::io::FinishError;
use mbrotli::{Compressor, Quality, StreamConfig};
use std::io::Write;
use support::{
CParams, IMPLEMENTED_QUALITIES, c_compress_flushing, c_decompress, c_decompress_partial,
encoder,
};
const LGWIN: u8 = 22;
fn flush_between_chunks(encoder: &mut Compressor, chunks: &[&[u8]]) -> Vec<u8> {
let mut sink = encoder
.writer(Vec::new(), StreamConfig::default())
.expect("a legal stream");
for (index, chunk) in chunks.iter().enumerate() {
sink.write_all(chunk).expect("write failed");
if index + 1 != chunks.len() {
sink.flush().expect("flush failed");
}
}
sink.finish()
.map_err(FinishError::into_error)
.expect("finish failed")
}
fn c_params(quality: Quality) -> CParams {
CParams::new(std::ffi::c_int::from(quality.get()), LGWIN.into())
}
fn chunk_sets() -> Vec<Vec<Vec<u8>>> {
let long = b"the quick brown fox jumps over the lazy dog. ".repeat(400);
vec![
vec![
b"hello hello hello ".to_vec(),
b"world world world".to_vec(),
],
vec![b"payload payload payload".to_vec(), Vec::new()],
vec![Vec::new(), b"payload payload payload".to_vec()],
vec![
b"first".to_vec(),
Vec::new(),
Vec::new(),
b"second".to_vec(),
],
vec![b"a".to_vec(), b"b".to_vec()],
vec![long.clone(), long],
vec![
(0u8..=255).cycle().take(9000).collect(),
(0u8..=255).rev().cycle().take(9000).collect(),
],
vec![Vec::new(), Vec::new()],
]
}
#[test]
fn flushed_prefix_decodes_before_the_stream_is_finished() {
for quality in IMPLEMENTED_QUALITIES {
for chunks in chunk_sets() {
let Some((_, head)) = chunks.split_last() else {
continue;
};
let expected: Vec<u8> = head.concat();
let mut encoder = encoder(quality, LGWIN);
let mut sink = encoder
.writer(Vec::new(), StreamConfig::default())
.expect("a legal stream");
for chunk in head {
sink.write_all(chunk).expect("write failed");
}
sink.flush().expect("flush failed");
let partial = sink.get_ref().clone();
let decoded = c_decompress_partial(&partial, expected.len() + 1024)
.expect("the decoder rejected a flushed prefix");
assert_eq!(
decoded, expected,
"q{quality:?}: a flushed prefix did not decode to everything written"
);
}
}
}
#[test]
fn flushing_does_not_break_the_finished_stream() {
for quality in IMPLEMENTED_QUALITIES {
for chunks in chunk_sets() {
let borrowed: Vec<&[u8]> = chunks.iter().map(Vec::as_slice).collect();
let expected: Vec<u8> = chunks.concat();
let compressed = flush_between_chunks(&mut encoder(quality, LGWIN), &borrowed);
let decoded = c_decompress(&compressed, expected.len().max(1))
.expect("the decoder rejected a finished stream that had been flushed");
assert_eq!(
decoded, expected,
"q{quality:?}: a flushed stream did not round-trip"
);
}
}
}
#[test]
fn flushing_matches_the_reference_byte_for_byte() {
for quality in IMPLEMENTED_QUALITIES {
for chunks in chunk_sets() {
let borrowed: Vec<&[u8]> = chunks.iter().map(Vec::as_slice).collect();
let ours = flush_between_chunks(&mut encoder(quality, LGWIN), &borrowed);
let theirs = c_compress_flushing(c_params(quality), &borrowed);
assert_eq!(
ours,
theirs,
"q{quality:?}: flushed output differed from the reference for chunks {:?}",
chunks.iter().map(Vec::len).collect::<Vec<_>>()
);
}
}
}
#[test]
fn flushing_without_input_still_emits_the_header() {
for quality in IMPLEMENTED_QUALITIES {
let mut encoder = encoder(quality, LGWIN);
let mut sink = encoder
.writer(Vec::new(), StreamConfig::default())
.expect("a legal stream");
sink.flush().expect("flush failed");
assert!(
!sink.get_ref().is_empty(),
"q{quality:?}: an early flush emitted nothing"
);
let theirs = c_compress_flushing(c_params(quality), &[&[][..], &[][..]]);
let finished = sink
.finish()
.map_err(FinishError::into_error)
.expect("finish failed");
assert_eq!(
finished, theirs,
"q{quality:?}: an early flush differed from the reference"
);
}
}
#[test]
fn a_flush_with_nothing_pending_is_idempotent() {
for quality in IMPLEMENTED_QUALITIES {
let mut encoder = encoder(quality, LGWIN);
let mut sink = encoder
.writer(Vec::new(), StreamConfig::default())
.expect("a legal stream");
sink.write_all(b"some payload worth compressing")
.expect("write failed");
sink.flush().expect("first flush failed");
let after_one = sink.get_ref().len();
sink.flush().expect("second flush failed");
assert_eq!(
sink.get_ref().len(),
after_one,
"q{quality:?}: a redundant flush wrote bytes"
);
}
}
#[test]
fn a_stream_that_is_never_flushed_is_unchanged() {
for quality in IMPLEMENTED_QUALITIES {
let data = b"the quick brown fox jumps over the lazy dog. ".repeat(200);
let mut encoder = encoder(quality, LGWIN);
let mut sink = encoder
.writer(Vec::new(), StreamConfig::default())
.expect("a legal stream");
sink.write_all(&data).expect("write failed");
let streamed = sink
.finish()
.map_err(FinishError::into_error)
.expect("finish failed");
let theirs = c_compress_flushing(c_params(quality), &[&data[..]]);
assert_eq!(
streamed, theirs,
"q{quality:?}: an unflushed stream differed from the reference"
);
}
}