clnrm_core/formatting/
tap.rs1use crate::error::Result;
7use crate::formatting::formatter::{Formatter, FormatterType};
8use crate::formatting::test_result::{TestStatus, TestSuite};
9
10#[derive(Debug, Default)]
12pub struct TapFormatter;
13
14impl TapFormatter {
15 pub fn new() -> Self {
17 Self
18 }
19
20 fn generate_header() -> String {
22 "TAP version 13".to_string()
23 }
24
25 fn generate_plan(total: usize) -> String {
27 format!("1..{}", total)
28 }
29
30 fn generate_test_line(
32 index: usize,
33 result: &crate::formatting::test_result::TestResult,
34 ) -> Vec<String> {
35 let mut output = Vec::new();
36
37 let status = match result.status {
38 TestStatus::Passed => "ok",
39 TestStatus::Failed => "not ok",
40 TestStatus::Skipped => "ok",
41 TestStatus::Unknown => "not ok",
42 };
43
44 let mut line = format!("{} {} - {}", status, index, result.name);
45
46 if result.status == TestStatus::Skipped {
48 line.push_str(" # SKIP");
49 }
50
51 output.push(line);
52
53 if result.status == TestStatus::Failed {
55 if let Some(error) = &result.error {
56 output.push(" ---".to_string());
57 output.push(format!(" message: {}", Self::escape_yaml_string(error)));
58 output.push(" ...".to_string());
59 }
60 }
61
62 if let Some(duration) = result.duration {
64 output.push(format!(" # Duration: {:.3}s", duration.as_secs_f64()));
65 }
66
67 if let Some(stdout) = &result.stdout {
69 output.push(" # stdout:".to_string());
70 for line in stdout.lines() {
71 output.push(format!(" # {}", line));
72 }
73 }
74
75 if let Some(stderr) = &result.stderr {
76 output.push(" # stderr:".to_string());
77 for line in stderr.lines() {
78 output.push(format!(" # {}", line));
79 }
80 }
81
82 output
83 }
84
85 fn escape_yaml_string(s: &str) -> String {
87 if s.contains('\n') || s.contains('#') || s.contains(':') {
89 format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\""))
90 } else {
91 s.to_string()
92 }
93 }
94}
95
96impl Formatter for TapFormatter {
97 fn format(&self, suite: &TestSuite) -> Result<String> {
98 let mut output = Vec::new();
99
100 output.push(Self::generate_header());
102
103 output.push(Self::generate_plan(suite.total_count()));
105
106 for (index, result) in suite.results.iter().enumerate() {
108 let test_lines = Self::generate_test_line(index + 1, result);
109 output.extend(test_lines);
110 }
111
112 output.push(format!(
114 "# tests {}, passed {}, failed {}, skipped {}",
115 suite.total_count(),
116 suite.passed_count(),
117 suite.failed_count(),
118 suite.skipped_count()
119 ));
120
121 if let Some(duration) = suite.duration {
122 output.push(format!("# duration: {:.3}s", duration.as_secs_f64()));
123 }
124
125 Ok(output.join("\n"))
126 }
127
128 fn name(&self) -> &'static str {
129 "tap"
130 }
131
132 fn formatter_type(&self) -> FormatterType {
133 FormatterType::Tap
134 }
135}