tap_harness/
lib.rs

1#![doc = include_str!("../README.md")]
2// !!START_LINTS
3// Wick lints
4// Do not change anything between the START_LINTS and END_LINTS line.
5// This is automatically generated. Add exceptions after this section.
6#![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// !!END_LINTS
113// Add exceptions here
114#![allow()]
115
116use testanything::tap_test::TapTest;
117use testanything::tap_test_builder::TapTestBuilder;
118
119#[derive(Default, Debug)]
120/// [TestRunner] is the main way you'll interact with TAP tests.
121pub struct TestRunner {
122  desc: Option<String>,
123  blocks: Vec<TestBlock>,
124  output: Vec<String>,
125}
126
127impl TestRunner {
128  /// Create a new [TestRunner]
129  #[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  /// Add a [TestBlock] to the runner.
139  pub fn add_block(&mut self, block: TestBlock) {
140    self.blocks.push(block);
141  }
142
143  #[must_use]
144  /// Get the TAP output.
145  pub const fn get_tap_lines(&self) -> &Vec<String> {
146    &self.output
147  }
148
149  /// Execute the tests.
150  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  /// Print the TAP output.
188  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  /// Get the number of failed tests.
197  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)]
222/// A [TestBlock] organizes test cases together under one umbrella.
223pub struct TestBlock {
224  desc: Option<String>,
225  tests: Vec<TestCase>,
226  diagnostics: Vec<String>,
227}
228
229impl TestBlock {
230  /// Create a new [TestBlock].
231  #[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  /// Add a new test case.
241  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  /// Add a test failure.
256  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  /// Add a test success.
266  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  /// Add diagnostic messages to this test block.
276  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  /// Execute the [TestBlock]'s test cases.
285  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}