Skip to main content

xacli_testing/report/
xacli.rs

1use std::{fmt::Debug, time::Duration};
2
3use indexmap::IndexMap;
4use serde::{Deserialize, Serialize};
5
6/// Result of running tests
7#[derive(Clone, Serialize, Deserialize)]
8pub struct TestSuitesResult {
9    /// Test suites results (ordered)
10    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    /// Create empty result
23    pub fn new() -> Self {
24        Self {
25            suites: IndexMap::new(),
26        }
27    }
28
29    /// Read report from JSON file
30    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    /// Write report to JSON file
37    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    /// Serialize to JSON string
44    pub fn to_json(&self) -> Result<String, serde_json::Error> {
45        serde_json::to_string_pretty(self)
46    }
47
48    /// Count total tests
49    pub fn total(&self) -> usize {
50        self.suites.values().map(|s| s.tests.len()).sum()
51    }
52
53    /// Count passed tests
54    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    /// Count failed tests
63    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/// Result of running a test suite
79#[derive(Clone, Serialize, Deserialize)]
80pub struct TestSuiteResult {
81    /// Test cases results (ordered)
82    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/// Result of running a test case
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct TestCaseResult {
96    /// Test case configuration
97    pub config: TestCaseConfig,
98
99    /// Timestamp when the test started (milliseconds since UNIX epoch)
100    #[serde(with = "system_time_serde")]
101    pub timestamp: std::time::SystemTime,
102
103    /// Duration of the test execution
104    #[serde(with = "duration_serde")]
105    pub duration: Duration,
106
107    /// Captured standard output
108    pub stdout: String,
109
110    /// Captured standard error
111    pub stderr: String,
112
113    /// Test case status
114    pub status: TestCaseStatus,
115}
116
117/// Test case configuration
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct TestCaseConfig {
120    /// Commands to execute (subcommand path)
121    pub commands: Vec<String>,
122
123    /// Command-line arguments
124    pub args: Vec<String>,
125}
126
127/// Test case status variants
128#[derive(Debug, Clone, Serialize, Deserialize)]
129#[serde(tag = "type", rename_all = "snake_case")]
130pub enum TestCaseStatus {
131    /// Test passed (no child element)
132    Passed,
133
134    /// Test failed
135    Failed { failure: TestFailure },
136
137    /// Test had an error (e.g., exception, crash)
138    Error { error: TestError },
139
140    /// Test was skipped
141    Skipped { skipped: TestSkipped },
142}
143
144/// Failure information for a failed test
145#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct TestFailure {
147    /// Failure message
148    pub message: String,
149}
150
151/// Error information for a test with an error
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct TestError {
154    /// Error message
155    pub message: String,
156}
157
158/// Skipped test information
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct TestSkipped {
161    /// Reason for skipping
162    pub message: Option<String>,
163}
164
165/// Custom serialization for Duration (as milliseconds)
166mod 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
194/// Custom serialization for SystemTime (as milliseconds since UNIX epoch)
195mod 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}