1use crate::{
3 environment::Environment,
4 impl_extra,
5 summary::Summary,
6 test::{Status, Test},
7 tool::Tool,
8};
9
10use std::{
12 collections::{HashMap, HashSet},
13 time::SystemTime,
14};
15
16use serde::{Deserialize, Serialize};
18use serde_json::Value;
19
20#[derive(Deserialize, Serialize, Debug, PartialEq)]
23#[serde(rename_all = "camelCase")]
24pub struct Results {
25 tool: Tool,
26 summary: Summary,
27 tests: Vec<Test>,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 environment: Option<Environment>,
30 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
31 extra: HashMap<String, Value>,
32}
33
34pub struct ResultsBuilder {
36 tool: Tool,
37 tests: Vec<Test>,
38 environment: Option<Environment>,
39 extra: HashMap<String, Value>,
40}
41
42impl ResultsBuilder {
43 pub fn new(tool: Tool) -> Self {
45 Self {
46 tool,
47 tests: vec![],
48 environment: None,
49 extra: HashMap::new(),
50 }
51 }
52
53 pub fn add_test(&mut self, test: Test) {
55 self.tests.push(test);
56 }
57
58 pub fn environment(mut self, environment: Option<Environment>) {
60 self.environment = environment;
61 }
62
63 pub fn build(self, start: SystemTime, stop: SystemTime) -> Results {
65 let ResultsBuilder {
66 tool,
67 tests,
68 environment,
69 extra,
70 } = self;
71
72 let mut summary = Summary::new(start, stop);
73
74 summary.passed(
75 tests
76 .iter()
77 .filter(|t| t.status() == Status::Passed)
78 .count(),
79 );
80 summary.failed(
81 tests
82 .iter()
83 .filter(|t| t.status() == Status::Failed)
84 .count(),
85 );
86 summary.pending(
87 tests
88 .iter()
89 .filter(|t| t.status() == Status::Pending)
90 .count(),
91 );
92 summary.skipped(
93 tests
94 .iter()
95 .filter(|t| t.status() == Status::Skipped)
96 .count(),
97 );
98 summary.other(tests.iter().filter(|t| t.status() == Status::Other).count());
99
100 let mut suites = HashSet::new();
101 for t in &tests {
102 suites.extend(&t.suite);
103 }
104
105 let suite_count = suites.len();
106 if suite_count > 0 {
107 summary.suites(Some(suite_count));
108 }
109
110 Results {
111 tool,
112 summary,
113 tests,
114 environment,
115 extra,
116 }
117 }
118}
119
120impl_extra!(Results, ResultsBuilder);
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 use crate::tool::TOOL_NAME;
127
128 use std::time::Duration;
129
130 use serde_json::Result;
131
132 #[test]
133 fn add_passed() -> Result<()> {
134 const TEST_COUNT: usize = 2;
135
136 let tool = Tool::new();
137 let mut builder = ResultsBuilder::new(tool);
138
139 for t in 0..TEST_COUNT {
140 builder.add_test(Test::new(
141 format!("pass{t}"),
142 Status::Passed,
143 Duration::from_millis(0),
144 ));
145 }
146
147 let time = SystemTime::now();
148 let results = builder.build(time, time);
149
150 let tool_text = serde_json::to_string::<Tool>(&results.tool)?;
151 let summary_text = serde_json::to_string::<Summary>(&results.summary)?;
152 let tests_text = serde_json::to_string::<Vec<Test>>(&results.tests)?;
153 assert!(tool_text.contains(TOOL_NAME));
154 assert!(summary_text.contains(&format!(r#""tests":{TEST_COUNT}"#)));
155 assert!(summary_text.contains(&format!(r#""passed":{TEST_COUNT}"#)));
156 for t in 0..TEST_COUNT {
157 assert!(tests_text.contains(&format!(r#""name":"pass{t}""#)));
158 }
159
160 Ok(())
161 }
162
163 #[test]
164 fn add_failed() -> Result<()> {
165 const TEST_COUNT: usize = 4;
166
167 let tool = Tool::new();
168 let mut builder = ResultsBuilder::new(tool);
169
170 for t in 0..TEST_COUNT {
171 builder.add_test(Test::new(
172 format!("fail{t}"),
173 Status::Failed,
174 Duration::from_millis(0),
175 ));
176 }
177
178 let time = SystemTime::now();
179 let results = builder.build(time, time);
180
181 let tool_text = serde_json::to_string::<Tool>(&results.tool)?;
182 let summary_text = serde_json::to_string::<Summary>(&results.summary)?;
183 let tests_text = serde_json::to_string::<Vec<Test>>(&results.tests)?;
184 assert!(tool_text.contains(TOOL_NAME));
185 assert!(summary_text.contains(&format!(r#""tests":{TEST_COUNT}"#)));
186 assert!(summary_text.contains(&format!(r#""failed":{TEST_COUNT}"#)));
187 for t in 0..TEST_COUNT {
188 assert!(tests_text.contains(&format!(r#""name":"fail{t}""#)));
189 }
190
191 Ok(())
192 }
193
194 #[test]
195 fn add_pending() -> Result<()> {
196 const TEST_COUNT: usize = 6;
197
198 let tool = Tool::new();
199 let mut builder = ResultsBuilder::new(tool);
200
201 for t in 0..TEST_COUNT {
202 builder.add_test(Test::new(
203 format!("pending{t}"),
204 Status::Pending,
205 Duration::from_millis(0),
206 ));
207 }
208
209 let time = SystemTime::now();
210 let results = builder.build(time, time);
211
212 let tool_text = serde_json::to_string::<Tool>(&results.tool)?;
213 let summary_text = serde_json::to_string::<Summary>(&results.summary)?;
214 let tests_text = serde_json::to_string::<Vec<Test>>(&results.tests)?;
215 assert!(tool_text.contains(TOOL_NAME));
216 assert!(summary_text.contains(&format!(r#""tests":{TEST_COUNT}"#)));
217 assert!(summary_text.contains(&format!(r#""pending":{TEST_COUNT}"#)));
218 for t in 0..TEST_COUNT {
219 assert!(tests_text.contains(&format!(r#""name":"pending{t}""#)));
220 }
221
222 Ok(())
223 }
224
225 #[test]
226 fn add_skipped() -> Result<()> {
227 const TEST_COUNT: usize = 8;
228
229 let tool = Tool::new();
230 let mut builder = ResultsBuilder::new(tool);
231
232 for t in 0..TEST_COUNT {
233 builder.add_test(Test::new(
234 format!("skipped{t}"),
235 Status::Skipped,
236 Duration::from_millis(0),
237 ));
238 }
239
240 let time = SystemTime::now();
241 let results = builder.build(time, time);
242
243 let tool_text = serde_json::to_string::<Tool>(&results.tool)?;
244 let summary_text = serde_json::to_string::<Summary>(&results.summary)?;
245 let tests_text = serde_json::to_string::<Vec<Test>>(&results.tests)?;
246 assert!(tool_text.contains(TOOL_NAME));
247 assert!(summary_text.contains(&format!(r#""tests":{TEST_COUNT}"#)));
248 assert!(summary_text.contains(&format!(r#""skipped":{TEST_COUNT}"#)));
249 for t in 0..TEST_COUNT {
250 assert!(tests_text.contains(&format!(r#""name":"skipped{t}""#)));
251 }
252
253 Ok(())
254 }
255
256 #[test]
257 fn add_other() -> Result<()> {
258 const TEST_COUNT: usize = 10;
259
260 let tool = Tool::new();
261 let mut builder = ResultsBuilder::new(tool);
262
263 for t in 0..TEST_COUNT {
264 builder.add_test(Test::new(
265 format!("other{t}"),
266 Status::Other,
267 Duration::from_millis(0),
268 ));
269 }
270
271 let time = SystemTime::now();
272 let results = builder.build(time, time);
273
274 let tool_text = serde_json::to_string::<Tool>(&results.tool)?;
275 let summary_text = serde_json::to_string::<Summary>(&results.summary)?;
276 let tests_text = serde_json::to_string::<Vec<Test>>(&results.tests)?;
277 assert!(tool_text.contains(TOOL_NAME));
278 assert!(summary_text.contains(&format!(r#""tests":{TEST_COUNT}"#)));
279 assert!(summary_text.contains(&format!(r#""other":{TEST_COUNT}"#)));
280 for t in 0..TEST_COUNT {
281 assert!(tests_text.contains(&format!(r#""name":"other{t}""#)));
282 }
283
284 Ok(())
285 }
286
287 #[test]
288 fn add_many() -> Result<()> {
289 const PRESENT_SUITE: &str = "present";
290 const ABSENT_SUITE: &str = "absent";
291 const UNKNOWN_SUITE: &str = "unknown";
292
293 let tool = Tool::new();
294 let mut builder = ResultsBuilder::new(tool);
295
296 const PASS_COUNT: usize = 10;
297 for t in 0..PASS_COUNT {
298 let mut test = Test::new(format!("pass{t}"), Status::Passed, Duration::from_millis(0));
299 test.suite = vec![String::from(PRESENT_SUITE)];
300 builder.add_test(test);
301 }
302
303 const FAIL_COUNT: usize = 8;
304 for t in 0..FAIL_COUNT {
305 let mut test = Test::new(format!("fail{t}"), Status::Failed, Duration::from_millis(0));
306 test.suite = vec![String::from(PRESENT_SUITE)];
307 builder.add_test(test);
308 }
309
310 const PENDING_COUNT: usize = 6;
311 for t in 0..PENDING_COUNT {
312 let mut test = Test::new(
313 format!("pending{t}"),
314 Status::Pending,
315 Duration::from_millis(0),
316 );
317 test.suite = vec![String::from(ABSENT_SUITE)];
318 builder.add_test(test);
319 }
320
321 const SKIPPED_COUNT: usize = 4;
322 for t in 0..SKIPPED_COUNT {
323 let mut test = Test::new(
324 format!("skipped{t}"),
325 Status::Skipped,
326 Duration::from_millis(0),
327 );
328 test.suite = vec![String::from(ABSENT_SUITE)];
329 builder.add_test(test);
330 }
331
332 const OTHER_COUNT: usize = 2;
333 for t in 0..OTHER_COUNT {
334 let mut test = Test::new(format!("other{t}"), Status::Other, Duration::from_millis(0));
335 test.suite = vec![String::from(UNKNOWN_SUITE)];
336 builder.add_test(test);
337 }
338
339 const TOTAL_COUNT: usize =
340 PASS_COUNT + FAIL_COUNT + PENDING_COUNT + SKIPPED_COUNT + OTHER_COUNT;
341
342 let time = SystemTime::now();
343 let results = builder.build(time, time);
344
345 let tool_text = serde_json::to_string::<Tool>(&results.tool)?;
346 let summary_text = serde_json::to_string::<Summary>(&results.summary)?;
347 let tests_text = serde_json::to_string::<Vec<Test>>(&results.tests)?;
348 assert!(tool_text.contains(TOOL_NAME));
349
350 assert!(summary_text.contains(&format!(r#""tests":{TOTAL_COUNT}"#)));
351 assert!(summary_text.contains(&format!(r#""passed":{PASS_COUNT}"#)));
352 assert!(summary_text.contains(&format!(r#""failed":{FAIL_COUNT}"#)));
353 assert!(summary_text.contains(&format!(r#""pending":{PENDING_COUNT}"#)));
354 assert!(summary_text.contains(&format!(r#""skipped":{SKIPPED_COUNT}"#)));
355 assert!(summary_text.contains(&format!(r#""other":{OTHER_COUNT}"#)));
356 assert!(summary_text.contains(&format!(r#""suites":3"#)));
357
358 for t in 0..PASS_COUNT {
359 assert!(tests_text.contains(&format!(r#""name":"pass{t}""#)));
360 }
361 for t in 0..FAIL_COUNT {
362 assert!(tests_text.contains(&format!(r#""name":"fail{t}""#)));
363 }
364 for t in 0..PENDING_COUNT {
365 assert!(tests_text.contains(&format!(r#""name":"pending{t}""#)));
366 }
367 for t in 0..SKIPPED_COUNT {
368 assert!(tests_text.contains(&format!(r#""name":"skipped{t}""#)));
369 }
370 for t in 0..OTHER_COUNT {
371 assert!(tests_text.contains(&format!(r#""name":"other{t}""#)));
372 }
373
374 Ok(())
375 }
376}