1use crate::core::types::GPUInfo;
23use std::fs;
24use std::path::{Path, PathBuf};
25use std::process::Command;
26
27pub fn detect_gpu() -> GPUInfo {
41 let mut info = GPUInfo::default();
42
43 let rocm_path = detect_rocm_path();
45
46 let rocminfo_cmd = resolve_rocminfo_path(&rocm_path);
48
49 let rocminfo_ok = detect_gpu_from_rocminfo(&rocminfo_cmd, &mut info);
51
52 if info.rocm_version.is_empty() {
54 info.rocm_version = detect_rocm_version_from_file(&rocm_path);
55 }
56
57 if !rocminfo_ok || info.model.is_empty() {
59 detect_gpu_from_lspci(&mut info);
60 }
61
62 if info.gpu_count == 0 {
64 detect_gpu_from_sysfs(&mut info);
65 }
66
67 if info.gpu_count == 0 && !info.model.is_empty() {
69 info.gpu_count = 1;
70 }
71
72 if info.gpu_count > 0 {
74 let rocm_smi_cmd = resolve_rocm_smi_path(&rocm_path);
75 enrich_gpu_metrics(&rocm_smi_cmd, &mut info);
76 }
77
78 info
79}
80
81pub fn get_correct_gfx_from_marketing_name(marketing_name: &str, fallback_gfx: &str) -> String {
97 let name_lower = marketing_name.to_lowercase();
98
99 if name_lower.contains("7900 xtx") || name_lower.contains("7900xtx") {
102 return "gfx1100".to_string();
103 }
104 if name_lower.contains("7900 gre") || name_lower.contains("7900gre") {
105 return "gfx1100".to_string();
106 }
107 if name_lower.contains("7900 xt") || name_lower.contains("7900xt") {
108 return "gfx1100".to_string();
109 }
110 if name_lower.contains("7800 xt") || name_lower.contains("7800xt") {
111 return "gfx1101".to_string();
112 }
113 if name_lower.contains("7800 gre") || name_lower.contains("7800gre") {
114 return "gfx1101".to_string();
115 }
116 if name_lower.contains("7700 xt") || name_lower.contains("7700xt") {
117 return "gfx1101".to_string();
118 }
119 if name_lower.contains("7600 xt")
120 || name_lower.contains("7600xt")
121 || name_lower.contains("7600")
122 {
123 return "gfx1102".to_string();
124 }
125
126 if name_lower.contains("9070 xt")
128 || name_lower.contains("9070xt")
129 || name_lower.contains("9070 gre")
130 || name_lower.contains("9070gre")
131 {
132 return "gfx1200".to_string();
133 }
134 if name_lower.contains("9060") {
135 return "gfx1201".to_string();
136 }
137
138 if name_lower.contains("6950 xt")
140 || name_lower.contains("6950xt")
141 || name_lower.contains("6900 xt")
142 || name_lower.contains("6900xt")
143 {
144 return "gfx1030".to_string();
145 }
146 if name_lower.contains("6800 xt")
147 || name_lower.contains("6800xt")
148 || name_lower.contains("6800")
149 || name_lower.contains("6900")
150 {
151 return "gfx1030".to_string();
152 }
153 if name_lower.contains("6700 xt")
154 || name_lower.contains("6700xt")
155 || name_lower.contains("6750 xt")
156 || name_lower.contains("6750xt")
157 {
158 return "gfx1031".to_string();
159 }
160 if name_lower.contains("6600 xt")
161 || name_lower.contains("6600xt")
162 || name_lower.contains("6600")
163 || name_lower.contains("6650")
164 {
165 return "gfx1032".to_string();
166 }
167 if name_lower.contains("6500 xt") || name_lower.contains("6500xt") {
168 return "gfx1034".to_string();
169 }
170
171 if name_lower.contains("instinct")
173 || name_lower.contains("mi60")
174 || name_lower.contains("mi100")
175 || name_lower.contains("mi200")
176 || name_lower.contains("mi250")
177 || name_lower.contains("mi300")
178 {
179 return format!("gfx{}", fallback_gfx);
180 }
181
182 format!("gfx{}", fallback_gfx)
184}
185
186pub fn detect_rocm_path() -> Option<PathBuf> {
199 let mut search_paths: Vec<PathBuf> = Vec::new();
200
201 if let Ok(env_path) = std::env::var("ROCm_PATH") {
203 search_paths.push(PathBuf::from(env_path));
204 }
205
206 search_paths.push(PathBuf::from("/opt/rocm"));
208
209 for path in &search_paths {
211 if validate_rocm_path(path) {
212 return Some(path.clone());
213 }
214 }
215
216 if let Some(path) = find_versioned_rocm_in_dir("/opt") {
218 return Some(path);
219 }
220
221 let arch_paths = ["/usr/lib/rocm", "/usr/local/rocm"];
223 for path_str in &arch_paths {
224 let path = PathBuf::from(path_str);
225 if validate_rocm_path(&path) {
226 return Some(path);
227 }
228 }
229
230 if let Some(path) = find_versioned_rocm_in_dir("/usr/lib") {
232 return Some(path);
233 }
234
235 if let Ok(output) = Command::new("which").arg("rocminfo").output() {
237 if output.status.success() {
238 let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
239 if !path.is_empty() {
240 if let Some(parent) = Path::new(&path).parent() {
241 if let Some(rocm_root) = parent.parent() {
242 if validate_rocm_path(rocm_root) {
243 return Some(rocm_root.to_path_buf());
244 }
245 }
246 }
247 }
248 }
249 }
250
251 None
252}
253
254pub fn get_rocm_version() -> String {
265 let rocm_path = detect_rocm_path();
266
267 if let Some(ref path) = rocm_path {
269 let version_file = path.join(".info/version");
270 if let Ok(version) = read_version_file(&version_file) {
271 return version;
272 }
273 }
274
275 if let Some(ref path) = rocm_path {
277 let version_file = path.join("version");
278 if let Ok(version) = read_version_file(&version_file) {
279 return version;
280 }
281 }
282
283 if let Ok(output) = Command::new("rocminfo").arg("--version").output() {
285 if output.status.success() {
286 let stdout = String::from_utf8_lossy(&output.stdout);
287 if let Some(version) = parse_version_from_output(&stdout) {
288 return version;
289 }
290 }
291 }
292
293 if let Ok(output) = Command::new("rocm-smi").arg("--version").output() {
295 if output.status.success() {
296 let stdout = String::from_utf8_lossy(&output.stdout);
297 if let Some(version) = parse_version_from_output(&stdout) {
298 return version;
299 }
300 }
301 }
302
303 if let Some(ref path) = rocm_path {
305 if let Some(version) = extract_version_from_path(path) {
306 return version;
307 }
308 }
309
310 String::new()
311}
312
313fn resolve_rocminfo_path(rocm_path: &Option<PathBuf>) -> String {
319 if let Some(ref path) = rocm_path {
320 let rocminfo_path = path.join("bin/rocminfo");
321 if rocminfo_path.exists() {
322 return rocminfo_path.to_string_lossy().to_string();
323 }
324 }
325 if Path::new("/opt/rocm/bin/rocminfo").exists() {
326 "/opt/rocm/bin/rocminfo".to_string()
327 } else {
328 "rocminfo".to_string()
329 }
330}
331
332fn resolve_rocm_smi_path(rocm_path: &Option<PathBuf>) -> String {
334 if let Some(ref path) = rocm_path {
335 let rocm_smi_path = path.join("bin/rocm-smi");
336 if rocm_smi_path.exists() {
337 return rocm_smi_path.to_string_lossy().to_string();
338 }
339 }
340 if Path::new("/opt/rocm/bin/rocm-smi").exists() {
341 "/opt/rocm/bin/rocm-smi".to_string()
342 } else {
343 "rocm-smi".to_string()
344 }
345}
346
347fn detect_gpu_from_rocminfo(rocminfo_cmd: &str, info: &mut GPUInfo) -> bool {
351 let output = match Command::new(rocminfo_cmd).output() {
352 Ok(o) if o.status.success() => o,
353 _ => return false,
354 };
355
356 let stdout = String::from_utf8_lossy(&output.stdout);
357 let mut gpu_count = 0usize;
358 let mut current_marketing_name = String::new();
359 let mut current_device_type = String::new();
360
361 for line in stdout.lines() {
362 let trimmed = line.trim();
363
364 if trimmed.starts_with("Marketing Name:") {
366 current_marketing_name = trimmed
367 .strip_prefix("Marketing Name:")
368 .unwrap_or("")
369 .trim()
370 .to_string();
371 }
372
373 if trimmed.starts_with("Device Type:") {
375 current_device_type = trimmed
376 .strip_prefix("Device Type:")
377 .unwrap_or("")
378 .trim()
379 .to_string();
380 }
381
382 if line.contains("Name:") {
384 let name = line.replace("Name:", "").trim().to_string();
385 if !name.is_empty() && !name.to_lowercase().contains("cpu") {
386 gpu_count += 1;
387 if info.model.is_empty() {
388 info.model = name;
389 }
390 }
391 }
392
393 if trimmed.starts_with("Name:") && trimmed.contains("gfx") {
395 if let Some(name_value) = trimmed.strip_prefix("Name:") {
396 let name_value = name_value.trim();
397 if let Some(after_gfx) = name_value.strip_prefix("gfx") {
398 let gfx_num: String = after_gfx
399 .chars()
400 .take_while(|c| c.is_ascii_digit())
401 .collect();
402 if !gfx_num.is_empty() && gfx_num.len() >= 3 {
403 let is_igpu = current_marketing_name.to_lowercase().contains("ryzen")
405 || current_marketing_name.to_lowercase().contains("apu")
406 || current_marketing_name.to_lowercase().contains("integrated");
407 if !is_igpu && current_device_type == "GPU" && info.architecture.is_empty()
408 {
409 info.architecture = get_correct_gfx_from_marketing_name(
410 ¤t_marketing_name,
411 &gfx_num,
412 );
413 }
414 }
415 }
416 }
417 }
418 }
419
420 if gpu_count > 0 {
421 info.gpu_count = gpu_count;
422 true
423 } else {
424 false
425 }
426}
427
428fn detect_gpu_from_lspci(info: &mut GPUInfo) {
432 let lspci_cmd = if Path::new("/usr/bin/lspci").exists() {
433 "/usr/bin/lspci"
434 } else {
435 "lspci"
436 };
437
438 if let Ok(output) = Command::new(lspci_cmd).output() {
439 let stdout = String::from_utf8_lossy(&output.stdout);
440 for line in stdout.lines() {
441 if line.to_lowercase().contains("amd") && line.to_lowercase().contains("vga") {
442 if info.model.is_empty() {
443 info.model = line.trim().to_string();
444 }
445 info.gpu_count += 1;
446 }
447 }
448 }
449}
450
451fn detect_gpu_from_sysfs(info: &mut GPUInfo) {
455 let mut count = 0usize;
456 let mut model = String::new();
457
458 let entries = match fs::read_dir("/sys/class/drm") {
459 Ok(e) => e,
460 Err(_) => return,
461 };
462
463 for entry in entries.flatten() {
464 let name = entry.file_name();
465 let name = name.to_string_lossy();
466 if !name.starts_with("card") || name.contains("render") {
467 continue;
468 }
469
470 let vendor_path = entry.path().join("device/vendor");
471 let vendor = match fs::read_to_string(&vendor_path) {
472 Ok(value) => value,
473 Err(_) => continue,
474 };
475
476 if vendor.trim().eq_ignore_ascii_case("0x1002") {
478 count += 1;
479 if model.is_empty() {
480 let device_path = entry.path().join("device");
481 let uevent_path = device_path.join("uevent");
482 if let Ok(uevent) = fs::read_to_string(&uevent_path) {
483 for line in uevent.lines() {
484 if let Some(driver) = line.strip_prefix("DRIVER=") {
485 model = format!("AMD GPU ({})", driver);
486 break;
487 }
488 }
489 }
490 if model.is_empty() {
491 model = "AMD GPU".to_string();
492 }
493 }
494 }
495 }
496
497 if count > 0 {
498 info.gpu_count = count;
499 if info.model.is_empty() {
500 info.model = model;
501 }
502 }
503}
504
505fn detect_rocm_version_from_file(rocm_path: &Option<PathBuf>) -> String {
507 let version_path = if let Some(ref path) = rocm_path {
508 path.join(".info/version")
509 } else {
510 PathBuf::from("/opt/rocm/.info/version")
511 };
512
513 read_version_file(&version_path).unwrap_or_default()
514}
515
516fn read_version_file(path: &Path) -> Result<String, std::io::Error> {
518 let content = fs::read_to_string(path)?;
519 Ok(content.trim().to_string())
520}
521
522fn parse_version_from_output(output: &str) -> Option<String> {
524 for part in output.split_whitespace() {
526 let parts: Vec<&str> = part.split('.').collect();
527 if parts.len() >= 2 && parts.iter().all(|p| p.parse::<u32>().is_ok()) {
528 return Some(part.to_string());
529 }
530 }
531 None
532}
533
534fn extract_version_from_path(path: &Path) -> Option<String> {
536 let name = path.file_name()?.to_string_lossy();
537 if let Some(version) = name.strip_prefix("rocm-") {
538 let version = version.trim().to_string();
539 if !version.is_empty() {
540 return Some(version);
541 }
542 }
543 None
544}
545
546fn validate_rocm_path(path: &Path) -> bool {
548 if !path.exists() || !path.is_dir() {
549 return false;
550 }
551 path.join("bin").exists()
553 || path.join("lib").exists()
554 || path.join("rocminfo").exists()
555 || path.join("rocm-smi").exists()
556}
557
558fn find_versioned_rocm_in_dir(parent_dir: &str) -> Option<PathBuf> {
560 let entries = fs::read_dir(parent_dir).ok()?;
561 let mut versioned_paths: Vec<(Vec<i32>, PathBuf)> = entries
562 .filter_map(|e| e.ok())
563 .filter_map(|e| {
564 let name = e.file_name().to_string_lossy().to_string();
565 if name.starts_with("rocm-") {
566 let version = name.trim_start_matches("rocm-").to_string();
567 let parts: Vec<i32> = version.split('.').filter_map(|s| s.parse().ok()).collect();
568 if !parts.is_empty() {
569 Some((parts, e.path()))
570 } else {
571 None
572 }
573 } else {
574 None
575 }
576 })
577 .collect();
578
579 versioned_paths.sort_by(|a, b| {
581 for i in 0..std::cmp::max(a.0.len(), b.0.len()) {
582 let a_val = *a.0.get(i).unwrap_or(&0);
583 let b_val = *b.0.get(i).unwrap_or(&0);
584 if a_val != b_val {
585 return b_val.cmp(&a_val);
586 }
587 }
588 std::cmp::Ordering::Equal
589 });
590
591 for (_version_parts, path) in versioned_paths {
592 if validate_rocm_path(&path) {
593 return Some(path);
594 }
595 }
596
597 None
598}
599
600fn enrich_gpu_metrics(rocm_smi_cmd: &str, info: &mut GPUInfo) {
602 if let Ok(output) = Command::new(rocm_smi_cmd)
604 .args(["--showmeminfo", "vram", "--csv"])
605 .output()
606 {
607 if output.status.success() {
608 let stdout = String::from_utf8_lossy(&output.stdout);
609 for line in stdout.lines().skip(1) {
610 let parts: Vec<&str> = line.split(',').collect();
611 if parts.len() >= 2 {
612 if let Ok(value) = parts[1].trim().parse::<f32>() {
613 info.memory_gb = value / 1024.0;
614 break;
615 }
616 }
617 }
618 }
619 }
620
621 if let Ok(output) = Command::new(rocm_smi_cmd)
623 .args(["--showtemp", "--csv"])
624 .output()
625 {
626 if output.status.success() {
627 let stdout = String::from_utf8_lossy(&output.stdout);
628 for line in stdout.lines().skip(1) {
629 let parts: Vec<&str> = line.split(',').collect();
630 if parts.len() >= 2 {
631 if let Ok(value) = parts[1].trim().parse::<f32>() {
632 info.temperature_c = Some(value);
633 break;
634 }
635 }
636 }
637 }
638 }
639
640 if let Ok(output) = Command::new(rocm_smi_cmd)
642 .args(["--showpower", "--csv"])
643 .output()
644 {
645 if output.status.success() {
646 let stdout = String::from_utf8_lossy(&output.stdout);
647 for line in stdout.lines().skip(1) {
648 let parts: Vec<&str> = line.split(',').collect();
649 if parts.len() >= 2 {
650 if let Ok(value) = parts[1].trim().parse::<f32>() {
651 info.power_watts = Some(value);
652 break;
653 }
654 }
655 }
656 }
657 }
658}
659
660#[cfg(test)]
665mod tests {
666 use super::*;
667
668 #[test]
673 fn test_gfx_correction_rdna3_7900_xtx() {
674 assert_eq!(
675 get_correct_gfx_from_marketing_name("AMD Radeon RX 7900 XTX", "1030"),
676 "gfx1100"
677 );
678 }
679
680 #[test]
681 fn test_gfx_correction_rdna3_7900_xtx_no_space() {
682 assert_eq!(
683 get_correct_gfx_from_marketing_name("AMD Radeon RX 7900XTX", "1030"),
684 "gfx1100"
685 );
686 }
687
688 #[test]
689 fn test_gfx_correction_rdna3_7900_xt() {
690 assert_eq!(
691 get_correct_gfx_from_marketing_name("AMD Radeon RX 7900 XT", "1030"),
692 "gfx1100"
693 );
694 }
695
696 #[test]
697 fn test_gfx_correction_rdna3_7900_gre() {
698 assert_eq!(
699 get_correct_gfx_from_marketing_name("AMD Radeon RX 7900 GRE", "1030"),
700 "gfx1100"
701 );
702 }
703
704 #[test]
705 fn test_gfx_correction_rdna3_7800_xt() {
706 assert_eq!(
707 get_correct_gfx_from_marketing_name("AMD Radeon RX 7800 XT", "1030"),
708 "gfx1101"
709 );
710 }
711
712 #[test]
713 fn test_gfx_correction_rdna3_7700_xt() {
714 assert_eq!(
715 get_correct_gfx_from_marketing_name("AMD Radeon RX 7700 XT", "1030"),
716 "gfx1101"
717 );
718 }
719
720 #[test]
721 fn test_gfx_correction_rdna3_7600() {
722 assert_eq!(
723 get_correct_gfx_from_marketing_name("AMD Radeon RX 7600", "1030"),
724 "gfx1102"
725 );
726 }
727
728 #[test]
729 fn test_gfx_correction_rdna4_9070_xt() {
730 assert_eq!(
731 get_correct_gfx_from_marketing_name("AMD Radeon RX 9070 XT", "1200"),
732 "gfx1200"
733 );
734 }
735
736 #[test]
737 fn test_gfx_correction_rdna4_9060() {
738 assert_eq!(
739 get_correct_gfx_from_marketing_name("AMD Radeon RX 9060", "1201"),
740 "gfx1201"
741 );
742 }
743
744 #[test]
745 fn test_gfx_correction_rdna2_6900_xt() {
746 assert_eq!(
747 get_correct_gfx_from_marketing_name("AMD Radeon RX 6900 XT", "1030"),
748 "gfx1030"
749 );
750 }
751
752 #[test]
753 fn test_gfx_correction_rdna2_6800() {
754 assert_eq!(
755 get_correct_gfx_from_marketing_name("AMD Radeon RX 6800", "1030"),
756 "gfx1030"
757 );
758 }
759
760 #[test]
761 fn test_gfx_correction_rdna2_6700_xt() {
762 assert_eq!(
763 get_correct_gfx_from_marketing_name("AMD Radeon RX 6700 XT", "1030"),
764 "gfx1031"
765 );
766 }
767
768 #[test]
769 fn test_gfx_correction_rdna2_6600() {
770 assert_eq!(
771 get_correct_gfx_from_marketing_name("AMD Radeon RX 6600", "1030"),
772 "gfx1032"
773 );
774 }
775
776 #[test]
777 fn test_gfx_correction_rdna2_6500_xt() {
778 assert_eq!(
779 get_correct_gfx_from_marketing_name("AMD Radeon RX 6500 XT", "1030"),
780 "gfx1034"
781 );
782 }
783
784 #[test]
785 fn test_gfx_correction_cdna_instinct() {
786 assert_eq!(
788 get_correct_gfx_from_marketing_name("AMD Instinct MI250X", "90a0"),
789 "gfx90a0"
790 );
791 }
792
793 #[test]
794 fn test_gfx_correction_unknown_passthrough() {
795 assert_eq!(
797 get_correct_gfx_from_marketing_name("Unknown GPU Model", "999"),
798 "gfx999"
799 );
800 }
801
802 #[test]
807 fn test_detect_gpu_from_sysfs_no_drm_dir() {
808 let mut info = GPUInfo::default();
810 detect_gpu_from_sysfs(&mut info);
811 }
815
816 #[test]
817 fn test_detect_gpu_from_lspci_no_panic() {
818 let mut info = GPUInfo::default();
820 detect_gpu_from_lspci(&mut info);
821 }
823
824 #[test]
825 fn test_detect_gpu_from_rocminfo_bad_command() {
826 let mut info = GPUInfo::default();
828 let result = detect_gpu_from_rocminfo("/nonexistent/rocminfo", &mut info);
829 assert!(!result);
830 assert_eq!(info.gpu_count, 0);
831 }
832
833 #[test]
838 fn test_detect_rocm_version_from_file_with_real_install() {
839 let rocm_path = Some(PathBuf::from("/opt/rocm"));
841 let version = detect_rocm_version_from_file(&rocm_path);
842 if Path::new("/opt/rocm/.info/version").exists() {
843 assert!(
844 !version.is_empty(),
845 "ROCm version should be non-empty when file exists"
846 );
847 let parts: Vec<&str> = version.split('.').collect();
849 assert!(
850 parts.len() >= 2,
851 "Version should have at least major.minor: got '{}'",
852 version
853 );
854 }
855 }
856
857 #[test]
858 fn test_detect_rocm_version_from_file_missing() {
859 let rocm_path = Some(PathBuf::from("/nonexistent/rocm"));
860 let version = detect_rocm_version_from_file(&rocm_path);
861 assert!(
862 version.is_empty(),
863 "Version should be empty for nonexistent path"
864 );
865 }
866
867 #[test]
872 fn test_detect_rocm_path_finds_standard_location() {
873 let path = detect_rocm_path();
875 if Path::new("/opt/rocm").is_dir() {
876 assert!(path.is_some(), "Should detect ROCm at /opt/rocm");
877 let p = path.unwrap();
878 assert!(
879 p.as_os_str() == "/opt/rocm" || p.to_string_lossy().contains("rocm"),
880 "Path should contain 'rocm': got {:?}",
881 p
882 );
883 }
884 }
885
886 #[test]
887 fn test_detect_rocm_path_env_override() {
888 if Path::new("/opt/rocm").is_dir() {
891 std::env::set_var("ROCm_PATH", "/opt/rocm");
892 let path = detect_rocm_path();
893 assert_eq!(path, Some(PathBuf::from("/opt/rocm")));
894 std::env::remove_var("ROCm_PATH");
895 }
896 }
897
898 #[test]
899 fn test_validate_rocm_path_rejects_nonexistent() {
900 assert!(!validate_rocm_path(Path::new("/nonexistent/path")));
901 }
902
903 #[test]
904 fn test_validate_rocm_path_accepts_standard() {
905 if Path::new("/opt/rocm/bin").is_dir() {
906 assert!(validate_rocm_path(Path::new("/opt/rocm")));
907 }
908 }
909
910 #[test]
915 fn test_get_rocm_version_returns_nonempty_on_installed_system() {
916 if Path::new("/opt/rocm/.info/version").exists() {
917 let version = get_rocm_version();
918 assert!(
919 !version.is_empty(),
920 "Should detect ROCm version on installed system"
921 );
922 }
923 }
924
925 #[test]
930 fn test_parse_version_from_output() {
931 assert_eq!(
932 parse_version_from_output("ROCm version 7.2.1"),
933 Some("7.2.1".to_string())
934 );
935 assert_eq!(
936 parse_version_from_output("rocm-smi version 4.0.0"),
937 Some("4.0.0".to_string())
938 );
939 assert_eq!(parse_version_from_output("no version here"), None);
940 }
941
942 #[test]
943 fn test_extract_version_from_path() {
944 assert_eq!(
945 extract_version_from_path(Path::new("/opt/rocm-7.2.1")),
946 Some("7.2.1".to_string())
947 );
948 assert_eq!(extract_version_from_path(Path::new("/opt/rocm")), None);
949 }
950
951 #[test]
952 fn test_read_version_file() {
953 let dir = tempfile::tempdir().unwrap();
954 let version_file = dir.path().join("version");
955 fs::write(&version_file, "7.2.2\n").unwrap();
956 let version = read_version_file(&version_file).unwrap();
957 assert_eq!(version, "7.2.2");
958 }
959
960 #[test]
961 fn test_read_version_file_trims_whitespace() {
962 let dir = tempfile::tempdir().unwrap();
963 let version_file = dir.path().join("version");
964 fs::write(&version_file, " 7.2.2 \n").unwrap();
965 let version = read_version_file(&version_file).unwrap();
966 assert_eq!(version, "7.2.2");
967 }
968
969 #[test]
970 fn test_find_versioned_rocm_no_dir() {
971 let result = find_versioned_rocm_in_dir("/nonexistent/dir");
972 assert!(result.is_none());
973 }
974
975 #[test]
982 #[ignore]
983 fn test_detect_gpu_real_hardware() {
984 let info = detect_gpu();
985 assert!(
986 info.gpu_count >= 1,
987 "Expected at least 1 AMD GPU, got gpu_count = {}",
988 info.gpu_count
989 );
990 assert!(
991 !info.model.is_empty(),
992 "GPU model should be non-empty on real hardware"
993 );
994 }
995
996 #[test]
998 #[ignore]
999 fn test_detect_gpu_real_hardware_architecture() {
1000 let info = detect_gpu();
1001 if info.gpu_count > 0 {
1002 assert!(
1003 !info.architecture.is_empty(),
1004 "Architecture should be detected on real AMD GPU"
1005 );
1006 assert!(
1007 info.architecture.starts_with("gfx"),
1008 "Architecture should start with 'gfx': got '{}'",
1009 info.architecture
1010 );
1011 }
1012 }
1013
1014 #[test]
1016 #[ignore]
1017 fn test_detect_rocm_version_real_system() {
1018 let info = detect_gpu();
1019 assert!(
1020 !info.rocm_version.is_empty(),
1021 "ROCm version should be detected on installed system"
1022 );
1023 }
1024
1025 #[test]
1027 #[ignore]
1028 fn test_detect_rocm_path_real_system() {
1029 let path = detect_rocm_path();
1030 assert!(
1031 path.is_some(),
1032 "ROCm path should be detected on installed system"
1033 );
1034 let p = path.unwrap();
1035 assert!(
1036 p.exists() && p.is_dir(),
1037 "ROCm path should be an existing directory"
1038 );
1039 }
1040
1041 #[test]
1043 #[ignore]
1044 fn test_get_rocm_version_real_system() {
1045 let version = get_rocm_version();
1046 assert!(
1047 !version.is_empty(),
1048 "Should detect ROCm version via multi-method fallback"
1049 );
1050 let parts: Vec<&str> = version.split('.').collect();
1052 assert!(
1053 parts.len() >= 2,
1054 "Version should have at least major.minor: got '{}'",
1055 version
1056 );
1057 }
1058
1059 #[test]
1061 #[ignore]
1062 fn test_detect_gpu_real_hardware_metrics() {
1063 let info = detect_gpu();
1064 if info.gpu_count > 0 {
1065 assert!(
1067 info.memory_gb > 0.0,
1068 "GPU memory should be positive: got {} GB",
1069 info.memory_gb
1070 );
1071 }
1072 }
1073
1074 #[test]
1076 #[ignore]
1077 fn test_detect_gpu_matches_rocm_smi() {
1078 let info = detect_gpu();
1079
1080 let output = Command::new("/opt/rocm/bin/rocm-smi")
1082 .args(["--showproductinfo", "--csv"])
1083 .output()
1084 .expect("rocm-smi should be available");
1085
1086 if output.status.success() {
1087 let stdout = String::from_utf8_lossy(&output.stdout);
1088 if stdout.contains("card") {
1090 assert!(
1091 info.gpu_count >= 1,
1092 "GPU count should be >= 1 if rocm-smi reports cards"
1093 );
1094 }
1095 }
1096 }
1097}