1use std::panic::UnwindSafe;
2use std::path::Path;
3use std::{io, panic};
4
5use colored_truecolor::Colorize;
6
7use crate::output::{
8 ASSERT_LENGTH_EQUALS, ASSERT_LENGTH_UN0EQUALS, ASSERT_NOT_PANIC, ASSERT_PANIC, IS_CONTAINS,
9 IS_EQUALS, IS_EXISTS, IS_INFERIOR, IS_KO, IS_NOT_CONTAINS, IS_NOT_EXISTS, IS_OK, IS_SUPERIOR,
10 IS_UNEQUALS, THEORY_IS_FALSE, THEORY_IS_TRUE,
11};
12use crate::run;
13
14#[derive(Clone, Copy)]
18pub struct Suite {
19 before_each: fn(),
20 after_each: fn(),
21}
22
23impl Suite {
24 pub fn new(before_each: fn(), after_each: fn()) -> Self {
31 Self {
32 before_each,
33 after_each,
34 }
35 }
36 #[must_use]
48 pub fn run(self, test: bool, success: &'static str, error: &'static str) -> Self {
49 let after = self.after_each;
50 let before = self.before_each;
51 run!(test, success, error, before, after);
52 self
53 }
54
55 pub fn end(&mut self) -> io::Result<()> {
61 Ok(())
62 }
63 #[must_use]
69 pub fn eq<X: PartialEq>(self, actual: &X, expected: &X) -> Self {
70 self.run(actual.eq(expected), IS_EQUALS, IS_UNEQUALS)
71 }
72 #[must_use]
76 pub fn ok<X, Y>(self, a: &Result<X, Y>) -> Self {
77 self.run(a.is_ok(), IS_OK, IS_KO)
78 }
79
80 #[must_use]
86 pub fn panic(self, c: impl FnOnce() + UnwindSafe) -> Self {
87 let result = panic::catch_unwind(c).is_ok();
88 self.run(result.eq(&false), ASSERT_PANIC, ASSERT_NOT_PANIC)
89 }
90
91 #[must_use]
97 pub fn not_panic(self, c: impl FnOnce() + UnwindSafe) -> Self {
98 let result = panic::catch_unwind(c);
99 self.run(result.is_ok(), ASSERT_NOT_PANIC, ASSERT_PANIC)
100 }
101
102 #[must_use]
106 pub fn ko<X, Y>(self, a: &Result<X, Y>) -> Self {
107 self.run(a.is_err(), IS_KO, IS_OK)
108 }
109 #[must_use]
116 pub fn len<X: ExactSizeIterator>(self, actual: &X, expected: &usize) -> Self {
117 self.run(
118 actual.len().eq(expected),
119 ASSERT_LENGTH_EQUALS,
120 ASSERT_LENGTH_UN0EQUALS,
121 )
122 }
123
124 #[must_use]
131 pub fn ne<X: PartialEq>(self, actual: &X, expected: &X) -> Self {
132 self.run(actual.ne(expected), IS_UNEQUALS, IS_EQUALS)
133 }
134 #[must_use]
141 pub fn gt<X: PartialOrd>(self, actual: &X, expected: &X) -> Self {
142 self.run(actual.gt(expected), IS_SUPERIOR, IS_INFERIOR)
143 }
144 #[must_use]
151 pub fn ge<X: PartialOrd>(self, actual: &X, expected: &X) -> Self {
152 self.run(actual.ge(expected), IS_SUPERIOR, IS_INFERIOR)
153 }
154
155 #[must_use]
162 pub fn str_contains(self, actual: &str, expected: &str) -> Self {
163 self.run(actual.contains(expected), IS_CONTAINS, IS_NOT_CONTAINS)
164 }
165
166 #[must_use]
173 pub fn path_exists(self, actual: &str, expected: bool) -> Self {
174 self.run(Path::new(actual).exists().eq(&expected), IS_OK, IS_KO)
175 }
176
177 #[must_use]
183 pub fn exists(self, actual: &str) -> Self {
184 self.run(Path::new(actual).exists(), IS_EXISTS, IS_NOT_EXISTS)
185 }
186
187 #[must_use]
194 pub fn str_not_contains(self, actual: &str, expected: &str) -> Self {
195 self.run(
196 actual.contains(expected).eq(&false),
197 IS_NOT_CONTAINS,
198 IS_CONTAINS,
199 )
200 }
201
202 #[must_use]
209 pub fn le<X: PartialOrd>(self, actual: &X, expected: &X) -> Self {
210 self.run(actual.le(expected), IS_INFERIOR, IS_SUPERIOR)
211 }
212 #[must_use]
219 pub fn lt<X: PartialOrd>(self, actual: &X, expected: &X) -> Self {
220 self.run(actual.lt(expected), IS_INFERIOR, IS_SUPERIOR)
221 }
222
223 #[must_use]
230 pub fn response<X: PartialEq>(
231 self,
232 title: &str,
233 description: &str,
234 c: &dyn Fn(X) -> X,
235 x: X,
236 expected: &X,
237 ) -> Self {
238 self.title(title, description)
239 .run(c(x).eq(expected), IS_EQUALS, IS_UNEQUALS)
240 }
241
242 #[must_use]
249 pub fn theorem<X: PartialEq>(
250 self,
251 title: &str,
252 description: &str,
253 c: &dyn Fn() -> X,
254 expected: &X,
255 ) -> Self {
256 self.sub_title(title, description)
257 .run(c().eq(expected), IS_OK, IS_KO)
258 }
259
260 #[must_use]
267 pub fn theory<X: PartialEq>(
268 self,
269 title: &str,
270 description: &str,
271 callback: &X,
272 expected: &X,
273 ) -> Self {
274 self.sub_title(title, description).run(
275 callback.eq(expected),
276 THEORY_IS_TRUE,
277 THEORY_IS_FALSE,
278 )
279 }
280
281 #[must_use]
288 pub fn chaos<X: PartialEq>(
289 self,
290 title: &str,
291 description: &str,
292 c: &dyn Fn() -> X,
293 expected: &X,
294 ) -> Self {
295 self.title(title, description)
296 .run(c().ne(expected), THEORY_IS_TRUE, THEORY_IS_FALSE)
297 }
298 fn title(self, title: &str, description: &str) -> Self {
299 println!(
300 "\n{}\n\n\t{}\n",
301 title.to_lowercase().true_color(55, 190, 176).bold(),
302 description.true_color(164, 229, 224).bold(),
303 );
304 self
305 }
306
307 fn sub_title(self, title: &str, description: &str) -> Self {
308 println!(
309 "\t{}\n\n\t{}\n",
310 title.to_lowercase().true_color(212, 241, 244).bold(),
311 description.to_lowercase().true_color(212, 241, 244).bold(),
312 );
313 self
314 }
315
316 #[must_use]
323 pub fn group(self, title: &str, description: &str, callback: fn(Self) -> Self) -> Self {
324 callback(self.title(title, description))
325 }
326
327 #[must_use]
334 pub fn sure(
335 self,
336 title: &str,
337 description: &str,
338 callback: &dyn Fn(Self) -> Self,
339 x: usize,
340 ) -> Self {
341 for _i in 0..x {
342 let _ = callback(self.title(title, description));
343 }
344 self
345 }
346}
347
348pub fn describe(
359 title: &str,
360 description: &str,
361 after_all_hook: fn(),
362 after_each_hook: fn(),
363 before_all_hook: fn(),
364 before_each_hook: fn(),
365 main: fn(Suite) -> Suite,
366) -> Suite {
367 before_all_hook();
368 println!(
369 "\n{}\n\n{}\n",
370 title.true_color(164, 229, 224).bold(),
371 description.true_color(164, 229, 224).bold(),
372 );
373 let data: Suite = main(Suite::new(before_each_hook, after_each_hook));
374 after_all_hook();
375 data
376}
377
378#[cfg(test)]
379mod test {
380 use crate::suite::Suite;
381 use crate::{always_panic, it};
382 use std::fs;
383 use std::ops::Mul;
384
385 fn before_each() {}
386 fn after_each() {}
387 fn before_all() {}
388 fn after_all() {}
389
390 fn sure(suite: Suite) -> Suite {
391 suite.eq(&4, &4).ne(&3, &4)
392 }
393 fn main(s: Suite) -> Suite {
394 s.group(
395 "Should be contains",
396 "All data string must be contains all expected strings",
397 |s| {
398 s.str_contains(
399 &fs::read_to_string("README.md").expect("Failed to parse README.md"),
400 "cargo add unit-testing",
401 )
402 },
403 )
404 .group(
405 "Check is theorem are valid",
406 "The triangle must be rectangle",
407 |s| {
408 s.theorem("ab = 3; bc = 4", "ac == 5", &ok, &true).theory(
409 "ab = 8; bc = 6",
410 "ac == 10",
411 &is_rect(8_f32, 6_f32),
412 &10_f32,
413 )
414 },
415 )
416 .group(
417 "Check is theories are valid",
418 "The triangle must be rectangle",
419 |s| {
420 s.theory(
421 "ab = 3; bc = 4",
422 "ac == 5",
423 &is_rect(3.0_f32, 4.0_f32),
424 &5.0_f32,
425 )
426 },
427 )
428 .group(
429 "Check if path exist",
430 "All given path must be exist on all Os",
431 |s| {
432 s.path_exists("README.md", true)
433 .path_exists(".", true)
434 .exists(".")
435 .exists("README.md")
436 },
437 )
438 .group(
439 "Should be not contains",
440 "The README.md must be not contains expected data",
441 |s| {
442 s.str_not_contains(
443 &fs::read_to_string("README.md").expect("Failed to parse README.md"),
444 "cargo add continuous-testing",
445 )
446 },
447 )
448 .group("Should be equals", "All values mut be equals", |s| {
449 s.eq(&1, &1)
450 .eq(&2, &2)
451 .response(
452 "Check if the callback no add a 0 before 10",
453 "Check if f(x) => 0",
454 &a,
455 10,
456 &0,
457 )
458 .response(
459 "Check if the callback is dividable by 2",
460 "Check if f(x) => x² % 2 == 0",
461 &b,
462 2,
463 &0,
464 )
465 .response(
466 "Check if the callback is dividable by 3",
467 "Check if f(x) => x² % 3 == 0",
468 &c,
469 3,
470 &0,
471 )
472 })
473 .group("Should be unequal", "Check if a and b are different", |s| {
474 s.ne(&1, &2).ne(&3, &2)
475 })
476 .group(
477 "Should be math len",
478 "All vec must be math the expected length",
479 |s| s.len(&vec!["", "", ""].iter(), &3),
480 )
481 .group("Should be match Ok", "Callbacks mut be return Ok", |s| {
482 s.group("Should be divisible by 2", "x % 2 == 0", |s| {
483 s.ok(&data(2)).ok(&data(4))
484 })
485 .group("Should be divisible by 3", "x % 3 == 0", |s| {
486 s.eq(&c(3).eq(&0), &true).eq(&c(12).eq(&0), &true)
487 })
488 })
489 .group("Should be match Err", "Callbacks must be return Err", |s| {
490 s.ko(&data(5)).ko(&data(15))
491 })
492 .group(
493 "Should panic",
494 "Callback should be panic if data is noty divisible by 2",
495 |s| s.panic(panic),
496 )
497 .group("Should not panic", "The callback should never panic", |s| {
498 s.not_panic(not_panic)
499 })
500 .sure(
501 "Check the persistence",
502 "Check if data return always the same result",
503 &sure,
504 5,
505 )
506 }
507 fn panic() {
508 always_panic!();
509 }
510 fn not_panic() {}
511 fn a(x: i32) -> i32 {
512 0.mul(x)
513 }
514 fn b(x: i32) -> i32 {
515 x.pow(2) % 2
516 }
517 fn c(x: i32) -> i32 {
518 x.pow(2) % 3
519 }
520 fn ok() -> bool {
521 3.0_f32.hypot(4.0).eq(&5.0)
522 }
523 fn is_rect(a: f32, b: f32) -> f32 {
524 a.hypot(b)
525 }
526 fn data(x: usize) -> Result<(), String> {
527 if x % 2 == 0 {
528 Ok(())
529 } else {
530 Err(String::from("not divisible by 2"))
531 }
532 }
533 #[test]
534 fn suite() {
535 it!(
536 "Check the suite it test case",
537 "Suite test accept no test failure, for guaranty the source code.",
538 before_each,
539 after_each,
540 before_all,
541 after_all,
542 main
543 );
544 }
545}