1pub mod env_setup;
18pub mod install;
19pub mod launcher;
20
21pub use env_setup::{
23 check_system_dependency, create_env_file, detect_correct_gpu_arch, detect_discrete_gpus,
24 DependencyCheckResult, EnvFileResult, GpuArchInfo, GpuFilterResult, SystemDependency,
25};
26pub use install::{clone_or_update_repo, install_sh, InstallConfig, InstallResult};
27pub use launcher::{launch_tui, BuildMode, LaunchResult};
28
29#[cfg(test)]
34mod tests {
35 use super::*;
36
37 #[test]
40 fn test_install_config_defaults() {
41 let config = InstallConfig::default();
42 assert_eq!(
43 config.repo_url,
44 "https://github.com/scooter-lacroix/Stan-s-ML-Stack.git"
45 );
46 assert!(!config.skip_build);
47 assert_eq!(config.branch, "main");
48 }
49
50 #[test]
51 fn test_install_config_custom() {
52 let config = InstallConfig {
53 repo_dir: "/tmp/test-repo".to_string(),
54 repo_url: "https://example.com/repo.git".to_string(),
55 skip_build: true,
56 branch: "develop".to_string(),
57 };
58 assert_eq!(config.repo_dir, "/tmp/test-repo");
59 assert!(config.skip_build);
60 assert_eq!(config.branch, "develop");
61 }
62
63 #[test]
64 fn test_install_result_success() {
65 let result = InstallResult::success(
66 "/tmp/repo".to_string(),
67 "/tmp/repo/rusty-stack/target/release/rusty-stack".to_string(),
68 );
69 assert!(result.success);
70 assert!(result.binary_path.is_some());
71 assert!(result.errors.is_empty());
72 }
73
74 #[test]
75 fn test_install_result_failure() {
76 let result = InstallResult::failure("Build failed".to_string());
77 assert!(!result.success);
78 assert!(result.binary_path.is_none());
79 assert!(!result.errors.is_empty());
80 }
81
82 #[test]
85 fn test_build_mode_default() {
86 let mode = BuildMode::default();
87 assert_eq!(mode, BuildMode::Release);
88 }
89
90 #[test]
91 fn test_launch_result_success() {
92 let result = LaunchResult::success("/path/to/rusty-stack".to_string());
93 assert!(result.success);
94 assert!(result.binary_path.is_some());
95 }
96
97 #[test]
98 fn test_launch_result_failure() {
99 let result = LaunchResult::failure("Build failed".to_string());
100 assert!(!result.success);
101 assert!(result.binary_path.is_none());
102 }
103
104 #[test]
107 fn test_gpu_arch_info_default() {
108 let info = GpuArchInfo::default();
109 assert_eq!(info.gpu_arch, "gfx1100");
110 assert_eq!(info.hsa_override_gfx_version, "11.0.0");
111 }
112
113 #[test]
114 fn test_gpu_arch_info_gfx1101() {
115 let info = GpuArchInfo::from_arch("gfx1101");
116 assert_eq!(info.gpu_arch, "gfx1101");
117 assert_eq!(info.hsa_override_gfx_version, "11.0.1");
118 }
119
120 #[test]
121 fn test_gpu_arch_info_gfx1102() {
122 let info = GpuArchInfo::from_arch("gfx1102");
123 assert_eq!(info.gpu_arch, "gfx1102");
124 assert_eq!(info.hsa_override_gfx_version, "11.0.2");
125 }
126
127 #[test]
128 fn test_gpu_arch_info_gfx1030() {
129 let info = GpuArchInfo::from_arch("gfx1030");
130 assert_eq!(info.gpu_arch, "gfx1030");
131 assert_eq!(info.hsa_override_gfx_version, "10.3.0");
132 }
133
134 #[test]
135 fn test_gpu_arch_info_gfx1200() {
136 let info = GpuArchInfo::from_arch("gfx1200");
137 assert_eq!(info.gpu_arch, "gfx1200");
138 assert_eq!(info.hsa_override_gfx_version, "12.0.0");
139 }
140
141 #[test]
142 fn test_gpu_arch_info_unknown_falls_back() {
143 let info = GpuArchInfo::from_arch("gfx9999");
144 assert_eq!(info.gpu_arch, "gfx9999");
145 assert_eq!(info.hsa_override_gfx_version, "11.0.0"); }
147
148 #[test]
149 fn test_gpu_filter_result_no_gpus() {
150 let result = GpuFilterResult {
151 discrete_gpu_indices: vec![],
152 hip_visible_devices: "0".to_string(),
153 gpu_count: 0,
154 };
155 assert!(result.discrete_gpu_indices.is_empty());
156 assert_eq!(result.hip_visible_devices, "0");
158 }
159
160 #[test]
161 fn test_gpu_filter_result_with_gpus() {
162 let result = GpuFilterResult {
163 discrete_gpu_indices: vec![0, 1],
164 hip_visible_devices: "0,1".to_string(),
165 gpu_count: 2,
166 };
167 assert_eq!(result.discrete_gpu_indices.len(), 2);
168 assert_eq!(result.hip_visible_devices, "0,1");
169 }
170
171 #[test]
174 fn test_system_dependency_list() {
175 let deps = SystemDependency::required_packages();
176 assert!(deps.contains(&"git"), "git should be required");
177 assert!(deps.contains(&"cmake"), "cmake should be required");
178 assert!(
179 deps.contains(&"python3-dev"),
180 "python3-dev should be required"
181 );
182 assert!(
183 deps.contains(&"build-essential"),
184 "build-essential should be required"
185 );
186 }
187
188 #[test]
189 fn test_dependency_check_result_installed() {
190 let result = DependencyCheckResult {
191 package: "git".to_string(),
192 installed: true,
193 mapped_name: "git".to_string(),
194 };
195 assert!(result.installed);
196 }
197
198 #[test]
199 fn test_dependency_check_result_missing() {
200 let result = DependencyCheckResult {
201 package: "nonexistent-pkg".to_string(),
202 installed: false,
203 mapped_name: "nonexistent-pkg".to_string(),
204 };
205 assert!(!result.installed);
206 }
207
208 #[test]
211 fn test_env_file_result_success() {
212 let result = EnvFileResult::success("/home/user/.mlstack_env".to_string());
213 assert!(result.success);
214 assert!(result.path.is_some());
215 assert!(result.errors.is_empty());
216 }
217
218 #[test]
219 fn test_env_file_result_failure() {
220 let result = EnvFileResult::failure("Permission denied".to_string());
221 assert!(!result.success);
222 assert!(result.path.is_none());
223 }
224
225 #[test]
226 fn test_env_file_content_has_required_variables() {
227 let content = env_setup::generate_env_file_content(
228 "0", "/opt/rocm", "7.2.0", "latest", "gfx1100", "11.0.0", "/usr/bin/python3",
235 );
236 assert!(
238 content.contains("HIP_VISIBLE_DEVICES"),
239 "env file must set HIP_VISIBLE_DEVICES"
240 );
241 assert!(
242 content.contains("CUDA_VISIBLE_DEVICES"),
243 "env file must set CUDA_VISIBLE_DEVICES"
244 );
245 assert!(
246 content.contains("PYTORCH_ROCM_DEVICE"),
247 "env file must set PYTORCH_ROCM_DEVICE"
248 );
249 assert!(content.contains("ROCM_HOME"), "env file must set ROCM_HOME");
250 assert!(
251 content.contains("ROCM_VERSION"),
252 "env file must set ROCM_VERSION"
253 );
254 assert!(
255 content.contains("ROCM_CHANNEL"),
256 "env file must set ROCM_CHANNEL"
257 );
258 assert!(content.contains("GPU_ARCH"), "env file must set GPU_ARCH");
259 assert!(
260 content.contains("LD_LIBRARY_PATH"),
261 "env file must set LD_LIBRARY_PATH"
262 );
263 assert!(content.contains("PATH"), "env file must update PATH");
264 }
265
266 #[test]
267 fn test_env_file_content_is_shell_sourceable() {
268 let content = env_setup::generate_env_file_content(
269 "0",
270 "/opt/rocm",
271 "7.2.0",
272 "latest",
273 "gfx1100",
274 "11.0.0",
275 "/usr/bin/python3",
276 );
277 assert!(
280 content.starts_with("# ML Stack Environment File"),
281 "env file must have header comment"
282 );
283 assert!(
284 content.contains("export "),
285 "env file must have export statements"
286 );
287 let export_count = content.matches("export ").count();
289 assert!(
290 export_count >= 10,
291 "should have at least 10 export statements, got {export_count}"
292 );
293 }
294
295 #[test]
298 fn test_rocm_optimization_hsa_override() {
299 let content = env_setup::generate_env_file_content(
300 "0",
301 "/opt/rocm",
302 "7.2.0",
303 "latest",
304 "gfx1100",
305 "11.0.0",
306 "/usr/bin/python3",
307 );
308 assert!(
309 content.contains("HSA_OVERRIDE_GFX_VERSION"),
310 "must set HSA_OVERRIDE_GFX_VERSION"
311 );
312 assert!(
313 content.contains("11.0.0"),
314 "must contain the correct HSA_OVERRIDE_GFX_VERSION value"
315 );
316 }
317
318 #[test]
319 fn test_rocm_optimization_miopen_settings() {
320 let content = env_setup::generate_env_file_content(
321 "0",
322 "/opt/rocm",
323 "7.2.0",
324 "latest",
325 "gfx1100",
326 "11.0.0",
327 "/usr/bin/python3",
328 );
329 assert!(
330 content.contains("MIOPEN_DEBUG_CONV_IMPLICIT_GEMM"),
331 "must set MIOPEN_DEBUG_CONV_IMPLICIT_GEMM"
332 );
333 assert!(
334 content.contains("MIOPEN_FIND_MODE"),
335 "must set MIOPEN_FIND_MODE"
336 );
337 assert!(
338 content.contains("MIOPEN_FIND_ENFORCE"),
339 "must set MIOPEN_FIND_ENFORCE"
340 );
341 }
342
343 #[test]
344 fn test_rocm_optimization_performance_vars() {
345 let content = env_setup::generate_env_file_content(
346 "0",
347 "/opt/rocm",
348 "7.2.0",
349 "latest",
350 "gfx1100",
351 "11.0.0",
352 "/usr/bin/python3",
353 );
354 assert!(
355 content.contains("HSA_ENABLE_SDMA"),
356 "must set HSA_ENABLE_SDMA"
357 );
358 assert!(
359 content.contains("GPU_MAX_HEAP_SIZE"),
360 "must set GPU_MAX_HEAP_SIZE"
361 );
362 assert!(
363 content.contains("GPU_MAX_ALLOC_PERCENT"),
364 "must set GPU_MAX_ALLOC_PERCENT"
365 );
366 }
367
368 #[test]
369 fn test_rocm_optimization_pytorch_vars() {
370 let content = env_setup::generate_env_file_content(
371 "0",
372 "/opt/rocm",
373 "7.2.0",
374 "latest",
375 "gfx1100",
376 "11.0.0",
377 "/usr/bin/python3",
378 );
379 assert!(
380 content.contains("TORCH_CUDA_ARCH_LIST"),
381 "must set TORCH_CUDA_ARCH_LIST"
382 );
383 assert!(
384 content.contains("PYTORCH_ALLOC_CONF"),
385 "must set PYTORCH_ALLOC_CONF"
386 );
387 assert!(
388 content.contains("PYTORCH_HIP_ALLOC_CONF"),
389 "must set PYTORCH_HIP_ALLOC_CONF"
390 );
391 }
392
393 #[test]
394 fn test_rocm_optimization_mpi_vars() {
395 let content = env_setup::generate_env_file_content(
396 "0",
397 "/opt/rocm",
398 "7.2.0",
399 "latest",
400 "gfx1100",
401 "11.0.0",
402 "/usr/bin/python3",
403 );
404 assert!(
405 content.contains("OMPI_MCA_opal_cuda_support"),
406 "must set OMPI MCA CUDA support"
407 );
408 assert!(content.contains("OMPI_MCA_pml"), "must set OMPI MCA PML");
409 }
410
411 #[test]
412 fn test_rocm_optimization_channel_selection() {
413 let content = env_setup::generate_env_file_content(
414 "0",
415 "/opt/rocm",
416 "7.0.0",
417 "legacy",
418 "gfx1030",
419 "10.3.0",
420 "/usr/bin/python3",
421 );
422 assert!(content.contains("ROCM_CHANNEL"), "must set ROCM_CHANNEL");
423 assert!(content.contains("legacy"), "must contain the channel value");
424 }
425
426 #[test]
429 fn test_gpu_arch_from_marketing_name_7900_xtx() {
430 let arch = detect_correct_gpu_arch(Some("Radeon RX 7900 XTX"));
431 assert_eq!(arch.gpu_arch, "gfx1100");
432 }
433
434 #[test]
435 fn test_gpu_arch_from_marketing_name_7800_xt() {
436 let arch = detect_correct_gpu_arch(Some("Radeon RX 7800 XT"));
437 assert_eq!(arch.gpu_arch, "gfx1101");
438 }
439
440 #[test]
441 fn test_gpu_arch_from_marketing_name_7600() {
442 let arch = detect_correct_gpu_arch(Some("Radeon RX 7600"));
443 assert_eq!(arch.gpu_arch, "gfx1102");
444 }
445
446 #[test]
447 fn test_gpu_arch_from_marketing_name_9070_xt() {
448 let arch = detect_correct_gpu_arch(Some("Radeon RX 9070 XT"));
449 assert_eq!(arch.gpu_arch, "gfx1200");
450 }
451
452 #[test]
453 fn test_gpu_arch_from_marketing_name_none() {
454 let arch = detect_correct_gpu_arch(None);
455 assert_eq!(arch.gpu_arch, "gfx1100"); }
457
458 #[test]
459 fn test_gpu_arch_from_marketing_name_unknown() {
460 let arch = detect_correct_gpu_arch(Some("Unknown GPU"));
461 assert_eq!(arch.gpu_arch, "gfx1100"); }
463
464 #[test]
467 fn test_is_integrated_gpu_filtering() {
468 assert!(env_setup::is_integrated_gpu_name("Raphael"));
470 assert!(env_setup::is_integrated_gpu_name("AMD Ryzen Graphics"));
471 assert!(env_setup::is_integrated_gpu_name("Integrated Graphics"));
472 assert!(env_setup::is_integrated_gpu_name("AMD Radeon Graphics"));
473 assert!(!env_setup::is_integrated_gpu_name("Radeon RX 7900 XTX"));
475 assert!(!env_setup::is_integrated_gpu_name("Radeon RX 7800 XT"));
476 }
477}