use std::fs;
use std::io::Cursor;
use std::path::Path;
use std::process::Command;
use std::time::Instant;
use tempfile::TempDir;
fn xorriso_available() -> bool {
Command::new("xorriso")
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
fn create_test_content(dir: &Path, file_count: usize, file_size: usize) {
for i in 0..file_count {
let filename = format!("file_{:04}.dat", i);
let content: Vec<u8> = (0..file_size).map(|j| ((i + j) % 256) as u8).collect();
fs::write(dir.join(&filename), &content).unwrap();
}
}
fn create_nested_dirs(dir: &Path, depth: usize, breadth: usize) {
fn create_recursive(dir: &Path, depth: usize, breadth: usize, prefix: &str) {
if depth == 0 {
return;
}
for i in 0..breadth {
let subdir = dir.join(format!("{}dir_{}", prefix, i));
fs::create_dir_all(&subdir).unwrap();
fs::write(
subdir.join("data.txt"),
format!("Depth {} index {}", depth, i),
)
.unwrap();
create_recursive(&subdir, depth - 1, breadth, &format!("{}_{}", prefix, i));
}
}
create_recursive(dir, depth, breadth, "");
}
fn create_iso_with_xorriso(content_dir: &Path, iso_path: &Path) -> bool {
let output = Command::new("xorriso")
.args([
"-as",
"mkisofs",
"-o",
iso_path.to_str().unwrap(),
"-V",
"BENCHMARK",
"-J",
"-R",
content_dir.to_str().unwrap(),
])
.output()
.expect("Failed to run xorriso");
output.status.success()
}
#[test]
fn bench_iso_open_small() {
if !xorriso_available() {
eprintln!("Skipping benchmark: xorriso not available");
return;
}
let temp_dir = TempDir::new().unwrap();
let content_dir = temp_dir.path().join("content");
let iso_path = temp_dir.path().join("small.iso");
fs::create_dir(&content_dir).unwrap();
create_test_content(&content_dir, 10, 1024);
assert!(create_iso_with_xorriso(&content_dir, &iso_path));
let iso_data = fs::read(&iso_path).unwrap();
let iso_size = iso_data.len();
let iterations = 100;
let start = Instant::now();
for _ in 0..iterations {
let cursor = Cursor::new(iso_data.clone());
let _image = hadris_iso::read::IsoImage::open(cursor).unwrap();
}
let elapsed = start.elapsed();
println!("\n=== Small ISO Open Benchmark ===");
println!("ISO size: {} bytes", iso_size);
println!("Iterations: {}", iterations);
println!("Total time: {:?}", elapsed);
println!("Average time per open: {:?}", elapsed / iterations);
}
#[test]
fn bench_iso_open_large() {
if !xorriso_available() {
eprintln!("Skipping benchmark: xorriso not available");
return;
}
let temp_dir = TempDir::new().unwrap();
let content_dir = temp_dir.path().join("content");
let iso_path = temp_dir.path().join("large.iso");
fs::create_dir(&content_dir).unwrap();
create_test_content(&content_dir, 100, 65536);
assert!(create_iso_with_xorriso(&content_dir, &iso_path));
let iso_data = fs::read(&iso_path).unwrap();
let iso_size = iso_data.len();
let iterations = 50;
let start = Instant::now();
for _ in 0..iterations {
let cursor = Cursor::new(iso_data.clone());
let _image = hadris_iso::read::IsoImage::open(cursor).unwrap();
}
let elapsed = start.elapsed();
println!("\n=== Large ISO Open Benchmark ===");
println!(
"ISO size: {} bytes ({:.2} MB)",
iso_size,
iso_size as f64 / 1024.0 / 1024.0
);
println!("Iterations: {}", iterations);
println!("Total time: {:?}", elapsed);
println!("Average time per open: {:?}", elapsed / iterations);
}
#[test]
fn bench_directory_traversal() {
if !xorriso_available() {
eprintln!("Skipping benchmark: xorriso not available");
return;
}
let temp_dir = TempDir::new().unwrap();
let content_dir = temp_dir.path().join("content");
let iso_path = temp_dir.path().join("nested.iso");
fs::create_dir(&content_dir).unwrap();
create_nested_dirs(&content_dir, 4, 3);
assert!(create_iso_with_xorriso(&content_dir, &iso_path));
let iso_data = fs::read(&iso_path).unwrap();
let iso_size = iso_data.len();
let cursor = Cursor::new(iso_data.clone());
let image = hadris_iso::read::IsoImage::open(cursor).unwrap();
let iterations = 100;
let start = Instant::now();
let mut total_entries = 0;
for _ in 0..iterations {
let root = image.root_dir();
let dir = root.iter(&image);
for entry in dir.entries() {
let _ = entry.unwrap();
total_entries += 1;
}
}
let elapsed = start.elapsed();
println!("\n=== Directory Traversal Benchmark ===");
println!("ISO size: {} bytes", iso_size);
println!("Iterations: {}", iterations);
println!("Total entries read: {}", total_entries);
println!("Entries per iteration: {}", total_entries / iterations);
println!("Total time: {:?}", elapsed);
println!(
"Average time per traversal: {:?}",
elapsed / iterations as u32
);
}
#[test]
fn bench_volume_descriptor_parsing() {
if !xorriso_available() {
eprintln!("Skipping benchmark: xorriso not available");
return;
}
let temp_dir = TempDir::new().unwrap();
let content_dir = temp_dir.path().join("content");
let iso_path = temp_dir.path().join("vd.iso");
fs::create_dir(&content_dir).unwrap();
fs::write(content_dir.join("test.txt"), "test").unwrap();
assert!(create_iso_with_xorriso(&content_dir, &iso_path));
let iso_data = fs::read(&iso_path).unwrap();
let iterations = 500;
let start = Instant::now();
for _ in 0..iterations {
let cursor = Cursor::new(iso_data.clone());
let image = hadris_iso::read::IsoImage::open(cursor).unwrap();
let _pvd = image.read_pvd();
let mut count = 0;
for _ in image.read_volume_descriptors() {
count += 1;
}
assert!(count > 0);
}
let elapsed = start.elapsed();
println!("\n=== Volume Descriptor Parsing Benchmark ===");
println!("Iterations: {}", iterations);
println!("Total time: {:?}", elapsed);
println!("Average time per parse: {:?}", elapsed / iterations);
}
#[test]
fn bench_joliet_encoding_decoding() {
use hadris_iso::joliet::{decode_joliet_name, encode_joliet_name};
let test_strings = [
"simple.txt",
"test_file_with_longer_name.txt",
"日本語ファイル.txt",
"文件名称很长的中文文档.doc",
"한국어_파일명.xlsx",
"mixed_混合_ファイル.pdf",
];
let iterations = 10000;
let start = Instant::now();
for _ in 0..iterations {
for s in &test_strings {
let _ = encode_joliet_name(s);
}
}
let encode_elapsed = start.elapsed();
let encoded: Vec<Vec<u8>> = test_strings.iter().map(|s| encode_joliet_name(s)).collect();
let start = Instant::now();
for _ in 0..iterations {
for enc in &encoded {
let _ = decode_joliet_name(enc);
}
}
let decode_elapsed = start.elapsed();
println!("\n=== Joliet Encoding/Decoding Benchmark ===");
println!("Test strings: {}", test_strings.len());
println!("Iterations: {}", iterations);
println!("Total encodes: {}", iterations * test_strings.len());
println!("Encode time: {:?}", encode_elapsed);
println!("Decode time: {:?}", decode_elapsed);
println!(
"Avg encode per string: {:?}",
encode_elapsed / (iterations * test_strings.len()) as u32
);
println!(
"Avg decode per string: {:?}",
decode_elapsed / (iterations * test_strings.len()) as u32
);
}
#[test]
fn bench_rrip_builder() {
use hadris_iso::rrip::RripBuilder;
let iterations = 10000;
let start = Instant::now();
for i in 0..iterations {
let mut builder = RripBuilder::new();
builder
.add_sp(0)
.add_rrip_er()
.add_px(0o100644, 1, 1000, 1000, i as u32)
.add_nm(b"test_file.txt")
.add_st();
let _ = builder.build();
}
let elapsed = start.elapsed();
println!("\n=== RRIP Builder Benchmark ===");
println!("Iterations: {}", iterations);
println!("Total time: {:?}", elapsed);
println!("Average time per build: {:?}", elapsed / iterations);
}
#[test]
fn bench_susp_iterator() {
use hadris_iso::susp::{SystemUseBuilder, SystemUseIter};
let mut builder = SystemUseBuilder::new();
builder
.add_sp(0)
.add_er("RRIP_1991A", "Rock Ridge", "IEEE P1282", 1)
.add_padding(4)
.add_st();
let data = builder.build();
let iterations = 100000;
let start = Instant::now();
for _ in 0..iterations {
let iter = SystemUseIter::new(&data, 0);
let mut count = 0;
for _ in iter {
count += 1;
}
assert!(count > 0);
}
let elapsed = start.elapsed();
println!("\n=== SUSP Iterator Benchmark ===");
println!("Data size: {} bytes", data.len());
println!("Iterations: {}", iterations);
println!("Total time: {:?}", elapsed);
println!("Average time per iteration: {:?}", elapsed / iterations);
}
#[test]
fn bench_boot_catalog_roundtrip() {
use hadris_iso::boot::{BootCatalog, BootSectionEntry, EmulationType, PlatformId};
let iterations = 10000;
let start = Instant::now();
for _ in 0..iterations {
let mut catalog = BootCatalog::default();
catalog.add_section(
PlatformId::X80X86,
vec![BootSectionEntry::new(
EmulationType::NoEmulation,
0x07C0,
4,
20,
)],
);
let mut buf = Vec::new();
catalog.write(&mut buf).unwrap();
let mut cursor = Cursor::new(buf);
let _parsed = BootCatalog::parse(&mut cursor).unwrap();
}
let elapsed = start.elapsed();
println!("\n=== Boot Catalog Roundtrip Benchmark ===");
println!("Iterations: {}", iterations);
println!("Total time: {:?}", elapsed);
println!("Average time per roundtrip: {:?}", elapsed / iterations);
}
#[test]
fn stress_test_many_files() {
if !xorriso_available() {
eprintln!("Skipping stress test: xorriso not available");
return;
}
let temp_dir = TempDir::new().unwrap();
let content_dir = temp_dir.path().join("content");
let iso_path = temp_dir.path().join("stress.iso");
fs::create_dir(&content_dir).unwrap();
let file_count = 500;
for i in 0..file_count {
let filename = format!("file_{:05}.txt", i);
fs::write(content_dir.join(&filename), format!("Content {}", i)).unwrap();
}
let start = Instant::now();
assert!(create_iso_with_xorriso(&content_dir, &iso_path));
let create_time = start.elapsed();
let iso_data = fs::read(&iso_path).unwrap();
let iso_size = iso_data.len();
let start = Instant::now();
let cursor = Cursor::new(iso_data);
let image = hadris_iso::read::IsoImage::open(cursor).unwrap();
let open_time = start.elapsed();
let pvd = image.read_pvd();
let root_extent = pvd.dir_record.header.extent.read();
let root_size = pvd.dir_record.header.data_len.read();
println!("\n=== Stress Test: Many Files ===");
println!("Files created: {}", file_count);
println!(
"ISO size: {} bytes ({:.2} KB)",
iso_size,
iso_size as f64 / 1024.0
);
println!("Root directory extent: sector {}", root_extent);
println!("Root directory size: {} bytes", root_size);
println!(
"Expected entries (approx): {} per 2048-byte sector",
2048 / 40
);
let start = Instant::now();
let root = image.root_dir();
let dir = root.iter(&image);
let mut entry_count = 0;
let mut total_bytes = 0;
for entry_result in dir.entries() {
let entry = entry_result.unwrap();
total_bytes += entry.size();
if !entry.is_special() {
entry_count += 1;
}
}
let traverse_time = start.elapsed();
println!("ISO creation time (xorriso): {:?}", create_time);
println!("ISO open time: {:?}", open_time);
println!("Directory traversal time: {:?}", traverse_time);
println!("Entries found: {} (plus 2 special entries)", entry_count);
println!("Total bytes read from directory: {}", total_bytes);
if entry_count < file_count {
eprintln!("Warning: Found fewer entries than created. This may indicate:");
eprintln!(" - Multi-sector directory traversal issue");
eprintln!(" - The directory iterator may stop at sector boundaries");
eprintln!(" - Expected: {}, Found: {}", file_count, entry_count);
}
}