forge-guard 0.2.0

Pre-deployment smart contract auditing framework for Foundry
Documentation
//! `forge-guard benchmark` — run performance benchmarks.

use crate::benchmark::BenchmarkRunner;

use super::BenchmarkArgs;
use anyhow::Result;
use colored::*;

/// Run performance benchmarks for the audit framework.
pub fn run(args: &BenchmarkArgs) -> Result<()> {
    eprintln!("{}", "⚡ Forge Guard — Benchmark".bold());
    eprintln!("   Iterations: {}", args.iterations);
    eprintln!("   Warmup:     {}", args.warmup);

    let mut runner = BenchmarkRunner::new(args.iterations, args.warmup);

    // Run benchmarks for each module
    let results = match &args.module {
        Some(module) => vec![runner.benchmark_module(module)?],
        None => runner.benchmark_all()?,
    };

    // Display results
    println!(
        "\n{}",
        "═══════════════════════════════════════".bright_blue()
    );
    println!(
        "{}",
        "         BENCHMARK RESULTS              "
            .bright_blue()
            .bold()
    );
    println!(
        "{}",
        "═══════════════════════════════════════".bright_blue()
    );

    for result in &results {
        println!("\n  📊 {}:", result.name.bold());
        println!("     Avg:     {:.2}ms", result.avg_ms);
        println!("     Min:     {:.2}ms", result.min_ms);
        println!("     Max:     {:.2}ms", result.max_ms);
        println!("     Median:  {:.2}ms", result.median_ms);
        println!("     P99:     {:.2}ms", result.p99_ms);
        println!("     Samples: {}", result.samples);
    }

    // Save results if requested
    if let Some(save_path) = &args.save {
        let json = serde_json::to_string_pretty(&results)?;
        std::fs::write(save_path, json)?;
        println!(
            "\n{} Results saved to {}",
            "💾".green(),
            save_path.display()
        );
    }

    // Compare with baseline
    if let Some(baseline_path) = &args.compare {
        let baseline_content = std::fs::read_to_string(baseline_path)?;
        let baseline: Vec<crate::benchmark::BenchmarkResult> =
            serde_json::from_str(&baseline_content)?;
        println!("\n{}", "── Comparison with Baseline ──".bold());
        for result in &results {
            if let Some(baseline) = baseline.iter().find(|b| b.name == result.name) {
                let diff = result.avg_ms - baseline.avg_ms;
                let pct = (diff / baseline.avg_ms) * 100.0;
                if diff > 0.0 {
                    println!(
                        "  {} {}: +{:.1}ms ({:+.1}%)",
                        "🔴".red(),
                        result.name,
                        diff,
                        pct
                    );
                } else {
                    println!(
                        "  {} {}: {:.1}ms ({:+.1}%)",
                        "🟢".green(),
                        result.name,
                        diff,
                        pct
                    );
                }
            }
        }
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_benchmark_arg_defaults() {
        let args = BenchmarkArgs {
            shared: super::super::SharedFlags {
                chain: "ethereum".into(),
                project: std::path::PathBuf::from("."),
                json: false,
                markdown: false,
                html: false,
                strict: false,
                offline: false,
                production: false,
                report: false,
                parallelism: 4,
            },
            iterations: 10,
            compare: None,
            save: None,
            module: None,
            warmup: 3,
        };
        assert_eq!(args.iterations, 10);
        assert_eq!(args.warmup, 3);
        assert!(args.save.is_none());
        assert!(args.compare.is_none());
        assert!(args.module.is_none());
    }

    #[test]
    fn test_benchmark_module_specified() {
        let args = BenchmarkArgs {
            shared: super::super::SharedFlags::default(),
            iterations: 50,
            compare: None,
            save: Some(std::path::PathBuf::from("results.json")),
            module: Some("pattern_matching".into()),
            warmup: 5,
        };
        assert_eq!(args.iterations, 50);
        assert_eq!(args.warmup, 5);
        assert_eq!(args.module.as_deref(), Some("pattern_matching"));
        assert!(args.save.is_some());
    }

    #[test]
    fn test_benchmark_module_unknown_returns_error() {
        let mut runner = crate::benchmark::BenchmarkRunner::new(3, 1);
        let result = runner.benchmark_module("nonexistent_module");
        assert!(result.is_err());
        let err = result.unwrap_err();
        assert!(err.to_string().contains("Unknown benchmark module"));
    }
}