diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-28 02:19:47 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-28 02:19:47 +0000 |
| commit | 43e53b4ccd9fcebf20cc9c1bdbfe568ddd8051b9 (patch) | |
| tree | 4aea212e5b1d307affb8e0ce5db1cc898e217889 /grasp-audit/src/result.rs | |
| parent | f053827e0a157f348d9cf834f026a8de322abfe2 (diff) | |
grasp-audit improve cli output eg. add colors and condense
Diffstat (limited to 'grasp-audit/src/result.rs')
| -rw-r--r-- | grasp-audit/src/result.rs | 76 |
1 files changed, 61 insertions, 15 deletions
diff --git a/grasp-audit/src/result.rs b/grasp-audit/src/result.rs index de377e5..2bec5c8 100644 --- a/grasp-audit/src/result.rs +++ b/grasp-audit/src/result.rs | |||
| @@ -1,7 +1,32 @@ | |||
| 1 | //! Test result types | 1 | //! Test result types |
| 2 | 2 | ||
| 3 | use std::collections::BTreeMap; | ||
| 3 | use std::time::{Duration, Instant}; | 4 | use std::time::{Duration, Instant}; |
| 4 | 5 | ||
| 6 | // ANSI color codes | ||
| 7 | const GREEN: &str = "\x1b[32m"; | ||
| 8 | const RED: &str = "\x1b[31m"; | ||
| 9 | const RESET: &str = "\x1b[0m"; | ||
| 10 | const BOLD: &str = "\x1b[1m"; | ||
| 11 | |||
| 12 | /// Extract spec category from a spec_ref by removing trailing test number | ||
| 13 | /// e.g., "GRASP-01:event-acceptance:1.1" -> "GRASP-01:event-acceptance" | ||
| 14 | /// e.g., "NIP-01:basic:2" -> "NIP-01:basic" | ||
| 15 | fn extract_spec_category(spec_ref: &str) -> String { | ||
| 16 | let parts: Vec<&str> = spec_ref.split(':').collect(); | ||
| 17 | if parts.len() >= 2 { | ||
| 18 | // Check if the last part looks like a test number (starts with digit) | ||
| 19 | if let Some(last) = parts.last() { | ||
| 20 | if last.chars().next().map(|c| c.is_ascii_digit()).unwrap_or(false) { | ||
| 21 | // Remove the trailing number part | ||
| 22 | return parts[..parts.len() - 1].join(":"); | ||
| 23 | } | ||
| 24 | } | ||
| 25 | } | ||
| 26 | // Return as-is if no trailing number found | ||
| 27 | spec_ref.to_string() | ||
| 28 | } | ||
| 29 | |||
| 5 | /// Result of a single test | 30 | /// Result of a single test |
| 6 | #[derive(Debug, Clone)] | 31 | #[derive(Debug, Clone)] |
| 7 | pub struct TestResult { | 32 | pub struct TestResult { |
| @@ -108,34 +133,55 @@ impl AuditResult { | |||
| 108 | self.results.len() | 133 | self.results.len() |
| 109 | } | 134 | } |
| 110 | 135 | ||
| 111 | /// Print a detailed report | 136 | /// Print a detailed report with tests grouped by spec_ref |
| 112 | pub fn print_report(&self) { | 137 | pub fn print_report(&self) { |
| 113 | println!("\n{}", self.spec); | 138 | println!("\n{}{}{}", BOLD, self.spec, RESET); |
| 114 | println!("{}", "═".repeat(60)); | 139 | println!("{}", "═".repeat(60)); |
| 115 | println!(); | ||
| 116 | 140 | ||
| 117 | let passed = self.passed_count(); | 141 | let passed = self.passed_count(); |
| 118 | let total = self.total_count(); | 142 | let total = self.total_count(); |
| 119 | 143 | ||
| 144 | // Group results by spec category (strip trailing test number like ":1.1") | ||
| 145 | let mut grouped: BTreeMap<String, Vec<&TestResult>> = BTreeMap::new(); | ||
| 120 | for result in &self.results { | 146 | for result in &self.results { |
| 121 | let status = if result.passed { "✓" } else { "✗" }; | 147 | // Extract category from spec_ref (e.g., "GRASP-01:event-acceptance:1.1" -> "GRASP-01:event-acceptance") |
| 148 | let category = extract_spec_category(&result.spec_ref); | ||
| 149 | grouped | ||
| 150 | .entry(category) | ||
| 151 | .or_default() | ||
| 152 | .push(result); | ||
| 153 | } | ||
| 122 | 154 | ||
| 123 | println!("{} {} ({})", status, result.name, result.spec_ref); | 155 | // Print grouped results |
| 124 | println!(" Requirement: {}", result.requirement); | 156 | for (category, results) in &grouped { |
| 157 | println!("\n{}[{}]{}", BOLD, category, RESET); | ||
| 125 | 158 | ||
| 126 | if let Some(error) = &result.error { | 159 | for result in results { |
| 127 | println!(" Error: {}", error); | 160 | let (color, status) = if result.passed { |
| 128 | } | 161 | (GREEN, "✓") |
| 162 | } else { | ||
| 163 | (RED, "✗") | ||
| 164 | }; | ||
| 165 | |||
| 166 | println!(" {}{} {}{}", color, status, result.name, RESET); | ||
| 129 | 167 | ||
| 130 | println!(" Duration: {:?}", result.duration); | 168 | if let Some(error) = &result.error { |
| 131 | println!(); | 169 | println!(" {}Error: {}{}", RED, error, RESET); |
| 170 | } | ||
| 171 | } | ||
| 132 | } | 172 | } |
| 133 | 173 | ||
| 134 | println!( | 174 | println!(); |
| 135 | "Results: {}/{} passed ({:.1}%)", | 175 | let pass_rate = if total > 0 { |
| 136 | passed, | ||
| 137 | total, | ||
| 138 | (passed as f64 / total as f64) * 100.0 | 176 | (passed as f64 / total as f64) * 100.0 |
| 177 | } else { | ||
| 178 | 0.0 | ||
| 179 | }; | ||
| 180 | |||
| 181 | let summary_color = if passed == total { GREEN } else { RED }; | ||
| 182 | println!( | ||
| 183 | "{}Results: {}/{} passed ({:.1}%){}", | ||
| 184 | summary_color, passed, total, pass_rate, RESET | ||
| 139 | ); | 185 | ); |
| 140 | println!(); | 186 | println!(); |
| 141 | } | 187 | } |