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}