upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/grasp-audit/src/result.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-11-04 06:17:55 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-11-04 06:17:55 +0000
commit001ca45e385c05b0eaa36d9879e051853aaff107 (patch)
tree603fb85d2563db5b7c418e9fd143d479bd09676e /grasp-audit/src/result.rs
parentd428baf30feec295870fadda2d335d1e7f89507b (diff)
created POC grasp-auditor
Diffstat (limited to 'grasp-audit/src/result.rs')
-rw-r--r--grasp-audit/src/result.rs189
1 files changed, 189 insertions, 0 deletions
diff --git a/grasp-audit/src/result.rs b/grasp-audit/src/result.rs
new file mode 100644
index 0000000..f591304
--- /dev/null
+++ b/grasp-audit/src/result.rs
@@ -0,0 +1,189 @@
1//! Test result types
2
3use std::time::{Duration, Instant};
4
5/// Result of a single test
6#[derive(Debug, Clone)]
7pub struct TestResult {
8 pub name: String,
9 pub spec_ref: String,
10 pub requirement: String,
11 pub passed: bool,
12 pub error: Option<String>,
13 pub duration: Duration,
14}
15
16impl TestResult {
17 /// Create a new test result
18 pub fn new(name: &str, spec_ref: &str, requirement: &str) -> Self {
19 TestResult {
20 name: name.to_string(),
21 spec_ref: spec_ref.to_string(),
22 requirement: requirement.to_string(),
23 passed: false,
24 error: None,
25 duration: Duration::default(),
26 }
27 }
28
29 /// Run a test function and capture the result
30 pub async fn run<F, Fut>(mut self, test_fn: F) -> Self
31 where
32 F: FnOnce() -> Fut,
33 Fut: std::future::Future<Output = Result<(), String>>,
34 {
35 let start = Instant::now();
36
37 match test_fn().await {
38 Ok(()) => {
39 self.passed = true;
40 }
41 Err(e) => {
42 self.passed = false;
43 self.error = Some(e);
44 }
45 }
46
47 self.duration = start.elapsed();
48 self
49 }
50
51 /// Mark test as passed
52 pub fn pass(mut self) -> Self {
53 self.passed = true;
54 self
55 }
56
57 /// Mark test as failed with error
58 pub fn fail(mut self, error: impl Into<String>) -> Self {
59 self.passed = false;
60 self.error = Some(error.into());
61 self
62 }
63}
64
65/// Collection of test results for a spec
66#[derive(Debug, Clone)]
67pub struct AuditResult {
68 pub spec: String,
69 pub results: Vec<TestResult>,
70}
71
72impl AuditResult {
73 /// Create a new audit result
74 pub fn new(spec: impl Into<String>) -> Self {
75 Self {
76 spec: spec.into(),
77 results: Vec::new(),
78 }
79 }
80
81 /// Add a test result
82 pub fn add(&mut self, result: TestResult) {
83 self.results.push(result);
84 }
85
86 /// Merge another audit result
87 pub fn merge(&mut self, other: AuditResult) {
88 self.results.extend(other.results);
89 }
90
91 /// Check if all tests passed
92 pub fn all_passed(&self) -> bool {
93 self.results.iter().all(|r| r.passed)
94 }
95
96 /// Get count of passed tests
97 pub fn passed_count(&self) -> usize {
98 self.results.iter().filter(|r| r.passed).count()
99 }
100
101 /// Get count of failed tests
102 pub fn failed_count(&self) -> usize {
103 self.results.iter().filter(|r| !r.passed).count()
104 }
105
106 /// Get total count of tests
107 pub fn total_count(&self) -> usize {
108 self.results.len()
109 }
110
111 /// Print a detailed report
112 pub fn print_report(&self) {
113 println!("\n{}", self.spec);
114 println!("{}", "═".repeat(60));
115 println!();
116
117 let passed = self.passed_count();
118 let total = self.total_count();
119
120 for result in &self.results {
121 let status = if result.passed { "✓" } else { "✗" };
122
123 println!("{} {} ({})", status, result.name, result.spec_ref);
124 println!(" Requirement: {}", result.requirement);
125
126 if let Some(error) = &result.error {
127 println!(" Error: {}", error);
128 }
129
130 println!(" Duration: {:?}", result.duration);
131 println!();
132 }
133
134 println!("Results: {}/{} passed ({:.1}%)",
135 passed,
136 total,
137 (passed as f64 / total as f64) * 100.0
138 );
139 println!();
140 }
141
142 /// Get a summary string
143 pub fn summary(&self) -> String {
144 format!(
145 "{}: {}/{} passed",
146 self.spec,
147 self.passed_count(),
148 self.total_count()
149 )
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[tokio::test]
158 async fn test_result_pass() {
159 let result = TestResult::new("test", "SPEC:1", "Must work")
160 .run(|| async { Ok(()) })
161 .await;
162
163 assert!(result.passed);
164 assert!(result.error.is_none());
165 }
166
167 #[tokio::test]
168 async fn test_result_fail() {
169 let result = TestResult::new("test", "SPEC:1", "Must work")
170 .run(|| async { Err("Failed".to_string()) })
171 .await;
172
173 assert!(!result.passed);
174 assert_eq!(result.error, Some("Failed".to_string()));
175 }
176
177 #[test]
178 fn test_audit_result() {
179 let mut audit = AuditResult::new("Test Spec");
180
181 audit.add(TestResult::new("test1", "SPEC:1", "Req1").pass());
182 audit.add(TestResult::new("test2", "SPEC:2", "Req2").fail("Error"));
183
184 assert_eq!(audit.total_count(), 2);
185 assert_eq!(audit.passed_count(), 1);
186 assert_eq!(audit.failed_count(), 1);
187 assert!(!audit.all_passed());
188 }
189}