1#![doc = include_str!("../README.md")]
2#![allow(unknown_lints)]
7#![deny(
8 clippy::await_holding_lock,
9 clippy::borrow_as_ptr,
10 clippy::branches_sharing_code,
11 clippy::cast_lossless,
12 clippy::clippy::collection_is_never_read,
13 clippy::cloned_instead_of_copied,
14 clippy::cognitive_complexity,
15 clippy::create_dir,
16 clippy::deref_by_slicing,
17 clippy::derivable_impls,
18 clippy::derive_partial_eq_without_eq,
19 clippy::equatable_if_let,
20 clippy::exhaustive_structs,
21 clippy::expect_used,
22 clippy::expl_impl_clone_on_copy,
23 clippy::explicit_deref_methods,
24 clippy::explicit_into_iter_loop,
25 clippy::explicit_iter_loop,
26 clippy::filetype_is_file,
27 clippy::flat_map_option,
28 clippy::format_push_string,
29 clippy::fn_params_excessive_bools,
30 clippy::future_not_send,
31 clippy::get_unwrap,
32 clippy::implicit_clone,
33 clippy::if_then_some_else_none,
34 clippy::impl_trait_in_params,
35 clippy::implicit_clone,
36 clippy::inefficient_to_string,
37 clippy::inherent_to_string,
38 clippy::iter_not_returning_iterator,
39 clippy::large_types_passed_by_value,
40 clippy::large_include_file,
41 clippy::let_and_return,
42 clippy::manual_assert,
43 clippy::manual_ok_or,
44 clippy::manual_split_once,
45 clippy::manual_let_else,
46 clippy::manual_string_new,
47 clippy::map_flatten,
48 clippy::map_unwrap_or,
49 clippy::missing_enforced_import_renames,
50 clippy::missing_assert_message,
51 clippy::missing_const_for_fn,
52 clippy::must_use_candidate,
53 clippy::mut_mut,
54 clippy::needless_for_each,
55 clippy::needless_option_as_deref,
56 clippy::needless_pass_by_value,
57 clippy::needless_collect,
58 clippy::needless_continue,
59 clippy::non_send_fields_in_send_ty,
60 clippy::nonstandard_macro_braces,
61 clippy::option_if_let_else,
62 clippy::option_option,
63 clippy::rc_mutex,
64 clippy::redundant_else,
65 clippy::same_name_method,
66 clippy::semicolon_if_nothing_returned,
67 clippy::str_to_string,
68 clippy::string_to_string,
69 clippy::too_many_lines,
70 clippy::trivially_copy_pass_by_ref,
71 clippy::trivial_regex,
72 clippy::try_err,
73 clippy::unnested_or_patterns,
74 clippy::unused_async,
75 clippy::unwrap_or_else_default,
76 clippy::useless_let_if_seq,
77 bad_style,
78 clashing_extern_declarations,
79 dead_code,
80 deprecated,
81 explicit_outlives_requirements,
82 improper_ctypes,
83 invalid_value,
84 missing_copy_implementations,
85 missing_debug_implementations,
86 mutable_transmutes,
87 no_mangle_generic_items,
88 non_shorthand_field_patterns,
89 overflowing_literals,
90 path_statements,
91 patterns_in_fns_without_body,
92 private_in_public,
93 trivial_bounds,
94 trivial_casts,
95 trivial_numeric_casts,
96 type_alias_bounds,
97 unconditional_recursion,
98 unreachable_pub,
99 unsafe_code,
100 unstable_features,
101 unused,
102 unused_allocation,
103 unused_comparisons,
104 unused_import_braces,
105 unused_parens,
106 unused_qualifications,
107 while_true,
108 missing_docs
109)]
110#![warn(clippy::exhaustive_enums)]
111#![allow(unused_attributes, clippy::derive_partial_eq_without_eq, clippy::box_default)]
112#![allow()]
115
116use testanything::tap_test::TapTest;
117use testanything::tap_test_builder::TapTestBuilder;
118
119#[derive(Default, Debug)]
120pub struct TestRunner {
122 desc: Option<String>,
123 blocks: Vec<TestBlock>,
124 output: Vec<String>,
125}
126
127impl TestRunner {
128 #[must_use]
130 pub const fn new(desc: Option<String>) -> Self {
131 Self {
132 desc,
133 blocks: vec![],
134 output: vec![],
135 }
136 }
137
138 pub fn add_block(&mut self, block: TestBlock) {
140 self.blocks.push(block);
141 }
142
143 #[must_use]
144 pub const fn get_tap_lines(&self) -> &Vec<String> {
146 &self.output
147 }
148
149 pub fn run(&mut self) {
151 let description = self
152 .desc
153 .as_ref()
154 .map_or_else(|| "TAP Stream".to_owned(), |v| v.clone());
155
156 let mut total_tests = 0;
157 for block in &self.blocks {
158 total_tests += block.num_tests();
159 }
160
161 let plan_line = format!("1..{} # {}", total_tests, description);
162 let mut all_lines = vec![plan_line];
163
164 let mut test_num = 0;
165 for block in &mut self.blocks {
166 if let Some(desc) = block.desc.as_ref() {
167 all_lines.push(format!("# {}", desc));
168 }
169 let mut block_passed = true;
170 for result in block.run() {
171 test_num += 1;
172 let tap = result.status_line(test_num);
173 all_lines.push(tap);
174 block_passed = block_passed && result.passed;
175 if !result.passed {
176 let mut formatted_diagnostics = format_diagnostics(&result.diagnostics);
177 all_lines.append(&mut formatted_diagnostics);
178 }
179 }
180 if !block_passed {
181 all_lines.append(&mut format_diagnostics(&block.diagnostics));
182 }
183 }
184 self.output = all_lines;
185 }
186
187 pub fn print(&self) {
189 let lines = self.get_tap_lines();
190 for line in lines {
191 println!("{}", line);
192 }
193 }
194
195 #[must_use]
196 pub fn num_failed(&self) -> u32 {
198 let lines = self.get_tap_lines();
199 let mut num_failed: u32 = 0;
200
201 for line in lines {
202 if line.starts_with("not ok") {
203 num_failed += 1;
204 }
205 }
206 num_failed
207 }
208}
209
210fn format_diagnostic_line<T: std::fmt::Display>(line: T) -> String {
211 format!("# {}", line)
212}
213
214fn format_diagnostics<T>(lines: &[T]) -> Vec<String>
215where
216 T: std::fmt::Display,
217{
218 lines.iter().map(format_diagnostic_line).collect()
219}
220
221#[derive(Default, Debug)]
222pub struct TestBlock {
224 desc: Option<String>,
225 tests: Vec<TestCase>,
226 diagnostics: Vec<String>,
227}
228
229impl TestBlock {
230 #[must_use]
232 pub const fn new(desc: Option<String>) -> Self {
233 Self {
234 desc,
235 tests: vec![],
236 diagnostics: vec![],
237 }
238 }
239
240 pub fn add_test<T: Into<String>>(
242 &mut self,
243 test: impl FnOnce() -> bool + Sync + Send + 'static,
244 description: T,
245 diagnostics: Option<Vec<String>>,
246 ) {
247 self.tests.push(TestCase {
248 test: Some(Box::new(test)),
249 result: Some(false),
250 description: description.into(),
251 diagnostics,
252 });
253 }
254
255 pub fn fail<T: Into<String>>(&mut self, description: T, diagnostics: Option<Vec<String>>) {
257 self.tests.push(TestCase {
258 test: None,
259 result: Some(false),
260 description: description.into(),
261 diagnostics,
262 });
263 }
264
265 pub fn succeed<T: Into<String>>(&mut self, description: T, diagnostics: Option<Vec<String>>) {
267 self.tests.push(TestCase {
268 test: None,
269 result: Some(true),
270 description: description.into(),
271 diagnostics,
272 });
273 }
274
275 pub fn add_diagnostic_messages(&mut self, messages: Vec<String>) {
277 self.diagnostics = messages;
278 }
279
280 fn num_tests(&self) -> usize {
281 self.tests.len()
282 }
283
284 pub fn run(&mut self) -> Vec<TapTest> {
286 let mut tests: Vec<TapTest> = vec![];
287 for test_case in &mut self.tests {
288 let mut tap_test = TapTestBuilder::new();
289 tap_test.name(test_case.description.clone());
290
291 if let Some(diag) = &test_case.diagnostics {
292 let diags: Vec<&str> = diag.iter().map(|s| s.as_str()).collect();
293 tap_test.diagnostics(&diags);
294 }
295
296 let tap_test = tap_test.passed(test_case.exec()).finalize();
297 tests.push(tap_test);
298 }
299 tests
300 }
301}
302
303#[derive()]
304struct TestCase {
305 test: Option<Box<dyn FnOnce() -> bool + Sync + Send>>,
306 result: Option<bool>,
307 description: String,
308 diagnostics: Option<Vec<String>>,
309}
310
311impl std::fmt::Debug for TestCase {
312 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
313 f.debug_struct("TestCase")
314 .field("result", &self.result)
315 .field("description", &self.description)
316 .field("diagnostics", &self.diagnostics)
317 .finish()
318 }
319}
320
321impl TestCase {
322 fn exec(&mut self) -> bool {
323 match self.test.take() {
324 Some(test) => {
325 let result = (test)();
326 self.result = Some(result);
327 result
328 }
329 None => self.result.unwrap_or(false),
330 }
331 }
332}