#![cfg(feature = "lzma2")]
use std::io::Cursor;
use zesven::read::Archive;
use zesven::write::{WriteOptions, Writer};
use zesven::{ArchivePath, MemoryLimit, Threads};
fn payload(len: usize) -> Vec<u8> {
let mut data = Vec::with_capacity(len);
let mut state = 0x5DEE_CE66_D3A5_1B9Cu64;
while data.len() < len {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
data.extend_from_slice(
b"resource limits change how the work is done, not what it produces ",
);
data.extend_from_slice(&state.to_le_bytes());
}
data.truncate(len);
data
}
fn archive(options: WriteOptions, data: &[u8], entries: usize) -> Vec<u8> {
let mut writer = Writer::create(Cursor::new(Vec::new()))
.unwrap()
.options(options);
for i in 0..entries {
writer
.add_bytes(ArchivePath::new(&format!("{i:02}.bin")).unwrap(), data)
.unwrap();
}
writer.finish_into_inner().unwrap().1.into_inner()
}
#[test]
fn test_single_thread_output_does_not_depend_on_the_machine() {
let data = payload(2 * 1024 * 1024);
let single = || {
archive(
WriteOptions::new()
.threads(Threads::Single)
.deterministic(true),
&data,
6,
)
};
assert_eq!(single(), single());
let explicit_one = archive(
WriteOptions::new()
.threads(Threads::count_or_single(1))
.deterministic(true),
&data,
6,
);
assert_eq!(single(), explicit_one);
}
#[test]
fn test_output_does_not_depend_on_how_many_workers_run() {
let data = payload(3 * 1024 * 1024);
for solid in [false, true] {
let build = |threads, limit| {
let mut options = WriteOptions::new().threads(threads).memory_limit(limit);
if solid {
options = options.solid();
}
archive(options, &data, 6)
};
let many = build(Threads::count_or_single(8), MemoryLimit::Auto);
let few = build(Threads::count_or_single(2), MemoryLimit::Auto);
let starved = build(Threads::count_or_single(8), MemoryLimit::bytes_or_auto(1));
assert_eq!(
many, few,
"solid={solid}: eight threads and two produced different archives",
);
assert_eq!(
many, starved,
"solid={solid}: a tight memory budget changed the archive, not just the speed",
);
}
}
#[test]
fn test_single_thread_compresses_no_worse_than_many() {
let data = payload(4 * 1024 * 1024);
let one = archive(
WriteOptions::new().solid().threads(Threads::Single),
&data,
8,
);
let many = archive(WriteOptions::new().solid().threads(Threads::Auto), &data, 8);
assert!(
one.len() <= many.len(),
"one thread produced {} bytes, more than the {} many threads produced; \
the sequential path should compress at least as well",
one.len(),
many.len(),
);
}
#[test]
fn test_every_resource_setting_round_trips() {
let data = payload(1024 * 1024);
let settings = [
WriteOptions::new().threads(Threads::Single),
WriteOptions::new().threads(Threads::count_or_single(2)),
WriteOptions::new().memory_limit(MemoryLimit::bytes_or_auto(32 * 1024 * 1024)),
WriteOptions::new()
.solid()
.threads(Threads::count_or_single(3)),
WriteOptions::new()
.solid()
.memory_limit(MemoryLimit::bytes_or_auto(64 * 1024 * 1024)),
];
for (index, options) in settings.into_iter().enumerate() {
let bytes = archive(options, &data, 4);
let mut opened = Archive::open(Cursor::new(bytes)).unwrap();
for entry in 0..4 {
assert_eq!(
opened.extract_to_vec(&format!("{entry:02}.bin")).unwrap(),
data,
"setting {index} did not round-trip entry {entry}",
);
}
}
}
#[test]
fn test_a_tight_memory_limit_still_writes() {
let data = payload(2 * 1024 * 1024);
let bytes = archive(
WriteOptions::new()
.level(9)
.unwrap()
.memory_limit(MemoryLimit::bytes_or_auto(1)),
&data,
4,
);
let mut opened = Archive::open(Cursor::new(bytes)).unwrap();
assert_eq!(opened.extract_to_vec("00.bin").unwrap(), data);
}