heroforge-core 0.2.2

Pure Rust core library for reading and writing Fossil SCM repositories
Documentation
//! Performance tests for heroforge using builder pattern

use heroforge_core::Repository;
use std::path::Path;
use std::time::Instant;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let test_dir = Path::new("/tmp/fossil_perf_test");
    if test_dir.exists() {
        std::fs::remove_dir_all(test_dir)?;
    }
    std::fs::create_dir_all(test_dir)?;

    println!("=== heroforge Performance Tests ===\n");

    // Test 1: Repository creation
    let repo_path = test_dir.join("perf.forge");
    let start = Instant::now();
    let repo = Repository::init(&repo_path)?;
    let init_time = start.elapsed();
    println!(
        "Repository init:        {:>8.2} ms",
        init_time.as_secs_f64() * 1000.0
    );

    // Test 2: Initial check-in using builder pattern
    let start = Instant::now();
    let init_hash = repo
        .commit_builder()
        .message("initial empty check-in")
        .author("perf-user")
        .initial()
        .execute()?;
    let initial_time = start.elapsed();
    println!(
        "Initial check-in:       {:>8.2} ms",
        initial_time.as_secs_f64() * 1000.0
    );

    // Test 3: Single file commits (100 commits) using builder pattern
    let num_commits = 100;
    let start = Instant::now();
    let mut parent = init_hash;
    for i in 0..num_commits {
        let content = format!("Content version {}\n", i);
        parent = repo
            .commit_builder()
            .message(&format!("Commit {}", i))
            .author("perf-user")
            .parent(&parent)
            .file("file.txt", content.as_bytes())
            .execute()?;
    }
    let commit_time = start.elapsed();
    println!(
        "{} single-file commits: {:>8.2} ms ({:.2} ms/commit)",
        num_commits,
        commit_time.as_secs_f64() * 1000.0,
        commit_time.as_secs_f64() * 1000.0 / num_commits as f64
    );

    // Test 4: Multi-file commit (50 files) using builder pattern
    let num_files = 50;
    let mut files_data: Vec<(String, Vec<u8>)> = Vec::new();
    for i in 0..num_files {
        let name = format!("src/file_{:03}.rs", i);
        let content = format!("// File {}\npub fn func_{}() -> i32 {{ {} }}\n", i, i, i);
        files_data.push((name, content.into_bytes()));
    }
    let files: Vec<(&str, &[u8])> = files_data
        .iter()
        .map(|(n, c)| (n.as_str(), c.as_slice()))
        .collect();

    let start = Instant::now();
    let multi_hash = repo
        .commit_builder()
        .message("Add 50 files")
        .author("perf-user")
        .parent(&parent)
        .files(&files)
        .execute()?;
    let multi_time = start.elapsed();
    println!(
        "{}-file commit:          {:>8.2} ms ({:.2} ms/file)",
        num_files,
        multi_time.as_secs_f64() * 1000.0,
        multi_time.as_secs_f64() * 1000.0 / num_files as f64
    );

    // Test 5: File listing using builder pattern
    let start = Instant::now();
    let file_list = repo.files().at_commit(&multi_hash).list()?;
    let list_time = start.elapsed();
    println!(
        "List {} files:           {:>8.2} ms",
        file_list.len(),
        list_time.as_secs_f64() * 1000.0
    );

    // Test 6: Read all files using builder pattern
    let start = Instant::now();
    for file in &file_list {
        let _ = repo.files().at_commit(&multi_hash).read(&file.name)?;
    }
    let read_time = start.elapsed();
    println!(
        "Read {} files:           {:>8.2} ms ({:.2} ms/file)",
        file_list.len(),
        read_time.as_secs_f64() * 1000.0,
        read_time.as_secs_f64() * 1000.0 / file_list.len() as f64
    );

    // Test 7: Large file write using builder pattern
    let large_size = 1024 * 1024; // 1 MB
    let large_content: Vec<u8> = (0..large_size).map(|i| (i % 256) as u8).collect();

    let start = Instant::now();
    let large_hash = repo
        .commit_builder()
        .message("Add 1MB file")
        .author("perf-user")
        .parent(&multi_hash)
        .file("large_file.bin", &large_content)
        .execute()?;
    let large_write_time = start.elapsed();
    println!(
        "Write 1 MB file:        {:>8.2} ms ({:.2} MB/s)",
        large_write_time.as_secs_f64() * 1000.0,
        1.0 / large_write_time.as_secs_f64()
    );

    // Test 8: Large file read using builder pattern
    let start = Instant::now();
    let read_content = repo.files().at_commit(&large_hash).read("large_file.bin")?;
    let large_read_time = start.elapsed();
    assert_eq!(read_content.len(), large_size);
    println!(
        "Read 1 MB file:         {:>8.2} ms ({:.2} MB/s)",
        large_read_time.as_secs_f64() * 1000.0,
        1.0 / large_read_time.as_secs_f64()
    );

    // Test 9: Write 100,000 files in different directories
    // Filename is the MD5 hash of content for verification
    println!("\nPreparing 100,000 files...");
    let num_mass_files = 100_000;

    let prep_start = Instant::now();
    let mut mass_files_data: Vec<(String, Vec<u8>)> = Vec::with_capacity(num_mass_files);

    for i in 0..num_mass_files {
        // Create files in 1000 different directories (100 files per directory)
        let dir_num = i / 100;
        let content = format!("File {} in directory {}\nSome content line 2\n", i, dir_num);
        let content_bytes = content.into_bytes();
        // Use MD5 hash as filename for verification
        let content_hash = format!("{:x}", md5::compute(&content_bytes));
        let name = format!("dir_{:04}/{}.txt", dir_num, content_hash);
        mass_files_data.push((name, content_bytes));
    }
    let prep_time = prep_start.elapsed();
    println!(
        "Prepared {} files: {:>8.2} ms",
        num_mass_files,
        prep_time.as_secs_f64() * 1000.0
    );

    let mass_files: Vec<(&str, &[u8])> = mass_files_data
        .iter()
        .map(|(n, c)| (n.as_str(), c.as_slice()))
        .collect();

    println!("Writing {} files to repo...", num_mass_files);
    let start = Instant::now();
    let mass_hash = repo
        .commit_builder()
        .message("Add 100,000 files")
        .author("perf-user")
        .parent(&large_hash)
        .files(&mass_files)
        .execute()?;
    let mass_write_time = start.elapsed();
    println!(
        "Write {} files:  {:>8.2} ms ({:.2} files/sec)",
        num_mass_files,
        mass_write_time.as_secs_f64() * 1000.0,
        num_mass_files as f64 / mass_write_time.as_secs_f64()
    );

    // Test 10: List all 100,000 files using builder pattern
    let start = Instant::now();
    let mass_file_list = repo.files().at_commit(&mass_hash).list()?;
    let mass_list_time = start.elapsed();
    println!(
        "List {} files:     {:>8.2} ms",
        mass_file_list.len(),
        mass_list_time.as_secs_f64() * 1000.0
    );

    // Test 11: Read all files and verify MD5 hash matches filename using builder pattern
    println!("\nReading and verifying all {} files...", num_mass_files);
    let start = Instant::now();
    let mut read_count = 0;
    let mut verified_count = 0;
    let mut total_bytes = 0usize;
    for file in &mass_file_list {
        let content = repo.files().at_commit(&mass_hash).read(&file.name)?;
        total_bytes += content.len();
        read_count += 1;

        // Verify: filename contains MD5 hash of content
        let content_hash = format!("{:x}", md5::compute(&content));
        if file.name.contains(&content_hash) {
            verified_count += 1;
        }
    }
    let mass_read_time = start.elapsed();
    println!(
        "Read {} files:      {:>8.2} ms ({:.2} files/sec, {:.2} MB total)",
        read_count,
        mass_read_time.as_secs_f64() * 1000.0,
        read_count as f64 / mass_read_time.as_secs_f64(),
        total_bytes as f64 / (1024.0 * 1024.0)
    );
    println!(
        "Verified {} of {} files (MD5 hash matches filename)",
        verified_count, read_count
    );

    // Test 12: Branch creation using builder pattern
    let start = Instant::now();
    let _branch = repo
        .branches()
        .create("perf-branch")
        .from_commit(&mass_hash)
        .author("perf-user")
        .execute()?;
    let branch_time = start.elapsed();
    println!(
        "\nCreate branch:          {:>8.2} ms",
        branch_time.as_secs_f64() * 1000.0
    );

    // Test 13: Tag creation using builder pattern
    let start = Instant::now();
    repo.tags()
        .create("perf-tag")
        .at_commit(&mass_hash)
        .author("perf-user")
        .execute()?;
    let tag_time = start.elapsed();
    println!(
        "Add tag:                {:>8.2} ms",
        tag_time.as_secs_f64() * 1000.0
    );

    // Test 14: List check-ins using history builder
    let start = Instant::now();
    let checkins = repo.history().recent(100)?;
    let checkin_time = start.elapsed();
    println!(
        "List {} check-ins:      {:>8.2} ms",
        checkins.len(),
        checkin_time.as_secs_f64() * 1000.0
    );

    // Test 15: List branches and tags using builders
    let start = Instant::now();
    let branches = repo.branches().list()?;
    let tags = repo.tags().list()?;
    let list_bt_time = start.elapsed();
    println!(
        "List branches/tags:     {:>8.2} ms ({} branches, {} tags)",
        list_bt_time.as_secs_f64() * 1000.0,
        branches.len(),
        tags.len()
    );

    // Summary
    println!("\n=== Summary ===");
    let total_commits = num_commits + 4; // single file + multi + large + mass + branch
    println!("Total commits created: {}", total_commits);
    println!("Total files in repo:   {}", mass_file_list.len());
    println!(
        "Avg commit time:       {:.2} ms",
        (commit_time.as_secs_f64() * 1000.0) / num_commits as f64
    );
    println!(
        "Avg file read time:    {:.4} ms",
        (mass_read_time.as_secs_f64() * 1000.0) / read_count as f64
    );
    println!(
        "Mass write rate:       {:.0} files/sec",
        num_mass_files as f64 / mass_write_time.as_secs_f64()
    );
    println!(
        "Mass read rate:        {:.0} files/sec",
        read_count as f64 / mass_read_time.as_secs_f64()
    );

    // Cleanup
    std::fs::remove_dir_all(test_dir)?;

    println!("\n[PASS] All performance tests completed!");

    Ok(())
}