pub mod bench_mem;
pub mod compress_strategy;
pub mod config;
pub mod decompress_binding;
pub mod runner;
pub use config::BenchConfig;
use std::fs;
use std::io::{self, Read, Seek, SeekFrom};
use crate::hc::types::LZ4HC_CLEVEL_MAX;
use config::LZ4_MAX_DICT_SIZE;
use runner::{bench_c_level, bench_file_table};
fn synthetic_test(
c_level: i32,
c_level_last: i32,
dict: &[u8],
config: &BenchConfig,
) -> io::Result<()> {
const BENCHED_SIZE: usize = 10_000_000;
let src_buffer = crate::lorem::gen_buffer(BENCHED_SIZE, 0);
bench_c_level(
&src_buffer,
"Lorem ipsum",
c_level,
c_level_last,
config,
dict,
&[BENCHED_SIZE], )
}
fn bench_files_separately(
file_names: &[&str],
c_level: i32,
c_level_last: i32,
dict: &[u8],
config: &BenchConfig,
) -> io::Result<()> {
let c_level = c_level.min(LZ4HC_CLEVEL_MAX);
let c_level_last = c_level_last.min(LZ4HC_CLEVEL_MAX).max(c_level);
let mut bench_error = false;
for file_name in file_names {
if let Err(e) = bench_file_table(&[file_name], c_level, c_level_last, dict, config) {
eprintln!("bench error for {}: {}", file_name, e);
bench_error = true;
}
}
if bench_error {
Err(io::Error::other("benchmark reported errors"))
} else {
Ok(())
}
}
pub fn bench_files(
file_names: &[&str],
c_level: i32,
c_level_last: i32,
dict_file: Option<&str>,
config: &BenchConfig,
) -> io::Result<()> {
let c_level = c_level.min(LZ4HC_CLEVEL_MAX);
let mut c_level_last = c_level_last;
if config.decode_only {
if config.display_level >= 2 {
if config.skip_checksums {
eprintln!(
"Benchmark Decompression of LZ4 Frame _without_ checksum even when present "
);
} else {
eprintln!("Benchmark Decompression of LZ4 Frame + Checksum when present ");
}
}
c_level_last = c_level;
}
c_level_last = c_level_last.min(LZ4HC_CLEVEL_MAX).max(c_level);
if c_level_last > c_level && config.display_level >= 2 {
eprintln!("Benchmarking levels from {} to {}", c_level, c_level_last);
}
let dict_buf: Vec<u8> = if let Some(dict_path) = dict_file {
if config.decode_only {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Error : LZ4 Frame decoder mode not compatible with dictionary yet",
));
}
let meta = fs::metadata(dict_path).map_err(|e| {
io::Error::new(
e.kind(),
format!("Dictionary error : could not stat dictionary file: {}", e),
)
})?;
let dict_file_size = meta.len() as usize;
if dict_file_size == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Dictionary error : could not stat dictionary file",
));
}
let mut f = fs::File::open(dict_path).map_err(|e| {
io::Error::new(
e.kind(),
format!("Dictionary error : could not open dictionary file: {}", e),
)
})?;
let dict_size = if dict_file_size > LZ4_MAX_DICT_SIZE {
let offset = (dict_file_size - LZ4_MAX_DICT_SIZE) as u64;
f.seek(SeekFrom::Start(offset)).map_err(|e| {
io::Error::new(
e.kind(),
format!("Dictionary error : could not seek dictionary file: {}", e),
)
})?;
LZ4_MAX_DICT_SIZE
} else {
dict_file_size
};
let mut buf = vec![0u8; dict_size];
f.read_exact(&mut buf).map_err(|e| {
io::Error::new(
e.kind(),
format!("Dictionary error : could not read dictionary file: {}", e),
)
})?;
buf
} else {
Vec::new()
};
if file_names.is_empty() {
synthetic_test(c_level, c_level_last, &dict_buf, config)
} else if config.bench_separately {
bench_files_separately(file_names, c_level, c_level_last, &dict_buf, config)
} else {
bench_file_table(file_names, c_level, c_level_last, &dict_buf, config)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bench_files_synthetic_ok() {
let mut config = BenchConfig::default();
config.set_nb_seconds(0); config.set_notification_level(0); let result = bench_files(&[], 1, 1, None, &config);
assert!(
result.is_ok(),
"synthetic test should return Ok: {:?}",
result.err()
);
}
#[test]
fn bench_files_missing_file_returns_err() {
let config = BenchConfig::default();
let result = bench_files(&["/nonexistent/file.bin"], 1, 1, None, &config);
assert!(result.is_err(), "nonexistent file should return Err");
}
#[test]
fn bench_files_with_real_file_three_levels() {
use std::io::Write;
let mut tmp = tempfile::NamedTempFile::new().expect("tmp");
let data: Vec<u8> = (0u8..=255).cycle().take(65536).collect();
tmp.write_all(&data).unwrap();
let path = tmp.path().to_str().unwrap().to_owned();
let mut config = BenchConfig::default();
config.set_nb_seconds(0);
config.set_notification_level(0);
let result = bench_files(&[&path], 1, 3, None, &config);
assert!(
result.is_ok(),
"3-level file bench should succeed: {:?}",
result.err()
);
}
#[test]
fn bench_files_separately_flag() {
use std::io::Write;
let mut tmp1 = tempfile::NamedTempFile::new().expect("tmp1");
let mut tmp2 = tempfile::NamedTempFile::new().expect("tmp2");
let data: Vec<u8> = (0u8..=255).cycle().take(4096).collect();
tmp1.write_all(&data).unwrap();
tmp2.write_all(&data).unwrap();
let path1 = tmp1.path().to_str().unwrap().to_owned();
let path2 = tmp2.path().to_str().unwrap().to_owned();
let mut config = BenchConfig::default();
config.set_nb_seconds(0);
config.set_notification_level(0);
config.set_bench_separately(true);
let result = bench_files(&[&path1, &path2], 1, 1, None, &config);
assert!(
result.is_ok(),
"bench_separately should succeed: {:?}",
result.err()
);
}
#[test]
fn bench_files_clamps_level_range() {
let mut config = BenchConfig::default();
config.set_nb_seconds(0);
config.set_notification_level(0);
let result = bench_files(&[], 3, 1, None, &config);
assert!(result.is_ok(), "clamped level range should succeed");
}
#[test]
fn bench_files_missing_dict_returns_err() {
let config = BenchConfig::default();
let result = bench_files(&[], 1, 1, Some("/nonexistent/dict.bin"), &config);
assert!(result.is_err(), "missing dict file should return Err");
}
}