use std::io::{Cursor, Seek, SeekFrom, Write};
use riegeli::{ReaderOptions, RecordReader, RecordWriter, RiegeliError, WriterOptions};
#[cfg(any(feature = "brotli", feature = "zstd"))]
use riegeli::CompressionType;
fn write_to_buf(records: &[&[u8]], options: WriterOptions) -> Result<Vec<u8>, RiegeliError> {
let mut buf = Vec::<u8>::new();
let cursor = Cursor::new(&mut buf);
{
let mut w = RecordWriter::new(cursor, options)?;
for rec in records {
w.write_record(rec)?;
}
w.flush()?;
}
Ok(buf)
}
fn roundtrip(file_data: &[u8]) -> Result<Vec<Vec<u8>>, RiegeliError> {
let cursor = Cursor::new(file_data);
let mut reader = RecordReader::new(cursor, ReaderOptions::new())?;
let mut records = Vec::new();
while let Some(rec) = reader.read_record()? {
records.push(rec);
}
Ok(records)
}
#[test]
#[cfg(feature = "brotli")]
fn criterion_15_1_brotli_level_11_smaller_than_default() {
let mut payload = Vec::with_capacity(100 * 1024);
for i in 0u32..(100 * 1024 / 64) {
let base = (i * 7 + 13) as u8;
for j in 0u8..64 {
payload.push(base.wrapping_add(j / 4));
}
}
let records: &[&[u8]] = &[&payload];
let default_opts = WriterOptions::new().compression(CompressionType::Brotli);
let high_opts = WriterOptions::new()
.compression(CompressionType::Brotli)
.compression_level(11);
let default_file = write_to_buf(records, default_opts).expect("default write ok");
let high_file = write_to_buf(records, high_opts).expect("high-quality write ok");
assert!(
high_file.len() < default_file.len(),
"quality 11 ({} bytes) should be smaller than quality 6 ({} bytes)",
high_file.len(),
default_file.len()
);
let decoded = roundtrip(&high_file).expect("roundtrip ok");
assert_eq!(decoded.len(), 1);
assert_eq!(decoded[0], payload);
}
#[test]
#[cfg(feature = "brotli")]
fn criterion_15_5_bucket_fraction_half_produces_multiple_buckets() {
fn encode_u64(mut v: u64) -> Vec<u8> {
let mut out = Vec::new();
loop {
if v < 0x80 {
out.push(v as u8);
break;
}
out.push((v as u8 & 0x7f) | 0x80);
v >>= 7;
}
out
}
let mut records: Vec<Vec<u8>> = Vec::new();
for i in 0u32..1000 {
let mut rec = Vec::new();
rec.push(0x08); rec.extend_from_slice(&encode_u64(i as u64));
rec.push(0x15); rec.extend_from_slice(&i.to_le_bytes());
records.push(rec);
}
let refs: Vec<&[u8]> = records.iter().map(|r| r.as_slice()).collect();
let opts_multi = WriterOptions::new()
.compression(CompressionType::Brotli)
.transpose(true)
.chunk_size(1 << 20)
.bucket_fraction(0.001);
let opts_single = WriterOptions::new()
.compression(CompressionType::Brotli)
.transpose(true)
.chunk_size(1 << 20)
.bucket_fraction(1.0);
let multi_file = write_to_buf(&refs, opts_multi).expect("multi-bucket write ok");
let single_file = write_to_buf(&refs, opts_single).expect("single-bucket write ok");
assert_ne!(
multi_file, single_file,
"multi-bucket and single-bucket files should differ"
);
let decoded = roundtrip(&multi_file).expect("multi-bucket roundtrip ok");
assert_eq!(decoded.len(), 1000);
for (got, expected) in decoded.iter().zip(records.iter()) {
assert_eq!(got, expected, "record mismatch in multi-bucket roundtrip");
}
}
#[test]
fn criterion_15_6_bucket_fraction_zero_clamps_to_min() {
fn encode_u64(mut v: u64) -> Vec<u8> {
let mut out = Vec::new();
loop {
if v < 0x80 {
out.push(v as u8);
break;
}
out.push((v as u8 & 0x7f) | 0x80);
v >>= 7;
}
out
}
let records: Vec<Vec<u8>> = (0u32..10)
.map(|i| {
let mut rec = Vec::new();
rec.push(0x08);
rec.extend_from_slice(&encode_u64(i as u64));
rec
})
.collect();
let refs: Vec<&[u8]> = records.iter().map(|r| r.as_slice()).collect();
let opts = WriterOptions::new().transpose(true).bucket_fraction(0.0);
let file = write_to_buf(&refs, opts).expect("bucket_fraction=0.0 should not error");
let decoded = roundtrip(&file).expect("roundtrip ok");
assert_eq!(decoded.len(), records.len());
}
#[test]
fn criterion_15_7_final_padding_aligns_after_flush() {
use std::sync::{Arc, Mutex};
struct SharedVec(Arc<Mutex<Vec<u8>>>);
impl Write for SharedVec {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0.lock().unwrap().extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl Seek for SharedVec {
fn seek(&mut self, _pos: SeekFrom) -> std::io::Result<u64> {
Ok(self.0.lock().unwrap().len() as u64)
}
}
const ALIGNMENT: u64 = 65536;
let shared = Arc::new(Mutex::new(Vec::<u8>::new()));
let sv = SharedVec(Arc::clone(&shared));
let opts = WriterOptions::new().final_padding(ALIGNMENT);
let mut w = RecordWriter::new(sv, opts).expect("writer ok");
for i in 0u32..10 {
w.write_record(format!("record-{i}").as_bytes())
.expect("write ok");
}
w.flush().expect("flush ok");
let size1 = shared.lock().unwrap().len() as u64;
assert_eq!(
size1 % ALIGNMENT,
0,
"after first flush: file size {size1} not multiple of {ALIGNMENT}"
);
for i in 10u32..20 {
w.write_record(format!("record-{i}").as_bytes())
.expect("write ok");
}
w.flush().expect("flush ok");
let size2 = shared.lock().unwrap().len() as u64;
assert_eq!(
size2 % ALIGNMENT,
0,
"after second flush: file size {size2} not multiple of {ALIGNMENT}"
);
assert!(size2 >= size1, "file should only grow");
drop(w);
let final_data = shared.lock().unwrap().clone();
let decoded = roundtrip(&final_data).expect("roundtrip ok");
assert_eq!(decoded.len(), 20);
}
#[test]
fn criterion_15_8_window_log_with_none_compression_is_error() {
let opts = WriterOptions::new()
.compression(CompressionType::None)
.window_log(Some(15));
struct NullWriter;
impl Write for NullWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl Seek for NullWriter {
fn seek(&mut self, _pos: SeekFrom) -> std::io::Result<u64> {
Ok(0)
}
}
let result: Result<RecordWriter<NullWriter>, RiegeliError> =
RecordWriter::new(NullWriter, opts);
assert!(
result.is_err(),
"window_log with CompressionType::None should return an error"
);
if let Err(err) = result {
assert!(
matches!(err, RiegeliError::MalformedData(_)),
"expected MalformedData error, got: {err:?}"
);
}
}
#[test]
#[cfg(feature = "snappy")]
fn criterion_15_8_window_log_with_snappy_is_error() {
let opts = WriterOptions::new()
.compression(CompressionType::Snappy)
.window_log(Some(15));
struct NullWriter;
impl Write for NullWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl Seek for NullWriter {
fn seek(&mut self, _pos: SeekFrom) -> std::io::Result<u64> {
Ok(0)
}
}
let result = RecordWriter::new(NullWriter, opts);
assert!(
result.is_err(),
"window_log with CompressionType::Snappy should return an error"
);
}