unit_testing/unit/
mod.rs

1use crate::objects::{Failure, Success, Take, Testable, Theory};
2use crate::output::{
3    IS_BEGIN, IS_BETWEEN, IS_CONTAINS, IS_EQUALS, IS_EXECUTABLE, IS_EXISTS, IS_FAIL, IS_FINNISH,
4    IS_INFERIOR, IS_KO, IS_MATCH, IS_NOT_BEGIN, IS_NOT_BETWEEN, IS_NOT_CONTAINS, IS_NOT_EXECUTABLE,
5    IS_NOT_EXISTS, IS_NOT_FAIL, IS_NOT_FINNISH, IS_NOT_MATCH, IS_NOT_SUCCESS, IS_OK, IS_SUCCESS,
6    IS_SUPERIOR, IS_UNEQUALS, THEORY_IS_FALSE, THEORY_IS_TRUE,
7};
8use colored_truecolor::Colorize;
9use is_executable::IsExecutable;
10use progress_bar::{
11    finalize_progress_bar, inc_progress_bar, init_progress_bar_with_eta,
12    print_progress_bar_final_info, print_progress_bar_info, set_progress_bar_action, Color, Style,
13};
14use regex::Regex;
15use std::cell::Cell;
16use std::collections::{HashMap, HashSet};
17use std::path::Path;
18use std::process::ExitStatus;
19use std::thread::sleep;
20use std::time::{Duration, Instant};
21use std::{fs, io};
22
23///
24/// # To run units tests
25///
26pub struct Unit {
27    s: Cell<usize>,
28    f: Cell<usize>,
29    sleep: u64,
30    success_take: HashMap<usize, u128>,
31    failure_take: HashMap<usize, u128>,
32    success: HashMap<usize, String>,
33    failure: HashMap<usize, String>,
34}
35
36impl Success for Unit {
37    fn run(&mut self, callbacks: Vec<&dyn Fn() -> Result<ExitStatus, io::Error>>) -> &mut Self {
38        for &c in &callbacks {
39            self.check(c().unwrap().success(), IS_SUCCESS, IS_NOT_SUCCESS);
40        }
41        self
42    }
43
44    fn success(&mut self, callbacks: Vec<&dyn Fn() -> bool>) -> &mut Self {
45        for &c in &callbacks {
46            self.check(c(), IS_SUCCESS, IS_FAIL);
47        }
48        self
49    }
50}
51
52impl Theory for Unit {
53    fn chaos(&mut self, callback: &dyn Fn() -> bool) -> &mut Self {
54        self.theory(false, &callback)
55    }
56
57    fn theory<T: PartialEq>(&mut self, expected: T, callback: &dyn Fn() -> T) -> &mut Self {
58        self.take(callback() == expected, THEORY_IS_TRUE, THEORY_IS_FALSE)
59    }
60
61    fn theorem<T: PartialEq>(&mut self, expected: T, actual: &dyn Fn() -> T) -> &mut Self {
62        self.take(expected.eq(&actual()), THEORY_IS_TRUE, THEORY_IS_FALSE)
63    }
64}
65
66impl Take for Unit {
67    fn assert_that(&mut self, t: bool) -> bool {
68        self.assert(t)
69    }
70
71    fn take(&mut self, t: bool, s: &str, e: &str) -> &mut Self {
72        let i: Instant = Instant::now();
73        if self.assert_that(t) {
74            assert_eq!(self.success.insert(self.s.get(), s.to_string()), None);
75            assert_eq!(
76                self.success_take
77                    .insert(self.s.get(), i.elapsed().as_nanos()),
78                None
79            );
80        } else {
81            assert_eq!(self.failure.insert(self.f.get(), e.to_string()), None);
82            assert_eq!(
83                self.failure_take
84                    .insert(self.f.get(), i.elapsed().as_nanos()),
85                None
86            );
87        }
88
89        self
90    }
91
92    fn check(&mut self, t: bool, s: &str, e: &str) {
93        let i: Instant = Instant::now();
94        if self.assert_that(t) {
95            assert!(self.success.insert(self.s.get(), s.to_string()).is_some());
96            assert!(self
97                .success_take
98                .insert(self.s.get(), i.elapsed().as_nanos())
99                .is_some());
100        } else {
101            assert!(self.failure.insert(self.f.get(), e.to_string()).is_some());
102            assert!(self
103                .failure_take
104                .insert(self.f.get(), i.elapsed().as_nanos())
105                .is_some());
106        }
107    }
108}
109
110impl Failure for Unit {
111    fn command_fail(
112        &mut self,
113        callbacks: Vec<&dyn Fn() -> Result<ExitStatus, io::Error>>,
114    ) -> &mut Self {
115        for &c in &callbacks {
116            let status: ExitStatus = c().unwrap();
117            self.check(!status.success(), IS_FAIL, IS_NOT_FAIL);
118        }
119        self
120    }
121
122    fn fail(&mut self, callbacks: Vec<&dyn Fn() -> bool>) -> &mut Self {
123        for &c in &callbacks {
124            self.check(!c(), IS_FAIL, IS_NOT_FAIL);
125        }
126        self
127    }
128}
129
130impl Testable for Unit {
131    fn matches(&mut self, pattern: &str, values: Vec<String>) -> &mut Self {
132        let r = Regex::new(pattern).unwrap();
133
134        for x in &values {
135            self.check(r.is_match(x.as_str()), IS_MATCH, IS_NOT_MATCH);
136        }
137        self
138    }
139
140    fn capture(&mut self, pattern: &str, x: &str, key: usize, values: Vec<String>) -> &mut Self {
141        let r: Regex = Regex::new(pattern).unwrap();
142        let caps = r.captures(x).unwrap();
143        for v in &values {
144            self.check(
145                caps.get(key)
146                    .expect("failed to get key")
147                    .as_str()
148                    .eq(v.as_str()),
149                IS_MATCH,
150                IS_NOT_MATCH,
151            );
152        }
153        self
154    }
155
156    fn it(
157        title: &str,
158        description: &str,
159        sleep_time: u64,
160        callbacks: Vec<&dyn Fn(&mut Self) -> &mut Self>,
161    ) {
162        println!("\n{}\n", description.white().bold());
163        println!(
164            "      {}",
165            format_args!("{} {}", "[ OK ]".green().bold(), title.blue().bold())
166        );
167        let mut x = Self::new(sleep_time);
168
169        let mut j = &mut x;
170        for &c in &callbacks {
171            j = c(j);
172        }
173
174        let _ = j.end();
175    }
176
177    fn ok(&mut self, f: bool) -> &mut Self {
178        self.take(f, IS_OK, IS_KO)
179    }
180
181    fn ko(&mut self, f: bool) -> &mut Self {
182        self.take(!f, IS_KO, IS_OK)
183    }
184
185    fn assert(&mut self, test: bool) -> bool {
186        if test {
187            self.s.set(self.s.get() + 1);
188        } else {
189            self.f.set(self.f.get() + 1);
190        }
191        test
192    }
193
194    fn eq<T: PartialEq>(&mut self, a: T, b: T) -> &mut Self {
195        self.take(a.eq(&b), IS_EQUALS, IS_UNEQUALS)
196    }
197
198    fn ne<T: PartialEq>(&mut self, a: T, b: T) -> &mut Self {
199        self.take(a.ne(&b), IS_UNEQUALS, IS_EQUALS)
200    }
201
202    fn gt<T: PartialOrd>(&mut self, a: T, min: T) -> &mut Self {
203        self.take(a.gt(&min), IS_SUPERIOR, IS_INFERIOR)
204    }
205
206    fn ge<T: PartialOrd>(&mut self, a: T, max: T) -> &mut Self {
207        self.take(a.ge(&max), IS_INFERIOR, IS_SUPERIOR)
208    }
209    fn lt<T: PartialOrd>(&mut self, a: T, max: T) -> &mut Self {
210        self.take(a.lt(&max), IS_SUPERIOR, IS_INFERIOR)
211    }
212
213    fn le<T: PartialOrd>(&mut self, a: T, max: T) -> &mut Self {
214        self.take(a.le(&max), IS_INFERIOR, IS_SUPERIOR)
215    }
216
217    fn between<T: PartialOrd>(&mut self, a: T, min: T, max: T) -> &mut Self {
218        self.take(a > min && a < max, IS_BETWEEN, IS_NOT_BETWEEN)
219    }
220
221    fn vec_contains<T: PartialEq>(&mut self, a: Vec<T>, b: T) -> &mut Self {
222        self.take(a.contains(&b), IS_CONTAINS, IS_NOT_CONTAINS)
223    }
224
225    fn exe(&mut self, p: &str) -> &mut Self {
226        self.take(
227            Path::new(p).is_executable(),
228            IS_EXECUTABLE,
229            IS_NOT_EXECUTABLE,
230        )
231    }
232
233    fn vec_no_contains<T: PartialEq>(&mut self, a: Vec<T>, b: T) -> &mut Self {
234        self.take(!a.contains(&b), IS_CONTAINS, IS_NOT_CONTAINS)
235    }
236
237    fn option_contains<T: PartialEq>(&mut self, a: Option<T>, b: T) -> &mut Self {
238        self.take(a.expect("failed") == b, IS_CONTAINS, IS_NOT_CONTAINS)
239    }
240
241    fn hash_contains(&mut self, a: &mut HashSet<String>, b: String) -> &mut Self {
242        self.take(a.contains(&b), IS_CONTAINS, IS_NOT_CONTAINS)
243    }
244
245    fn str_contains(&mut self, a: &str, b: &str) -> &mut Self {
246        self.take(a.contains(b), IS_CONTAINS, IS_NOT_CONTAINS)
247    }
248
249    fn file_contains(&mut self, f: &str, v: &str) -> &mut Self {
250        self.take(
251            fs::read_to_string(f)
252                .unwrap_or_else(|_| panic!("The filename {f} has not been founded"))
253                .contains(v),
254            IS_CONTAINS,
255            IS_NOT_CONTAINS,
256        )
257    }
258
259    fn exists(&mut self, p: &str) -> &mut Self {
260        self.take(Path::new(p).exists(), IS_EXISTS, IS_NOT_EXISTS)
261    }
262
263    fn not_exists(&mut self, p: &str) -> &mut Self {
264        self.take(!Path::new(p).exists(), IS_NOT_EXISTS, IS_EXISTS)
265    }
266
267    fn start_with(&mut self, actual: &str, expected: &str) -> &mut Self {
268        self.take(actual.starts_with(expected), IS_BEGIN, IS_NOT_BEGIN)
269    }
270
271    fn end_with(&mut self, actual: &str, expected: &str) -> &mut Self {
272        self.take(actual.ends_with(expected), IS_FINNISH, IS_NOT_FINNISH)
273    }
274
275    fn end(&mut self) -> bool {
276        let total: usize = self.f.get() + self.s.get();
277        init_progress_bar_with_eta(total);
278        set_progress_bar_action("[ :: ]", Color::Green, Style::Bold);
279
280        let mut failure = self.failure.values();
281        let mut success = self.success.values();
282        let mut success_take = self.success_take.values();
283        let mut failures_take = self.failure_take.values();
284
285        for _i in 0..total {
286            sleep(Duration::from_millis(self.sleep));
287
288            if let Some(x) = success.next() {
289                print_progress_bar_info(
290                    "[ OK ]",
291                    format!(
292                        "{} {} {} {}",
293                        x.blue().bold(),
294                        "take".white().bold(),
295                        success_take.next().expect("").to_string().cyan().bold(),
296                        "ns".blue().bold()
297                    )
298                    .as_str(),
299                    Color::Green,
300                    Style::Bold,
301                );
302            }
303
304            if let Some(x) = failure.next() {
305                print_progress_bar_info(
306                    "[ KO ]",
307                    format!(
308                        "{} {} {} {}",
309                        x.purple().bold(),
310                        "take".white().bold(),
311                        failures_take.next().expect("").to_string().cyan().bold(),
312                        "ns".blue().bold()
313                    )
314                    .as_str(),
315                    Color::Red,
316                    Style::Bold,
317                );
318            }
319            inc_progress_bar();
320        }
321
322        print_progress_bar_final_info(
323            "[ OK ]",
324            format!(
325                "{} {} {} {}",
326                "Assertions :".blue().bold(),
327                self.s.get().to_string().green().bold(),
328                "Failures :".blue().bold(),
329                self.f.get().to_string().red().bold(),
330            )
331            .as_str(),
332            Color::Green,
333            Style::Bold,
334        );
335        finalize_progress_bar();
336        true
337    }
338
339    fn new(sleep_time: u64) -> Self {
340        Self {
341            s: Cell::new(0),
342            f: Cell::new(0),
343            sleep: sleep_time,
344            success_take: HashMap::new(),
345            failure_take: HashMap::new(),
346            success: HashMap::new(),
347            failure: HashMap::new(),
348        }
349    }
350}
351
352#[cfg(test)]
353mod test {
354    use crate::check_that;
355    use crate::objects::{Testable, Theory};
356    use crate::output::DISABLE_PROGRESS_TIME;
357    use crate::unit::Unit;
358    use std::collections::HashSet;
359
360    fn ok() -> bool {
361        true
362    }
363
364    fn ko() -> bool {
365        false
366    }
367
368    fn must_pass(u: &mut Unit) -> &mut Unit {
369        u.ok(ok()).ko(ko())
370    }
371
372    fn must_exists(u: &mut Unit) -> &mut Unit {
373        u.exists(".").exists("README.md")
374    }
375
376    fn must_linux(u: &mut Unit) -> &mut Unit {
377        u
378    }
379
380    fn must_equals(u: &mut Unit) -> &mut Unit {
381        u.eq("README.md", "README.md")
382            .eq(4, 4)
383            .eq(4.4, 4.4)
384            .eq(true, true)
385            .eq(false, false)
386    }
387
388    fn must_contains(u: &mut Unit) -> &mut Unit {
389        let mut v: Vec<String> = Vec::new();
390        let o = Some("a".to_string());
391        v.push("value".to_string());
392        v.push("h".to_string());
393        u.vec_contains(v, "h".to_string())
394            .option_contains(o, "a".to_string())
395            .str_contains("linux", "linux")
396            .file_contains("README.md", "Installation")
397            .hash_contains(&mut HashSet::from(["a".to_string()]), "a".to_string())
398    }
399
400    fn must_unequals(u: &mut Unit) -> &mut Unit {
401        u.ne("README.md", ".")
402            .ne(4, 6)
403            .ne(5.6, 4.4)
404            .ne(false, true)
405            .ne(false, true)
406    }
407
408    fn must_superior(u: &mut Unit) -> &mut Unit {
409        u.gt(1, 0).gt(5, 2)
410    }
411
412    fn programs(u: &mut Unit) -> &mut Unit {
413        u
414    }
415
416    fn no_programs(u: &mut Unit) -> &mut Unit {
417        u
418    }
419
420    fn must_inferior(u: &mut Unit) -> &mut Unit {
421        u.lt(10, 50).lt(50, 200)
422    }
423
424    fn must_between(u: &mut Unit) -> &mut Unit {
425        u.between(10, 5, 50).between(50, 10, 200)
426    }
427
428    fn pythagore() -> f32 {
429        3.0_f32.hypot(4.0)
430    }
431
432    fn pythagore_not_work() -> bool {
433        4.0_f32.hypot(4.0) == 5.0
434    }
435
436    fn must_theory(u: &mut Unit) -> &mut Unit {
437        u.theory(5.0, &pythagore).chaos(&pythagore_not_work)
438    }
439
440    #[test]
441    pub fn all() {
442        check_that!(
443            "Test the assert framework",
444            "Check if all values passes on success, test can be have failures.",
445            DISABLE_PROGRESS_TIME,
446            vec![
447                &must_between,
448                &programs,
449                &must_theory,
450                &no_programs,
451                &must_unequals,
452                &must_linux,
453                &must_equals,
454                &must_exists,
455                &must_pass,
456                &must_contains,
457                &must_superior,
458                &must_inferior,
459            ]
460        );
461    }
462}