1mod 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#[must_use]
47pub fn validate_ptx(ptx: &str) -> PtxValidationResult {
48 PtxAnalyzer::default().analyze(ptx)
49}
50
51pub 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 let ptx_result = validate_ptx(ptx);
69 suite.add_result(GpuPixelResult::from_ptx_validation(&ptx_result));
70
71 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 assert!(!result
145 .bugs
146 .iter()
147 .any(|b| matches!(b.class, PtxBugClass::SharedMemU64Addressing)));
148 }
149
150 #[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 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 assert!(!suite.all_passed());
206 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 let config = KernelPixelConfig {
226 strict_ptx: true,
227 ..Default::default()
228 };
229 let suite = run_kernel_pixels("test_kernel", ptx, &config);
230
231 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 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 #[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 #[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 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 assert!(result.bug_class.is_some());
392 }
393
394 #[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 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 assert!(suite.results.len() >= 4);
499 }
500
501 #[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 #[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 #[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 #[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 #[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()); 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 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 assert!(!result.has_bug(&PtxBugClass::SharedMemU64Addressing));
742 }
743
744 #[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 #[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 #[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 }
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 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 #[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 #[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 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 assert!(suite.all_passed());
1075 }
1076
1077 #[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 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 assert!(suite.all_passed());
1109 }
1110
1111 #[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 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 #[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}