use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CheckStatus {
Passed,
Warning,
Failed,
}
impl CheckStatus {
pub fn label(self) -> &'static str {
match self {
CheckStatus::Passed => "passed",
CheckStatus::Warning => "warning",
CheckStatus::Failed => "failed",
}
}
}
impl fmt::Display for CheckStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.label())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct VerificationCheck {
pub name: String,
pub status: CheckStatus,
pub message: String,
}
impl VerificationCheck {
pub fn passed(name: impl Into<String>, message: impl Into<String>) -> Self {
Self {
name: name.into(),
status: CheckStatus::Passed,
message: message.into(),
}
}
pub fn warning(name: impl Into<String>, message: impl Into<String>) -> Self {
Self {
name: name.into(),
status: CheckStatus::Warning,
message: message.into(),
}
}
pub fn failed(name: impl Into<String>, message: impl Into<String>) -> Self {
Self {
name: name.into(),
status: CheckStatus::Failed,
message: message.into(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct VerificationResult {
pub component_id: String,
pub success: bool,
pub checks: Vec<VerificationCheck>,
}
impl VerificationResult {
pub fn new(component_id: impl Into<String>, checks: Vec<VerificationCheck>) -> Self {
let success = !checks.iter().any(|c| c.status == CheckStatus::Failed);
Self {
component_id: component_id.into(),
success,
checks,
}
}
pub fn ok(component_id: impl Into<String>) -> Self {
Self {
component_id: component_id.into(),
success: true,
checks: vec![],
}
}
pub fn fail(component_id: impl Into<String>, reason: impl Into<String>) -> Self {
Self {
component_id: component_id.into(),
success: false,
checks: vec![VerificationCheck::failed("verification", reason)],
}
}
pub fn is_all_passed(&self) -> bool {
self.checks.iter().all(|c| c.status == CheckStatus::Passed)
}
pub fn has_warnings(&self) -> bool {
self.checks.iter().any(|c| c.status == CheckStatus::Warning)
}
pub fn check_count(&self) -> usize {
self.checks.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_verification_result_all_pass_success() {
let result = VerificationResult::new(
"pytorch",
vec![
VerificationCheck::passed("binary_exists", "Found at /usr/bin/python"),
VerificationCheck::passed("version_matches", "2.2.0 installed"),
VerificationCheck::passed("import_test", "import torch succeeded"),
],
);
assert!(result.success, "All-pass should have success=true");
assert!(result.is_all_passed());
assert!(!result.has_warnings());
assert_eq!(result.check_count(), 3);
}
#[test]
fn test_verification_result_one_failure_not_success() {
let result = VerificationResult::new(
"triton",
vec![
VerificationCheck::passed("binary_exists", "Found"),
VerificationCheck::failed("import_test", "import triton failed with ImportError"),
],
);
assert!(!result.success, "Any failure should have success=false");
assert!(!result.is_all_passed());
}
#[test]
fn test_verification_result_warnings_only_success() {
let result = VerificationResult::new(
"rocm",
vec![
VerificationCheck::passed("rocm_detect", "ROCm found at /opt/rocm"),
VerificationCheck::warning("version_old", "ROCm 7.0 is not the latest"),
],
);
assert!(result.success, "Warnings-only should have success=true");
assert!(result.has_warnings());
assert!(!result.is_all_passed());
}
#[test]
fn test_verification_result_ok_empty() {
let result = VerificationResult::ok("empty-comp");
assert!(result.success);
assert!(result.checks.is_empty());
assert_eq!(result.check_count(), 0);
}
#[test]
fn test_verification_result_fail_single() {
let result = VerificationResult::fail("broken-comp", "installation failed");
assert!(!result.success);
assert_eq!(result.checks.len(), 1);
assert_eq!(result.checks[0].status, CheckStatus::Failed);
assert_eq!(result.checks[0].name, "verification");
}
#[test]
fn test_check_status_serde_roundtrip() {
let statuses = [
CheckStatus::Passed,
CheckStatus::Warning,
CheckStatus::Failed,
];
for status in &statuses {
let json = serde_json::to_string(status).unwrap();
let back: CheckStatus = serde_json::from_str(&json).unwrap();
assert_eq!(*status, back);
}
}
#[test]
fn test_check_status_labels() {
assert_eq!(CheckStatus::Passed.label(), "passed");
assert_eq!(CheckStatus::Warning.label(), "warning");
assert_eq!(CheckStatus::Failed.label(), "failed");
}
#[test]
fn test_check_status_display() {
assert_eq!(format!("{}", CheckStatus::Passed), "passed");
assert_eq!(format!("{}", CheckStatus::Warning), "warning");
assert_eq!(format!("{}", CheckStatus::Failed), "failed");
}
#[test]
fn test_verification_check_constructors() {
let passed = VerificationCheck::passed("test", "all good");
assert_eq!(passed.status, CheckStatus::Passed);
let warning = VerificationCheck::warning("test", "be careful");
assert_eq!(warning.status, CheckStatus::Warning);
let failed = VerificationCheck::failed("test", "broke");
assert_eq!(failed.status, CheckStatus::Failed);
}
#[test]
fn test_verification_check_serde_roundtrip() {
let check = VerificationCheck::failed("import_test", "ModuleNotFoundError");
let json = serde_json::to_string(&check).unwrap();
let back: VerificationCheck = serde_json::from_str(&json).unwrap();
assert_eq!(check, back);
}
#[test]
fn test_verification_result_serde_roundtrip() {
let result = VerificationResult::new(
"pytorch",
vec![
VerificationCheck::passed("binary", "found"),
VerificationCheck::warning("version", "not latest"),
],
);
let json = serde_json::to_string(&result).unwrap();
let back: VerificationResult = serde_json::from_str(&json).unwrap();
assert_eq!(result, back);
}
#[test]
fn test_verification_result_serializes_to_json() {
let result = VerificationResult::new(
"rocm",
vec![
VerificationCheck::passed("rocm_path", "/opt/rocm exists"),
VerificationCheck::passed("version", "7.2.1"),
],
);
let json = serde_json::to_string(&result).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["component_id"], "rocm");
assert_eq!(parsed["success"], true);
assert!(parsed["checks"].is_array());
assert_eq!(parsed["checks"].as_array().unwrap().len(), 2);
}
#[test]
fn test_verification_result_failure_serializes_correctly() {
let result = VerificationResult::new(
"triton",
vec![VerificationCheck::failed("import", "failed")],
);
let json = serde_json::to_string(&result).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["success"], false);
}
}