Skip to main content

jugar_probar/gpu_pixels/
mod.rs

1//! GPU Pixel Testing: Atomic verification of CUDA kernel correctness
2//!
3//! Inspired by pixel-level GUI testing, GPU pixel testing verifies the smallest
4//! testable units of GPU kernel behavior to catch bugs like:
5//! - Shared memory addressing (u64 vs u32)
6//! - Loop branch direction (START vs END)
7//! - Kernel name mismatches
8//! - Tile/thread bounds violations
9//!
10//! # Architecture
11//!
12//! ```text
13//! ┌─────────────────────────────────────────────────────────────────┐
14//! │                    GPU Pixel Testing                            │
15//! ├─────────────────────────────────────────────────────────────────┤
16//! │   ┌────────────┐    ┌────────────┐    ┌────────────┐            │
17//! │   │ PTX Static │    │ Kernel     │    │ Regression │            │
18//! │   │ Analysis   │───►│ Pixel      │───►│ Detection  │            │
19//! │   │            │    │ Tests      │    │            │            │
20//! │   └────────────┘    └────────────┘    └────────────┘            │
21//! └─────────────────────────────────────────────────────────────────┘
22//! ```
23
24mod kernel_pixels;
25mod ptx_analysis;
26mod regression;
27
28pub use kernel_pixels::{
29    standard_pixel_tests, GpuPixelResult, GpuPixelTest, GpuPixelTestSuite, KernelPixelConfig,
30};
31pub use ptx_analysis::{PtxAnalyzer, PtxBug, PtxBugClass, PtxValidationResult};
32pub use regression::{
33    run_regression_suite, GpuRegressionSuite, RegressionConfig, RegressionResult,
34};
35
36/// Quick validation of PTX for common GPU kernel bugs
37///
38/// # Example
39/// ```ignore
40/// use probar::gpu_pixels::validate_ptx;
41///
42/// let ptx = kernel.emit_ptx();
43/// let result = validate_ptx(&ptx);
44/// assert!(result.is_valid(), "PTX bugs: {:?}", result.bugs);
45/// ```
46#[must_use]
47pub fn validate_ptx(ptx: &str) -> PtxValidationResult {
48    PtxAnalyzer::default().analyze(ptx)
49}
50
51/// Run all GPU pixel tests for a kernel
52///
53/// # Example
54/// ```ignore
55/// use probar::gpu_pixels::run_kernel_pixels;
56///
57/// let results = run_kernel_pixels("gemm_tiled", &ptx, &config);
58/// assert!(results.all_passed());
59/// ```
60pub fn run_kernel_pixels(
61    kernel_name: &str,
62    ptx: &str,
63    config: &KernelPixelConfig,
64) -> GpuPixelTestSuite {
65    let mut suite = GpuPixelTestSuite::new(kernel_name);
66
67    // PTX static analysis
68    let ptx_result = validate_ptx(ptx);
69    suite.add_result(GpuPixelResult::from_ptx_validation(&ptx_result));
70
71    // Kernel-specific pixel tests
72    suite.run_kernel_pixels(ptx, config);
73
74    suite
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn test_validate_ptx_empty() {
83        let result = validate_ptx("");
84        assert!(!result.is_valid());
85    }
86
87    #[test]
88    fn test_validate_ptx_minimal_valid() {
89        let ptx = r#"
90.version 8.0
91.target sm_70
92.address_size 64
93
94.visible .entry test_kernel() {
95    ret;
96}
97"#;
98        let result = validate_ptx(ptx);
99        assert!(result.is_valid());
100    }
101
102    #[test]
103    fn test_shared_memory_u64_bug_detected() {
104        let ptx = r#"
105.version 8.0
106.target sm_70
107.address_size 64
108
109.visible .entry buggy_kernel() {
110    .reg .u64 %rd<5>;
111    .reg .f32 %f<2>;
112    .shared .align 16 .b8 smem[4096];
113
114    st.shared.f32 [%rd0], %f0;
115    ret;
116}
117"#;
118        let result = validate_ptx(ptx);
119        assert!(!result.is_valid());
120        assert!(result
121            .bugs
122            .iter()
123            .any(|b| matches!(b.class, PtxBugClass::SharedMemU64Addressing)));
124    }
125
126    #[test]
127    fn test_shared_memory_u32_valid() {
128        let ptx = r#"
129.version 8.0
130.target sm_70
131.address_size 64
132
133.visible .entry valid_kernel() {
134    .reg .u32 %r<5>;
135    .reg .f32 %f<2>;
136    .shared .align 16 .b8 smem[4096];
137
138    st.shared.f32 [%r0], %f0;
139    ret;
140}
141"#;
142        let result = validate_ptx(ptx);
143        // Should not have shared memory addressing bug
144        assert!(!result
145            .bugs
146            .iter()
147            .any(|b| matches!(b.class, PtxBugClass::SharedMemU64Addressing)));
148    }
149
150    // ========================================================================
151    // Additional tests for run_kernel_pixels function
152    // ========================================================================
153
154    #[test]
155    fn test_run_kernel_pixels_valid_kernel() {
156        let ptx = r#"
157.version 8.0
158.target sm_70
159.address_size 64
160
161.visible .entry gemm_tiled() {
162    ret;
163}
164"#;
165        let config = KernelPixelConfig::default();
166        let suite = run_kernel_pixels("gemm_tiled", ptx, &config);
167
168        assert_eq!(suite.kernel_name, "gemm_tiled");
169        assert!(suite.all_passed());
170        assert_eq!(suite.failed_count(), 0);
171    }
172
173    #[test]
174    fn test_run_kernel_pixels_with_u64_bug() {
175        let ptx = r#"
176.version 8.0
177.target sm_70
178.address_size 64
179
180.visible .entry buggy() {
181    .shared .b8 smem[1024];
182    st.shared.f32 [%rd0], %f0;
183    ret;
184}
185"#;
186        let config = KernelPixelConfig::default();
187        let suite = run_kernel_pixels("buggy", ptx, &config);
188
189        // Should detect bugs
190        assert!(!suite.all_passed());
191        assert!(suite.failed_count() > 0);
192    }
193
194    #[test]
195    fn test_run_kernel_pixels_no_entry_point() {
196        let ptx = r#"
197.version 8.0
198.target sm_70
199.address_size 64
200"#;
201        let config = KernelPixelConfig::default();
202        let suite = run_kernel_pixels("missing_entry", ptx, &config);
203
204        // Should fail PTX validation (no entry point)
205        assert!(!suite.all_passed());
206        // Verify it detected the missing entry point
207        let failures = suite.failures();
208        assert!(!failures.is_empty());
209    }
210
211    #[test]
212    fn test_run_kernel_pixels_with_shared_memory_needs_barrier() {
213        let ptx = r#"
214.version 8.0
215.target sm_70
216.address_size 64
217
218.visible .entry test_kernel() {
219    .shared .b8 smem[1024];
220    st.shared.f32 [%r0], %f0;
221    ret;
222}
223"#;
224        // With strict mode, missing barrier should be detected
225        let config = KernelPixelConfig {
226            strict_ptx: true,
227            ..Default::default()
228        };
229        let suite = run_kernel_pixels("test_kernel", ptx, &config);
230
231        // Should fail due to missing barrier
232        assert!(!suite.all_passed());
233    }
234
235    #[test]
236    fn test_run_kernel_pixels_with_barrier_sync() {
237        let ptx = r#"
238.version 8.0
239.target sm_70
240.address_size 64
241
242.visible .entry test_kernel() {
243    .shared .b8 smem[1024];
244    st.shared.f32 [%r0], %f0;
245    bar.sync 0;
246    ret;
247}
248"#;
249        let config = KernelPixelConfig::default();
250        let suite = run_kernel_pixels("test_kernel", ptx, &config);
251
252        // Should pass all tests (valid shared mem addressing + barrier)
253        assert!(suite.all_passed());
254    }
255
256    #[test]
257    fn test_run_kernel_pixels_non_strict_mode() {
258        let ptx = r#"
259.version 8.0
260.target sm_70
261.address_size 64
262
263.visible .entry test_kernel() {
264    ret;
265}
266"#;
267        let config = KernelPixelConfig {
268            test_degenerate_dims: false,
269            test_boundaries: false,
270            strict_ptx: false,
271            timeout: std::time::Duration::from_secs(1),
272        };
273        let suite = run_kernel_pixels("test_kernel", ptx, &config);
274
275        assert!(suite.all_passed());
276    }
277
278    // ========================================================================
279    // Tests for KernelPixelConfig
280    // ========================================================================
281
282    #[test]
283    fn test_kernel_pixel_config_default() {
284        let config = KernelPixelConfig::default();
285        assert!(config.test_degenerate_dims);
286        assert!(config.test_boundaries);
287        assert!(config.strict_ptx);
288        assert_eq!(config.timeout, std::time::Duration::from_secs(5));
289    }
290
291    #[test]
292    fn test_kernel_pixel_config_custom() {
293        let config = KernelPixelConfig {
294            test_degenerate_dims: false,
295            test_boundaries: false,
296            strict_ptx: false,
297            timeout: std::time::Duration::from_millis(500),
298        };
299        assert!(!config.test_degenerate_dims);
300        assert!(!config.test_boundaries);
301        assert!(!config.strict_ptx);
302        assert_eq!(config.timeout, std::time::Duration::from_millis(500));
303    }
304
305    #[test]
306    fn test_kernel_pixel_config_clone() {
307        let config = KernelPixelConfig::default();
308        let cloned = config.clone();
309        assert_eq!(cloned.test_degenerate_dims, config.test_degenerate_dims);
310        assert_eq!(cloned.test_boundaries, config.test_boundaries);
311        assert_eq!(cloned.strict_ptx, config.strict_ptx);
312        assert_eq!(cloned.timeout, config.timeout);
313    }
314
315    #[test]
316    fn test_kernel_pixel_config_debug() {
317        let config = KernelPixelConfig::default();
318        let debug_str = format!("{:?}", config);
319        assert!(debug_str.contains("KernelPixelConfig"));
320        assert!(debug_str.contains("test_degenerate_dims"));
321        assert!(debug_str.contains("strict_ptx"));
322    }
323
324    // ========================================================================
325    // Tests for GpuPixelResult
326    // ========================================================================
327
328    #[test]
329    fn test_gpu_pixel_result_pass() {
330        let result = GpuPixelResult::pass("test", std::time::Duration::from_millis(10));
331        assert!(result.passed);
332        assert!(result.error.is_none());
333        assert!(result.bug_class.is_none());
334        assert_eq!(result.name, "test");
335    }
336
337    #[test]
338    fn test_gpu_pixel_result_fail() {
339        let result = GpuPixelResult::fail(
340            "test",
341            "error message",
342            std::time::Duration::from_millis(10),
343        );
344        assert!(!result.passed);
345        assert_eq!(result.error, Some("error message".to_string()));
346        assert!(result.bug_class.is_none());
347    }
348
349    #[test]
350    fn test_gpu_pixel_result_fail_with_bug() {
351        let result = GpuPixelResult::fail_with_bug(
352            "test",
353            "shared mem bug",
354            PtxBugClass::SharedMemU64Addressing,
355            std::time::Duration::from_millis(10),
356        );
357        assert!(!result.passed);
358        assert!(result.error.is_some());
359        assert_eq!(result.bug_class, Some(PtxBugClass::SharedMemU64Addressing));
360    }
361
362    #[test]
363    fn test_gpu_pixel_result_from_valid_ptx_validation() {
364        let ptx = r#"
365.version 8.0
366.target sm_70
367.visible .entry test() { ret; }
368"#;
369        let validation = validate_ptx(ptx);
370        let result = GpuPixelResult::from_ptx_validation(&validation);
371        assert!(result.passed);
372        assert_eq!(result.name, "ptx_validation");
373    }
374
375    #[test]
376    fn test_gpu_pixel_result_from_invalid_ptx_validation() {
377        // Empty PTX is invalid (no entry point)
378        let validation = validate_ptx("");
379        let result = GpuPixelResult::from_ptx_validation(&validation);
380        assert!(!result.passed);
381        assert!(result.error.is_some());
382    }
383
384    #[test]
385    fn test_gpu_pixel_result_from_ptx_with_bug() {
386        let ptx = "st.shared.f32 [%rd0], %f0;";
387        let validation = validate_ptx(ptx);
388        let result = GpuPixelResult::from_ptx_validation(&validation);
389        assert!(!result.passed);
390        // Should have the bug class set
391        assert!(result.bug_class.is_some());
392    }
393
394    // ========================================================================
395    // Tests for GpuPixelTestSuite
396    // ========================================================================
397
398    #[test]
399    fn test_gpu_pixel_test_suite_new() {
400        let suite = GpuPixelTestSuite::new("my_kernel");
401        assert_eq!(suite.kernel_name, "my_kernel");
402        assert!(suite.results.is_empty());
403        assert_eq!(suite.duration, std::time::Duration::ZERO);
404    }
405
406    #[test]
407    fn test_gpu_pixel_test_suite_add_result() {
408        let mut suite = GpuPixelTestSuite::new("test");
409        let result = GpuPixelResult::pass("t1", std::time::Duration::from_millis(5));
410        suite.add_result(result);
411        assert_eq!(suite.results.len(), 1);
412        assert_eq!(suite.duration, std::time::Duration::from_millis(5));
413    }
414
415    #[test]
416    fn test_gpu_pixel_test_suite_failures() {
417        let mut suite = GpuPixelTestSuite::new("test");
418        suite.add_result(GpuPixelResult::pass(
419            "t1",
420            std::time::Duration::from_millis(1),
421        ));
422        suite.add_result(GpuPixelResult::fail(
423            "t2",
424            "error",
425            std::time::Duration::from_millis(1),
426        ));
427        suite.add_result(GpuPixelResult::fail(
428            "t3",
429            "error",
430            std::time::Duration::from_millis(1),
431        ));
432
433        let failures = suite.failures();
434        assert_eq!(failures.len(), 2);
435        assert!(!failures[0].passed);
436        assert!(!failures[1].passed);
437    }
438
439    #[test]
440    fn test_gpu_pixel_test_suite_summary_pass() {
441        let mut suite = GpuPixelTestSuite::new("gemm");
442        suite.add_result(GpuPixelResult::pass(
443            "t1",
444            std::time::Duration::from_millis(1),
445        ));
446        let summary = suite.summary();
447        assert!(summary.contains("[PASS]"));
448        assert!(summary.contains("gemm"));
449        assert!(summary.contains("1/1"));
450    }
451
452    #[test]
453    fn test_gpu_pixel_test_suite_summary_fail() {
454        let mut suite = GpuPixelTestSuite::new("buggy");
455        suite.add_result(GpuPixelResult::fail(
456            "t1",
457            "error",
458            std::time::Duration::from_millis(1),
459        ));
460        let summary = suite.summary();
461        assert!(summary.contains("[FAIL]"));
462        assert!(summary.contains("buggy"));
463        assert!(summary.contains("0/1"));
464    }
465
466    #[test]
467    fn test_gpu_pixel_test_suite_run_kernel_pixels_without_shared() {
468        let mut suite = GpuPixelTestSuite::new("test");
469        let ptx = r#"
470.version 8.0
471.target sm_70
472.visible .entry test() { ret; }
473"#;
474        let config = KernelPixelConfig::default();
475        suite.run_kernel_pixels(ptx, &config);
476
477        // Should have run: shared_mem_addressing, kernel_entry_exists, loop_structure
478        // (no barrier_sync because no .shared keyword)
479        assert!(suite.results.len() >= 3);
480    }
481
482    #[test]
483    fn test_gpu_pixel_test_suite_run_kernel_pixels_with_shared() {
484        let mut suite = GpuPixelTestSuite::new("test");
485        let ptx = r#"
486.version 8.0
487.target sm_70
488.visible .entry test() {
489    .shared .b8 smem[1024];
490    bar.sync 0;
491    ret;
492}
493"#;
494        let config = KernelPixelConfig::default();
495        suite.run_kernel_pixels(ptx, &config);
496
497        // Should have run: shared_mem_addressing, kernel_entry_exists, loop_structure, barrier_sync
498        assert!(suite.results.len() >= 4);
499    }
500
501    // ========================================================================
502    // Tests for GpuPixelTest
503    // ========================================================================
504
505    #[test]
506    fn test_gpu_pixel_test_new() {
507        let test = GpuPixelTest::new(
508            "my_test",
509            "Tests something important",
510            PtxBugClass::MissingBarrierSync,
511        );
512        assert_eq!(test.name, "my_test");
513        assert_eq!(test.description, "Tests something important");
514        assert_eq!(test.catches, PtxBugClass::MissingBarrierSync);
515    }
516
517    #[test]
518    fn test_gpu_pixel_test_clone() {
519        let test = GpuPixelTest::new("test", "desc", PtxBugClass::InvalidSyntax);
520        let cloned = test.clone();
521        assert_eq!(cloned.name, test.name);
522        assert_eq!(cloned.description, test.description);
523        assert_eq!(cloned.catches, test.catches);
524    }
525
526    #[test]
527    fn test_gpu_pixel_test_debug() {
528        let test = GpuPixelTest::new("test", "desc", PtxBugClass::LoopBranchToEnd);
529        let debug_str = format!("{:?}", test);
530        assert!(debug_str.contains("GpuPixelTest"));
531        assert!(debug_str.contains("test"));
532    }
533
534    // ========================================================================
535    // Tests for standard_pixel_tests
536    // ========================================================================
537
538    #[test]
539    fn test_standard_pixel_tests_count() {
540        let tests = standard_pixel_tests();
541        assert_eq!(tests.len(), 4);
542    }
543
544    #[test]
545    fn test_standard_pixel_tests_all_variants() {
546        let tests = standard_pixel_tests();
547        let names: Vec<&str> = tests.iter().map(|t| t.name.as_str()).collect();
548        assert!(names.contains(&"shared_mem_u32_addressing"));
549        assert!(names.contains(&"loop_branch_to_start"));
550        assert!(names.contains(&"barrier_sync_present"));
551        assert!(names.contains(&"kernel_entry_exists"));
552    }
553
554    // ========================================================================
555    // Tests for PtxBugClass Display
556    // ========================================================================
557
558    #[test]
559    fn test_ptx_bug_class_display_shared_mem() {
560        assert_eq!(
561            format!("{}", PtxBugClass::SharedMemU64Addressing),
562            "shared_mem_u64"
563        );
564    }
565
566    #[test]
567    fn test_ptx_bug_class_display_loop_branch() {
568        assert_eq!(
569            format!("{}", PtxBugClass::LoopBranchToEnd),
570            "loop_branch_to_end"
571        );
572    }
573
574    #[test]
575    fn test_ptx_bug_class_display_missing_barrier() {
576        assert_eq!(
577            format!("{}", PtxBugClass::MissingBarrierSync),
578            "missing_barrier"
579        );
580    }
581
582    #[test]
583    fn test_ptx_bug_class_display_non_inplace() {
584        assert_eq!(
585            format!("{}", PtxBugClass::NonInPlaceLoopAccumulator),
586            "non_inplace_accum"
587        );
588    }
589
590    #[test]
591    fn test_ptx_bug_class_display_invalid_syntax() {
592        assert_eq!(format!("{}", PtxBugClass::InvalidSyntax), "invalid_syntax");
593    }
594
595    #[test]
596    fn test_ptx_bug_class_display_missing_entry() {
597        assert_eq!(
598            format!("{}", PtxBugClass::MissingEntryPoint),
599            "missing_entry"
600        );
601    }
602
603    // ========================================================================
604    // Tests for PtxValidationResult
605    // ========================================================================
606
607    #[test]
608    fn test_ptx_validation_result_is_valid_with_kernels_no_bugs() {
609        let result = PtxValidationResult {
610            bugs: vec![],
611            kernel_names: vec!["kernel1".to_string()],
612            lines_analyzed: 10,
613        };
614        assert!(result.is_valid());
615    }
616
617    #[test]
618    fn test_ptx_validation_result_invalid_no_kernels() {
619        let result = PtxValidationResult {
620            bugs: vec![],
621            kernel_names: vec![],
622            lines_analyzed: 10,
623        };
624        assert!(!result.is_valid());
625    }
626
627    #[test]
628    fn test_ptx_validation_result_invalid_with_bugs() {
629        let result = PtxValidationResult {
630            bugs: vec![ptx_analysis::PtxBug {
631                class: PtxBugClass::InvalidSyntax,
632                line: 1,
633                instruction: "bad".to_string(),
634                message: "error".to_string(),
635            }],
636            kernel_names: vec!["kernel".to_string()],
637            lines_analyzed: 10,
638        };
639        assert!(!result.is_valid());
640    }
641
642    #[test]
643    fn test_ptx_validation_result_bug_count() {
644        let result = PtxValidationResult {
645            bugs: vec![
646                ptx_analysis::PtxBug {
647                    class: PtxBugClass::SharedMemU64Addressing,
648                    line: 1,
649                    instruction: "st".to_string(),
650                    message: "err".to_string(),
651                },
652                ptx_analysis::PtxBug {
653                    class: PtxBugClass::SharedMemU64Addressing,
654                    line: 2,
655                    instruction: "st".to_string(),
656                    message: "err".to_string(),
657                },
658                ptx_analysis::PtxBug {
659                    class: PtxBugClass::MissingBarrierSync,
660                    line: 3,
661                    instruction: String::new(),
662                    message: "err".to_string(),
663                },
664            ],
665            kernel_names: vec!["k".to_string()],
666            lines_analyzed: 10,
667        };
668        assert_eq!(result.bug_count(&PtxBugClass::SharedMemU64Addressing), 2);
669        assert_eq!(result.bug_count(&PtxBugClass::MissingBarrierSync), 1);
670        assert_eq!(result.bug_count(&PtxBugClass::InvalidSyntax), 0);
671    }
672
673    #[test]
674    fn test_ptx_validation_result_has_bug() {
675        let result = PtxValidationResult {
676            bugs: vec![ptx_analysis::PtxBug {
677                class: PtxBugClass::LoopBranchToEnd,
678                line: 1,
679                instruction: "bra".to_string(),
680                message: "err".to_string(),
681            }],
682            kernel_names: vec!["k".to_string()],
683            lines_analyzed: 5,
684        };
685        assert!(result.has_bug(&PtxBugClass::LoopBranchToEnd));
686        assert!(!result.has_bug(&PtxBugClass::SharedMemU64Addressing));
687    }
688
689    // ========================================================================
690    // Tests for PtxAnalyzer
691    // ========================================================================
692
693    #[test]
694    fn test_ptx_analyzer_default() {
695        let analyzer = PtxAnalyzer::default();
696        assert!(!analyzer.strict);
697    }
698
699    #[test]
700    fn test_ptx_analyzer_strict() {
701        let analyzer = PtxAnalyzer::strict();
702        assert!(analyzer.strict);
703    }
704
705    #[test]
706    fn test_ptx_analyzer_analyze_empty() {
707        let analyzer = PtxAnalyzer::default();
708        let result = analyzer.analyze("");
709        assert!(result.kernel_names.is_empty());
710        assert!(result.bugs.is_empty()); // Empty string has no bugs (only whitespace/empty)
711                                         // lines_analyzed is the count of lines returned by .lines()
712                                         // For empty string, .lines() returns 0 elements
713        assert_eq!(result.lines_analyzed, 0);
714    }
715
716    #[test]
717    fn test_ptx_analyzer_analyze_multiple_bugs() {
718        let ptx = r#"
719.version 8.0
720.target sm_70
721.visible .entry test() {
722    .shared .b8 smem[1024];
723    st.shared.f32 [%rd0], %f0;
724    ret;
725}
726"#;
727        let analyzer = PtxAnalyzer::strict();
728        let result = analyzer.analyze(ptx);
729
730        // Should find: SharedMemU64Addressing, MissingBarrierSync
731        assert!(result.has_bug(&PtxBugClass::SharedMemU64Addressing));
732        assert!(result.has_bug(&PtxBugClass::MissingBarrierSync));
733    }
734
735    #[test]
736    fn test_ptx_analyzer_ld_shared_detection() {
737        let ptx = "ld.shared.f32 %f0, [%r0];";
738        let analyzer = PtxAnalyzer::default();
739        let result = analyzer.analyze(ptx);
740        // ld.shared doesn't match the regex [sl]t\.shared
741        assert!(!result.has_bug(&PtxBugClass::SharedMemU64Addressing));
742    }
743
744    // ========================================================================
745    // Tests for RegressionConfig
746    // ========================================================================
747
748    #[test]
749    fn test_regression_config_default() {
750        let config = RegressionConfig::default();
751        assert_eq!(config.baseline_dir, ".gpu_baselines");
752        assert!(!config.auto_update);
753    }
754
755    #[test]
756    fn test_regression_config_custom() {
757        let config = RegressionConfig {
758            baseline_dir: "/custom/path".to_string(),
759            auto_update: true,
760            pixel_config: KernelPixelConfig {
761                strict_ptx: false,
762                ..Default::default()
763            },
764        };
765        assert_eq!(config.baseline_dir, "/custom/path");
766        assert!(config.auto_update);
767        assert!(!config.pixel_config.strict_ptx);
768    }
769
770    #[test]
771    fn test_regression_config_clone() {
772        let config = RegressionConfig::default();
773        let cloned = config.clone();
774        assert_eq!(cloned.baseline_dir, config.baseline_dir);
775        assert_eq!(cloned.auto_update, config.auto_update);
776    }
777
778    #[test]
779    fn test_regression_config_debug() {
780        let config = RegressionConfig::default();
781        let debug_str = format!("{:?}", config);
782        assert!(debug_str.contains("RegressionConfig"));
783        assert!(debug_str.contains("baseline_dir"));
784    }
785
786    // ========================================================================
787    // Tests for RegressionResult
788    // ========================================================================
789
790    #[test]
791    fn test_regression_result_passed_true() {
792        let suite = GpuPixelTestSuite::new("test");
793        let result = RegressionResult {
794            kernel_name: "test".to_string(),
795            is_regression: false,
796            details: None,
797            pixel_results: suite,
798            duration: std::time::Duration::from_millis(10),
799        };
800        assert!(result.passed());
801    }
802
803    #[test]
804    fn test_regression_result_passed_false_regression() {
805        let suite = GpuPixelTestSuite::new("test");
806        let result = RegressionResult {
807            kernel_name: "test".to_string(),
808            is_regression: true,
809            details: Some("Regression detected".to_string()),
810            pixel_results: suite,
811            duration: std::time::Duration::from_millis(10),
812        };
813        assert!(!result.passed());
814    }
815
816    #[test]
817    fn test_regression_result_passed_false_pixel_failure() {
818        let mut suite = GpuPixelTestSuite::new("test");
819        suite.add_result(GpuPixelResult::fail(
820            "t1",
821            "error",
822            std::time::Duration::from_millis(1),
823        ));
824        let result = RegressionResult {
825            kernel_name: "test".to_string(),
826            is_regression: false,
827            details: None,
828            pixel_results: suite,
829            duration: std::time::Duration::from_millis(10),
830        };
831        assert!(!result.passed());
832    }
833
834    #[test]
835    fn test_regression_result_clone() {
836        let suite = GpuPixelTestSuite::new("test");
837        let result = RegressionResult {
838            kernel_name: "kernel".to_string(),
839            is_regression: true,
840            details: Some("details".to_string()),
841            pixel_results: suite,
842            duration: std::time::Duration::from_millis(5),
843        };
844        let cloned = result.clone();
845        assert_eq!(cloned.kernel_name, result.kernel_name);
846        assert_eq!(cloned.is_regression, result.is_regression);
847        assert_eq!(cloned.details, result.details);
848    }
849
850    #[test]
851    fn test_regression_result_debug() {
852        let suite = GpuPixelTestSuite::new("test");
853        let result = RegressionResult {
854            kernel_name: "test".to_string(),
855            is_regression: false,
856            details: None,
857            pixel_results: suite,
858            duration: std::time::Duration::from_millis(1),
859        };
860        let debug_str = format!("{:?}", result);
861        assert!(debug_str.contains("RegressionResult"));
862        assert!(debug_str.contains("kernel_name"));
863    }
864
865    // ========================================================================
866    // Tests for GpuRegressionSuite
867    // ========================================================================
868
869    #[test]
870    fn test_gpu_regression_suite_new() {
871        let suite = GpuRegressionSuite::new(RegressionConfig::default());
872        assert!(suite.results().is_empty());
873        assert!(suite.all_passed());
874    }
875
876    #[test]
877    fn test_gpu_regression_suite_add_baseline() {
878        let mut suite = GpuRegressionSuite::new(RegressionConfig::default());
879        suite.add_baseline("kernel1", "ptx content");
880        // Baseline is added internally
881    }
882
883    #[test]
884    fn test_gpu_regression_suite_test_kernel_no_baseline() {
885        let mut suite = GpuRegressionSuite::new(RegressionConfig::default());
886        let ptx = ".version 8.0\n.visible .entry k() { ret; }";
887        let result = suite.test_kernel("new_kernel", ptx);
888        assert!(!result.is_regression);
889    }
890
891    #[test]
892    fn test_gpu_regression_suite_test_kernel_with_matching_baseline() {
893        let mut suite = GpuRegressionSuite::new(RegressionConfig::default());
894        let ptx = r#"
895.version 8.0
896.visible .entry test() { ret; }
897"#;
898        suite.add_baseline("test", ptx);
899        let result = suite.test_kernel("test", ptx);
900        assert!(!result.is_regression);
901    }
902
903    #[test]
904    fn test_gpu_regression_suite_barrier_removed_regression() {
905        let mut suite = GpuRegressionSuite::new(RegressionConfig::default());
906
907        let baseline = r#"
908.version 8.0
909.visible .entry test() {
910    .shared .b8 smem[1024];
911    bar.sync 0;
912    ret;
913}
914"#;
915        let current = r#"
916.version 8.0
917.visible .entry test() {
918    .shared .b8 smem[1024];
919    ret;
920}
921"#;
922        suite.add_baseline("test", baseline);
923        let result = suite.test_kernel("test", current);
924        assert!(result.is_regression);
925        assert!(result
926            .details
927            .as_ref()
928            .is_some_and(|d| d.contains("barrier")));
929    }
930
931    #[test]
932    fn test_gpu_regression_suite_kernel_name_changed_regression() {
933        let mut suite = GpuRegressionSuite::new(RegressionConfig::default());
934
935        let baseline = ".version 8.0\n.visible .entry old_name() { ret; }";
936        let current = ".version 8.0\n.visible .entry new_name() { ret; }";
937
938        suite.add_baseline("test", baseline);
939        let result = suite.test_kernel("test", current);
940        assert!(result.is_regression);
941        assert!(result
942            .details
943            .as_ref()
944            .is_some_and(|d| d.contains("kernel name")));
945    }
946
947    #[test]
948    fn test_gpu_regression_suite_u64_addressing_regression() {
949        let mut suite = GpuRegressionSuite::new(RegressionConfig::default());
950
951        let baseline = r#"
952.version 8.0
953.visible .entry test() {
954    st.shared.f32 [%r0], %f0;
955    ret;
956}
957"#;
958        let current = r#"
959.version 8.0
960.visible .entry test() {
961    st.shared.f32 [%rd0], %f0;
962    ret;
963}
964"#;
965        suite.add_baseline("test", baseline);
966        let result = suite.test_kernel("test", current);
967        // Should detect regression (switched from u32 to u64)
968        assert!(result.is_regression);
969    }
970
971    #[test]
972    fn test_gpu_regression_suite_all_passed() {
973        let mut suite = GpuRegressionSuite::new(RegressionConfig::default());
974        let ptx = ".version 8.0\n.visible .entry k() { ret; }";
975        suite.test_kernel("k1", ptx);
976        suite.test_kernel("k2", ptx);
977        assert!(suite.all_passed());
978    }
979
980    #[test]
981    fn test_gpu_regression_suite_summary() {
982        let mut suite = GpuRegressionSuite::new(RegressionConfig::default());
983        let ptx = ".version 8.0\n.visible .entry k() { ret; }";
984        suite.test_kernel("k1", ptx);
985        suite.test_kernel("k2", ptx);
986        let summary = suite.summary();
987        assert!(summary.contains("GPU Regression Suite"));
988        assert!(summary.contains("2/2"));
989        assert!(summary.contains("0 regressions"));
990    }
991
992    #[test]
993    fn test_gpu_regression_suite_debug() {
994        let suite = GpuRegressionSuite::new(RegressionConfig::default());
995        let debug_str = format!("{:?}", suite);
996        assert!(debug_str.contains("GpuRegressionSuite"));
997    }
998
999    // ========================================================================
1000    // Tests for run_regression_suite function
1001    // ========================================================================
1002
1003    #[test]
1004    fn test_run_regression_suite_empty() {
1005        let kernels: Vec<(&str, &str)> = vec![];
1006        let baselines: Vec<(&str, &str)> = vec![];
1007        let suite = run_regression_suite(&kernels, &baselines, RegressionConfig::default());
1008        assert!(suite.results().is_empty());
1009        assert!(suite.all_passed());
1010    }
1011
1012    #[test]
1013    fn test_run_regression_suite_multiple_kernels() {
1014        let kernels = vec![
1015            ("k1", ".version 8.0\n.visible .entry k1() { ret; }"),
1016            ("k2", ".version 8.0\n.visible .entry k2() { ret; }"),
1017            ("k3", ".version 8.0\n.visible .entry k3() { ret; }"),
1018        ];
1019        let baselines: Vec<(&str, &str)> = vec![];
1020        let suite = run_regression_suite(&kernels, &baselines, RegressionConfig::default());
1021        assert_eq!(suite.results().len(), 3);
1022    }
1023
1024    #[test]
1025    fn test_run_regression_suite_with_baselines() {
1026        let baselines = vec![("k1", ".version 8.0\n.visible .entry k1() { ret; }")];
1027        let kernels = vec![("k1", ".version 8.0\n.visible .entry k1() { ret; }")];
1028        let suite = run_regression_suite(&kernels, &baselines, RegressionConfig::default());
1029        assert!(!suite.results()[0].is_regression);
1030    }
1031
1032    #[test]
1033    fn test_run_regression_suite_with_custom_config() {
1034        let config = RegressionConfig {
1035            baseline_dir: "/tmp/baselines".to_string(),
1036            auto_update: true,
1037            pixel_config: KernelPixelConfig {
1038                strict_ptx: false,
1039                ..Default::default()
1040            },
1041        };
1042        let kernels = vec![("k", ".version 8.0\n.visible .entry k() { ret; }")];
1043        let baselines: Vec<(&str, &str)> = vec![];
1044        let suite = run_regression_suite(&kernels, &baselines, config);
1045        assert_eq!(suite.results().len(), 1);
1046    }
1047
1048    // ========================================================================
1049    // Tests for pixel_kernel_entry_exists (via run_kernel_pixels)
1050    // ========================================================================
1051
1052    #[test]
1053    fn test_pixel_kernel_entry_exists_missing() {
1054        let ptx = ".version 8.0\n.target sm_70\n// no entry point";
1055        let config = KernelPixelConfig::default();
1056        let suite = run_kernel_pixels("test", ptx, &config);
1057
1058        // Should fail because no entry point
1059        let failures = suite.failures();
1060        assert!(!failures.is_empty());
1061        assert!(failures
1062            .iter()
1063            .any(|f| f.name.contains("entry")
1064                || f.error.as_ref().is_some_and(|e| e.contains("entry"))));
1065    }
1066
1067    #[test]
1068    fn test_pixel_kernel_entry_exists_present() {
1069        let ptx = ".version 8.0\n.visible .entry my_kernel() { ret; }";
1070        let config = KernelPixelConfig::default();
1071        let suite = run_kernel_pixels("my_kernel", ptx, &config);
1072
1073        // Should pass - entry point exists
1074        assert!(suite.all_passed());
1075    }
1076
1077    // ========================================================================
1078    // Tests for pixel_loop_structure
1079    // ========================================================================
1080
1081    #[test]
1082    fn test_pixel_loop_structure_strict_true_no_loop() {
1083        let ptx = ".version 8.0\n.visible .entry test() { ret; }";
1084        let config = KernelPixelConfig {
1085            strict_ptx: true,
1086            ..Default::default()
1087        };
1088        let suite = run_kernel_pixels("test", ptx, &config);
1089        // No loop = should pass
1090        assert!(suite.all_passed());
1091    }
1092
1093    #[test]
1094    fn test_pixel_loop_structure_strict_false() {
1095        let ptx = r#"
1096.version 8.0
1097.visible .entry test() {
1098    bra some_end;
1099    ret;
1100}
1101"#;
1102        let config = KernelPixelConfig {
1103            strict_ptx: false,
1104            ..Default::default()
1105        };
1106        let suite = run_kernel_pixels("test", ptx, &config);
1107        // Non-strict mode should pass even with potential loop issues
1108        assert!(suite.all_passed());
1109    }
1110
1111    // ========================================================================
1112    // Tests for pixel_barrier_sync
1113    // ========================================================================
1114
1115    #[test]
1116    fn test_pixel_barrier_sync_present() {
1117        let ptx = r#"
1118.version 8.0
1119.visible .entry test() {
1120    .shared .b8 smem[1024];
1121    bar.sync 0;
1122    ret;
1123}
1124"#;
1125        let config = KernelPixelConfig::default();
1126        let suite = run_kernel_pixels("test", ptx, &config);
1127        assert!(suite.all_passed());
1128    }
1129
1130    #[test]
1131    fn test_pixel_barrier_sync_missing() {
1132        let ptx = r#"
1133.version 8.0
1134.visible .entry test() {
1135    .shared .b8 smem[1024];
1136    ret;
1137}
1138"#;
1139        let config = KernelPixelConfig::default();
1140        let suite = run_kernel_pixels("test", ptx, &config);
1141
1142        // Should fail because no bar.sync with shared memory
1143        assert!(!suite.all_passed());
1144        let failures = suite.failures();
1145        assert!(failures
1146            .iter()
1147            .any(|f| f.name.contains("barrier")
1148                || f.bug_class == Some(PtxBugClass::MissingBarrierSync)));
1149    }
1150
1151    // ========================================================================
1152    // Edge case tests
1153    // ========================================================================
1154
1155    #[test]
1156    fn test_validate_ptx_whitespace_only() {
1157        let result = validate_ptx("   \n\t\n   ");
1158        assert!(!result.is_valid());
1159    }
1160
1161    #[test]
1162    fn test_run_kernel_pixels_complex_valid() {
1163        let ptx = r#"
1164.version 8.0
1165.target sm_70
1166.address_size 64
1167
1168.visible .entry complex_kernel(
1169    .param .u64 input_ptr,
1170    .param .u64 output_ptr,
1171    .param .u32 size
1172) {
1173    .reg .u32 %r<10>;
1174    .reg .u64 %rd<5>;
1175    .reg .f32 %f<20>;
1176    .shared .align 16 .b8 tile[4096];
1177
1178    // Load
1179    ld.param.u64 %rd0, [input_ptr];
1180    ld.param.u64 %rd1, [output_ptr];
1181
1182    // Compute with shared memory (u32 addressing)
1183    st.shared.f32 [%r0], %f0;
1184    bar.sync 0;
1185    ld.shared.f32 %f1, [%r1];
1186
1187    // Store result
1188    st.global.f32 [%rd1], %f1;
1189
1190    ret;
1191}
1192"#;
1193        let config = KernelPixelConfig::default();
1194        let suite = run_kernel_pixels("complex_kernel", ptx, &config);
1195        assert!(suite.all_passed());
1196    }
1197
1198    #[test]
1199    fn test_run_kernel_pixels_multiple_kernels_in_ptx() {
1200        let ptx = r#"
1201.version 8.0
1202.target sm_70
1203
1204.visible .entry kernel_a() { ret; }
1205.visible .entry kernel_b() { ret; }
1206.visible .entry kernel_c() { ret; }
1207"#;
1208        let config = KernelPixelConfig::default();
1209        let suite = run_kernel_pixels("multi", ptx, &config);
1210        assert!(suite.all_passed());
1211    }
1212
1213    #[test]
1214    fn test_gpu_pixel_test_suite_duration_accumulates() {
1215        let mut suite = GpuPixelTestSuite::new("test");
1216        suite.add_result(GpuPixelResult::pass(
1217            "t1",
1218            std::time::Duration::from_millis(10),
1219        ));
1220        suite.add_result(GpuPixelResult::pass(
1221            "t2",
1222            std::time::Duration::from_millis(20),
1223        ));
1224        suite.add_result(GpuPixelResult::pass(
1225            "t3",
1226            std::time::Duration::from_millis(30),
1227        ));
1228
1229        assert_eq!(suite.duration, std::time::Duration::from_millis(60));
1230    }
1231
1232    #[test]
1233    fn test_gpu_pixel_result_duration() {
1234        let duration = std::time::Duration::from_secs(1);
1235        let result = GpuPixelResult::pass("test", duration);
1236        assert_eq!(result.duration, duration);
1237    }
1238}