rusty_stack/verification/
mod.rs1use crate::component_status;
12use serde::Serialize;
13
14#[derive(Debug, Clone, Serialize)]
20pub struct VerificationItem {
21 pub component_id: String,
22 pub label: String,
23 pub installed: bool,
24 pub category: String,
25}
26
27#[derive(Debug, Clone, Serialize)]
29pub struct VerificationResult {
30 pub mode: String,
31 pub items: Vec<VerificationItem>,
32 pub passed: usize,
33 pub failed: usize,
34 pub total: usize,
35 pub all_passed: bool,
36}
37
38fn full_verify_components() -> Vec<(&'static str, &'static str)> {
44 vec![
45 ("rocm", "ROCm"),
46 ("pytorch", "PyTorch"),
47 ("triton", "Triton"),
48 ("mpi4py", "MPI4Py"),
49 ("deepspeed", "DeepSpeed"),
50 ("ml-stack-core", "ML Stack Core"),
51 ]
52}
53
54fn enhanced_verify_components() -> Vec<(&'static str, &'static str)> {
56 let mut components = full_verify_components();
57 components.extend(vec![
58 ("flash-attn", "Flash Attention"),
59 ("vllm", "vLLM"),
60 ("aiter", "AITER"),
61 ("onnx", "ONNX Runtime"),
62 ("bitsandbytes", "bitsandbytes"),
63 ("rocm-smi", "ROCm SMI"),
64 ("migraphx", "MIGraphX"),
65 ("pytorch-profiler", "PyTorch Profiler"),
66 ("wandb", "Weights & Biases"),
67 ("megatron", "Megatron-LM"),
68 ("comfyui", "ComfyUI"),
69 ("textgen", "text-generation-webui"),
70 ("vllm-studio", "vLLM Studio"),
71 ("llama-cpp", "llama.cpp (HIP)"),
72 ("basic-env", "Basic Environment"),
73 ("enhanced-env", "Enhanced Environment"),
74 ("permanent-env", "Permanent Environment"),
75 ]);
76 components
77}
78
79fn build_verify_components() -> Vec<(&'static str, &'static str)> {
81 let mut components = full_verify_components();
82 components.extend(vec![
83 ("onnx", "ONNX Runtime"),
84 ("flash-attn", "Flash Attention"),
85 ("migraphx", "MIGraphX"),
86 ]);
87 components
88}
89
90fn verify_components(component_list: &[(impl AsRef<str>, impl AsRef<str>)]) -> VerificationResult {
96 let python_candidates = component_status::python_interpreters();
97 let mut items = Vec::new();
98 let mut passed = 0usize;
99 let mut failed = 0usize;
100
101 for (id, label) in component_list {
102 let id_str = id.as_ref();
103 let label_str = label.as_ref();
104 let installed = component_status::is_component_installed_by_id(id_str, &python_candidates);
105 if installed {
106 passed += 1;
107 } else {
108 failed += 1;
109 }
110 items.push(VerificationItem {
111 component_id: id_str.to_string(),
112 label: label_str.to_string(),
113 installed,
114 category: category_for_component(id_str),
115 });
116 }
117
118 let total = items.len();
119 VerificationResult {
120 mode: "full".to_string(),
121 items,
122 passed,
123 failed,
124 total,
125 all_passed: failed == 0,
126 }
127}
128
129pub fn full_verify() -> VerificationResult {
137 let mut result = verify_components(&full_verify_components());
138 result.mode = "full".to_string();
139 result
140}
141
142pub fn enhanced_verify() -> VerificationResult {
146 let mut result = verify_components(&enhanced_verify_components());
147 result.mode = "enhanced".to_string();
148 result
149}
150
151pub fn verify_and_build() -> VerificationResult {
156 let mut result = verify_components(&build_verify_components());
157 result.mode = "build".to_string();
158 result
159}
160
161pub fn failed_components(result: &VerificationResult) -> Vec<String> {
163 result
164 .items
165 .iter()
166 .filter(|item| !item.installed)
167 .map(|item| item.component_id.clone())
168 .collect()
169}
170
171fn category_for_component(id: &str) -> String {
176 match id {
177 "rocm" | "rocm-smi" => "ROCm".to_string(),
178 "pytorch" | "triton" | "flash-attn" | "onnx" | "migraphx" | "pytorch-profiler" => {
179 "ML Framework".to_string()
180 }
181 "mpi4py" | "deepspeed" | "megatron" => "Training".to_string(),
182 "vllm" | "aiter" | "vllm-studio" => "Inference".to_string(),
183 "bitsandbytes" | "wandb" => "Utilities".to_string(),
184 "ml-stack-core" => "Core".to_string(),
185 "comfyui" | "textgen" => "Applications".to_string(),
186 "basic-env" | "enhanced-env" | "permanent-env" => "Environment".to_string(),
187 _ => "Other".to_string(),
188 }
189}
190
191pub fn format_result_human(result: &VerificationResult) -> String {
197 let mut output = String::new();
198 let mode_label = match result.mode.as_str() {
199 "full" => "Full Verification",
200 "enhanced" => "Enhanced Verification",
201 "build" => "Verify & Build",
202 _ => "Verification",
203 };
204
205 output.push_str(&format!(
206 "Rusty Stack {} — {} components checked\n",
207 mode_label, result.total
208 ));
209 output.push_str(&format!(
210 " Passed: {} | Failed: {} | Result: {}\n\n",
211 result.passed,
212 result.failed,
213 if result.all_passed {
214 "ALL PASSED ✓"
215 } else {
216 "SOME FAILED ✗"
217 }
218 ));
219
220 for item in &result.items {
221 let status = if item.installed { "✓" } else { "✗" };
222 output.push_str(&format!(
223 " {} {:30} [{}]\n",
224 status, item.label, item.category
225 ));
226 }
227
228 if !result.all_passed {
229 output.push_str("\nFailed components:\n");
230 for item in &result.items {
231 if !item.installed {
232 output.push_str(&format!(" - {} ({})\n", item.label, item.component_id));
233 }
234 }
235 }
236
237 output
238}
239
240pub fn format_result_json(result: &VerificationResult) -> String {
242 serde_json::to_string_pretty(result).unwrap_or_else(|e| {
243 format!(
244 "{{\"error\": \"failed to serialize verification result: {}\"}}",
245 e
246 )
247 })
248}
249
250#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn test_full_verify_returns_result() {
260 let result = full_verify();
261 assert_eq!(result.mode, "full");
262 assert!(
263 result.total > 0,
264 "Full verify should check at least 1 component"
265 );
266 assert_eq!(result.total, result.passed + result.failed);
267 }
268
269 #[test]
270 fn test_enhanced_verify_returns_more_components() {
271 let full = full_verify();
272 let enhanced = enhanced_verify();
273 assert!(
274 enhanced.total > full.total,
275 "Enhanced should check more components than full ({} > {})",
276 enhanced.total,
277 full.total
278 );
279 }
280
281 #[test]
282 fn test_verify_and_build_includes_build_components() {
283 let result = verify_and_build();
284 assert_eq!(result.mode, "build");
285 let ids: Vec<&str> = result
287 .items
288 .iter()
289 .map(|i| i.component_id.as_str())
290 .collect();
291 assert!(ids.contains(&"onnx"), "build verify should include onnx");
292 assert!(
293 ids.contains(&"flash-attn"),
294 "build verify should include flash-attn"
295 );
296 assert!(
297 ids.contains(&"migraphx"),
298 "build verify should include migraphx"
299 );
300 }
301
302 #[test]
303 fn test_failed_components_extracts_failures() {
304 let result = VerificationResult {
305 mode: "test".to_string(),
306 items: vec![
307 VerificationItem {
308 component_id: "rocm".to_string(),
309 label: "ROCm".to_string(),
310 installed: true,
311 category: "ROCm".to_string(),
312 },
313 VerificationItem {
314 component_id: "pytorch".to_string(),
315 label: "PyTorch".to_string(),
316 installed: false,
317 category: "ML Framework".to_string(),
318 },
319 ],
320 passed: 1,
321 failed: 1,
322 total: 2,
323 all_passed: false,
324 };
325 let failed = failed_components(&result);
326 assert_eq!(failed, vec!["pytorch"]);
327 }
328
329 #[test]
330 fn test_format_human_output() {
331 let result = full_verify();
332 let text = format_result_human(&result);
333 assert!(
334 text.contains("Verification"),
335 "Human output should contain 'Verification'"
336 );
337 assert!(
338 text.contains("Passed:"),
339 "Human output should contain 'Passed:'"
340 );
341 }
342
343 #[test]
344 fn test_format_json_output() {
345 let result = full_verify();
346 let json = format_result_json(&result);
347 let parsed: serde_json::Value =
348 serde_json::from_str(&json).expect("JSON should be parseable");
349 assert!(parsed.get("mode").is_some(), "JSON should contain 'mode'");
350 assert!(parsed.get("items").is_some(), "JSON should contain 'items'");
351 assert!(
352 parsed.get("all_passed").is_some(),
353 "JSON should contain 'all_passed'"
354 );
355 }
356
357 #[test]
358 fn test_full_verify_includes_core_components() {
359 let result = full_verify();
360 let ids: Vec<&str> = result
361 .items
362 .iter()
363 .map(|i| i.component_id.as_str())
364 .collect();
365 assert!(ids.contains(&"rocm"), "full verify should include rocm");
366 assert!(
367 ids.contains(&"pytorch"),
368 "full verify should include pytorch"
369 );
370 assert!(ids.contains(&"triton"), "full verify should include triton");
371 assert!(ids.contains(&"mpi4py"), "full verify should include mpi4py");
372 assert!(
373 ids.contains(&"deepspeed"),
374 "full verify should include deepspeed"
375 );
376 assert!(
377 ids.contains(&"ml-stack-core"),
378 "full verify should include ml-stack-core"
379 );
380 }
381
382 #[test]
383 fn test_enhanced_verify_includes_all_categories() {
384 let result = enhanced_verify();
385 let categories: Vec<&str> = result.items.iter().map(|i| i.category.as_str()).collect();
386 assert!(
387 categories.contains(&"ROCm"),
388 "enhanced should include ROCm category"
389 );
390 assert!(
391 categories.contains(&"ML Framework"),
392 "enhanced should include ML Framework category"
393 );
394 assert!(
395 categories.contains(&"Environment"),
396 "enhanced should include Environment category"
397 );
398 }
399
400 #[test]
401 fn test_no_shell_subprocess_in_verification() {
402 let result = full_verify();
407 assert!(result.total > 0);
408 }
409}