1use derive_getters::Getters;
9use time::{Duration, OffsetDateTime};
10
11#[derive(Debug, Clone, Getters)]
13pub struct TestSuite {
14 pub name: String,
15 pub package: String,
16 pub timestamp: OffsetDateTime,
17 pub hostname: String,
18 pub testcases: Vec<TestCase>,
19 pub system_out: Option<String>,
20 pub system_err: Option<String>,
21}
22
23impl TestSuite {
24 pub fn new(name: &str) -> Self {
26 TestSuite {
27 hostname: "localhost".into(),
28 package: format!("testsuite/{}", &name),
29 name: name.into(),
30 timestamp: OffsetDateTime::now_utc(),
31 testcases: Vec::new(),
32 system_out: None,
33 system_err: None,
34 }
35 }
36
37 pub fn add_testcase(&mut self, testcase: TestCase) {
39 self.testcases.push(testcase);
40 }
41
42 pub fn add_testcases(&mut self, testcases: impl IntoIterator<Item = TestCase>) {
44 self.testcases.extend(testcases);
45 }
46
47 pub fn set_timestamp(&mut self, timestamp: OffsetDateTime) {
51 self.timestamp = timestamp;
52 }
53
54 pub fn set_system_out(&mut self, system_out: &str) {
55 self.system_out = Some(system_out.to_owned());
56 }
57
58 pub fn set_system_err(&mut self, system_err: &str) {
59 self.system_err = Some(system_err.to_owned());
60 }
61
62 pub fn tests(&self) -> usize {
63 self.testcases.len()
64 }
65
66 pub fn errors(&self) -> usize {
67 self.testcases.iter().filter(|x| x.is_error()).count()
68 }
69
70 pub fn failures(&self) -> usize {
71 self.testcases.iter().filter(|x| x.is_failure()).count()
72 }
73
74 pub fn skipped(&self) -> usize {
75 self.testcases.iter().filter(|x| x.is_skipped()).count()
76 }
77
78 pub fn time(&self) -> Duration {
79 self.testcases
80 .iter()
81 .fold(Duration::ZERO, |sum, d| sum + d.time)
82 }
83}
84
85#[derive(Debug, Clone, Getters)]
87pub struct TestSuiteBuilder {
88 pub testsuite: TestSuite,
89}
90
91impl TestSuiteBuilder {
92 pub fn new(name: &str) -> Self {
94 TestSuiteBuilder {
95 testsuite: TestSuite::new(name),
96 }
97 }
98
99 pub fn add_testcase(&mut self, testcase: TestCase) -> &mut Self {
101 self.testsuite.testcases.push(testcase);
102 self
103 }
104
105 pub fn add_testcases(&mut self, testcases: impl IntoIterator<Item = TestCase>) -> &mut Self {
107 self.testsuite.testcases.extend(testcases);
108 self
109 }
110
111 pub fn set_timestamp(&mut self, timestamp: OffsetDateTime) -> &mut Self {
115 self.testsuite.timestamp = timestamp;
116 self
117 }
118
119 pub fn set_system_out(&mut self, system_out: &str) -> &mut Self {
120 self.testsuite.system_out = Some(system_out.to_owned());
121 self
122 }
123
124 pub fn set_system_err(&mut self, system_err: &str) -> &mut Self {
125 self.testsuite.system_err = Some(system_err.to_owned());
126 self
127 }
128
129 pub fn build(&self) -> TestSuite {
131 self.testsuite.clone()
132 }
133}
134
135#[derive(Debug, Clone, Getters)]
137pub struct TestCase {
138 pub name: String,
139 pub time: Duration,
140 pub result: TestResult,
141 pub classname: Option<String>,
142 pub filepath: Option<String>,
143 pub system_out: Option<String>,
144 pub system_err: Option<String>,
145}
146
147#[derive(Debug, Clone)]
149pub enum TestResult {
150 Success,
151 Skipped,
152 SkippedWithCause {
153 type_: String,
154 message: String,
155 cause: Option<String>,
156 },
157 Error {
158 type_: String,
159 message: String,
160 cause: Option<String>,
161 },
162 Failure {
163 type_: String,
164 message: String,
165 cause: Option<String>,
166 },
167}
168
169impl TestCase {
170 pub fn success(name: &str, time: Duration) -> Self {
172 TestCase {
173 name: name.into(),
174 time,
175 result: TestResult::Success,
176 classname: None,
177 filepath: None,
178 system_out: None,
179 system_err: None,
180 }
181 }
182
183 pub fn set_classname(&mut self, classname: &str) {
185 self.classname = Some(classname.to_owned());
186 }
187
188 pub fn set_filepath(&mut self, filepath: &str) {
190 self.filepath = Some(filepath.to_owned());
191 }
192
193 pub fn set_system_out(&mut self, system_out: &str) {
195 self.system_out = Some(system_out.to_owned());
196 }
197
198 pub fn set_system_err(&mut self, system_err: &str) {
200 self.system_err = Some(system_err.to_owned());
201 }
202
203 pub fn is_success(&self) -> bool {
205 matches!(self.result, TestResult::Success)
206 }
207
208 pub fn error(name: &str, time: Duration, type_: &str, message: &str) -> Self {
212 TestCase {
213 name: name.into(),
214 time,
215 result: TestResult::Error {
216 type_: type_.into(),
217 message: message.into(),
218 cause: None,
219 },
220 classname: None,
221 filepath: None,
222 system_out: None,
223 system_err: None,
224 }
225 }
226
227 pub fn is_error(&self) -> bool {
229 matches!(self.result, TestResult::Error { .. })
230 }
231
232 pub fn failure(name: &str, time: Duration, type_: &str, message: &str) -> Self {
236 TestCase {
237 name: name.into(),
238 time,
239 result: TestResult::Failure {
240 type_: type_.into(),
241 message: message.into(),
242 cause: None,
243 },
244 classname: None,
245 filepath: None,
246 system_out: None,
247 system_err: None,
248 }
249 }
250
251 pub fn is_failure(&self) -> bool {
253 matches!(self.result, TestResult::Failure { .. })
254 }
255
256 pub fn skipped(name: &str) -> Self {
260 TestCase {
261 name: name.into(),
262 time: Duration::ZERO,
263 result: TestResult::Skipped,
264 classname: None,
265 filepath: None,
266 system_out: None,
267 system_err: None,
268 }
269 }
270
271 pub fn skipped_with_cause(name: &str, type_: &str, message: &str) -> Self {
275 TestCase {
276 name: name.into(),
277 time: Duration::ZERO,
278 result: TestResult::SkippedWithCause {
279 type_: type_.into(),
280 message: message.into(),
281 cause: None,
282 },
283 classname: None,
284 filepath: None,
285 system_out: None,
286 system_err: None,
287 }
288 }
289
290 pub fn is_skipped(&self) -> bool {
292 matches!(
293 self.result,
294 TestResult::Skipped | TestResult::SkippedWithCause { .. }
295 )
296 }
297}
298
299#[derive(Debug, Clone, Getters)]
301pub struct TestCaseBuilder {
302 pub testcase: TestCase,
303}
304
305impl TestCaseBuilder {
306 pub fn success(name: &str, time: Duration) -> Self {
308 TestCaseBuilder {
309 testcase: TestCase::success(name, time),
310 }
311 }
312
313 pub fn set_classname(&mut self, classname: &str) -> &mut Self {
315 self.testcase.classname = Some(classname.to_owned());
316 self
317 }
318
319 pub fn set_filepath(&mut self, filepath: &str) -> &mut Self {
321 self.testcase.filepath = Some(filepath.to_owned());
322 self
323 }
324
325 pub fn set_system_out(&mut self, system_out: &str) -> &mut Self {
327 self.testcase.system_out = Some(system_out.to_owned());
328 self
329 }
330
331 pub fn set_system_err(&mut self, system_err: &str) -> &mut Self {
333 self.testcase.system_err = Some(system_err.to_owned());
334 self
335 }
336
337 pub fn set_trace(&mut self, trace: &str) -> &mut Self {
341 match self.testcase.result {
342 TestResult::Error { ref mut cause, .. } => *cause = Some(trace.to_owned()),
343 TestResult::Failure { ref mut cause, .. } => *cause = Some(trace.to_owned()),
344 _ => {}
345 }
346 self
347 }
348
349 pub fn error(name: &str, time: Duration, type_: &str, message: &str) -> Self {
353 TestCaseBuilder {
354 testcase: TestCase::error(name, time, type_, message),
355 }
356 }
357
358 pub fn failure(name: &str, time: Duration, type_: &str, message: &str) -> Self {
362 TestCaseBuilder {
363 testcase: TestCase::failure(name, time, type_, message),
364 }
365 }
366
367 pub fn skipped(name: &str) -> Self {
371 TestCaseBuilder {
372 testcase: TestCase::skipped(name),
373 }
374 }
375
376 pub fn skipped_with_cause(name: &str, type_: &str, message: &str) -> Self {
380 TestCaseBuilder {
381 testcase: TestCase::skipped_with_cause(name, type_, message),
382 }
383 }
384
385 pub fn build(&self) -> TestCase {
387 self.testcase.clone()
388 }
389}
390
391#[cfg(doctest)]
393doc_comment::doctest!("../README.md");