Skip to main content

trueno_gpu/testing/
mod.rs

1//! E2E Visual Testing Framework for GPU Kernels
2//!
3//! This module provides pixel-level visual regression testing for GPU computations
4//! using the **sovereign stack only** - NO external crates.
5//!
6//! # Architecture (Sovereign Stack)
7//!
8//! ```text
9//! GPU Output → GpuPixelRenderer → trueno-viz → PNG → compare_png_bytes → Pass/Fail
10//!                                                           ↑
11//!                                                    Golden Baseline
12//! ```
13//!
14//! # Dependencies (Sovereign Stack)
15//!
16//! - `trueno-viz` v0.1.4: PNG encoding, Framebuffer (optional, feature = "viz")
17//! - `simular` v0.2.0: Deterministic RNG for reproducible tests
18//! - `renacer` v0.7.0: Profiling and anomaly detection (optional)
19//!
20//! # Features
21//!
22//! - `viz`: Enable GPU pixel renderer with trueno-viz
23//! - `stress-test`: Enable randomized frame-by-frame stress testing
24//! - `tui-monitor`: Enable TUI monitoring mode via presentar
25
26pub mod stress;
27pub mod tui;
28
29pub use stress::{
30    verify_performance, Anomaly, AnomalyKind, FrameProfile, PerformanceResult,
31    PerformanceThresholds, StressConfig, StressReport, StressRng, StressTestRunner,
32};
33
34pub use tui::{progress_bar, render_to_string, TuiConfig, TuiState};
35
36/// GPU-specific bug classification based on diff patterns
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum BugClass {
39    /// Race condition: Non-deterministic output
40    RaceCondition,
41    /// Floating-point precision drift
42    FloatingPointDrift,
43    /// Accumulator not initialized to zero
44    AccumulatorInit,
45    /// Loop counter SSA bug
46    LoopCounter,
47    /// Memory addressing error
48    MemoryAddressing,
49    /// Thread synchronization issue
50    ThreadSync,
51    /// Unknown pattern
52    Unknown,
53}
54
55impl BugClass {
56    /// Description of the bug class
57    #[must_use]
58    pub const fn description(&self) -> &'static str {
59        match self {
60            Self::RaceCondition => "Race condition: non-deterministic output",
61            Self::FloatingPointDrift => "FP precision drift in accumulation",
62            Self::AccumulatorInit => "Accumulator not initialized to zero",
63            Self::LoopCounter => "Loop counter SSA bug (wrong iteration count)",
64            Self::MemoryAddressing => "Memory addressing error (offset/alignment)",
65            Self::ThreadSync => "Thread synchronization issue (barrier)",
66            Self::Unknown => "Unknown bug pattern",
67        }
68    }
69
70    /// Suggested fix for the bug class
71    #[must_use]
72    pub const fn suggested_fix(&self) -> &'static str {
73        match self {
74            Self::RaceCondition => "Add __syncthreads() / bar.sync; use atomics",
75            Self::FloatingPointDrift => "Use Kahan summation or pairwise reduction",
76            Self::AccumulatorInit => "Initialize accumulator to 0.0 before loop",
77            Self::LoopCounter => "Fix loop bound; use in-place += instead of reassignment",
78            Self::MemoryAddressing => "Check index calculations and stride",
79            Self::ThreadSync => "Add barrier synchronization at workgroup boundaries",
80            Self::Unknown => "Manual inspection required",
81        }
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn test_bug_class_descriptions() {
91        // Test all variants have non-empty descriptions
92        let variants = [
93            BugClass::RaceCondition,
94            BugClass::FloatingPointDrift,
95            BugClass::AccumulatorInit,
96            BugClass::LoopCounter,
97            BugClass::MemoryAddressing,
98            BugClass::ThreadSync,
99            BugClass::Unknown,
100        ];
101
102        for variant in variants {
103            assert!(
104                !variant.description().is_empty(),
105                "{variant:?} has empty description"
106            );
107            assert!(
108                !variant.suggested_fix().is_empty(),
109                "{variant:?} has empty fix"
110            );
111        }
112    }
113
114    #[test]
115    fn test_bug_class_equality() {
116        assert_eq!(BugClass::RaceCondition, BugClass::RaceCondition);
117        assert_ne!(BugClass::RaceCondition, BugClass::Unknown);
118    }
119
120    #[test]
121    fn test_bug_class_clone() {
122        let original = BugClass::FloatingPointDrift;
123        let cloned = original;
124        assert_eq!(original, cloned);
125    }
126}