clnrm_core/formatting/
human.rs1use crate::error::Result;
7use crate::formatting::formatter::{Formatter, FormatterType};
8use crate::formatting::test_result::{TestStatus, TestSuite};
9
10#[derive(Debug, Default)]
12pub struct HumanFormatter {
13 use_colors: bool,
15}
16
17impl HumanFormatter {
18 pub fn new() -> Self {
20 Self::with_colors(true)
21 }
22
23 pub fn with_colors(use_colors: bool) -> Self {
25 Self { use_colors }
26 }
27
28 fn format_status(&self, status: &TestStatus) -> String {
30 let (symbol, color) = match status {
31 TestStatus::Passed => ("✓", "\x1b[32m"), TestStatus::Failed => ("✗", "\x1b[31m"), TestStatus::Skipped => ("⊘", "\x1b[33m"), TestStatus::Unknown => ("?", "\x1b[90m"), };
36
37 if self.use_colors {
38 format!("{}{}\x1b[0m", color, symbol)
39 } else {
40 symbol.to_string()
41 }
42 }
43
44 fn format_test_name(&self, name: &str, passed: bool) -> String {
46 if self.use_colors {
47 if passed {
48 format!("\x1b[32m{}\x1b[0m", name)
49 } else {
50 format!("\x1b[31m{}\x1b[0m", name)
51 }
52 } else {
53 name.to_string()
54 }
55 }
56
57 fn format_duration(&self, duration_ms: f64) -> String {
59 if duration_ms < 1.0 {
60 "<1ms".to_string()
61 } else if duration_ms < 1000.0 {
62 format!("{}ms", duration_ms as u64)
63 } else {
64 format!("{:.2}s", duration_ms / 1000.0)
65 }
66 }
67
68 fn format_summary(&self, suite: &TestSuite) -> String {
70 let total = suite.total_count();
71 let passed = suite.passed_count();
72 let failed = suite.failed_count();
73 let skipped = suite.skipped_count();
74
75 let status_text = if suite.is_success() {
76 if self.use_colors {
77 "\x1b[32mPASSED\x1b[0m"
78 } else {
79 "PASSED"
80 }
81 } else if self.use_colors {
82 "\x1b[31mFAILED\x1b[0m"
83 } else {
84 "FAILED"
85 };
86
87 let mut parts = vec![format!("{} tests", total), format!("{} passed", passed)];
88
89 if failed > 0 {
90 parts.push(format!("{} failed", failed));
91 }
92
93 if skipped > 0 {
94 parts.push(format!("{} skipped", skipped));
95 }
96
97 format!("{}: {}", status_text, parts.join(", "))
98 }
99}
100
101impl Formatter for HumanFormatter {
102 fn format(&self, suite: &TestSuite) -> Result<String> {
103 let mut output = Vec::new();
104
105 output.push(format!("Test Suite: {}", suite.name));
107 output.push(String::from(""));
108
109 for result in &suite.results {
111 let status = self.format_status(&result.status);
112 let name = self.format_test_name(&result.name, result.is_passed());
113
114 let mut line = format!(" {} {}", status, name);
115
116 if let Some(duration) = result.duration {
118 let duration_str = self.format_duration(duration.as_secs_f64() * 1000.0);
119 line.push_str(&format!(" ({})", duration_str));
120 }
121
122 output.push(line);
123
124 if let Some(error) = &result.error {
126 let error_lines: Vec<&str> = error.lines().collect();
127 for error_line in error_lines {
128 output.push(format!(" {}", error_line));
129 }
130 }
131
132 if let Some(stdout) = &result.stdout {
134 output.push(String::from(" stdout:"));
135 for stdout_line in stdout.lines() {
136 output.push(format!(" {}", stdout_line));
137 }
138 }
139
140 if let Some(stderr) = &result.stderr {
142 output.push(String::from(" stderr:"));
143 for stderr_line in stderr.lines() {
144 output.push(format!(" {}", stderr_line));
145 }
146 }
147 }
148
149 output.push(String::from(""));
151 output.push("─".repeat(60));
152 output.push(self.format_summary(suite));
153
154 if let Some(duration) = suite.duration {
156 let duration_str = self.format_duration(duration.as_secs_f64() * 1000.0);
157 output.push(format!("Duration: {}", duration_str));
158 }
159
160 output.push(String::from(""));
161
162 Ok(output.join("\n"))
163 }
164
165 fn name(&self) -> &'static str {
166 "human"
167 }
168
169 fn formatter_type(&self) -> FormatterType {
170 FormatterType::Human
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use crate::formatting::test_result::TestResult;
178 use std::time::Duration;
179
180 #[test]
181 fn test_human_formatter_empty_suite() -> Result<()> {
182 let formatter = HumanFormatter::with_colors(false);
184 let suite = TestSuite::new("empty_suite");
185
186 let output = formatter.format(&suite)?;
188
189 assert!(output.contains("Test Suite: empty_suite"));
191 assert!(output.contains("FAILED") || output.contains("0 tests"));
193 assert!(output.contains("0 tests"));
194
195 Ok(())
196 }
197
198 #[test]
199 fn test_human_formatter_all_passed() -> Result<()> {
200 let formatter = HumanFormatter::with_colors(false);
202 let suite = TestSuite::new("passing_suite")
203 .add_result(TestResult::passed("test1"))
204 .add_result(TestResult::passed("test2"));
205
206 let output = formatter.format(&suite)?;
208
209 assert!(output.contains("Test Suite: passing_suite"));
211 assert!(output.contains("✓ test1"));
212 assert!(output.contains("✓ test2"));
213 assert!(output.contains("PASSED"));
214 assert!(output.contains("2 tests"));
215 assert!(output.contains("2 passed"));
216
217 Ok(())
218 }
219
220 #[test]
221 fn test_human_formatter_with_failures() -> Result<()> {
222 let formatter = HumanFormatter::with_colors(false);
224 let suite = TestSuite::new("failing_suite")
225 .add_result(TestResult::passed("test1"))
226 .add_result(TestResult::failed(
227 "test2",
228 "assertion failed: expected 2, got 1",
229 ));
230
231 let output = formatter.format(&suite)?;
233
234 assert!(output.contains("Test Suite: failing_suite"));
236 assert!(output.contains("✓ test1"));
237 assert!(output.contains("✗ test2"));
238 assert!(output.contains("assertion failed: expected 2, got 1"));
239 assert!(output.contains("FAILED"));
240 assert!(output.contains("2 tests"));
241 assert!(output.contains("1 passed"));
242 assert!(output.contains("1 failed"));
243
244 Ok(())
245 }
246
247 #[test]
248 fn test_human_formatter_with_skipped() -> Result<()> {
249 let formatter = HumanFormatter::with_colors(false);
251 let suite = TestSuite::new("suite_with_skipped")
252 .add_result(TestResult::passed("test1"))
253 .add_result(TestResult::skipped("test2"));
254
255 let output = formatter.format(&suite)?;
257
258 assert!(output.contains("✓ test1"));
260 assert!(output.contains("⊘ test2"));
261 assert!(output.contains("1 passed"));
262 assert!(output.contains("1 skipped"));
263
264 Ok(())
265 }
266
267 #[test]
268 fn test_human_formatter_with_duration() -> Result<()> {
269 let formatter = HumanFormatter::with_colors(false);
271 let suite = TestSuite::new("suite_with_duration")
272 .add_result(TestResult::passed("fast_test").with_duration(Duration::from_millis(50)))
273 .add_result(TestResult::passed("slow_test").with_duration(Duration::from_millis(1500)));
274
275 let output = formatter.format(&suite)?;
277
278 assert!(output.contains("fast_test (50ms)"));
280 assert!(output.contains("slow_test (1.50s)"));
281
282 Ok(())
283 }
284
285 #[test]
286 fn test_human_formatter_with_stdout_stderr() -> Result<()> {
287 let formatter = HumanFormatter::with_colors(false);
289 let suite = TestSuite::new("suite_with_output").add_result(
290 TestResult::passed("test_with_output")
291 .with_stdout("stdout line 1\nstdout line 2")
292 .with_stderr("stderr line 1"),
293 );
294
295 let output = formatter.format(&suite)?;
297
298 assert!(output.contains("stdout:"));
300 assert!(output.contains("stdout line 1"));
301 assert!(output.contains("stdout line 2"));
302 assert!(output.contains("stderr:"));
303 assert!(output.contains("stderr line 1"));
304
305 Ok(())
306 }
307
308 #[test]
309 fn test_human_formatter_name() {
310 let formatter = HumanFormatter::new();
312
313 assert_eq!(formatter.name(), "human");
315 }
316
317 #[test]
318 fn test_human_formatter_type() {
319 let formatter = HumanFormatter::new();
321
322 assert_eq!(formatter.formatter_type(), FormatterType::Human);
324 }
325
326 #[test]
327 fn test_format_duration_less_than_1ms() {
328 let formatter = HumanFormatter::new();
330
331 let result = formatter.format_duration(0.5);
333
334 assert_eq!(result, "<1ms");
336 }
337
338 #[test]
339 fn test_format_duration_milliseconds() {
340 let formatter = HumanFormatter::new();
342
343 let result = formatter.format_duration(150.0);
345
346 assert_eq!(result, "150ms");
348 }
349
350 #[test]
351 fn test_format_duration_seconds() {
352 let formatter = HumanFormatter::new();
354
355 let result = formatter.format_duration(1500.0);
357
358 assert_eq!(result, "1.50s");
360 }
361}