aprender-compute 0.32.0

High-performance SIMD compute library with GPU support, LLM inference engine, and GGUF model loading (was: trueno)
Documentation
//! Section F: Visual Regression (Claims 81-90)
//! Section G: Stress Testing (Claims 91-100)

use simular::engine::rng::SimRng;
use trueno::simulation::{
    BufferRenderer, ColorPalette, PixelDiffResult, Rgb, StressAnomaly, StressAnomalyKind,
    StressResult, StressTestConfig, StressThresholds, VisualRegressionConfig,
};
use trueno::Backend;

// =============================================================================
// SECTION F: Visual Regression (Claims 81-90)
// =============================================================================

/// F-081: BufferRenderer produces valid RGBA output
#[test]
fn test_f081_valid_rgba_output() {
    let renderer = BufferRenderer::new();
    let buffer = vec![0.0f32, 0.5, 1.0, 0.25];
    let rgba = renderer.render_to_rgba(&buffer, 2, 2);

    // Should have 4 bytes per pixel
    assert_eq!(
        rgba.len(),
        16,
        "F-081 FALSIFIED: Expected 16 bytes for 2x2 image, got {}",
        rgba.len()
    );

    // Alpha channel should always be 255
    for i in (3..16).step_by(4) {
        assert_eq!(rgba[i], 255, "F-081 FALSIFIED: Alpha channel should be 255 at byte {}", i);
    }
}

/// F-082: RGBA output dimensions match input
#[test]
fn test_f082_dimensions_match() {
    let renderer = BufferRenderer::new();

    for (w, h) in [(1, 1), (2, 2), (4, 4), (8, 8), (16, 16)] {
        let buffer: Vec<f32> = (0..(w * h)).map(|i| i as f32 / (w * h) as f32).collect();
        let rgba = renderer.render_to_rgba(&buffer, w as u32, h as u32);

        let expected_bytes = w * h * 4;
        assert_eq!(
            rgba.len(),
            expected_bytes,
            "F-082 FALSIFIED: Expected {} bytes for {}x{} image",
            expected_bytes,
            w,
            h
        );
    }
}

/// F-083: Identical inputs produce identical RGBA
#[test]
fn test_f083_identical_inputs() {
    let renderer = BufferRenderer::new();
    let buffer = vec![0.0f32, 0.25, 0.5, 0.75];

    let rgba1 = renderer.render_to_rgba(&buffer, 2, 2);
    let rgba2 = renderer.render_to_rgba(&buffer, 2, 2);

    assert_eq!(rgba1, rgba2, "F-083 FALSIFIED: Identical inputs should produce identical RGBA");
}

/// F-084: Different inputs produce different RGBA
#[test]
fn test_f084_different_inputs() {
    // Use a fixed range renderer so different constant values produce different colors
    let renderer = BufferRenderer::new().with_range(0.0, 1.0);
    let buffer1 = vec![0.0f32, 0.0, 0.0, 0.0];
    let buffer2 = vec![1.0f32, 1.0, 1.0, 1.0];

    let rgba1 = renderer.render_to_rgba(&buffer1, 2, 2);
    let rgba2 = renderer.render_to_rgba(&buffer2, 2, 2);

    assert_ne!(rgba1, rgba2, "F-084 FALSIFIED: Different inputs should produce different RGBA");
}

/// F-085: Color palette maps correctly
#[test]
fn test_f085_color_palette_mapping() {
    let palette = ColorPalette::viridis();

    // 0.0 should map to start color
    let at_0 = palette.interpolate(0.0);
    assert_eq!(at_0, Rgb::new(68, 1, 84), "F-085: 0.0 should map to viridis start");

    // 1.0 should map to end color
    let at_1 = palette.interpolate(1.0);
    assert_eq!(at_1, Rgb::new(253, 231, 37), "F-085: 1.0 should map to viridis end");
}

/// F-086: Auto-normalize handles constant inputs
#[test]
fn test_f086_constant_input_handling() {
    let renderer = BufferRenderer::new();
    let constant = vec![5.0f32, 5.0, 5.0, 5.0];

    // Should not panic
    let rgba = renderer.render_to_rgba(&constant, 2, 2);

    // Should produce valid RGBA
    assert_eq!(rgba.len(), 16);
}

