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 gfx_arch = format!("gfx{gfx_num}");
409 let is_igpu = crate::gpu::device_is_integrated(
410 Some(¤t_marketing_name),
411 None,
412 Some(&gfx_arch),
413 None,
414 );
415 if !is_igpu && current_device_type == "GPU" && info.architecture.is_empty()
416 {
417 info.architecture = get_correct_gfx_from_marketing_name(
418 ¤t_marketing_name,
419 &gfx_num,
420 );
421 }
422 }
423 }
424 }
425 }
426 }
427
428 let discrete_count = crate::installer::detect_discrete_gpus().len();
433 if discrete_count > 0 {
434 info.gpu_count = discrete_count;
435 true
436 } else if gpu_count > 0 {
437 info.gpu_count = gpu_count;
438 true
439 } else {
440 false
441 }
442}
443
444fn detect_gpu_from_lspci(info: &mut GPUInfo) {
448 let lspci_cmd = if Path::new("/usr/bin/lspci").exists() {
449 "/usr/bin/lspci"
450 } else {
451 "lspci"
452 };
453
454 if let Ok(output) = Command::new(lspci_cmd).output() {
455 let stdout = String::from_utf8_lossy(&output.stdout);
456 for line in stdout.lines() {
457 if line.to_lowercase().contains("amd") && line.to_lowercase().contains("vga") {
458 if info.model.is_empty() {
459 info.model = line.trim().to_string();
460 }
461 info.gpu_count += 1;
462 }
463 }
464 }
465}
466
467fn detect_gpu_from_sysfs(info: &mut GPUInfo) {
471 let mut count = 0usize;
472 let mut model = String::new();
473
474 let entries = match fs::read_dir("/sys/class/drm") {
475 Ok(e) => e,
476 Err(_) => return,
477 };
478
479 for entry in entries.flatten() {
480 let name = entry.file_name();
481 let name = name.to_string_lossy();
482 if !name.starts_with("card") || name.contains("render") {
483 continue;
484 }
485
486 let vendor_path = entry.path().join("device/vendor");
487 let vendor = match fs::read_to_string(&vendor_path) {
488 Ok(value) => value,
489 Err(_) => continue,
490 };
491
492 if vendor.trim().eq_ignore_ascii_case("0x1002") {
494 count += 1;
495 if model.is_empty() {
496 let device_path = entry.path().join("device");
497 let uevent_path = device_path.join("uevent");
498 if let Ok(uevent) = fs::read_to_string(&uevent_path) {
499 for line in uevent.lines() {
500 if let Some(driver) = line.strip_prefix("DRIVER=") {
501 model = format!("AMD GPU ({})", driver);
502 break;
503 }
504 }
505 }
506 if model.is_empty() {
507 model = "AMD GPU".to_string();
508 }
509 }
510 }
511 }
512
513 if count > 0 {
514 info.gpu_count = count;
515 if info.model.is_empty() {
516 info.model = model;
517 }
518 }
519}
520
521fn detect_rocm_version_from_file(rocm_path: &Option<PathBuf>) -> String {
523 let version_path = if let Some(ref path) = rocm_path {
524 path.join(".info/version")
525 } else {
526 PathBuf::from("/opt/rocm/.info/version")
527 };
528
529 read_version_file(&version_path).unwrap_or_default()
530}
531
532fn read_version_file(path: &Path) -> Result<String, std::io::Error> {
534 let content = fs::read_to_string(path)?;
535 Ok(content.trim().to_string())
536}
537
538fn parse_version_from_output(output: &str) -> Option<String> {
540 for part in output.split_whitespace() {
542 let parts: Vec<&str> = part.split('.').collect();
543 if parts.len() >= 2 && parts.iter().all(|p| p.parse::<u32>().is_ok()) {
544 return Some(part.to_string());
545 }
546 }
547 None
548}
549
550fn extract_version_from_path(path: &Path) -> Option<String> {
552 let name = path.file_name()?.to_string_lossy();
553 if let Some(version) = name.strip_prefix("rocm-") {
554 let version = version.trim().to_string();
555 if !version.is_empty() {
556 return Some(version);
557 }
558 }
559 None
560}
561
562fn validate_rocm_path(path: &Path) -> bool {
564 if !path.exists() || !path.is_dir() {
565 return false;
566 }
567 path.join("bin").exists()
569 || path.join("lib").exists()
570 || path.join("rocminfo").exists()
571 || path.join("rocm-smi").exists()
572}
573
574fn find_versioned_rocm_in_dir(parent_dir: &str) -> Option<PathBuf> {
576 let entries = fs::read_dir(parent_dir).ok()?;
577 let mut versioned_paths: Vec<(Vec<i32>, PathBuf)> = entries
578 .filter_map(|e| e.ok())
579 .filter_map(|e| {
580 let name = e.file_name().to_string_lossy().to_string();
581 if name.starts_with("rocm-") {
582 let version = name.trim_start_matches("rocm-").to_string();
583 let parts: Vec<i32> = version.split('.').filter_map(|s| s.parse().ok()).collect();
584 if !parts.is_empty() {
585 Some((parts, e.path()))
586 } else {
587 None
588 }
589 } else {
590 None
591 }
592 })
593 .collect();
594
595 versioned_paths.sort_by(|a, b| {
597 for i in 0..std::cmp::max(a.0.len(), b.0.len()) {
598 let a_val = *a.0.get(i).unwrap_or(&0);
599 let b_val = *b.0.get(i).unwrap_or(&0);
600 if a_val != b_val {
601 return b_val.cmp(&a_val);
602 }
603 }
604 std::cmp::Ordering::Equal
605 });
606
607 for (_version_parts, path) in versioned_paths {
608 if validate_rocm_path(&path) {
609 return Some(path);
610 }
611 }
612
613 None
614}
615
616fn enrich_gpu_metrics(rocm_smi_cmd: &str, info: &mut GPUInfo) {
618 if let Ok(output) = Command::new(rocm_smi_cmd)
620 .args(["--showmeminfo", "vram", "--csv"])
621 .output()
622 {
623 if output.status.success() {
624 let stdout = String::from_utf8_lossy(&output.stdout);
625 for line in stdout.lines().skip(1) {
626 let parts: Vec<&str> = line.split(',').collect();
627 if parts.len() >= 2 {
628 if let Ok(value) = parts[1].trim().parse::<f32>() {
629 info.memory_gb = value / 1024.0;
630 break;
631 }
632 }
633 }
634 }
635 }
636
637 if let Ok(output) = Command::new(rocm_smi_cmd)
639 .args(["--showtemp", "--csv"])
640 .output()
641 {
642 if output.status.success() {
643 let stdout = String::from_utf8_lossy(&output.stdout);
644 for line in stdout.lines().skip(1) {
645 let parts: Vec<&str> = line.split(',').collect();
646 if parts.len() >= 2 {
647 if let Ok(value) = parts[1].trim().parse::<f32>() {
648 info.temperature_c = Some(value);
649 break;
650 }
651 }
652 }
653 }
654 }
655
656 if let Ok(output) = Command::new(rocm_smi_cmd)
658 .args(["--showpower", "--csv"])
659 .output()
660 {
661 if output.status.success() {
662 let stdout = String::from_utf8_lossy(&output.stdout);
663 for line in stdout.lines().skip(1) {
664 let parts: Vec<&str> = line.split(',').collect();
665 if parts.len() >= 2 {
666 if let Ok(value) = parts[1].trim().parse::<f32>() {
667 info.power_watts = Some(value);
668 break;
669 }
670 }
671 }
672 }
673 }
674}
675
676#[cfg(test)]
681mod tests {
682 use super::*;
683
684 #[test]
689 fn test_gfx_correction_rdna3_7900_xtx() {
690 assert_eq!(
691 get_correct_gfx_from_marketing_name("AMD Radeon RX 7900 XTX", "1030"),
692 "gfx1100"
693 );
694 }
695
696 #[test]
697 fn test_gfx_correction_rdna3_7900_xtx_no_space() {
698 assert_eq!(
699 get_correct_gfx_from_marketing_name("AMD Radeon RX 7900XTX", "1030"),
700 "gfx1100"
701 );
702 }
703
704 #[test]
705 fn test_gfx_correction_rdna3_7900_xt() {
706 assert_eq!(
707 get_correct_gfx_from_marketing_name("AMD Radeon RX 7900 XT", "1030"),
708 "gfx1100"
709 );
710 }
711
712 #[test]
713 fn test_gfx_correction_rdna3_7900_gre() {
714 assert_eq!(
715 get_correct_gfx_from_marketing_name("AMD Radeon RX 7900 GRE", "1030"),
716 "gfx1100"
717 );
718 }
719
720 #[test]
721 fn test_gfx_correction_rdna3_7800_xt() {
722 assert_eq!(
723 get_correct_gfx_from_marketing_name("AMD Radeon RX 7800 XT", "1030"),
724 "gfx1101"
725 );
726 }
727
728 #[test]
729 fn test_gfx_correction_rdna3_7700_xt() {
730 assert_eq!(
731 get_correct_gfx_from_marketing_name("AMD Radeon RX 7700 XT", "1030"),
732 "gfx1101"
733 );
734 }
735
736 #[test]
737 fn test_gfx_correction_rdna3_7600() {
738 assert_eq!(
739 get_correct_gfx_from_marketing_name("AMD Radeon RX 7600", "1030"),
740 "gfx1102"
741 );
742 }
743
744 #[test]
745 fn test_gfx_correction_rdna4_9070_xt() {
746 assert_eq!(
747 get_correct_gfx_from_marketing_name("AMD Radeon RX 9070 XT", "1200"),
748 "gfx1200"
749 );
750 }
751
752 #[test]
753 fn test_gfx_correction_rdna4_9060() {
754 assert_eq!(
755 get_correct_gfx_from_marketing_name("AMD Radeon RX 9060", "1201"),
756 "gfx1201"
757 );
758 }
759
760 #[test]
761 fn test_gfx_correction_rdna2_6900_xt() {
762 assert_eq!(
763 get_correct_gfx_from_marketing_name("AMD Radeon RX 6900 XT", "1030"),
764 "gfx1030"
765 );
766 }
767
768 #[test]
769 fn test_gfx_correction_rdna2_6800() {
770 assert_eq!(
771 get_correct_gfx_from_marketing_name("AMD Radeon RX 6800", "1030"),
772 "gfx1030"
773 );
774 }
775
776 #[test]
777 fn test_gfx_correction_rdna2_6700_xt() {
778 assert_eq!(
779 get_correct_gfx_from_marketing_name("AMD Radeon RX 6700 XT", "1030"),
780 "gfx1031"
781 );
782 }
783
784 #[test]
785 fn test_gfx_correction_rdna2_6600() {
786 assert_eq!(
787 get_correct_gfx_from_marketing_name("AMD Radeon RX 6600", "1030"),
788 "gfx1032"
789 );
790 }
791
792 #[test]
793 fn test_gfx_correction_rdna2_6500_xt() {
794 assert_eq!(
795 get_correct_gfx_from_marketing_name("AMD Radeon RX 6500 XT", "1030"),
796 "gfx1034"
797 );
798 }
799
800 #[test]
801 fn test_gfx_correction_cdna_instinct() {
802 assert_eq!(
804 get_correct_gfx_from_marketing_name("AMD Instinct MI250X", "90a0"),
805 "gfx90a0"
806 );
807 }
808
809 #[test]
810 fn test_gfx_correction_unknown_passthrough() {
811 assert_eq!(
813 get_correct_gfx_from_marketing_name("Unknown GPU Model", "999"),
814 "gfx999"
815 );
816 }
817
818 #[test]
823 fn test_detect_gpu_from_sysfs_no_drm_dir() {
824 let mut info = GPUInfo::default();
826 detect_gpu_from_sysfs(&mut info);
827 }
831
832 #[test]
833 fn test_detect_gpu_from_lspci_no_panic() {
834 let mut info = GPUInfo::default();
836 detect_gpu_from_lspci(&mut info);
837 }
839
840 #[test]
841 fn test_detect_gpu_from_rocminfo_bad_command() {
842 let mut info = GPUInfo::default();
844 let result = detect_gpu_from_rocminfo("/nonexistent/rocminfo", &mut info);
845 assert!(!result);
846 assert_eq!(info.gpu_count, 0);
847 }
848
849 #[test]
854 fn test_detect_rocm_version_from_file_with_real_install() {
855 let rocm_path = Some(PathBuf::from("/opt/rocm"));
857 let version = detect_rocm_version_from_file(&rocm_path);
858 if Path::new("/opt/rocm/.info/version").exists() {
859 assert!(
860 !version.is_empty(),
861 "ROCm version should be non-empty when file exists"
862 );
863 let parts: Vec<&str> = version.split('.').collect();
865 assert!(
866 parts.len() >= 2,
867 "Version should have at least major.minor: got '{}'",
868 version
869 );
870 }
871 }
872
873 #[test]
874 fn test_detect_rocm_version_from_file_missing() {
875 let rocm_path = Some(PathBuf::from("/nonexistent/rocm"));
876 let version = detect_rocm_version_from_file(&rocm_path);
877 assert!(
878 version.is_empty(),
879 "Version should be empty for nonexistent path"
880 );
881 }
882
883 #[test]
888 fn test_detect_rocm_path_finds_standard_location() {
889 let path = detect_rocm_path();
891 if Path::new("/opt/rocm").is_dir() {
892 assert!(path.is_some(), "Should detect ROCm at /opt/rocm");
893 let p = path.unwrap();
894 assert!(
895 p.as_os_str() == "/opt/rocm" || p.to_string_lossy().contains("rocm"),
896 "Path should contain 'rocm': got {:?}",
897 p
898 );
899 }
900 }
901
902 #[test]
903 fn test_detect_rocm_path_env_override() {
904 let _env = crate::test_support::lock_env();
905 if Path::new("/opt/rocm").is_dir() {
908 std::env::set_var("ROCm_PATH", "/opt/rocm");
909 let path = detect_rocm_path();
910 assert_eq!(path, Some(PathBuf::from("/opt/rocm")));
911 std::env::remove_var("ROCm_PATH");
912 }
913 }
914
915 #[test]
916 fn test_validate_rocm_path_rejects_nonexistent() {
917 assert!(!validate_rocm_path(Path::new("/nonexistent/path")));
918 }
919
920 #[test]
921 fn test_validate_rocm_path_accepts_standard() {
922 if Path::new("/opt/rocm/bin").is_dir() {
923 assert!(validate_rocm_path(Path::new("/opt/rocm")));
924 }
925 }
926
927 #[test]
932 fn test_get_rocm_version_returns_nonempty_on_installed_system() {
933 if Path::new("/opt/rocm/.info/version").exists() {
934 let version = get_rocm_version();
935 assert!(
936 !version.is_empty(),
937 "Should detect ROCm version on installed system"
938 );
939 }
940 }
941
942 #[test]
947 fn test_parse_version_from_output() {
948 assert_eq!(
949 parse_version_from_output("ROCm version 7.2.1"),
950 Some("7.2.1".to_string())
951 );
952 assert_eq!(
953 parse_version_from_output("rocm-smi version 4.0.0"),
954 Some("4.0.0".to_string())
955 );
956 assert_eq!(parse_version_from_output("no version here"), None);
957 }
958
959 #[test]
960 fn test_extract_version_from_path() {
961 assert_eq!(
962 extract_version_from_path(Path::new("/opt/rocm-7.2.1")),
963 Some("7.2.1".to_string())
964 );
965 assert_eq!(extract_version_from_path(Path::new("/opt/rocm")), None);
966 }
967
968 #[test]
969 fn test_read_version_file() {
970 let dir = tempfile::tempdir().unwrap();
971 let version_file = dir.path().join("version");
972 fs::write(&version_file, "7.2.2\n").unwrap();
973 let version = read_version_file(&version_file).unwrap();
974 assert_eq!(version, "7.2.2");
975 }
976
977 #[test]
978 fn test_read_version_file_trims_whitespace() {
979 let dir = tempfile::tempdir().unwrap();
980 let version_file = dir.path().join("version");
981 fs::write(&version_file, " 7.2.2 \n").unwrap();
982 let version = read_version_file(&version_file).unwrap();
983 assert_eq!(version, "7.2.2");
984 }
985
986 #[test]
987 fn test_find_versioned_rocm_no_dir() {
988 let result = find_versioned_rocm_in_dir("/nonexistent/dir");
989 assert!(result.is_none());
990 }
991
992 #[test]
999 #[ignore]
1000 fn test_detect_gpu_real_hardware() {
1001 let info = detect_gpu();
1002 assert!(
1003 info.gpu_count >= 1,
1004 "Expected at least 1 AMD GPU, got gpu_count = {}",
1005 info.gpu_count
1006 );
1007 assert!(
1008 !info.model.is_empty(),
1009 "GPU model should be non-empty on real hardware"
1010 );
1011 }
1012
1013 #[test]
1015 #[ignore]
1016 fn test_detect_gpu_real_hardware_architecture() {
1017 let info = detect_gpu();
1018 if info.gpu_count > 0 {
1019 assert!(
1020 !info.architecture.is_empty(),
1021 "Architecture should be detected on real AMD GPU"
1022 );
1023 assert!(
1024 info.architecture.starts_with("gfx"),
1025 "Architecture should start with 'gfx': got '{}'",
1026 info.architecture
1027 );
1028 }
1029 }
1030
1031 #[test]
1033 #[ignore]
1034 fn test_detect_rocm_version_real_system() {
1035 let info = detect_gpu();
1036 assert!(
1037 !info.rocm_version.is_empty(),
1038 "ROCm version should be detected on installed system"
1039 );
1040 }
1041
1042 #[test]
1044 #[ignore]
1045 fn test_detect_rocm_path_real_system() {
1046 let path = detect_rocm_path();
1047 assert!(
1048 path.is_some(),
1049 "ROCm path should be detected on installed system"
1050 );
1051 let p = path.unwrap();
1052 assert!(
1053 p.exists() && p.is_dir(),
1054 "ROCm path should be an existing directory"
1055 );
1056 }
1057
1058 #[test]
1060 #[ignore]
1061 fn test_get_rocm_version_real_system() {
1062 let version = get_rocm_version();
1063 assert!(
1064 !version.is_empty(),
1065 "Should detect ROCm version via multi-method fallback"
1066 );
1067 let parts: Vec<&str> = version.split('.').collect();
1069 assert!(
1070 parts.len() >= 2,
1071 "Version should have at least major.minor: got '{}'",
1072 version
1073 );
1074 }
1075
1076 #[test]
1078 #[ignore]
1079 fn test_detect_gpu_real_hardware_metrics() {
1080 let info = detect_gpu();
1081 if info.gpu_count > 0 {
1082 assert!(
1084 info.memory_gb > 0.0,
1085 "GPU memory should be positive: got {} GB",
1086 info.memory_gb
1087 );
1088 }
1089 }
1090
1091 #[test]
1093 #[ignore]
1094 fn test_detect_gpu_matches_rocm_smi() {
1095 let info = detect_gpu();
1096
1097 let output = Command::new("/opt/rocm/bin/rocm-smi")
1099 .args(["--showproductinfo", "--csv"])
1100 .output()
1101 .expect("rocm-smi should be available");
1102
1103 if output.status.success() {
1104 let stdout = String::from_utf8_lossy(&output.stdout);
1105 if stdout.contains("card") {
1107 assert!(
1108 info.gpu_count >= 1,
1109 "GPU count should be >= 1 if rocm-smi reports cards"
1110 );
1111 }
1112 }
1113 }
1114}