unit_testing/suite/
mod.rs

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///
15/// # Represent a test suite
16///
17#[derive(Clone, Copy)]
18pub struct Suite {
19    before_each: fn(),
20    after_each: fn(),
21}
22
23impl Suite {
24    ///
25    /// # Initialize the suite
26    ///
27    /// - `before_each` The callback to execute before each test
28    /// - `after_each` The callback to execute after each test
29    ///
30    pub fn new(before_each: fn(), after_each: fn()) -> Self {
31        Self {
32            before_each,
33            after_each,
34        }
35    }
36    ///
37    /// # Run a test
38    ///
39    /// - `x` The test
40    /// - `s` The success message
41    /// - `e` The error message
42    ///
43    /// # Panics
44    ///
45    /// if test fail
46    ///
47    #[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    ///
56    /// # End of the test suite
57    ///
58    /// # Errors
59    ///
60    pub fn end(&mut self) -> io::Result<()> {
61        Ok(())
62    }
63    ///
64    /// # Check equality
65    ///
66    /// - `actual`      The actual value
67    /// - `expected`    The expected value
68    #[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    ///
73    ///  - `a` The result to check if match Ok
74    ///
75    #[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    ///
81    /// # Check if a callback panic
82    ///
83    /// - `c` The callback to check
84    ///
85    #[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    ///
92    /// # Check if a callback don't panic
93    ///
94    /// - `c` The callback to check
95    ///
96    #[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    ///
103    /// - `a` The data to check if X match Err
104    ///
105    #[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    ///
110    /// # Check the len
111    ///
112    /// - `actual` The actual len
113    /// - `expected`The expected len
114    ///
115    #[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    ///
125    /// # Check inequality
126    ///
127    /// - `actual`      The actual value
128    /// - `expected`    The expected value
129    ///
130    #[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    ///
135    /// # Check if actual is greater than expected
136    ///
137    /// - `actual` The actual value
138    /// - `expected` The expected value
139    ///
140    #[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    ///
145    /// # Check if actual is greater or equal than expected
146    ///
147    /// - `actual` The actual value
148    /// - `expected` The expected value
149    ///
150    #[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    ///
156    /// # Check if actual is containing expected
157    ///
158    /// - `actual` The actual value
159    /// - `expected` The expected value
160    ///
161    #[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    ///
167    /// # Check if an actual path matches the expected value
168    ///
169    /// - `actual` The actual value
170    /// - `expected` The expected value
171    ///
172    #[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    ///
178    /// # Check if an actual path exists
179    ///
180    /// - `actual` The actual path
181    ///
182    #[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    ///
188    /// # Check if actual is not containing expected
189    ///
190    /// - `actual` The actual value
191    /// - `expected` The expected value
192    ///
193    #[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    ///
203    /// # Check if actual is lower or equal than expected
204    ///
205    /// - `actual` The actual value
206    /// - `expected` The expected value
207    ///
208    #[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    ///
213    /// # Check if actual is lower than expected
214    ///
215    /// - `actual` The actual value
216    /// - `expected` The expected value
217    ///
218    #[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    ///
224    /// # Check if the callback exit code match the expected exit code
225    ///
226    /// - `actual` The callback to check
227    /// - `expected` The expected code
228    ///
229    #[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    ///
243    /// # Check if a theorem is equal to expected value
244    ///
245    /// - `c` The theorem callback
246    /// - `expected` The expected value
247    ///
248    #[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    ///
261    /// # Check if a theorem is equal to expected value
262    ///
263    /// - `c` The theorem callback
264    /// - `expected` The expected value
265    ///
266    #[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    ///
282    /// # Check if a theorem is different to expected
283    ///
284    /// - `c` The theorem callback
285    /// - `expected` The expected value
286    ///
287    #[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    ///
317    /// # Check if actual is lower than expected
318    ///
319    /// - `description` The actual value
320    /// - `expected` The expected value
321    ///
322    #[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    ///
328    /// # Check if actual is lower than expected
329    ///
330    /// - `description` The actual value
331    /// - `expected` The expected value
332    ///
333    #[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
348///
349/// # Start a test suite
350///
351/// - `description`         The test suite description
352/// - `after_all_hook`      A callback
353/// - `after_each_hook`     A callback
354/// - `before_all_hook`     A callback
355/// - `before_each_hook`    A callback
356/// - `main`                The main callback
357///
358pub 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}