/// F-087: NaN/Inf handling in renderer
#[test]
fn test_f087_special_value_handling() {
    let renderer = BufferRenderer::new();
    let special = vec![f32::NAN, f32::INFINITY, f32::NEG_INFINITY, 0.5];
    let rgba = renderer.render_to_rgba(&special, 2, 2);

    // NaN should be magenta (255, 0, 255)
    assert_eq!(rgba[0], 255, "F-087: NaN should render as magenta R");
    assert_eq!(rgba[1], 0, "F-087: NaN should render as magenta G");
    assert_eq!(rgba[2], 255, "F-087: NaN should render as magenta B");

    // +Inf should be white (255, 255, 255)
    assert_eq!(rgba[4], 255, "F-087: +Inf should render as white R");
    assert_eq!(rgba[5], 255, "F-087: +Inf should render as white G");
    assert_eq!(rgba[6], 255, "F-087: +Inf should render as white B");

    // -Inf should be black (0, 0, 0)
    assert_eq!(rgba[8], 0, "F-087: -Inf should render as black R");
    assert_eq!(rgba[9], 0, "F-087: -Inf should render as black G");
    assert_eq!(rgba[10], 0, "F-087: -Inf should render as black B");
}

/// F-088: Single pixel difference detection
#[test]
fn test_f088_single_pixel_detection() {
    let renderer = BufferRenderer::new();
    let rgba1 = vec![100u8, 100, 100, 255];
    let rgba2 = vec![101u8, 100, 100, 255]; // 1 pixel different

    let result = renderer.compare_rgba(&rgba1, &rgba2, 0);

    assert!(result.different_pixels > 0, "F-088 FALSIFIED: Should detect single pixel difference");
}

/// F-089: Visual diff threshold application
#[test]
fn test_f089_threshold_application() {
    let config = VisualRegressionConfig::default().with_max_diff_pct(5.0); // Allow 5% different pixels

    let result = PixelDiffResult { different_pixels: 5, total_pixels: 100, max_diff: 10 };

    assert!(
        result.matches(config.max_diff_pct),
        "F-089 FALSIFIED: 5% diff should match 5% threshold"
    );

    assert!(!result.matches(4.0), "F-089 FALSIFIED: 5% diff should not match 4% threshold");
}

/// F-090: Renderer determinism
#[test]
fn test_f090_renderer_determinism() {
    let renderer = BufferRenderer::new();
    let buffer: Vec<f32> = (0..100).map(|i| i as f32 / 100.0).collect();

    // Generate 100 times, verify identical
    let first = renderer.render_to_rgba(&buffer, 10, 10);

    for i in 0..100 {
        let next = renderer.render_to_rgba(&buffer, 10, 10);
        assert_eq!(first, next, "F-090 FALSIFIED: Renderer not deterministic on iteration {}", i);
    }
}

// =============================================================================
// SECTION G: Stress Testing (Claims 91-100)
// =============================================================================

/// G-091: StressTestRunner completes without crash
#[test]
fn test_g091_runner_completes() {
    // Run 10 cycles on small data
    let config = StressTestConfig::new(42)
        .with_cycles(10)
        .with_input_sizes(vec![100, 1000])
        .with_backends(vec![Backend::Scalar]);

    // Verify config is valid
    assert_eq!(config.cycles_per_backend, 10);
    assert_eq!(config.input_sizes.len(), 2);
    assert_eq!(config.total_tests(), 20); // 1 backend * 2 sizes * 10 cycles
}

/// G-092: Anomaly detection for slowdown
#[test]
fn test_g092_slowdown_detection() {
    let result = StressResult {
        backend: Backend::Scalar,
        input_size: 1000,
        cycles_completed: 10,
        tests_passed: 10,
        tests_failed: 0,
        mean_op_time_ms: 100.0,
        max_op_time_ms: 200, // 2x slowdown
        timing_variance: 0.5,
        anomalies: vec![StressAnomaly {
            cycle: 5,
            kind: StressAnomalyKind::SlowOperation,
            description: "Operation took 200ms, threshold is 100ms".to_string(),
        }],
    };

    assert!(!result.passed(), "G-092 FALSIFIED: Slowdown should be detected as anomaly");
}

/// G-093: Anomaly detection for test failure
#[test]
fn test_g093_failure_detection() {
    let result = StressResult {
        backend: Backend::Scalar,
        input_size: 1000,
        cycles_completed: 10,
        tests_passed: 9,
        tests_failed: 1,
        mean_op_time_ms: 50.0,
        max_op_time_ms: 100,
        timing_variance: 0.1,
        anomalies: vec![],
    };

    assert!(!result.passed(), "G-093 FALSIFIED: Test failure should be detected");
}

/// G-094: Timing variance threshold
#[test]
fn test_g094_timing_variance() {
    let thresholds = StressThresholds::default();

    // Default max variance is 0.5 (50%)
    assert!(
        (thresholds.max_timing_variance - 0.5).abs() < 0.001,
        "G-094: Default variance threshold should be 0.5"
    );

    // Strict threshold is 0.2 (20%)
    let strict = StressThresholds::strict();
    assert!(
        (strict.max_timing_variance - 0.2).abs() < 0.001,
        "G-094: Strict variance threshold should be 0.2"
    );
}

