xacli_testing/report/
xacli.rs1use std::{fmt::Debug, time::Duration};
2
3use indexmap::IndexMap;
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Serialize, Deserialize)]
8pub struct TestSuitesResult {
9 pub suites: IndexMap<String, TestSuiteResult>,
11}
12
13impl Debug for TestSuitesResult {
14 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 f.debug_struct("TestSuitesResult")
16 .field("suites", &self.suites.keys().collect::<Vec<&String>>())
17 .finish()
18 }
19}
20
21impl TestSuitesResult {
22 pub fn new() -> Self {
24 Self {
25 suites: IndexMap::new(),
26 }
27 }
28
29 pub fn from_file(path: impl AsRef<std::path::Path>) -> Result<Self, std::io::Error> {
31 let content = std::fs::read_to_string(path)?;
32 serde_json::from_str(&content)
33 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
34 }
35
36 pub fn to_file(&self, path: impl AsRef<std::path::Path>) -> Result<(), std::io::Error> {
38 let json = serde_json::to_string_pretty(self)
39 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
40 std::fs::write(path, json)
41 }
42
43 pub fn to_json(&self) -> Result<String, serde_json::Error> {
45 serde_json::to_string_pretty(self)
46 }
47
48 pub fn total(&self) -> usize {
50 self.suites.values().map(|s| s.tests.len()).sum()
51 }
52
53 pub fn passed(&self) -> usize {
55 self.suites
56 .values()
57 .flat_map(|s| s.tests.values())
58 .filter(|t| matches!(t.status, TestCaseStatus::Passed))
59 .count()
60 }
61
62 pub fn failed(&self) -> usize {
64 self.suites
65 .values()
66 .flat_map(|s| s.tests.values())
67 .filter(|t| matches!(t.status, TestCaseStatus::Failed { .. }))
68 .count()
69 }
70}
71
72impl Default for TestSuitesResult {
73 fn default() -> Self {
74 Self::new()
75 }
76}
77
78#[derive(Clone, Serialize, Deserialize)]
80pub struct TestSuiteResult {
81 pub tests: IndexMap<String, TestCaseResult>,
83}
84
85impl Debug for TestSuiteResult {
86 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87 f.debug_struct("TestSuiteResult")
88 .field("tests", &self.tests.keys().collect::<Vec<&String>>())
89 .finish()
90 }
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct TestCaseResult {
96 pub config: TestCaseConfig,
98
99 #[serde(with = "system_time_serde")]
101 pub timestamp: std::time::SystemTime,
102
103 #[serde(with = "duration_serde")]
105 pub duration: Duration,
106
107 pub stdout: String,
109
110 pub stderr: String,
112
113 pub status: TestCaseStatus,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct TestCaseConfig {
120 pub commands: Vec<String>,
122
123 pub args: Vec<String>,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
129#[serde(tag = "type", rename_all = "snake_case")]
130pub enum TestCaseStatus {
131 Passed,
133
134 Failed { failure: TestFailure },
136
137 Error { error: TestError },
139
140 Skipped { skipped: TestSkipped },
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct TestFailure {
147 pub message: String,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct TestError {
154 pub message: String,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct TestSkipped {
161 pub message: Option<String>,
163}
164
165mod duration_serde {
167 use serde::{Deserialize, Deserializer, Serialize, Serializer};
168 use std::time::Duration;
169
170 #[derive(Serialize, Deserialize)]
171 struct DurationMs {
172 ms: u128,
173 }
174
175 pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
176 where
177 S: Serializer,
178 {
179 DurationMs {
180 ms: duration.as_millis(),
181 }
182 .serialize(serializer)
183 }
184
185 pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
186 where
187 D: Deserializer<'de>,
188 {
189 let d = DurationMs::deserialize(deserializer)?;
190 Ok(Duration::from_millis(d.ms as u64))
191 }
192}
193
194mod system_time_serde {
196 use serde::{Deserialize, Deserializer, Serialize, Serializer};
197 use std::time::{Duration, SystemTime, UNIX_EPOCH};
198
199 #[derive(Serialize, Deserialize)]
200 struct TimestampMs {
201 epoch_ms: u128,
202 }
203
204 pub fn serialize<S>(time: &SystemTime, serializer: S) -> Result<S::Ok, S::Error>
205 where
206 S: Serializer,
207 {
208 let epoch_ms = time
209 .duration_since(UNIX_EPOCH)
210 .unwrap_or(Duration::ZERO)
211 .as_millis();
212 TimestampMs { epoch_ms }.serialize(serializer)
213 }
214
215 pub fn deserialize<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
216 where
217 D: Deserializer<'de>,
218 {
219 let t = TimestampMs::deserialize(deserializer)?;
220 Ok(UNIX_EPOCH + Duration::from_millis(t.epoch_ms as u64))
221 }
222}