use std::fs;
use std::time::Instant;
use tempfile::TempDir;
use zzz_arc::filter::FileFilter;
use zzz_arc::formats::zstd::ZstdFormat;
use zzz_arc::formats::{CompressionFormat, CompressionOptions};
type Result<T> = anyhow::Result<T>;
fn create_test_file(path: &std::path::Path, size_bytes: usize) -> Result<()> {
let content = "A".repeat(size_bytes);
fs::write(path, content)?;
Ok(())
}
fn create_test_directory(dir: &std::path::Path, num_files: usize, file_size: usize) -> Result<()> {
fs::create_dir_all(dir)?;
for i in 0..num_files {
let file_path = dir.join(format!("file_{i:04}.txt"));
create_test_file(&file_path, file_size)?;
}
Ok(())
}
#[test]
fn test_compression_performance_small_file() -> Result<()> {
let temp_dir = TempDir::new()?;
let source_file = temp_dir.path().join("small.txt");
let archive_path = temp_dir.path().join("small.zst");
create_test_file(&source_file, 1024)?;
let options = CompressionOptions::default();
let filter = FileFilter::new(true, &[])?;
let start = Instant::now();
let stats = ZstdFormat::compress(&source_file, &archive_path, &options, &filter, None)?;
let duration = start.elapsed();
println!("Small file (1KB) compression: {duration:?}");
println!("Compression ratio: {:.2}", stats.compression_ratio);
assert!(
duration.as_millis() < 1000,
"Small file compression took too long: {duration:?}"
);
assert!(stats.compression_ratio < 1.0, "File should have compressed");
Ok(())
}
#[test]
fn test_compression_performance_medium_file() -> Result<()> {
let temp_dir = TempDir::new()?;
let source_file = temp_dir.path().join("medium.txt");
let archive_path = temp_dir.path().join("medium.zst");
create_test_file(&source_file, 1024 * 1024)?;
let options = CompressionOptions::default();
let filter = FileFilter::new(true, &[])?;
let start = Instant::now();
let stats = ZstdFormat::compress(&source_file, &archive_path, &options, &filter, None)?;
let duration = start.elapsed();
println!("Medium file (1MB) compression: {duration:?}");
println!("Compression ratio: {:.2}", stats.compression_ratio);
println!(
"Throughput: {:.2} MB/s",
(stats.input_size as f64 / (1024.0 * 1024.0)) / duration.as_secs_f64()
);
assert!(
duration.as_secs() < 10,
"Medium file compression took too long: {duration:?}"
);
Ok(())
}
#[test]
fn test_compression_levels_performance() -> Result<()> {
let temp_dir = TempDir::new()?;
let source_file = temp_dir.path().join("level_test.txt");
let content = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(2000);
fs::write(&source_file, content)?;
let filter = FileFilter::new(true, &[])?;
let levels = [1, 3, 9, 19, 22];
println!("Compression level performance comparison:");
for level in levels {
let archive_path = temp_dir.path().join(format!("level_{level}.zst"));
let options = CompressionOptions {
level,
..Default::default()
};
let start = Instant::now();
let stats = ZstdFormat::compress(&source_file, &archive_path, &options, &filter, None)?;
let duration = start.elapsed();
println!(
"Level {}: {:?}, ratio: {:.3}, size: {} bytes",
level, duration, stats.compression_ratio, stats.output_size
);
assert!(
duration.as_secs() < 5,
"Level {level} took too long: {duration:?}"
);
assert!(stats.compression_ratio > 0.0 && stats.compression_ratio < 1.0);
}
Ok(())
}
#[test]
fn test_many_small_files_performance() -> Result<()> {
let temp_dir = TempDir::new()?;
let source_dir = temp_dir.path().join("many_files");
let archive_path = temp_dir.path().join("many_files.zst");
create_test_directory(&source_dir, 100, 1024)?;
let options = CompressionOptions::default();
let filter = FileFilter::new(true, &[])?;
let start = Instant::now();
let stats = ZstdFormat::compress(&source_dir, &archive_path, &options, &filter, None)?;
let duration = start.elapsed();
println!("Many files (100 × 1KB) compression: {duration:?}");
let size_kb = stats.input_size / 1024;
println!("Total input size: {size_kb} KB");
println!("Files per second: {:.1}", 100.0 / duration.as_secs_f64());
assert!(
duration.as_secs() < 5,
"Many files compression took too long: {duration:?}"
);
assert_eq!(stats.input_size, 100 * 1024);
Ok(())
}
#[test]
fn test_extraction_performance() -> Result<()> {
let temp_dir = TempDir::new()?;
let source_dir = temp_dir.path().join("extract_test");
let archive_path = temp_dir.path().join("extract_test.zst");
let extract_dir = temp_dir.path().join("extracted");
create_test_directory(&source_dir, 50, 2048)?;
let options = CompressionOptions::default();
let filter = FileFilter::new(true, &[])?;
ZstdFormat::compress(&source_dir, &archive_path, &options, &filter, None)?;
fs::create_dir(&extract_dir)?;
let extract_options = zzz_arc::formats::ExtractionOptions::default();
let start = Instant::now();
ZstdFormat::extract(&archive_path, &extract_dir, &extract_options, None)?;
let duration = start.elapsed();
println!("Extraction (50 × 2KB files): {duration:?}");
println!("Files per second: {:.1}", 50.0 / duration.as_secs_f64());
assert!(
duration.as_secs() < 3,
"Extraction took too long: {duration:?}"
);
assert!(extract_dir.join("extract_test").exists());
let extracted_files: Vec<_> = fs::read_dir(extract_dir.join("extract_test"))?
.collect::<std::result::Result<Vec<_>, std::io::Error>>()?;
assert_eq!(extracted_files.len(), 50, "Not all files were extracted");
Ok(())
}
#[test]
fn test_filtering_performance() -> Result<()> {
let temp_dir = TempDir::new()?;
let source_dir = temp_dir.path().join("filter_test");
let archive_path = temp_dir.path().join("filtered.zst");
fs::create_dir(&source_dir)?;
for i in 0..200 {
let filename = if i % 3 == 0 {
format!("keep_{i}.txt")
} else if i % 3 == 1 {
format!("exclude_{i}.log") } else {
format!("exclude_{i}.tmp") };
let file_path = source_dir.join(filename);
fs::write(file_path, format!("Content {i}"))?;
}
fs::write(source_dir.join(".DS_Store"), "mac junk")?;
fs::write(source_dir.join("thumbs.db"), "windows junk")?;
let options = CompressionOptions::default();
let custom_patterns = vec!["*.log".to_string()];
let filter = FileFilter::new(true, &custom_patterns)?;
let start = Instant::now();
let stats = ZstdFormat::compress(&source_dir, &archive_path, &options, &filter, None)?;
let duration = start.elapsed();
println!("Filtering performance (200 files with mixed patterns): {duration:?}");
println!(
"Compression completed with {} input bytes",
stats.input_size
);
assert!(
duration.as_secs() < 5,
"Filtering took too long: {duration:?}"
);
assert!(
stats.input_size < 3000,
"Too much data ({} bytes) - filtering may not have worked properly",
stats.input_size
);
println!(
"Filtered input size: {} bytes (expected ~670 bytes for ~67 files)",
stats.input_size
);
Ok(())
}
#[test]
fn test_listing_performance() -> Result<()> {
let temp_dir = TempDir::new()?;
let source_dir = temp_dir.path().join("list_test");
let archive_path = temp_dir.path().join("list_test.zst");
create_test_directory(&source_dir, 500, 100)?;
let options = CompressionOptions::default();
let filter = FileFilter::new(true, &[])?;
ZstdFormat::compress(&source_dir, &archive_path, &options, &filter, None)?;
let start = Instant::now();
let entries = ZstdFormat::list(&archive_path)?;
let duration = start.elapsed();
println!("Listing performance (500 files): {duration:?}");
println!(
"Entries per second: {:.1}",
entries.len() as f64 / duration.as_secs_f64()
);
assert!(
duration.as_millis() < 500,
"Listing took too long: {duration:?}"
);
assert!(entries.len() >= 500, "Not all entries were listed");
Ok(())
}
#[test]
fn test_memory_efficiency_large_directory() -> Result<()> {
let temp_dir = TempDir::new()?;
let source_dir = temp_dir.path().join("large_dir");
let archive_path = temp_dir.path().join("large.zst");
fs::create_dir(&source_dir)?;
for i in 0..20 {
let subdir = source_dir.join(format!("subdir_{i}"));
fs::create_dir(&subdir)?;
for j in 0..25 {
let file_path = subdir.join(format!("file_{i}_{j}.txt"));
create_test_file(&file_path, 512)?; }
}
let options = CompressionOptions::default();
let filter = FileFilter::new(true, &[])?;
let start = Instant::now();
let stats = ZstdFormat::compress(&source_dir, &archive_path, &options, &filter, None)?;
let duration = start.elapsed();
println!("Large directory (20 dirs × 25 files) compression: {duration:?}");
println!("Total files processed: 500");
println!(
"Throughput: {:.2} files/sec",
500.0 / duration.as_secs_f64()
);
assert!(
duration.as_secs() < 10,
"Large directory compression took too long: {duration:?}"
);
assert_eq!(stats.input_size, 20 * 25 * 512);
Ok(())
}