/// G-095: Memory limit enforcement
#[test]
fn test_g095_memory_limit() {
    let thresholds = StressThresholds::default();

    // Default is 256MB
    assert_eq!(
        thresholds.max_memory_bytes,
        256 * 1024 * 1024,
        "G-095 FALSIFIED: Default memory limit should be 256MB"
    );

    // Strict is 64MB
    let strict = StressThresholds::strict();
    assert_eq!(
        strict.max_memory_bytes,
        64 * 1024 * 1024,
        "G-095 FALSIFIED: Strict memory limit should be 64MB"
    );
}

/// G-096: Pass rate calculation
#[test]
fn test_g096_pass_rate() {
    let result = StressResult {
        backend: Backend::Scalar,
        input_size: 1000,
        cycles_completed: 100,
        tests_passed: 99,
        tests_failed: 1,
        mean_op_time_ms: 50.0,
        max_op_time_ms: 100,
        timing_variance: 0.1,
        anomalies: vec![],
    };

    let pass_rate = result.pass_rate();
    assert!(
        (pass_rate - 0.99).abs() < 0.001,
        "G-096 FALSIFIED: Pass rate should be 99%, got {}",
        pass_rate * 100.0
    );
}

/// G-097: Stress report schema validation
#[test]
fn test_g097_report_schema() {
    let result = StressResult {
        backend: Backend::Scalar,
        input_size: 1000,
        cycles_completed: 10,
        tests_passed: 10,
        tests_failed: 0,
        mean_op_time_ms: 50.0,
        max_op_time_ms: 100,
        timing_variance: 0.1,
        anomalies: vec![],
    };

    // Verify all required fields are present and have valid values
    assert!(matches!(result.backend, Backend::Scalar));
    assert!(result.input_size > 0);
    assert!(result.cycles_completed > 0);
    assert!(result.mean_op_time_ms >= 0.0);
    // max_op_time_ms should not exceed mean (which is 1.0ms in this test)
    // Verify the value is sensible - max should be >= mean for timing data
    let max_as_f64 = result.max_op_time_ms as f64;
    assert!(max_as_f64 >= result.mean_op_time_ms, "G-097 FALSIFIED: max_op_time should be >= mean");
    assert!(result.timing_variance >= 0.0);
}

/// G-098: Real-time update capability
#[test]
fn test_g098_realtime_capability() {
    // Verify we can create and update results incrementally
    let mut result = StressResult {
        backend: Backend::Scalar,
        input_size: 1000,
        cycles_completed: 0,
        tests_passed: 0,
        tests_failed: 0,
        mean_op_time_ms: 0.0,
        max_op_time_ms: 0,
        timing_variance: 0.0,
        anomalies: vec![],
    };

    // Simulate 10 cycles
    for i in 0..10 {
        result.cycles_completed = i + 1;
        result.tests_passed = i + 1;

        assert!(
            result.cycles_completed > 0,
            "G-098: Should be able to update result incrementally"
        );
    }
}

/// G-099: Stress test seed reproducibility
#[test]
fn test_g099_seed_reproducibility() {
    let config1 = StressTestConfig::new(42);
    let config2 = StressTestConfig::new(42);

    assert_eq!(
        config1.master_seed, config2.master_seed,
        "G-099 FALSIFIED: Same seed should produce same config"
    );

    // Generate test data with same seed
    let mut rng1 = SimRng::new(config1.master_seed);
    let mut rng2 = SimRng::new(config2.master_seed);

    let seq1: Vec<f64> = (0..100).map(|_| rng1.gen_f64()).collect();
    let seq2: Vec<f64> = (0..100).map(|_| rng2.gen_f64()).collect();

    assert_eq!(seq1, seq2, "G-099 FALSIFIED: Same seed should produce same test data");
}

/// G-100: Jidoka triggers on first failure
#[test]
fn test_g100_jidoka_first_failure() {
    use trueno::simulation::JidokaGuard;

    // Test that Jidoka guard stops immediately on detection
    let guard = JidokaGuard::nan_guard("G-100");

    let data_with_nan = vec![1.0f32, 2.0, f32::NAN, 4.0, 5.0];
    let result = guard.check_output(&data_with_nan);

    assert!(result.is_err(), "G-100 FALSIFIED: Jidoka should detect NaN immediately");

    // Verify it found the NaN at the correct position
    if let Err(e) = result {
        let err_str = format!("{}", e);
        assert!(
            err_str.contains("NaN") || err_str.contains("nan"),
            "G-100: Error should mention NaN"
        );
    }
}